Read/Save Azure DevOps pipeline variable in shell script - azure

I am using Azure DevOps for some application deployment. I need to have a saved variable build number that would update every time I do a build successfully and send the apk/ipa to the store.
Right now, from what I read in the Azure documentation and other post on StackOverflow about this, I setup my scripts this way.
This is my pipeline variable
This is my current script
the output is:
So, it seems to update my local variable but not my pipeline variable. I am unsure why since this is the example provided EVERYWHERE.
Sources:
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash
how to increment and save variable in azure devops pipeline
Thank you for the help!
Edit 1: Ok, so it seems that there is a variable/function called counter. I haven't figured out how to use it yet, but looking into it.
Edit 2:
Updated my azure-pipelines.yml
variables:
major: 1
minor: 0
patch: 0
build: $[counter(variables['patch'], 1)]
On my pipeline it looks like this
and my fastlane (ruby script ) lane looks like this
lane :tf do
`echo $major`
`echo $minor`
`echo $patch`
`echo $build` # Nothing
`echo $MAJOR`
`echo $MINOR`
`echo $PATCH`
`echo $BUILD` # Nothing
`echo $(major)` # fails
end
those show nothing.
This azure DevOps is very depressing. It says here I can do a bash call to this variable.

Variables used with macro syntax are expanded in runtime before task is executed, that is why there is value '1' in last log, even when value previously was set in previous step to '2'.
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#runtime-expression-syntax
Try to use runtime expression instead, it is expanded in runtime

Two solutions for you.
1. Use counter expression.
counter(<prefix>, <feed>)
counter expression is for pipeline, and it has two parameters, prefix and seed. Seed is based on the prefix.
When the prefix is been set and run for the first time, the counter result will start from the feed value. But for the later run based on the same prefix, the counter result will ignore the feed value, it will be 'last time counter result + 1'
2, Change the pipeline definition directly.
For example, I can use the below python code to get and change the variable of classic pipeline:
import json
import requests
org_name = "xxx"
project_name = "xxx"
pipeline_definition_id = "xxx"
personal_access_token = "xxx"
key = 'variables'
var_name = 'BUILDNUMBER'
url = "https://dev.azure.com/"+org_name+"/"+project_name+"/_apis/build/definitions/"+pipeline_definition_id+"?api-version=6.0"
payload={}
headers = {
'Authorization': 'Basic '+personal_access_token
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
json_content = response.text
def get_content_of_json(json_content, key, var_name):
data = json.loads(json_content)
return data[key][var_name].get('value')
def change_content_of_json(json_content, key, var_name):
data = json.loads(json_content)
data[key][var_name]['value'] = str(int(get_content_of_json(json_content,key,var_name)) + 1)
return data
json_data = change_content_of_json(json_content, key, var_name)
url2 = "https://dev.azure.com/"+org_name+"/"+project_name+"/_apis/build/definitions/"+pipeline_definition_id+"?api-version=6.0"
payload2 = json.dumps(json_data)
headers2 = {
'Authorization': 'Basic '+personal_access_token,
'Content-Type': 'application/json'
}
response2 = requests.request("PUT", url2, headers=headers2, data=payload2)
Write a JSON demo for you, you can import it in your DevOps and design yourself classic pipeline based on this:
{
"options": [
{
"enabled": false,
"definition": {
"id": "5d58cc01-7c75-450c-be18-a388ddb129ec"
},
"inputs": {
"branchFilters": "[\"+refs/heads/*\"]",
"additionalFields": "{}"
}
},
{
"enabled": false,
"definition": {
"id": "a9db38f9-9fdc-478c-b0f9-464221e58316"
},
"inputs": {
"workItemType": "Bug",
"assignToRequestor": "true",
"additionalFields": "{}"
}
}
],
"variables": {
"BUILDNUMBER": {
"value": "7"
},
"system.debug": {
"value": "false",
"allowOverride": true
}
},
"properties": {},
"tags": [],
"_links": {
"self": {
"href": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/build/Definitions/359?revision=11"
},
"web": {
"href": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_build/definition?definitionId=359"
},
"editor": {
"href": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_build/designer?id=359&_a=edit-build-definition"
},
"badge": {
"href": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/build/status/359"
}
},
"jobAuthorizationScope": 2,
"jobTimeoutInMinutes": 60,
"jobCancelTimeoutInMinutes": 5,
"process": {
"phases": [
{
"steps": [
{
"environment": {},
"enabled": true,
"continueOnError": false,
"alwaysRun": false,
"displayName": "PowerShell Script",
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"condition": "succeeded()",
"task": {
"id": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
"versionSpec": "2.*",
"definitionType": "task"
},
"inputs": {
"targetType": "inline",
"filePath": "",
"arguments": "",
"script": "# Write your PowerShell commands here.\n\nWrite-Host \"Hello World\"\n\npip install requests\n",
"errorActionPreference": "stop",
"warningPreference": "default",
"informationPreference": "default",
"verbosePreference": "default",
"debugPreference": "default",
"progressPreference": "silentlyContinue",
"failOnStderr": "false",
"showWarnings": "false",
"ignoreLASTEXITCODE": "false",
"pwsh": "false",
"workingDirectory": "",
"runScriptInSeparateScope": "false"
}
},
{
"environment": {},
"enabled": true,
"continueOnError": false,
"alwaysRun": false,
"displayName": "Run a Python script",
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"condition": "succeeded()",
"task": {
"id": "6392f95f-7e76-4a18-b3c7-7f078d2f7700",
"versionSpec": "0.*",
"definitionType": "task"
},
"inputs": {
"scriptSource": "inline",
"scriptPath": "",
"script": "import json\nimport requests\n\n\norg_name = \"BowmanCP\"\nproject_name = \"BowmanCP\"\npipeline_definition_id = \"359\"\npersonal_access_token = \"OnhlbXFzd29hdXJrdGhvNGJhemJza3hhenZldnRhbXhhZTVhNDMycXZoNzRicmo3YTZjc3E=\"\n\nkey = 'variables'\nvar_name = 'BUILDNUMBER'\n\nurl = \"https://dev.azure.com/\"+org_name+\"/\"+project_name+\"/_apis/build/definitions/\"+pipeline_definition_id+\"?api-version=6.0\"\n\npayload={}\nheaders = {\n 'Authorization': 'Basic '+personal_access_token\n}\n\nresponse = requests.request(\"GET\", url, headers=headers, data=payload)\nprint(response.text)\njson_content = response.text\ndef get_content_of_json(json_content, key, var_name):\n data = json.loads(json_content)\n return data[key][var_name].get('value')\n\ndef change_content_of_json(json_content, key, var_name):\n data = json.loads(json_content)\n data[key][var_name]['value'] = str(int(get_content_of_json(json_content,key,var_name)) + 1)\n return data\n\njson_data = change_content_of_json(json_content, key, var_name)\n\n\nurl2 = \"https://dev.azure.com/\"+org_name+\"/\"+project_name+\"/_apis/build/definitions/\"+pipeline_definition_id+\"?api-version=6.0\"\n\npayload2 = json.dumps(json_data)\nheaders2 = {\n 'Authorization': 'Basic '+personal_access_token,\n 'Content-Type': 'application/json'\n}\n\nresponse2 = requests.request(\"PUT\", url2, headers=headers2, data=payload2)\n",
"arguments": "",
"pythonInterpreter": "",
"workingDirectory": "",
"failOnStderr": "false"
}
}
],
"name": "Agent job 1",
"refName": "Job_1",
"condition": "succeeded()",
"target": {
"executionOptions": {
"type": 0
},
"allowScriptsAuthAccessOption": false,
"type": 1
},
"jobAuthorizationScope": 2
}
],
"target": {
"agentSpecification": {
"identifier": "windows-2019"
}
},
"type": 1
},
"repository": {
"properties": {
"cleanOptions": "0",
"labelSources": "0",
"labelSourcesFormat": "$(build.buildNumber)",
"reportBuildStatus": "true",
"fetchDepth": "1",
"gitLfsSupport": "false",
"skipSyncSource": "false",
"checkoutNestedSubmodules": "false"
},
"id": "421488b2-be68-4b8e-8faf-8302da314071",
"type": "TfsGit",
"name": "XunitTest_Auto",
"url": "https://dev.azure.com/BowmanCP/BowmanCP/_git/XunitTest_Auto",
"defaultBranch": "refs/heads/main",
"clean": "false",
"checkoutSubmodules": false
},
"processParameters": {},
"quality": 1,
"authoredBy": {
"displayName": "Bowman Zhu",
"url": "https://spsprodsea2.vssps.visualstudio.com/A64545e3d-c12d-4c81-b77f-4de83783d9bd/_apis/Identities/af91e22a-cc35-4c8e-8af3-f49c4a1b9b6a",
"_links": {
"avatar": {
"href": "https://dev.azure.com/BowmanCP/_apis/GraphProfile/MemberAvatars/aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl"
}
},
"id": "af91e22a-cc35-4c8e-8af3-f49c4a1b9b6a",
"uniqueName": "xxx#xxx.com",
"imageUrl": "https://dev.azure.com/BowmanCP/_apis/GraphProfile/MemberAvatars/aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl",
"descriptor": "aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl"
},
"drafts": [],
"queue": {
"_links": {
"self": {
"href": "https://dev.azure.com/BowmanCP/_apis/build/Queues/18"
}
},
"id": 18,
"name": "Azure Pipelines",
"url": "https://dev.azure.com/BowmanCP/_apis/build/Queues/18",
"pool": {
"id": 9,
"name": "Azure Pipelines",
"isHosted": true
}
},
"id": 359,
"name": "ChangeVariable",
"url": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/build/Definitions/359?revision=11",
"uri": "vstfs:///Build/Definition/359",
"path": "\\",
"type": 2,
"queueStatus": 0,
"revision": 11,
"createdDate": "2022-11-07T10:14:18.003Z",
"project": {
"id": "c6358b04-e91a-4bd1-a894-1adb543134d6",
"name": "BowmanCP",
"url": "https://dev.azure.com/BowmanCP/_apis/projects/c6358b04-e91a-4bd1-a894-1adb543134d6",
"state": 1,
"revision": 178,
"visibility": 0,
"lastUpdateTime": "2022-09-05T06:02:02.693Z"
}
}

it seems to update my local variable but not my pipeline variable
Refer to this doc: Set variables in scripts
A script in your pipeline can define a variable so that it can be consumed by one of the subsequent steps in the pipeline.Set variables in scripts
The method you used to update the Pipeline Variable value is correct.
It will not work on the current task, but the updated value can be used in the next tasks.
For example:
steps:
- bash: |
echo $(TestVariable)
echo "##vso[task.setvariable variable=TestVariable;]2"
displayName: 'Bash Script'
- bash: |
echo $(TestVariable)
For the requirement about using counter expression, you can refer to my another ticket: Azure DevOps: release version

I eventually used this formula that works without having to hack the NSA and FBI to get a build number updated. It's not exactly what I wanted, but whatever, I will deal with this atm.
then using the ENV['BUILDNUMBER'] in my ruby script, and it reads the env variable with the counter. It is the type of solution I needed, although it doesn't do exactly what I want.

Related

Azure Devops Create Pipeline (Classic way) REST API

I am migrating an Azure devops organization to another organization,
I ran into a problem where i can not create a pipeline which created in the classic way (NOT A YAML).
This is the JSON
{
"name": "PP_NAME",
"folder": "",
"configuration": {
"designerJson": {
"options": [
{
"enabled": false,
"definition": {
"id": "DEF_ID"
},
"inputs": {
"branchFilters": "[\"+refs/heads/*\"]",
"additionalFields": "{}"
}
},
{
"enabled": false,
"definition": {
"id": "DEF_ID"
},
"inputs": {
"workItemType": "Task",
"assignToRequestor": "true",
"additionalFields": "{}"
}
}
],
"variables": {
"system.debug": {
"value": "false",
"allowOverride": true
}
},
"tags": [],
"process": {
"phases": [
{
"name": "Agent job 1",
"refName": "Job_1",
"condition": "succeeded()",
"target": {
"executionOptions": {
"type": 0
},
"allowScriptsAuthAccessOption": false,
"type": 1
},
"jobAuthorizationScope": "project"
}
],
"target": {
"agentSpecification": {
"identifier": "windows-2019"
}
},
"type": 1
},
"quality": "definition",
"path": "\\",
"repository": {
"id": "REPOSITORY_ID",
"name": "test 1",
"type": "TfsGit"
}
},
"path": "\\",
"type": "designerJson"
}
}
The output :
{
"$id": "1",
"innerException": null,
"message": "This API does not support creating pipelines of configuration type DesignerJson.",
"typeName": "Microsoft.Azure.Pipelines.WebApi.UnsupportedConfigurationTypeException, Microsoft.Azure.Pipelines.WebApi",
"typeKey": "UnsupportedConfigurationTypeException",
"errorCode": 0,
"eventId": 3000
}
According the the AZDO documentation, it is possible to create a pipeline with the classic way.
Thanks !
To create a classic pipeline, you can use this REST API Definitions - Create๏ผŽ
If you are not sure about the request body, you can use REST API Definitions - Get to get the definition of a classic pipeline as a reference.

How can i release only the builds that have a specific Tag?

I have a release with several artifacts and several stages. The thing is that I am trying to make a conditional, so that only the artifacts that have a certain tag are deployed.
In my case, the tag that I put to the builds is date and hours : $(Date:yyyyMMdd)
And the condition is the following:
and(succeeded(), startsWith(variables['Build.SourceBranchName'], 'refs/tags/variables[tag]'))
Where the variable [tag] is manually changed according to the day.I deploy only once a day, so this kind of tag is useful to me, since I can use it as a variable and change the variable [tag] every day that I have to deploy.
The error I have is that the condition does not work.
If I create the tag in the build that, for example is 20221011 (Date:yyyyMMdd) but when I release the artifact, it skips the release, when it would not be the case, because the variable of my [tag] I have set it, manually as "20221011". Which is the same value as the tag that i have on the build.
What am I doing wrong ?
The error I have is as follows :
Evaluating: and(succeeded(), startsWith(variables['Build.SourceBranchName'], 'refs/tags/20221011')) Expanded: and(True, startsWith('develop', 'refs/tags/20221011')) Result: False
I write a YAML for you, it can check whether pipeline run based on tag, and check whether the tag matches datetime format.
# trigger:
# - none
pool:
vmImage: ubuntu-latest
variables:
# matchTag: $[startsWith(variables['Build.SourceBranch'], 'refs/heads/main')]
isTag: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')]
steps:
- task: PowerShell#2
inputs:
targetType: 'inline'
script: |
Write-Host "Main step."
Write-Host $(Build.SourceBranch)
Write-Host $(isTag)
# xxx #This will skip the continue step.
- task: PowerShell#2
name: check_datetime_format
inputs:
targetType: 'inline'
script: |
Write-Host "Continue step."
$str = "$(Build.SourceBranch)"
#get the string after refs/tags/
$tag = $str.Substring(10)
$format = "yyyyMMdd"
Write-Host $tag
try {
[DateTime]::ParseExact($tag, $format, $null)
Write-Host "The datetime is correct"
Write-Host "##vso[task.setvariable variable=matchTime]true"
}
catch {
Write-Host "The datetime is not correct."
Write-Host "##vso[task.setvariable variable=matchTime]false"
}
condition: and(succeeded(), eq(variables.isTag, 'true'))
- task: PowerShell#2
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
Write-Host $(matchTime)
- task: PowerShell#2
condition: and(succeeded(), eq(variables.matchTime, 'true'))
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
Write-Host "The format is correct."
If you want solution for release pipeline for DevOps feature concept, then save the following data to a .json file, and then import it:
{
"source": 2,
"revision": 10,
"description": null,
"isDeleted": false,
"variables": {
"isTag": {
"value": "$[startsWith(variables['Build.SourceBranch'], 'refs/tags/')]"
}
},
"variableGroups": [],
"environments": [
{
"id": 13,
"name": "Stage 1",
"rank": 1,
"owner": {
"displayName": "Bowman Zhu",
"url": "https://spsprodsea2.vssps.visualstudio.com/A64545e3d-c12d-4c81-b77f-4de83783d9bd/_apis/Identities/af91e22a-cc35-4c8e-8af3-f49c4a1b9b6a",
"_links": {
"avatar": {
"href": "https://dev.azure.com/BowmanCP/_apis/GraphProfile/MemberAvatars/aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl"
}
},
"id": "af91e22a-cc35-4c8e-8af3-f49c4a1b9b6a",
"uniqueName": "xxx#163.com",
"imageUrl": "https://dev.azure.com/BowmanCP/_apis/GraphProfile/MemberAvatars/aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl",
"descriptor": "aad.ZGU3N2NiY2YtZTgzYy03ZDkwLWI0YTYtOTk3Nzg3NDczMzBl"
},
"variables": {},
"variableGroups": [],
"preDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 37
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 1
}
},
"deployStep": {
"id": 38
},
"postDeployApprovals": {
"approvals": [
{
"rank": 1,
"isAutomated": true,
"isNotificationOn": false,
"id": 39
}
],
"approvalOptions": {
"requiredApproverCount": null,
"releaseCreatorCanBeApprover": false,
"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false,
"enforceIdentityRevalidation": false,
"timeoutInMinutes": 0,
"executionOrder": 2
}
},
"deployPhases": [
{
"deploymentInput": {
"parallelExecution": {
"parallelExecutionType": 0
},
"agentSpecification": null,
"skipArtifactsDownload": false,
"artifactsDownloadInput": {
"downloadInputs": []
},
"queueId": 13,
"demands": [],
"enableAccessToken": false,
"timeoutInMinutes": 0,
"jobCancelTimeoutInMinutes": 1,
"condition": "succeeded()",
"overrideInputs": {}
},
"rank": 1,
"phaseType": 1,
"name": "Agent job",
"refName": null,
"workflowTasks": [
{
"environment": {},
"taskId": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
"version": "2.*",
"name": "PowerShell Script",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"targetType": "inline",
"filePath": "",
"arguments": "",
"script": "Write-Host \"Main step.\"\nWrite-Host $(Build.SourceBranch)\nWrite-Host $(isTag)",
"errorActionPreference": "stop",
"warningPreference": "default",
"informationPreference": "default",
"verbosePreference": "default",
"debugPreference": "default",
"progressPreference": "silentlyContinue",
"failOnStderr": "false",
"showWarnings": "false",
"ignoreLASTEXITCODE": "false",
"pwsh": "false",
"workingDirectory": "",
"runScriptInSeparateScope": "false"
}
},
{
"environment": {},
"taskId": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
"version": "2.*",
"name": "PowerShell Script",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "and(succeeded(), eq(variables.isTag, 'true'))",
"inputs": {
"targetType": "inline",
"filePath": "",
"arguments": "",
"script": "Write-Host \"Continue step.\"\n$str = \"$(Build.SourceBranch)\"\n#get the string after refs/tags/\n$tag = $str.Substring(10)\n$format = \"yyyyMMdd\"\nWrite-Host $tag\ntry {\n [DateTime]::ParseExact($tag, $format, $null)\n Write-Host \"The datetime is correct\"\n Write-Host \"##vso[task.setvariable variable=matchTime]true\"\n}\ncatch {\n Write-Host \"The datetime is not correct.\"\n Write-Host \"##vso[task.setvariable variable=matchTime]false\"\n}",
"errorActionPreference": "stop",
"warningPreference": "default",
"informationPreference": "default",
"verbosePreference": "default",
"debugPreference": "default",
"progressPreference": "silentlyContinue",
"failOnStderr": "false",
"showWarnings": "false",
"ignoreLASTEXITCODE": "false",
"pwsh": "false",
"workingDirectory": "",
"runScriptInSeparateScope": "false"
}
},
{
"environment": {},
"taskId": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
"version": "2.*",
"name": "PowerShell Script",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"targetType": "inline",
"filePath": "",
"arguments": "",
"script": "Write-Host $(matchTime)",
"errorActionPreference": "stop",
"warningPreference": "default",
"informationPreference": "default",
"verbosePreference": "default",
"debugPreference": "default",
"progressPreference": "silentlyContinue",
"failOnStderr": "false",
"showWarnings": "false",
"ignoreLASTEXITCODE": "false",
"pwsh": "false",
"workingDirectory": "",
"runScriptInSeparateScope": "false"
}
},
{
"environment": {},
"taskId": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
"version": "2.*",
"name": "PowerShell Script",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"retryCountOnTaskFailure": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "and(succeeded(), eq(variables.matchTime, 'true'))",
"inputs": {
"targetType": "inline",
"filePath": "",
"arguments": "",
"script": "Write-Host \"The format is correct.\"",
"errorActionPreference": "stop",
"warningPreference": "default",
"informationPreference": "default",
"verbosePreference": "default",
"debugPreference": "default",
"progressPreference": "silentlyContinue",
"failOnStderr": "false",
"showWarnings": "false",
"ignoreLASTEXITCODE": "false",
"pwsh": "false",
"workingDirectory": "",
"runScriptInSeparateScope": "false"
}
}
]
}
],
"environmentOptions": {
"emailNotificationType": "OnlyOnFailure",
"emailRecipients": "release.environment.owner;release.creator",
"skipArtifactsDownload": false,
"timeoutInMinutes": 0,
"enableAccessToken": false,
"publishDeploymentStatus": true,
"badgeEnabled": false,
"autoLinkWorkItems": false,
"pullRequestDeploymentEnabled": false
},
"demands": [],
"conditions": [
{
"name": "ReleaseStarted",
"conditionType": 1,
"value": ""
}
],
"executionPolicy": {
"concurrencyCount": 1,
"queueDepthCount": 0
},
"schedules": [],
"currentRelease": {
"id": 55,
"url": "https://vsrm.dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/Release/releases/55",
"_links": {}
},
"retentionPolicy": {
"daysToKeep": 30,
"releasesToKeep": 3,
"retainBuild": true
},
"processParameters": {},
"properties": {
"BoardsEnvironmentType": {
"$type": "System.String",
"$value": "unmapped"
},
"LinkBoardsWorkItems": {
"$type": "System.String",
"$value": "False"
}
},
"preDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"postDeploymentGates": {
"id": 0,
"gatesOptions": null,
"gates": []
},
"environmentTriggers": [],
"badgeUrl": "https://vsrm.dev.azure.com/BowmanCP/_apis/public/Release/badge/c6358b04-e91a-4bd1-a894-1adb543134d6/13/13"
}
],
"artifacts": [
{
"sourceId": "c6358b04-e91a-4bd1-a894-1adb543134d6:324",
"type": "Build",
"alias": "_Test_Tag_Trigger",
"definitionReference": {
"defaultVersionBranch": {
"id": "",
"name": ""
},
"defaultVersionSpecific": {
"id": "",
"name": ""
},
"defaultVersionTags": {
"id": "20220104",
"name": "20220104"
},
"defaultVersionType": {
"id": "latestWithBuildDefinitionBranchAndTagsType",
"name": "Latest from the build pipeline default branch with tags"
},
"definition": {
"id": "324",
"name": "Test_Tag_Trigger"
},
"definitions": {
"id": "",
"name": ""
},
"IsMultiDefinitionType": {
"id": "False",
"name": "False"
},
"project": {
"id": "c6358b04-e91a-4bd1-a894-1adb543134d6",
"name": "BowmanCP"
},
"repository": {
"id": "",
"name": ""
},
"artifactSourceDefinitionUrl": {
"id": "https://dev.azure.com/BowmanCP/_permalink/_build/index?collectionId=b1cc953d-b564-4eec-a222-84393e4406b1&projectId=c6358b04-e91a-4bd1-a894-1adb543134d6&definitionId=324",
"name": ""
}
},
"isPrimary": true,
"isRetained": false
}
],
"triggers": [],
"releaseNameFormat": "Release-$(rev:r)",
"tags": [],
"properties": {
"DefinitionCreationSource": {
"$type": "System.String",
"$value": "ReleaseNew"
},
"IntegrateBoardsWorkItems": {
"$type": "System.String",
"$value": "False"
},
"IntegrateJiraWorkItems": {
"$type": "System.String",
"$value": "false"
}
},
"id": 13,
"name": "Test_Tag_Trigger",
"path": "\\",
"projectReference": null,
"url": "https://vsrm.dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/Release/definitions/13",
"_links": {
"self": {
"href": "https://vsrm.dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_apis/Release/definitions/13"
},
"web": {
"href": "https://dev.azure.com/BowmanCP/c6358b04-e91a-4bd1-a894-1adb543134d6/_release?definitionId=13"
}
}
}
You can check the configuration on my side, and write your own release pipeline based on it.
In my situation, if the source is based on tag such as '20220104'(match the datetime format), my PowerShell script will judge whether the tag matches the datetime format, and then judge whether do the next operations.
By the way, in release pipeline of DevOps concept, it is impossible to set the git repo as the source and select the tag. Only based on build artifact can achieve this(select the tag).
If the condition passed:
If the condition didn't pass:
The logic of the JSON data is basically the same as the YAML, for your situation, I think the YAML pipeline can also be a 'release pipeline'(The actual functionality is a release, but it doesn't use the release pipeline provided by DevOps.).

Terraform compliance: how do i check that my GCP resources have a label with a given key?

I have just discovered terraform compliance and am testing it out against my terraform code that deploys resources to GCP.
GCP labels enable fine-grained cost analysis and every labels attribute is a map (bigquery_dataset#labels) so I would like to verify that every resource that has a labels argument includes a certain key that we use for analysing costs. Here is an example terraform configuration:
variable "project"{
type = string
}
provider "google" {
region = "europe-west2"
}
resource "google_bigquery_dataset" "dataset1" {
dataset_id = "dataset1"
project = var.project
labels = { "label1" : "some_value" }
}
resource "google_bigquery_dataset" "dataset2" {
dataset_id = "dataset2"
project = var.project
labels = { "anotherlabel" : "some_value" }
}
resource "google_bigquery_dataset" "dataset3" {
dataset_id = "dataset3"
project = var.project
}
dataset3 is not acceptable because it doesn't have any labels. dataset2 is unacceptable because while it does have labels, it doesn't have the one I want it to have, label1
I would like to use terraform compliance to check for these anomolies but I can't figure out how. here is my feature file, test.feature:
Feature: Terraform Compliance
Scenario: My scenario
Given I have any resource defined
When it has labels
Then it must contain label1
I then issue :
terraform init ^^ \
terraform plan --var=project=myproject --out=plan.out && \
terraform-compliance --planfile plan.out -f .
which produces this output:
terraform-compliance v1.3.24 initiated
. Converting terraform plan file.
๐Ÿšฉ Features : /tmp/tctest/
๐Ÿšฉ Plan File : /tmp/tctest/plan.out.json
๐Ÿšฉ Running tests. ๐ŸŽ‰
Feature: Terraform Compliance # /tmp/terraformcompliancetest/test.feature
Scenario: My scenario
Given I have any resource defined
When it has labels
Failure: google_bigquery_dataset.dataset2 (google_bigquery_dataset) does not have label1 property.
Failure: google_bigquery_dataset.dataset3 (google_bigquery_dataset) does not have label1 property.
Then it must contain label1
Failure:
1 features (0 passed, 1 failed)
1 scenarios (0 passed, 1 failed)
3 steps (2 passed, 1 failed)
Run 1629659085 finished within a moment
Clearly this isn't giving me what I want. I've pored over the docs but can't figure out what I'm supposed to be putting in my feature file. Can anyone help?
UPDATE As requested on twitter, here is the contents of plan.out.json
{
"format_version": "0.1",
"terraform_version": "0.14.7",
"variables": {
"project": {
"value": "myproject"
}
},
"planned_values": {
"root_module": {
"resources": [
{
"address": "google_bigquery_dataset.dataset1",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset1",
"provider_name": "registry.terraform.io/hashicorp/google",
"schema_version": 0,
"values": {
"dataset_id": "dataset1",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": {
"label1": "some_value"
},
"location": "US",
"project": "myproject",
"timeouts": null
}
},
{
"address": "google_bigquery_dataset.dataset2",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset2",
"provider_name": "registry.terraform.io/hashicorp/google",
"schema_version": 0,
"values": {
"dataset_id": "dataset2",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": {
"anotherlabel": "some_value"
},
"location": "US",
"project": "myproject",
"timeouts": null
}
},
{
"address": "google_bigquery_dataset.dataset3",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset3",
"provider_name": "registry.terraform.io/hashicorp/google",
"schema_version": 0,
"values": {
"dataset_id": "dataset3",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": null,
"location": "US",
"project": "myproject",
"timeouts": null
}
}
]
}
},
"resource_changes": [
{
"address": "google_bigquery_dataset.dataset1",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset1",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"dataset_id": "dataset1",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": {
"label1": "some_value"
},
"location": "US",
"project": "myproject",
"timeouts": null
},
"after_unknown": {
"access": true,
"creation_time": true,
"default_encryption_configuration": [],
"etag": true,
"id": true,
"labels": {},
"last_modified_time": true,
"self_link": true
}
}
},
{
"address": "google_bigquery_dataset.dataset2",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset2",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"dataset_id": "dataset2",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": {
"anotherlabel": "some_value"
},
"location": "US",
"project": "myproject",
"timeouts": null
},
"after_unknown": {
"access": true,
"creation_time": true,
"default_encryption_configuration": [],
"etag": true,
"id": true,
"labels": {},
"last_modified_time": true,
"self_link": true
}
}
},
{
"address": "google_bigquery_dataset.dataset3",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset3",
"provider_name": "registry.terraform.io/hashicorp/google",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"dataset_id": "dataset3",
"default_encryption_configuration": [],
"default_partition_expiration_ms": null,
"default_table_expiration_ms": null,
"delete_contents_on_destroy": false,
"description": null,
"friendly_name": null,
"labels": null,
"location": "US",
"project": "myproject",
"timeouts": null
},
"after_unknown": {
"access": true,
"creation_time": true,
"default_encryption_configuration": [],
"etag": true,
"id": true,
"last_modified_time": true,
"self_link": true
}
}
}
],
"configuration": {
"provider_config": {
"google": {
"name": "google",
"expressions": {
"region": {
"constant_value": "europe-west2"
}
}
}
},
"root_module": {
"resources": [
{
"address": "google_bigquery_dataset.dataset1",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset1",
"provider_config_key": "google",
"expressions": {
"dataset_id": {
"constant_value": "dataset1"
},
"labels": {
"constant_value": {
"label1": "some_value"
}
},
"project": {
"references": [
"var.project"
]
}
},
"schema_version": 0
},
{
"address": "google_bigquery_dataset.dataset2",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset2",
"provider_config_key": "google",
"expressions": {
"dataset_id": {
"constant_value": "dataset2"
},
"labels": {
"constant_value": {
"anotherlabel": "some_value"
}
},
"project": {
"references": [
"var.project"
]
}
},
"schema_version": 0
},
{
"address": "google_bigquery_dataset.dataset3",
"mode": "managed",
"type": "google_bigquery_dataset",
"name": "dataset3",
"provider_config_key": "google",
"expressions": {
"dataset_id": {
"constant_value": "dataset3"
},
"project": {
"references": [
"var.project"
]
}
},
"schema_version": 0
}
],
"variables": {
"project": {}
}
}
}
}
Writing it from mobile, so didnt try thoroughly.
Can you please try ?
Scenario: My scenario
Given I have any resource defined
When it has labels
Then it must have labels
Then it must contain label1
As you see the fix is adding a โ€œthenโ€ after โ€œwhenโ€ because all โ€œWhenโ€ directives are for filtering while โ€œThenโ€ directives drills down the resource data.
Hope it fixes :)
//Edit:
Here is the output I got ;
๐Ÿšฉ Running tests. ๐ŸŽ‰
Feature: test
Scenario: My scenario
Given I have any resource defined
When it has labels
Then it must have labels
Failure: google_bigquery_dataset.dataset2 (any resource) does not have label1 property.
Failure: google_bigquery_dataset.dataset3 (any resource) does not have label1 property.
Then it must have label1
Failure:
1 features (0 passed, 1 failed)
1 scenarios (0 passed, 1 failed)
4 steps (3 passed, 1 failed)
Had loads of help from #emre-erkunt and his reply almost gave me what I need (and will make that reply as an answer). I modified it slightly to include two scenarios:
Feature: Terraform Compliance
Scenario: Labels must be defined
Given I have resource that supports labels defined
When it has labels
Then it must have labels
Then its value must not be null
Scenario: Labels must include label1
Given I have resource that supports labels defined
When it has labels
Then it must have labels
Then it must have label1
Which returns:
terraform-compliance v1.3.24 initiated
. Converting terraform plan file.
๐Ÿšฉ Features : /tmp/tctest/
๐Ÿšฉ Plan File : /tmp/tctest/plan.out.json
๐Ÿšฉ Running tests. ๐ŸŽ‰
Feature: Terraform Compliance # /tmp/tctest/test.feature
Scenario: Labels must be defined
Given I have resource that supports labels defined
When it has labels
Then it must have labels
Failure: labels property in google_bigquery_dataset.dataset3 is considered to be Null. It is set to None.
Then its value must not be null
Failure:
Scenario: Labels must include label1
Given I have resource that supports labels defined
When it has labels
Then it must have labels
Failure: google_bigquery_dataset.dataset2 (resource that supports labels) does not have label1 property.
Failure: google_bigquery_dataset.dataset3 (resource that supports labels) does not have label1 property.
Then it must have label1
Failure:
1 features (0 passed, 1 failed)
2 scenarios (0 passed, 2 failed)
8 steps (6 passed, 2 failed)
Run 1629663083 finished within a moment
which is satisfactory and I think as close as I'm going to get to exactly what I want.
Massive thank you to Emre.

Azure Logic App, deployed using DevOps, first step "When a HTTP request is received", how to get the URL as output variable

Long title: Azure Logic App, deployed using DevOps, first step "When a HTTP request is received", how to get the URL as output variable for use in deployment of App Service calling this Logic App?
My azure application is composed of two parts:
a Logic App
an App Service (web)
which are both deployed using DevOps.
The first step of the logic app is "When a HTTP request is received".
The web has a dependency on the generated url
(e.g. https://prod-23.usgovarizona.logic.azure.us:443/workflows/.../triggers/request/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Frequest%2Frun&sv=1.0&sig=...) of the logic app.
How do I get the URL of the logic apps' first step as an output variable,
so that I can supply that value in the deployment of the App Service,
which calls the Logic App?
I looked at https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-outputs?tabs=azure-powershell but that didn't help me.
release.json
{
"source": 2,
"revision": 59,
"description": null,
"lastRelease": {
"id": 853,
"name": "Release-37",
"artifacts": [],
"_links": {},
"description": "Triggered by SMS Scheduler 2020.206.01.",
"releaseDefinition": {
"id": 14,
"projectReference": null,
"_links": {}
},
},
"variables": {
"depr_AppTeam": {
"value": "SDIS"
},
"depr_DeptAbbr": {
"value": "ENT"
},
"depr_ResourceGroupName": {
"value": "$AppTeam+ \"-\"+ $AppName +\"-\"+$Env + \"-rg\""
}
},
"variableGroups": [
43
],
"environments": [
{
"id": 24,
"name": "DEV",
"rank": 1,
"variables": {},
"variableGroups": [],
"deployStep": {
"id": 90
},
"deployPhases": [
{
"deploymentInput": {
"parallelExecution": {
"parallelExecutionType": 0
},
"agentSpecification": {
"identifier": "vs2017-win2016"
},
"skipArtifactsDownload": false,
"artifactsDownloadInput": {
"downloadInputs": []
},
"queueId": 64,
"demands": [],
"enableAccessToken": false,
"timeoutInMinutes": 0,
"jobCancelTimeoutInMinutes": 1,
"condition": "succeeded()",
"overrideInputs": {}
},
"rank": 1,
"phaseType": 1,
"name": "Agent job",
"refName": null,
"workflowTasks": [
{
"environment": {},
"taskId": "94a...",
"version": "2.*",
"name": "Azure Deployment:Create Or Update Resource Group action on $(AppTeam)-$(AppName)-$(Release.EnvironmentName)-rg",
"refName": "",
"enabled": true,
"alwaysRun": false,
"continueOnError": false,
"timeoutInMinutes": 0,
"definitionType": "task",
"overrideInputs": {},
"condition": "succeeded()",
"inputs": {
"ConnectedServiceName": "nov...",
"action": "Create Or Update Resource Group",
"resourceGroupName": "$(AppTeam)-$(AppName)-$(Release.EnvironmentName)-rg",
"location": "USGov Arizona",
"templateLocation": "Linked artifact",
"csmFileLink": "",
"csmParametersFileLink": "",
"csmFile": "$(System.DefaultWorkingDirectory)/_ent_sms_scheduler/Job1/template.json",
"csmParametersFile": "$(System.DefaultWorkingDirectory)/_ent_sms_scheduler/Job1/parameters.json",
"overrideParameters": "",
"deploymentMode": "Incremental",
"enableDeploymentPrerequisites": "None",
"deploymentGroupEndpoint": "",
"project": "",
"deploymentGroupName": "",
"copyAzureVMTags": "true",
"runAgentServiceAsUser": "false",
"userName": "",
"password": "",
"outputVariable": "",
"deploymentName": "",
"deploymentOutputs": "",
"addSpnToEnvironment": "false"
}
},
]
}
],
},
],
"triggers": [
{
"artifactAlias": "_ent_sms_scheduler",
"triggerConditions": [],
"triggerType": 1
}
],
"releaseNameFormat": "Release-$(rev:r)",
"tags": [],
"properties": {
"DefinitionCreationSource": {
"$type": "System.String",
"$value": "ReleaseNew"
},
"IntegrateJiraWorkItems": {
"$type": "System.String",
"$value": "false"
},
"IntegrateBoardsWorkItems": {
"$type": "System.String",
"$value": "False"
}
},
"id": 14,
"name": "SMS SCHEDULER",
"path": "\\",
"projectReference": null,
"url": "https://vsrm.dev.azure.com/.../.../_apis/Release/definitions/14",
"_links": {
"self": {
"href": "https://vsrm.dev.azure.com/.../.../_apis/Release/definitions/14"
},
"web": {
"href": "https://dev.azure.com/.../.../_release?definitionId=14"
}
}
}
release task:
steps:
- task: AzureResourceGroupDeployment#2
displayName: 'Azure Deployment:Create Or Update Resource Group action on $(AppTeam)-$(AppName)-$(Release.EnvironmentName)-rg'
inputs:
azureSubscription: 'ENT-eComm-Deployment-NonProd-Gov-Connection'
resourceGroupName: '$(AppTeam)-$(AppName)-$(Release.EnvironmentName)-rg'
location: 'USGov Arizona'
csmFile: '$(System.DefaultWorkingDirectory)/_ent_sms_scheduler/Job1/template.json'
csmParametersFile: '$(System.DefaultWorkingDirectory)/_ent_sms_scheduler/Job1/parameters.json'
It seems that you deploy Azure Logic App first, and then App Service.
You can add an Azure Powershell task after you deploy the Azure Logic app. Call Get-AzLogicAppTriggerCallbackUrl
command to get specific Logic App trigger callback URL. Example:
Get-AzLogicAppTriggerCallbackUrl -ResourceGroupName "ResourceGroup11" -Name "LogicApp1" -TriggerName "manual"
Value
-----
https://prod-03.westus.logic.azure.com:443/workflows/c4ed9335bc864140a11f4508d19acea3/triggers/manual/run?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=
And then you can define variables whose value is the response URL. Follow use variables as inputs and detailed sample to output this variable to following tasks.

Create the message route in azure iothub using rest api

I am able to create the "message route" in azure portal and able to route messages to servicebusqueue if the query matching, I want to create the message route using the restapi instead of using azure portal, I have seen many documents but unable to find the proper one. Whether creating the message route using restapi is possible or not? if yes,How can I achieve this and please provide the respective links to refer?
I haven't tried this through REST API, but as Roman suggested,
You can check the IotHubResource_CreateOrUpdate which will help you understand how to Create or update the metadata of an Iot hub. The usual pattern to modify a property is to retrieve the IoT hub metadata and security metadata, and then combine them with the modified values in a new body to update the IoT hub.
Sample Request:
PUT https://management.azure.com/subscriptions/91d12660-3dec-467a-be2a-213b5544ddc0/resourceGroups/myResourceGroup/providers/Microsoft.Devices/IotHubs/testHub?api-version=2018-04-01
Request Body:
{
"name": "iot-dps-cit-hub-1",
"type": "Microsoft.Devices/IotHubs",
"location": "centraluseuap",
"tags": {},
"etag": "AAAAAAFD6M4=",
"properties": {
"operationsMonitoringProperties": {
"events": {
"None": "None",
"Connections": "None",
"DeviceTelemetry": "None",
"C2DCommands": "None",
"DeviceIdentityOperations": "None",
"FileUploadOperations": "None",
"Routes": "None"
}
},
"state": "Active",
"provisioningState": "Succeeded",
"ipFilterRules": [],
"hostName": "iot-dps-cit-hub-1.azure-devices.net",
"eventHubEndpoints": {
"events": {
"retentionTimeInDays": 1,
"partitionCount": 2,
"partitionIds": [
"0",
"1"
],
"path": "iot-dps-cit-hub-1",
"endpoint": "sb://iothub-ns-iot-dps-ci-245306-76aca8e13b.servicebus.windows.net/"
},
"operationsMonitoringEvents": {
"retentionTimeInDays": 1,
"partitionCount": 2,
"partitionIds": [
"0",
"1"
],
"path": "iot-dps-cit-hub-1-operationmonitoring",
"endpoint": "sb://iothub-ns-iot-dps-ci-245306-76aca8e13b.servicebus.windows.net/"
}
},
"routing": {
"endpoints": {
"serviceBusQueues": [],
"serviceBusTopics": [],
"eventHubs": [],
"storageContainers": []
},
"routes": [],
"fallbackRoute": {
"name": "$fallback",
"source": "DeviceMessages",
"condition": "true",
"endpointNames": [
"events"
],
"isEnabled": true
}
},
"storageEndpoints": {
"$default": {
"sasTtlAsIso8601": "PT1H",
"connectionString": "",
"containerName": ""
}
},
"messagingEndpoints": {
"fileNotifications": {
"lockDurationAsIso8601": "PT1M",
"ttlAsIso8601": "PT1H",
"maxDeliveryCount": 10
}
},
"enableFileUploadNotifications": false,
"cloudToDevice": {
"maxDeliveryCount": 10,
"defaultTtlAsIso8601": "PT1H",
"feedback": {
"lockDurationAsIso8601": "PT1M",
"ttlAsIso8601": "PT1H",
"maxDeliveryCount": 10
}
},
"features": "None"
},
"sku": {
"name": "S1",
"tier": "Standard",
"capacity": 1
}
}

Resources