# Azure backlog api 教學 ## 最簡單的範例程式Demo 1. 首先,需取得personal access token ![](https://hackmd.io/_uploads/BJ4UTOIhh.png) ![](https://hackmd.io/_uploads/rJTFTOI32.png) 2. 使用以下程式訪問一個Azure Devops中的某個project backlog的所有資訊 (記得修改中文處) ```go package main import ( "encoding/base64" "fmt" "io" "net/http" ) func main() { url := "https://dev.azure.com/{這個project所屬組織名}/{這個project的名字}/{參與這個project的Teams名稱}/_apis/work/backlogs?api-version=7.0" // this is a personal access token personalToken := "剛剛取得的token" // create a new http request req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println(err) return } // add token to request header token := "Basic " + base64.StdEncoding.EncodeToString([]byte(":"+personalToken)) req.Header.Add("Authorization", token) // send request and get response client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println(err) return } defer resp.Body.Close() // parse response body body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } // print response fmt.Println("response:\n", string(body)) } ``` (Project Teams 可以從這裡找到) ![](https://hackmd.io/_uploads/HyjGkFInn.png) ## 實現 ```go package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "strconv" ) type addWorkItemParm struct { title string state string assignTo string discription string priority string effort string businessValue string valueArea string url string } // this is a personal access token var personalToken = "aze5mbddhhcwonzuczxqouu6facuwbiaqk7nmi6st6uhejfsrq4q" func getWorkItemInfo() { url := "https://dev.azure.com/techniquelab/lab/lab%20Team/_apis/work/backlogs?api-version=7.0" // create a new http request req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println(err) return } // add token to request header token := "Basic " + base64.StdEncoding.EncodeToString([]byte(":"+personalToken)) req.Header.Add("Authorization", token) // send request and get response client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println(err) return } defer resp.Body.Close() // parse response body body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } // print response fmt.Println("response:\n", string(body)) } // function to edit State func editWorkItem(organizationName string, projectName string, id string, parm addWorkItemParm) { url := "https://dev.azure.com/" + organizationName + "/" + projectName + "/_apis/wit/workitems/" + id + "?api-version=7.0" // setting request body that contain info about task payloadTemplate := `[ { "op": "add", "path": "/fields/System.State", "value": "%s" } ]` payload := []byte(fmt.Sprintf(payloadTemplate, parm.state)) // create a new request and using payload req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(payload)) if err != nil { fmt.Println(err) return } // add personal access token to request header token := "Basic " + base64.StdEncoding.EncodeToString([]byte(":"+personalToken)) req.Header.Add("Authorization", token) req.Header.Add("Content-Type", "application/json-patch+json") //to tell the server what type of data is being sent // send request to Azure DevOps and get response client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println(err) return } defer resp.Body.Close() // parse response body to string body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } // print response body fmt.Println("response:\n", string(body)) } func addWorkItem(organizationName string, projectName string, types string, parm addWorkItemParm) { url := "https://dev.azure.com/" + organizationName + "/" + projectName + "/_apis/wit/workitems/$" + types + "?api-version=7.0" // setting request body that contain info about task payloadTemplate := `[ { "op": "add", "path": "/fields/System.Title", "value": "%s" }, { "op": "add", "path": "/fields/System.State", "value": "New" }, { "op": "add", "path": "/fields/System.AssignedTo", "value": "%s" }, { "op": "add", "path": "/fields/System.Description", "value": "%s" }, { "op": "add", "path": "/fields/Microsoft.VSTS.Common.Priority", "value": "%s" }, { "op": "add", "path": "/fields/Microsoft.VSTS.Scheduling.Effort", "value": "%s" }, { "op": "add", "path": "/fields/Microsoft.VSTS.Common.BusinessValue", "value": "%s" }, { "op": "add", "path": "/fields/Microsoft.VSTS.Common.ValueArea", "value": "%s" }, { "op": "add", "path": "/relations/-", "value": { "rel": "System.LinkTypes.Hierarchy-Reverse", "url": "%s" } } ]` payload := []byte(fmt.Sprintf(payloadTemplate, parm.title, parm.assignTo, parm.discription, parm.priority, parm.effort, parm.businessValue, parm.valueArea, parm.url)) // create a new request and using payload req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(payload)) if err != nil { fmt.Println(err) return } // add personal access token to request header token := "Basic " + base64.StdEncoding.EncodeToString([]byte(":"+personalToken)) req.Header.Add("Authorization", token) req.Header.Add("Content-Type", "application/json-patch+json") //to tell the server what type of data is being sent // send request to Azure DevOps and get response client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println(err) return } defer resp.Body.Close() // parse response body to string body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } // print response body fmt.Println("response:\n", string(body)) //get the id of the work item that just created var responseData map[string]interface{} err = json.Unmarshal([]byte(body), &responseData) if err != nil { fmt.Println(err) return } id := strconv.FormatFloat(responseData["id"].(float64), 'f', -1, 64) //call this function to edit State editWorkItem(organizationName, projectName, id, parm) } func main() { //getWorkItemInfo() input := addWorkItemParm{ title: "test title", state: "Approved", assignTo: "YU-KAI WANG", discription: "test discription", priority: "1", effort: "1", businessValue: "1", valueArea: "Architectural", url: "https://dev.azure.com/techniquelab/lab/_workitems/edit/64", } addWorkItem("techniquelab", "lab", "Product Backlog Item", input) } ``` ![](https://hackmd.io/_uploads/SJ4zwyTTn.png) ![](https://hackmd.io/_uploads/rJFmwkTT3.png) ![](https://hackmd.io/_uploads/HJ44DJaTh.png) # Azure CI/CD 教學 ## 參考資料來源 https://ithelp.ithome.com.tw/articles/10263100 ## 使用者QA 1. CI時遇到No hosted parallelism has been purchased or granted問題, 如何解決? 一解為送表單開通免費額度,另一解為使用自己的server(需在azure-pipelines.yaml中額外設定,把pool中的name改成Default) 後續補充: 送表單後可能須等待數天才能開通服務,需耐心等待 2. 要怎麼將CI後的結果CD到k8s? 需要額外的伺服器以運行k8s? Docker image怎麼生出來? 第一步: 可以使用Pipelines中的release來執行CD流程;CI 後會有一個Artifacts,在創建CD pipeline時便是拿這個來使用 (或是直接拿git repo也可以) 第二步: 選擇部屬到k8s上 ![](https://hackmd.io/_uploads/H1e6JKLn2.png) 第三步:選擇來源(這裡直接選擇來源為Repos) ![](https://hackmd.io/_uploads/SJsT1Y83n.png) 第四步: 調整job,Kubernetes service connection 選擇環境有的,Namespace選lab(不知道為甚麼不能留白,用在Default namespace上),一定要用Configuration file不不然會直接跳錯 ![](https://hackmd.io/_uploads/HkR01FIh3.png) 儲存後,cerate release,要記得選Stages for a trigger change from automated to manual.,案create即可 3. 現存的內建自動生成CI build code指令沒辦法直接用,pipeline.yaml要怎麼設定?<br><br> dev.azure產生的build code azure-pipeline.yaml只適用於go1.11前(沒有go mod以前),而從官方document找到的這篇 go 的版本不對,稍加修改後便能用了 參考資料: https://learn.microsoft.com/en-us/azure/devops/pipelines/ecosystems/go?view=azure-devops&tabs=go-current 能成功build golang project的azure-pipeline.yaml ```yaml trigger: - main pool: vmImage: 'ubuntu-latest' steps: - task: GoTool@0 inputs: version: '1.20' - task: Go@0 inputs: command: 'get' arguments: '-d' workingDirectory: '$(System.DefaultWorkingDirectory)' - task: Go@0 inputs: command: 'build' workingDirectory: '$(System.DefaultWorkingDirectory)' - task: CopyFiles@2 inputs: TargetFolder: '$(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 inputs: artifactName: drop ```