Skip to content

Commit 27196e1

Browse files
EMaherCopilot
andcommitted
refactor: remove all token injection from manual identity guides
Manual guide markdown files are now fully static with generic 'for each environment' instructions. No more dynamic rendering — the identity-guide-service just returns the template content as-is. This makes the guides easier to maintain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7d64320 commit 27196e1

5 files changed

Lines changed: 68 additions & 184 deletions

File tree

src/services/identity-guide-service.ts

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,26 @@
22
// Licensed under the MIT license.
33
/**
44
* Identity setup guide generator
5-
* Step-by-step instructions for service principal, RBAC, federated credentials,
6-
* pipeline secrets/service connections. Optional az CLI automation per FR-021.
5+
* Returns the static manual guide content for the selected CI provider.
76
*/
87

98
import {
109
azureDevOpsIdentityGuideTemplate,
1110
githubActionsIdentityGuideTemplate,
1211
} from '../templates/generated/embedded-markdown.js';
13-
import { renderTemplate } from '../lib/render-template.js';
1412

1513
export interface IdentityGuideService {
16-
generateGitHubActionsGuide(
17-
subscriptionId: string,
18-
resourceGroup: string,
19-
environments: string[]
20-
): string;
21-
22-
generateAzureDevOpsGuide(
23-
environments: string[]
24-
): string;
14+
generateGitHubActionsGuide(): string;
15+
generateAzureDevOpsGuide(): string;
2516
}
2617

2718
class IdentityGuideServiceImpl implements IdentityGuideService {
28-
generateGitHubActionsGuide(
29-
subscriptionId: string,
30-
resourceGroup: string,
31-
environments: string[]
32-
): string {
33-
const federatedCredentialRows = environments.map((env) =>
34-
`| \`${env}\` | \`github-env-${env}\` | \`repo:<your-github-org>/<your-github-repo>:environment:${env}\` |`
35-
).join('\n');
36-
37-
const environmentSecrets = environments.map((env) => `### ${env} environment
38-
- Create a GitHub environment named \`${env}\` in **Settings** → **Environments**
39-
- Add environment secret \`AZURE_SUBSCRIPTION_ID\` with the subscription ID used for ${env}
40-
- Add environment secret \`APIM_RESOURCE_GROUP_${env.toUpperCase()}\` with the APIM resource group for ${env}
41-
- Add environment secret \`APIM_SERVICE_NAME_${env.toUpperCase()}\` with the APIM service name for ${env}
42-
`).join('\n');
43-
44-
const environmentAzureAccessNotes = environments.map((env) =>
45-
`- **${env}** — assign **Reader** on the resource group you will store in \`APIM_RESOURCE_GROUP_${env.toUpperCase()}\`, then assign **API Management Service Contributor** on the APIM instance you will store in \`APIM_SERVICE_NAME_${env.toUpperCase()}\`.`
46-
).join('\n');
47-
48-
return renderTemplate(githubActionsIdentityGuideTemplate, {
49-
SUBSCRIPTION_ID: subscriptionId,
50-
RESOURCE_GROUP: resourceGroup,
51-
FEDERATED_CREDENTIAL_ROWS: federatedCredentialRows,
52-
ENVIRONMENT_SECRET_SECTIONS: environmentSecrets,
53-
ENVIRONMENT_AZURE_ACCESS_NOTES: environmentAzureAccessNotes,
54-
});
19+
generateGitHubActionsGuide(): string {
20+
return githubActionsIdentityGuideTemplate;
5521
}
5622

57-
generateAzureDevOpsGuide(
58-
environments: string[]
59-
): string {
60-
const serviceConnectionRows = environments.map((environment) =>
61-
`| \`${environment}\` | \`AZURE_SERVICE_CONNECTION_${environment.toUpperCase()}\` | Azure Resource Manager → Workload identity federation → subscription that contains the ${environment} APIM instance |`
62-
).join('\n');
63-
64-
const variableGroupRows = environments.map((environment) =>
65-
`| \`${environment}\` | \`apim-${environment}\` | \`AZURE_SERVICE_CONNECTION\`, \`AZURE_SUBSCRIPTION_ID\`, \`APIM_RESOURCE_GROUP\`, \`APIM_SERVICE_NAME\`, \`APIM_RESOURCE_GROUP_${environment.toUpperCase()}\`, \`APIM_SERVICE_NAME_${environment.toUpperCase()}\` |`
66-
).join('\n');
67-
68-
return renderTemplate(azureDevOpsIdentityGuideTemplate, {
69-
SERVICE_CONNECTION_ROWS: serviceConnectionRows,
70-
VARIABLE_GROUP_ROWS: variableGroupRows,
71-
});
23+
generateAzureDevOpsGuide(): string {
24+
return azureDevOpsIdentityGuideTemplate;
7225
}
7326
}
7427

src/services/init-service.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ import { generateIdentitySetupPrompt } from '../templates/copilot/identity-setup
3535
import { generateConfigureFilterPrompt } from '../templates/copilot/configure-filter-prompt.js';
3636
import { generateConfigureOverridesPrompt } from '../templates/copilot/configure-overrides-prompt.js';
3737

38-
/** Placeholder values used in generated identity setup guides */
39-
const PLACEHOLDER_SUBSCRIPTION_ID = '<your-subscription-id>';
40-
const PLACEHOLDER_RESOURCE_GROUP = '<your-resource-group>';
41-
4238
export interface GeneratedFiles {
4339
pipelines: string[];
4440
configs: string[];
@@ -453,19 +449,11 @@ class InitServiceImpl implements InitService {
453449
* Save identity setup guide to file and tell user where to find it
454450
*/
455451
private async outputIdentityGuide(config: InitConfig, generatedFiles: GeneratedFiles): Promise<void> {
456-
// Use placeholder values for the guide — users replace these with their actual Azure details
457-
const subscriptionId = PLACEHOLDER_SUBSCRIPTION_ID;
458-
const resourceGroup = PLACEHOLDER_RESOURCE_GROUP;
459-
460452
let guide: string;
461453
if (config.ciProvider === 'github-actions') {
462-
guide = identityGuideService.generateGitHubActionsGuide(
463-
subscriptionId,
464-
resourceGroup,
465-
config.environments
466-
);
454+
guide = identityGuideService.generateGitHubActionsGuide();
467455
} else {
468-
guide = identityGuideService.generateAzureDevOpsGuide(config.environments);
456+
guide = identityGuideService.generateAzureDevOpsGuide();
469457
}
470458

471459
// Save guide to file

src/templates/identity/identity-guide-azure-devops.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,31 +55,31 @@ Use **Access control (IAM)** on each resource group and APIM instance to create
5555

5656
> 📖 [Create an Azure Resource Manager service connection using workload identity federation](https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure)
5757
58+
For each environment, do the following steps:
59+
5860
1. In **Azure DevOps**, open **Project settings****Service connections****New service connection**.
5961
2. Choose **Azure Resource Manager**.
6062
3. Select **Workload identity federation**.
6163
4. Use the app registration from Step 2.
62-
5. Create one service connection per environment using the exact names below unless you also plan to edit the generated pipeline YAML and variable-group values:
63-
64-
| Environment | Required service connection name | Scope guidance |
65-
|---|---|---|
66-
{{SERVICE_CONNECTION_ROWS}}
67-
64+
5. Name the service connection `AZURE_SERVICE_CONNECTION_<ENV>` (replace `<ENV>` with the upper-case environment name) unless you plan to edit the generated pipeline YAML.
6865
6. Complete the validation flow in Azure DevOps so the service connection can issue the issuer and subject values required for federation.
6966
7. If Azure DevOps asks you to finish the federated credential in Azure, follow the linked experience or copy the issuer/subject into the app registration's **Federated credentials** blade in the Azure portal.
7067

7168
## Step 5: Create variable groups in Azure DevOps
7269

7370
> 📖 [Add and use variable groups in Azure Pipelines](https://learn.microsoft.com/azure/devops/pipelines/library/variable-groups)
7471
75-
1. Go to **Pipelines****Library****+ Variable group**.
76-
2. Create one variable group per environment:
77-
78-
| Environment | Variable group name | Required variables |
79-
|---|---|---|
80-
{{VARIABLE_GROUP_ROWS}}
72+
For each environment, do the following steps:
8173

82-
3. Include both the non-suffixed variables used by the extractor pipeline and the environment-suffixed APIM variables used by the publish pipeline.
74+
1. Go to **Pipelines****Library****+ Variable group**.
75+
2. Name the variable group `apim-<environment>` (e.g. `apim-dev`, `apim-prod`).
76+
3. Add the following variables:
77+
- `AZURE_SERVICE_CONNECTION` — the service connection name from Step 4
78+
- `AZURE_SUBSCRIPTION_ID` — the Azure subscription ID for that environment
79+
- `APIM_RESOURCE_GROUP` — the resource group containing the APIM instance
80+
- `APIM_SERVICE_NAME` — the APIM service name
81+
- `APIM_RESOURCE_GROUP_<ENV>` — same value, upper-case environment suffix (used by the publish pipeline)
82+
- `APIM_SERVICE_NAME_<ENV>` — same value, upper-case environment suffix (used by the publish pipeline)
8383
4. Authorize each variable group for pipeline use when Azure DevOps prompts you.
8484

8585
## Step 6: Create Azure DevOps environments and approvals

src/templates/identity/identity-guide-github-actions.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
77
## Before you start
88

9-
- Azure subscription: `{{SUBSCRIPTION_ID}}`
10-
- Resource group containing an APIM instance: `{{RESOURCE_GROUP}}`
9+
- An Azure subscription with an API Management instance
1110
- GitHub repository admin access
1211
- Permission in Microsoft Entra ID to create or manage app registrations
1312

@@ -44,11 +43,12 @@ Helpful documentation:
4443

4544
> 📖 [Assign Azure roles using the Azure portal](https://learn.microsoft.com/azure/role-based-access-control/role-assignments-portal)
4645
47-
1. In the **Azure portal**, open your API Management service.
46+
For each environment, do the following steps:
47+
48+
1. In the **Azure portal**, open the resource group that contains the APIM instance for that environment.
4849
2. Go to **Access control (IAM)****Add role assignment**.
49-
3. For every environment configured by `apiops init`, assign access to the app registration or service principal:
50-
{{ENVIRONMENT_AZURE_ACCESS_NOTES}}
51-
4. If you are starting with a single shared APIM resource in resource group `{{RESOURCE_GROUP}}` and subscription `{{SUBSCRIPTION_ID}}`, those placeholders are the first values to replace.
50+
3. Assign **Reader** on the resource group to the app registration.
51+
4. Open the APIM service itself and assign **API Management Service Contributor** to the app registration.
5252
5. Wait a few minutes for RBAC changes to propagate before you test the workflow.
5353

5454
## Step 4: Add federated credentials in Azure portal
@@ -64,18 +64,17 @@ Helpful documentation:
6464
- **Entity type**: **Branch**
6565
- **Branch name**: `main`
6666
- **Credential name**: `github-main-branch`
67-
4. Add one credential for each GitHub environment used by publish workflows:
68-
69-
| GitHub environment | Suggested credential name | Subject value |
70-
|---|---|---|
71-
{{FEDERATED_CREDENTIAL_ROWS}}
67+
4. For each environment, add one additional federated credential:
68+
- **Entity type**: **Environment**
69+
- **Environment name**: the GitHub environment name (must match what you create in Step 5)
70+
- **Credential name**: `github-env-<environment>`
7271

7372
## Step 5: Create GitHub environments in the web UI
7473

7574
> 📖 [Using environments for deployment](https://docs.github.com/actions/deployment/targeting-different-environments/using-environments-for-deployment)
7675
7776
1. In GitHub, go to **Settings****Environments**.
78-
2. Create an environment for each deployment target used by `apiops init`.
77+
2. For each environment, create a GitHub environment with the same name used during `apiops init`.
7978
3. Add protection rules such as required reviewers for production environments if your process needs approvals.
8079

8180
## Step 6: Add repository and environment secrets in GitHub
@@ -86,9 +85,10 @@ Helpful documentation:
8685
2. Under **Repository secrets**, create:
8786
- `AZURE_CLIENT_ID` — the Application (client) ID from the app registration
8887
- `AZURE_TENANT_ID` — the Directory (tenant) ID from the app registration
89-
3. Then configure the environment secrets below:
90-
91-
{{ENVIRONMENT_SECRET_SECTIONS}}
88+
3. For each environment, add the following **environment secrets**:
89+
- `AZURE_SUBSCRIPTION_ID` — the Azure subscription ID for that environment
90+
- `APIM_RESOURCE_GROUP_<ENV>` — the resource group containing the APIM instance (replace `<ENV>` with the upper-case environment name)
91+
- `APIM_SERVICE_NAME_<ENV>` — the APIM service name (replace `<ENV>` with the upper-case environment name)
9292

9393
## Step 7: Verify the workflow end to end
9494

tests/unit/services/identity-guide-service.test.ts

Lines changed: 31 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -9,143 +9,86 @@ import { identityGuideService } from '../../../src/services/identity-guide-servi
99

1010
describe('identity-guide-service', () => {
1111
describe('generateGitHubActionsGuide', () => {
12-
it('should include subscription ID in guide', () => {
13-
const guide = identityGuideService.generateGitHubActionsGuide(
14-
'sub-12345',
15-
'my-rg',
16-
['dev', 'prod']
17-
);
18-
expect(guide).toContain('sub-12345');
19-
});
20-
21-
it('should include resource group in guide', () => {
22-
const guide = identityGuideService.generateGitHubActionsGuide(
23-
'sub-12345',
24-
'my-rg',
25-
['dev']
26-
);
27-
expect(guide).toContain('my-rg');
12+
it('should return static GitHub Actions guide content', () => {
13+
const guide = identityGuideService.generateGitHubActionsGuide();
14+
expect(guide).toContain('APIOps GitHub Actions identity setup guide');
2815
});
2916

3017
it('should mention the Copilot prompt file and UI flow', () => {
31-
const guide = identityGuideService.generateGitHubActionsGuide(
32-
'sub-12345',
33-
'my-rg',
34-
['dev']
35-
);
18+
const guide = identityGuideService.generateGitHubActionsGuide();
3619
expect(guide).toContain('.github/prompts/apiops-setup-workflow-identity.prompt.md');
3720
expect(guide).toContain('Azure portal');
3821
expect(guide).toContain('GitHub web UI');
3922
});
4023

4124
it('should explain the GitHub identity distinction', () => {
42-
const guide = identityGuideService.generateGitHubActionsGuide(
43-
'sub-12345',
44-
'my-rg',
45-
['dev']
46-
);
25+
const guide = identityGuideService.generateGitHubActionsGuide();
4726
expect(guide).toContain('GITHUB_TOKEN');
4827
expect(guide).toContain('only for Azure and APIM access');
4928
});
5029

5130
it('should include portal-based Azure access steps', () => {
52-
const guide = identityGuideService.generateGitHubActionsGuide(
53-
'sub-12345',
54-
'my-rg',
55-
['dev']
56-
);
31+
const guide = identityGuideService.generateGitHubActionsGuide();
5732
expect(guide).toContain('API Management Service Contributor');
5833
expect(guide).toContain('Access control (IAM)');
5934
expect(guide).toContain('Federated credentials');
6035
});
6136

6237
it('should include documentation links', () => {
63-
const guide = identityGuideService.generateGitHubActionsGuide(
64-
'sub-12345',
65-
'my-rg',
66-
['dev']
67-
);
38+
const guide = identityGuideService.generateGitHubActionsGuide();
6839
expect(guide).toContain('https://learn.microsoft.com/');
6940
expect(guide).toContain('https://docs.github.com/');
7041
});
7142

72-
it('should include environment-specific secrets for each environment', () => {
73-
const guide = identityGuideService.generateGitHubActionsGuide(
74-
'sub-12345',
75-
'my-rg',
76-
['dev', 'staging', 'prod']
77-
);
78-
expect(guide).toContain('dev environment');
79-
expect(guide).toContain('staging environment');
80-
expect(guide).toContain('prod environment');
81-
expect(guide).toContain('APIM_RESOURCE_GROUP_DEV');
82-
expect(guide).toContain('APIM_RESOURCE_GROUP_STAGING');
83-
expect(guide).toContain('APIM_RESOURCE_GROUP_PROD');
43+
it('should describe environment secrets generically', () => {
44+
const guide = identityGuideService.generateGitHubActionsGuide();
45+
expect(guide).toContain('APIM_RESOURCE_GROUP_<ENV>');
46+
expect(guide).toContain('APIM_SERVICE_NAME_<ENV>');
47+
expect(guide).toContain('For each environment');
8448
});
8549

8650
it('should include security notes', () => {
87-
const guide = identityGuideService.generateGitHubActionsGuide(
88-
'sub-12345',
89-
'my-rg',
90-
['dev']
91-
);
51+
const guide = identityGuideService.generateGitHubActionsGuide();
9252
expect(guide).toContain('Security Notes');
9353
expect(guide).toContain('least-privilege');
9454
});
9555

96-
it('should create federated credential for each environment', () => {
97-
const guide = identityGuideService.generateGitHubActionsGuide(
98-
'sub-12345',
99-
'my-rg',
100-
['dev', 'prod']
101-
);
102-
expect(guide).toContain('github-env-dev');
103-
expect(guide).toContain('github-env-prod');
104-
expect(guide).toContain('environment:dev');
105-
expect(guide).toContain('environment:prod');
106-
});
107-
108-
it('should render all template placeholders', () => {
109-
const guide = identityGuideService.generateGitHubActionsGuide(
110-
'sub-12345',
111-
'my-rg',
112-
['dev']
113-
);
114-
expect(guide).not.toContain('{{');
115-
expect(guide).not.toContain('}}');
56+
it('should not contain any template placeholders', () => {
57+
const guide = identityGuideService.generateGitHubActionsGuide();
58+
expect(guide).not.toMatch(/\{\{[^}]+\}\}/);
11659
});
11760
});
11861

11962
describe('generateAzureDevOpsGuide', () => {
63+
it('should return static Azure DevOps guide content', () => {
64+
const guide = identityGuideService.generateAzureDevOpsGuide();
65+
expect(guide).toContain('Identity setup guide for APIOps extract and publish Azure DevOps Pipelines');
66+
});
67+
12068
it('should mention the Copilot prompt file and UI flow', () => {
121-
const guide = identityGuideService.generateAzureDevOpsGuide(['dev', 'prod']);
69+
const guide = identityGuideService.generateAzureDevOpsGuide();
12270
expect(guide).toContain('.github/prompts/apiops-setup-pipeline-identity.prompt.md');
123-
expect(guide).toContain('Azure DevOps web portal');
71+
expect(guide).toContain('Azure DevOps');
12472
expect(guide).toContain('Azure portal');
12573
});
12674

12775
it('should explain the Azure DevOps identity distinction', () => {
128-
const guide = identityGuideService.generateAzureDevOpsGuide(['dev', 'prod']);
76+
const guide = identityGuideService.generateAzureDevOpsGuide();
12977
expect(guide).toContain('Build Service identity');
13078
expect(guide).toContain('separate from the Azure app registration');
13179
expect(guide).toContain('Create pull request');
13280
});
13381

134-
it('should include environment-specific service connections and variable groups', () => {
135-
const guide = identityGuideService.generateAzureDevOpsGuide(['dev', 'prod']);
136-
expect(guide).toContain('AZURE_SERVICE_CONNECTION_DEV');
137-
expect(guide).toContain('AZURE_SERVICE_CONNECTION_PROD');
138-
expect(guide).toContain('apim-dev');
139-
expect(guide).toContain('apim-prod');
140-
expect(guide).toContain('AZURE_SERVICE_CONNECTION');
141-
expect(guide).toContain('APIM_RESOURCE_GROUP_DEV');
142-
expect(guide).toContain('APIM_SERVICE_NAME_PROD');
82+
it('should describe service connections and variable groups generically', () => {
83+
const guide = identityGuideService.generateAzureDevOpsGuide();
84+
expect(guide).toContain('AZURE_SERVICE_CONNECTION_<ENV>');
85+
expect(guide).toContain('apim-<environment>');
86+
expect(guide).toContain('For each environment');
14387
});
14488

145-
it('should render all template placeholders', () => {
146-
const guide = identityGuideService.generateAzureDevOpsGuide(['dev']);
89+
it('should not contain any template placeholders', () => {
90+
const guide = identityGuideService.generateAzureDevOpsGuide();
14791
expect(guide).not.toMatch(/\{\{[^}]+\}\}/);
14892
});
149-
15093
});
15194
});

0 commit comments

Comments
 (0)