---
# System prepended metadata

title: Azure Project Wiki

---

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



