Skip to content

Commit fb0ed59

Browse files
committed
fixes for azure devops pipelines
1 parent 89d547f commit fb0ed59

13 files changed

Lines changed: 563 additions & 271 deletions

src/services/identity-guide-service.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ export interface IdentityGuideService {
2020
): string;
2121

2222
generateAzureDevOpsGuide(
23-
subscriptionId: string,
24-
resourceGroup: string,
2523
environments: string[]
2624
): string;
2725
}
@@ -63,16 +61,16 @@ class IdentityGuideServiceImpl implements IdentityGuideService {
6361
}
6462

6563
generateAzureDevOpsGuide(
66-
subscriptionId: string,
67-
resourceGroup: string,
6864
environments: string[]
6965
): string {
70-
const environmentsArrayPowerShell = environments.map((e) => `"${e}"`).join(', ');
71-
const environmentsArrayBash = environments.map((e) => `"${e}"`).join(' ');
66+
const environmentsArrayPowerShell = environments
67+
.map((environment) => `"${environment}"`)
68+
.join(', ');
69+
const environmentsArrayBash = environments
70+
.map((environment) => `"${environment}"`)
71+
.join(' ');
7272

7373
const coreSteps = this.renderTemplate(azureDevOpsIdentitySetupCoreTemplate, {
74-
SUBSCRIPTION_ID: subscriptionId,
75-
RESOURCE_GROUP: resourceGroup,
7674
ENVIRONMENTS_ARRAY_POWERSHELL: environmentsArrayPowerShell,
7775
ENVIRONMENTS_ARRAY_BASH: environmentsArrayBash,
7876
});

src/services/init-service.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ class InitServiceImpl implements InitService {
332332
// Extract pipeline
333333
const extractPipelineConfig: ExtractPipelineConfig = {
334334
artifactDir: config.artifactDir,
335+
environments: config.environments,
335336
};
336337
const extractContent = generateExtractPipeline(extractPipelineConfig);
337338
const extractPath = path.join(pipelinesDir, 'run-apim-extractor.yml');
@@ -407,11 +408,7 @@ class InitServiceImpl implements InitService {
407408
config.environments
408409
);
409410
} else {
410-
guide = identityGuideService.generateAzureDevOpsGuide(
411-
subscriptionId,
412-
resourceGroup,
413-
config.environments
414-
);
411+
guide = identityGuideService.generateAzureDevOpsGuide(config.environments);
415412
}
416413

417414
// Save guide to file

src/templates/azure-devops/extract-pipeline.ts

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,45 @@
22
// Licensed under the MIT license.
33
/**
44
* T045: Azure DevOps extract pipeline template
5-
* Manual trigger with configuration choice and auto-PR creation
5+
* Manual trigger with environment choice, configuration choice, and auto-PR creation
66
*/
77

88
export interface ExtractPipelineConfig {
99
artifactDir: string;
10+
environments: string[];
1011
}
1112

1213
export function generateExtractPipeline(config: ExtractPipelineConfig): string {
14+
const defaultEnv = config.environments[0];
15+
const envValues = config.environments.map((env) => ` - '${env}'`).join('\n');
16+
const varGroupBlocks = config.environments
17+
.map((env) => `- \${{ if eq(parameters.ENVIRONMENT, '${env}') }}:\n - group: apim-${env}`)
18+
.join('\n');
19+
1320
return `# Azure DevOps Pipeline: Run APIM Extractor
1421
1522
trigger: none
1623
1724
parameters:
25+
- name: ENVIRONMENT
26+
type: string
27+
displayName: 'Choose which environment to extract from'
28+
default: '${defaultEnv}'
29+
values:
30+
${envValues}
1831
- name: CONFIGURATION_YAML_PATH
1932
type: string
2033
displayName: 'Choose whether to extract all APIs or use the extraction configuration file'
2134
default: 'Extract All APIs'
2235
values:
2336
- 'Extract All APIs'
2437
- 'configuration.extractor.yaml'
25-
- name: resourceGroup
26-
type: string
27-
displayName: 'Azure Resource Group'
28-
default: $(APIM_RESOURCE_GROUP)
29-
- name: serviceName
30-
type: string
31-
displayName: 'APIM Service Name'
32-
default: $(APIM_SERVICE_NAME)
3338
3439
pool:
3540
vmImage: 'ubuntu-latest'
3641
3742
variables:
38-
- group: apim-common
43+
${varGroupBlocks}
3944
4045
steps:
4146
- checkout: self
@@ -56,43 +61,32 @@ steps:
5661
displayName: 'Install dependencies'
5762
5863
- bash: |
59-
echo "##vso[task.setvariable variable=RESOURCE_GROUP]\${{ parameters.resourceGroup }}"
60-
echo "##vso[task.setvariable variable=SERVICE_NAME]\${{ parameters.serviceName }}"
61-
displayName: 'Set parameters as variables'
62-
63-
- bash: |
64-
RESOURCE_GROUP='$(RESOURCE_GROUP)'
65-
SERVICE_NAME='$(SERVICE_NAME)'
64+
APIM_RESOURCE_GROUP='$(APIM_RESOURCE_GROUP)'
65+
APIM_SERVICE_NAME='$(APIM_SERVICE_NAME)'
6666
SERVICE_CONNECTION='$(AZURE_SERVICE_CONNECTION)'
6767
SUBSCRIPTION_ID='$(AZURE_SUBSCRIPTION_ID)'
6868
69-
is_unresolved() {
70-
local value="$1"
71-
case "$value" in
72-
""|\$\(*\)) return 0 ;;
73-
*) return 1 ;;
74-
esac
75-
}
76-
77-
if is_unresolved "$RESOURCE_GROUP"; then
78-
echo "##vso[task.logissue type=error]RESOURCE_GROUP was not resolved. Verify apim-common defines APIM_RESOURCE_GROUP and is authorized for this pipeline."
69+
if [[ -z "$APIM_RESOURCE_GROUP" || "$APIM_RESOURCE_GROUP" == '$('*')' ]]; then
70+
echo "##vso[task.logissue type=error]APIM_RESOURCE_GROUP is not set. Ensure variable group 'apim-\${{ parameters.ENVIRONMENT }}' is authorized and defines APIM_RESOURCE_GROUP."
7971
exit 2
8072
fi
8173
82-
if is_unresolved "$SERVICE_NAME"; then
83-
echo "##vso[task.logissue type=error]SERVICE_NAME was not resolved. Verify apim-common defines APIM_SERVICE_NAME and is authorized for this pipeline."
74+
if [[ -z "$APIM_SERVICE_NAME" || "$APIM_SERVICE_NAME" == '$('*')' ]]; then
75+
echo "##vso[task.logissue type=error]APIM_SERVICE_NAME is not set. Ensure variable group 'apim-\${{ parameters.ENVIRONMENT }}' is authorized and defines APIM_SERVICE_NAME."
8476
exit 2
8577
fi
8678
87-
if is_unresolved "$SERVICE_CONNECTION"; then
88-
echo "##vso[task.logissue type=error]AZURE_SERVICE_CONNECTION was not resolved. Verify apim-common defines AZURE_SERVICE_CONNECTION and is authorized for this pipeline."
79+
if [[ -z "$SERVICE_CONNECTION" || "$SERVICE_CONNECTION" == '$('*')' ]]; then
80+
echo "##vso[task.logissue type=error]AZURE_SERVICE_CONNECTION is not set. Ensure variable group 'apim-\${{ parameters.ENVIRONMENT }}' is authorized and defines AZURE_SERVICE_CONNECTION."
8981
exit 2
9082
fi
9183
92-
if is_unresolved "$SUBSCRIPTION_ID"; then
93-
echo "##vso[task.logissue type=error]AZURE_SUBSCRIPTION_ID was not resolved. Verify apim-common defines AZURE_SUBSCRIPTION_ID and is authorized for this pipeline."
84+
if [[ -z "$SUBSCRIPTION_ID" || "$SUBSCRIPTION_ID" == '$('*')' ]]; then
85+
echo "##vso[task.logissue type=error]AZURE_SUBSCRIPTION_ID is not set. Ensure variable group 'apim-\${{ parameters.ENVIRONMENT }}' is authorized and defines AZURE_SUBSCRIPTION_ID."
9486
exit 2
9587
fi
88+
89+
echo "All required variables are configured for environment '\${{ parameters.ENVIRONMENT }}'"
9690
displayName: 'Validate required variables'
9791
9892
- task: AzureCLI@2
@@ -103,25 +97,11 @@ steps:
10397
scriptType: 'bash'
10498
scriptLocation: 'inlineScript'
10599
inlineScript: |
106-
SUBSCRIPTION_ID='$(AZURE_SUBSCRIPTION_ID)'
107-
108-
is_unresolved() {
109-
local value="$1"
110-
case "$value" in
111-
""|\$\(*\)) return 0 ;;
112-
*) return 1 ;;
113-
esac
114-
}
115-
116-
if is_unresolved "$SUBSCRIPTION_ID"; then
117-
echo "##vso[task.logissue type=error]AZURE_SUBSCRIPTION_ID was not resolved. Ensure apim-common is authorized and defines AZURE_SUBSCRIPTION_ID."
118-
exit 2
119-
fi
120100
npx @peterhauge/apiops-cli extract \\
121-
--resource-group "$(RESOURCE_GROUP)" \\
122-
--service-name "$(SERVICE_NAME)" \\
101+
--resource-group "$(APIM_RESOURCE_GROUP)" \\
102+
--service-name "$(APIM_SERVICE_NAME)" \\
123103
--output ${config.artifactDir} \\
124-
--subscription-id "$SUBSCRIPTION_ID"
104+
--subscription-id "$(AZURE_SUBSCRIPTION_ID)"
125105
126106
- task: AzureCLI@2
127107
displayName: 'Run APIM Extract (With Configuration)'
@@ -131,47 +111,51 @@ steps:
131111
scriptType: 'bash'
132112
scriptLocation: 'inlineScript'
133113
inlineScript: |
134-
SUBSCRIPTION_ID='$(AZURE_SUBSCRIPTION_ID)'
135-
136-
is_unresolved() {
137-
local value="$1"
138-
case "$value" in
139-
""|\$\(*\)) return 0 ;;
140-
*) return 1 ;;
141-
esac
142-
}
143-
144-
if is_unresolved "$SUBSCRIPTION_ID"; then
145-
echo "##vso[task.logissue type=error]AZURE_SUBSCRIPTION_ID was not resolved. Ensure apim-common is authorized and defines AZURE_SUBSCRIPTION_ID."
146-
exit 2
147-
fi
148114
npx @peterhauge/apiops-cli extract \\
149-
--resource-group "$(RESOURCE_GROUP)" \\
150-
--service-name "$(SERVICE_NAME)" \\
115+
--resource-group "$(APIM_RESOURCE_GROUP)" \\
116+
--service-name "$(APIM_SERVICE_NAME)" \\
151117
--output ${config.artifactDir} \\
152118
--filter configuration.extractor.yaml \\
153-
--subscription-id "$SUBSCRIPTION_ID"
119+
--subscription-id "$(AZURE_SUBSCRIPTION_ID)"
154120
155121
- task: PublishPipelineArtifact@1
156122
displayName: 'Publish artifacts'
157123
inputs:
158124
targetPath: ${config.artifactDir}
159125
artifactName: apim-artifacts
160126
161-
- script: |
127+
- bash: |
162128
BRANCH_NAME="apim-extract-$(Build.BuildId)"
129+
TARGET_BRANCH="$(Build.SourceBranch)"
130+
BUILD_ID="$(Build.BuildId)"
131+
163132
git config user.name "Azure DevOps"
164133
git config user.email "azuredevops@microsoft.com"
165134
git checkout -b "$BRANCH_NAME"
166135
git add ${config.artifactDir}
167136
if git diff --cached --quiet; then
168137
echo "No changes to commit"
138+
exit 0
139+
fi
140+
git commit -m "chore: update APIM artifacts from extract"
141+
git push origin "$BRANCH_NAME"
142+
143+
PR_PAYLOAD=$(printf '{"title":"APIM Extract - Update artifacts","description":"Auto-generated by APIM extract pipeline run %s","sourceRefName":"refs/heads/%s","targetRefName":"%s"}' \\
144+
"$BUILD_ID" "$BRANCH_NAME" "$TARGET_BRANCH")
145+
146+
HTTP_STATUS=$(curl -s -o /tmp/pr_response.json -w "%{http_code}" -X POST \\
147+
-H "Content-Type: application/json" \\
148+
-H "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \\
149+
"$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullrequests?api-version=7.1" \\
150+
-d "$PR_PAYLOAD")
151+
152+
if [ "$HTTP_STATUS" = "201" ]; then
153+
PR_ID=$(python3 -c "import json; print(json.load(open('/tmp/pr_response.json'))['pullRequestId'])" 2>/dev/null)
154+
echo "##vso[task.logissue type=warning]Pull request created: $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_git/$(Build.Repository.Name)/pullrequest/$PR_ID"
169155
else
170-
git commit -m "chore: update APIM artifacts from extract"
171-
git push origin "$BRANCH_NAME"
172-
echo "##vso[task.logissue type=warning]Branch '$BRANCH_NAME' pushed. Please create a pull request to merge the changes."
156+
echo "##vso[task.logissue type=warning]Could not auto-create PR (HTTP $HTTP_STATUS). Please create a PR from '$BRANCH_NAME' to '$TARGET_BRANCH' manually."
173157
fi
174-
displayName: 'Create branch with changes'
158+
displayName: 'Create branch and open pull request'
175159
env:
176160
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
177161
`;

src/templates/azure-devops/publish-pipeline.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ export interface PublishPipelineConfig {
1111
}
1212

1313
export function generatePublishPipeline(config: PublishPipelineConfig): string {
14+
const defaultEnvironment = config.environments[0] ?? 'dev';
1415
const envValues = config.environments.map((env) => ` - '${env}'`).join('\n');
1516

16-
const stages = config.environments.map((env, idx) => {
17-
const dependsOn = idx === 0 ? '' : ` dependsOn: Publish_${config.environments[idx - 1]}\n`;
17+
const stages = config.environments.map((env) => {
18+
const envUpper = env.toUpperCase();
1819

19-
return `${dependsOn}- stage: Publish_${env}
20+
return `- stage: Publish_${env}
2021
displayName: 'Publish to ${env}'
21-
condition: or(eq('\${{ parameters.ENVIRONMENT }}', '${env}'), eq('\${{ parameters.ENVIRONMENT }}', 'all'))
22+
condition: eq('\${{ parameters.ENVIRONMENT }}', '${env}')
2223
variables:
2324
- group: apim-${env}
2425
jobs:
@@ -37,29 +38,49 @@ export function generatePublishPipeline(config: PublishPipelineConfig): string {
3738
- task: UseNode@1
3839
displayName: 'Setup Node.js'
3940
inputs:
40-
versionSpec: '22.x'
41+
version: '22.x'
4142
42-
- script: npm ci
43+
- script: |
44+
node -v
45+
npm -v
46+
displayName: 'Verify Node.js version'
47+
48+
- script: |
49+
if [[ -f package-lock.json || -f npm-shrinkwrap.json ]]; then
50+
npm ci
51+
else
52+
npm install
53+
fi
4354
displayName: 'Install dependencies'
4455
4556
- task: replacetokens@6
4657
displayName: 'Substitute tokens in configuration.${env}.yaml'
4758
inputs:
4859
sources: 'configuration.${env}.yaml'
60+
tokenPattern: 'custom'
4961
tokenPrefix: '{#['
5062
tokenSuffix: ']#}'
63+
missingVarAction: 'keep'
64+
65+
- script: |
66+
if grep -q '{#\\[' configuration.${env}.yaml; then
67+
echo "Unresolved tokens remain in configuration.${env}.yaml"
68+
grep -o '{#\\[[^]]*\\]#}' configuration.${env}.yaml | sort -u
69+
exit 1
70+
fi
71+
displayName: 'Validate token substitution (${env})'
5172
5273
- task: AzureCLI@2
5374
displayName: 'Publish to ${env} (incremental - last commit only)'
5475
condition: ne('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo')
5576
inputs:
56-
azureSubscription: '$(AZURE_SERVICE_CONNECTION_${env.toUpperCase()})'
77+
azureSubscription: 'AZURE_SERVICE_CONNECTION_${envUpper}'
5778
scriptType: 'bash'
5879
scriptLocation: 'inlineScript'
5980
inlineScript: |
6081
npx apiops publish \\
61-
--resource-group $(APIM_RESOURCE_GROUP_${env.toUpperCase()}) \\
62-
--service-name $(APIM_SERVICE_NAME_${env.toUpperCase()}) \\
82+
--resource-group $(APIM_RESOURCE_GROUP_${envUpper}) \\
83+
--service-name $(APIM_SERVICE_NAME_${envUpper}) \\
6384
--source ${config.artifactDir} \\
6485
--overrides configuration.${env}.yaml \\
6586
--commit-id $(Build.SourceVersion) \\
@@ -69,13 +90,13 @@ export function generatePublishPipeline(config: PublishPipelineConfig): string {
6990
displayName: 'Publish to ${env} (all artifacts)'
7091
condition: eq('\${{ parameters.COMMIT_ID_CHOICE }}', 'publish-all-artifacts-in-repo')
7192
inputs:
72-
azureSubscription: '$(AZURE_SERVICE_CONNECTION_${env.toUpperCase()})'
93+
azureSubscription: 'AZURE_SERVICE_CONNECTION_${envUpper}'
7394
scriptType: 'bash'
7495
scriptLocation: 'inlineScript'
7596
inlineScript: |
7697
npx apiops publish \\
77-
--resource-group $(APIM_RESOURCE_GROUP_${env.toUpperCase()}) \\
78-
--service-name $(APIM_SERVICE_NAME_${env.toUpperCase()}) \\
98+
--resource-group $(APIM_RESOURCE_GROUP_${envUpper}) \\
99+
--service-name $(APIM_SERVICE_NAME_${envUpper}) \\
79100
--source ${config.artifactDir} \\
80101
--overrides configuration.${env}.yaml \\
81102
--subscription-id $(AZURE_SUBSCRIPTION_ID)
@@ -106,9 +127,8 @@ parameters:
106127
- name: ENVIRONMENT
107128
type: string
108129
displayName: 'Choose which environment to publish to'
109-
default: 'all'
130+
default: '${defaultEnvironment}'
110131
values:
111-
- 'all'
112132
${envValues}
113133
114134
stages:

0 commit comments

Comments
 (0)