# Azure backlog api 教學
## 最簡單的範例程式Demo
1. 首先,需取得personal access token


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 可以從這裡找到)

## 實現
```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)
}
```



# 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上

第三步:選擇來源(這裡直接選擇來源為Repos)

第四步: 調整job,Kubernetes service connection
選擇環境有的,Namespace選lab(不知道為甚麼不能留白,用在Default namespace上),一定要用Configuration file不不然會直接跳錯

儲存後,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
```