diff --git a/docs/guides/environment-overrides.md b/docs/guides/environment-overrides.md index 0f355488..f1ca59a8 100644 --- a/docs/guides/environment-overrides.md +++ b/docs/guides/environment-overrides.md @@ -27,6 +27,8 @@ apiops publish \ If you ran `apiops init`, a Copilot prompt file was generated at `.github/prompts/apiops-configure-overrides.prompt.md`. Open it in VS Code and ask GitHub Copilot to help you configure environment overrides — it will guide you through setting up environment-specific values interactively. +If you didn't run `apiops init`, you can copy the prompt directly from the repository: [`src/templates/copilot/configure-overrides-prompt.md`](https://github.com/Azure/apiops-cli/blob/main/src/templates/copilot/configure-overrides-prompt.md). Save it as `.github/prompts/apiops-configure-overrides.prompt.md` in your repo and open it in VS Code. + ## IDE Autocompletion with JSON Schema A JSON Schema is available for `configuration.{env}.yaml` override files. Add yaml-language-server comment at the top of your override file. Requires yaml language extension in VSCode. diff --git a/docs/guides/filtering-resources.md b/docs/guides/filtering-resources.md index 8d0b77e8..cde5ad88 100644 --- a/docs/guides/filtering-resources.md +++ b/docs/guides/filtering-resources.md @@ -354,3 +354,5 @@ backends: ## Copilot-Assisted Configuration If you ran `apiops init`, a Copilot prompt file was generated at `.github/prompts/apiops-configure-filter.prompt.md`. Open it in VS Code and ask GitHub Copilot to help you configure your filter — it will walk you through selecting resources interactively. + +If you didn't run `apiops init`, you can copy the prompt directly from the repository: [`src/templates/copilot/configure-filter-prompt.md`](https://github.com/Azure/apiops-cli/blob/main/src/templates/copilot/configure-filter-prompt.md). Save it as `.github/prompts/apiops-configure-filter.prompt.md` in your repo and open it in VS Code. diff --git a/src/templates/copilot/configure-filter-prompt.md b/src/templates/copilot/configure-filter-prompt.md index c29ba6d9..95a1c5f9 100644 --- a/src/templates/copilot/configure-filter-prompt.md +++ b/src/templates/copilot/configure-filter-prompt.md @@ -5,14 +5,11 @@ description: 'Configure APIOps resource extraction filters' # Configure APIOps Extractor Filters -> **How to use:** Open this file in VS Code with GitHub Copilot and ask -> Copilot to help you design a `configuration.extractor.yaml` file for your -> repository. +> **How to use:** Open this file in VS Code with GitHub Copilot and ask Copilot to help you design a `configuration.extractor.yaml` file for your repository. ## Goal -Create a `configuration.extractor.yaml` file that limits APIOps extraction to -the Azure API Management resources your team wants to manage in source control. +Create a `configuration.extractor.yaml` file that limits APIOps extraction to the Azure API Management resources your team wants to manage in source control. --- @@ -20,55 +17,25 @@ the Azure API Management resources your team wants to manage in source control. These rules apply to **every** step below. Follow them strictly: -1. **Confirm before proceeding.** At the end of every step, summarize what you - learned or propose, then **STOP and wait for the user to confirm** before - moving to the next step. Never chain steps together without an explicit - "yes" / "go ahead" from the user. - - **Hard stop rule:** When you ask for confirmation, end the response there. - Do **not** include the next question, next resource type, or any forward - action in the same message. - - This hard stop applies to **step boundaries** (Step 0, Step 2, Step 3, - Step 4). In Step 1, follow the single-resource-type cadence below. -2. **Never assume or invent names.** Do not invent API, product, backend, - named value, or any other resource names. Use only names that come from the - live APIM instance or that the user explicitly provides. The local artifact - directory is not authoritative — it may be stale or empty. When unsure, ask. -3. **Default is extract-everything.** APIOps extracts **all** resources of a - type when that type is **omitted** from the filter. Only add a type to the - filter when the user wants to narrow it (SOME) or exclude it (NONE). Do not - add a type just to list every resource. -4. **Empty array means exclude all.** Setting a type to `[]` excludes every - resource of that type. Use this only when the user explicitly wants NONE. -5. **The JSON schema is the source of structure.** To determine which resource - types support sub-entries and what those sub-entries are (for example, - `apis` → `operations`, `diagnostics`, `schemas`, `releases`), consult the - `extractor-config` JSON schema referenced in the file's - `# yaml-language-server: $schema=...` comment (the public schema URL). +1. **Confirm before proceeding.** At the end of every step, summarize what you learned or propose, then **STOP and wait for the user to confirm** before moving to the next step. Never chain steps together without an explicit "yes" / "go ahead" from the user. + - **Hard stop rule:** When you ask for confirmation, end the response there. Do **not** include the next question, next resource type, or any forward action in the same message. + - This hard stop applies to **step boundaries** (Step 0, Step 2, Step 3, Step 4). In Step 1, follow the single-resource-type cadence below. +2. **Never assume or invent names.** Do not invent API, product, backend, named value, or any other resource names. Use only names that come from the live APIM instance or that the user explicitly provides. The local artifact directory is not authoritative — it may be stale or empty. When unsure, ask. +3. **Default is extract-everything.** APIOps extracts **all** resources of a type when that type is **omitted** from the filter. Only add a type to the filter when the user wants to narrow it (SOME) or exclude it (NONE). Do not add a type just to list every resource. +4. **Empty array means exclude all.** Setting a type to `[]` excludes every resource of that type. Use this only when the user explicitly wants NONE. +5. **The JSON schema is the source of structure.** To determine which resource types support sub-entries and what those sub-entries are (for example, `apis` → `operations`, `diagnostics`, `schemas`, `releases`), consult the `extractor-config` JSON schema referenced in the file's `# yaml-language-server: $schema=...` comment (the public schema URL). --- ## Step 0 — Determine the Authoritative Resource List -The filter runs at **extraction time against the live Azure API Management -instance**. The local artifact directory may be stale, partial, or empty, so it -is **not** an authoritative list of what exists in Azure. Establish the source -of truth first: - -1. **Prefer querying the live APIM instance.** Ask the user for (or reuse if - already known) the subscription ID, resource group, and APIM service name, - and whether the Azure CLI is logged in. If Azure is reachable, enumerate the - resource types and names directly from the instance (for example with - `az apim` / `az rest` calls) and use that as the source of truth. -2. **Fallback when Azure cannot be queried.** Do **not** treat the local - artifacts as the definitive list. Instead, in Step 1 ask the user - type-by-type; for SOME, the user must provide the resource (and - sub-resource) names themselves. -3. Check whether `configuration.extractor.yaml` already exists (it may have - been created by `apiops init`). If it exists, note its current contents — - you will update it in place rather than overwriting it. - -Tell the user which mode you will use (live-Azure list vs. user-provided -names), and confirm the connection details if querying Azure. +The filter runs at **extraction time against the live Azure API Management instance**. The local artifact directory may be stale, partial, or empty, so it is **not** an authoritative list of what exists in Azure. Establish the source of truth first: + +1. **Prefer querying the live APIM instance.** Ask the user for (or reuse if already known) the subscription ID, resource group, and APIM service name, and whether the Azure CLI is logged in. If Azure is reachable, enumerate the resource types and names directly from the instance (for example with `az apim` / `az rest` calls) and use that as the source of truth. +2. **Fallback when Azure cannot be queried.** Do **not** treat the local artifacts as the definitive list. Instead, in Step 1 ask the user type-by-type; for SOME, the user must provide the resource (and sub-resource) names themselves. +3. Check whether `configuration.extractor.yaml` already exists (it may have been created by `apiops init`). If it exists, note its current contents — you will update it in place rather than overwriting it. + +Tell the user which mode you will use (live-Azure list vs. user-provided names), and confirm the connection details if querying Azure. **STOP. Do not proceed until the user confirms the source of truth.** @@ -76,64 +43,34 @@ names), and confirm the connection details if querying Azure. ## Step 1 — Decide Scope Per Resource Type (one type at a time) -Walk through the resource types **one type at a time**. For each type, ask the -user which scope they want: +Walk through the resource types **one type at a time**. For each type, ask the user which scope they want: -- **Extract ALL** — include every resource of this type. Leave this type - **out** of the filter (APIOps extracts everything by default). -- **Extract NONE** — exclude all resources of this type. Add the type with an - empty array: `tags: []`. -- **Extract SOME** — include only specific resources. The user provides which - names (or wildcard patterns) to include. Matching is case-insensitive and - supports `*` and `?` wildcards. +- **Extract ALL** — include every resource of this type. Leave this type **out** of the filter (APIOps extracts everything by default). +- **Extract NONE** — exclude all resources of this type. Add the type with an empty array: `tags: []`. +- **Extract SOME** — include only specific resources. The user provides which names (or wildcard patterns) to include. Matching is case-insensitive and supports `*` and `?` wildcards. **Single-resource-type cadence for Step 1:** -- Ask about exactly **one** resource type at a time. Do not batch multiple - types into one prompt. -- **Ask ALL / NONE / SOME first.** Do **not** enumerate or query any names up - front. For ALL or NONE, record the answer and move on — no enumeration is - needed. +- Ask about exactly **one** resource type at a time. Do not batch multiple types into one prompt. +- **Ask ALL / NONE / SOME first.** Do **not** enumerate or query any names up front. For ALL or NONE, record the answer and move on — no enumeration is needed. - **Only when the user answers SOME**, then gather names: - - If you can query the live APIM instance, list that type's names from Azure - to help the user choose. - - Otherwise, ask the user to provide the names/patterns. Do **not** invent - names or pull them from the local artifacts. -- When the user answers a type unambiguously, record the decision and move to - the next type. -- **Update `configuration.extractor.yaml` immediately after each decision that - affects the file** (SOME or NONE adds/updates that type's section; ALL means - no change since the type is omitted). Keep the file in sync as you go rather - than waiting until the end. + - If you can query the live APIM instance, list that type's names from Azure to help the user choose. + - Otherwise, ask the user to provide the names/patterns. Do **not** invent names or pull them from the local artifacts. +- When the user answers a type unambiguously, record the decision and move to the next type. +- **Update `configuration.extractor.yaml` immediately after each decision that affects the file** (SOME or NONE adds/updates that type's section; ALL means no change since the type is omitted). Keep the file in sync as you go rather than waiting until the end. - Only pause for clarification when the answer is ambiguous. -Resource types to consider (ask only about types that exist in the -instance/artifacts or that the user mentions): +Resource types to consider (ask only about types that exist in the instance/artifacts or that the user mentions): -`apis`, `products`, `namedValues`, `backends`, `loggers`, `diagnostics`, -`tags`, `versionSets`, `policyFragments`, `gateways`, `groups`, -`subscriptions`, `schemas`, `policies`, `workspaces`. +`apis`, `products`, `namedValues`, `backends`, `loggers`, `diagnostics`, `tags`, `versionSets`, `policyFragments`, `gateways`, `groups`, `subscriptions`, `schemas`, `policies`, `workspaces`. -> **APIs can be filtered at the sub-resource level.** Whenever `apis` is SOME -> and specific API names are listed in the filter, **ask about each listed -> API's sub-resources** — `operations`, `diagnostics`, `schemas`, and -> `releases`. The user may want everything for that API, or only a subset -> (for example, a single revision or release). Omit a sub-filter to include -> all of that sub-type; set it to `[]` to exclude all. +> **APIs can be filtered at the sub-resource level.** Whenever `apis` is SOME and specific API names are listed in the filter, **ask about each listed API's sub-resources** — `operations`, `diagnostics`, `schemas`, and `releases`. The user may want everything for that API, or only a subset (for example, a single revision or release). Omit a sub-filter to include all of that sub-type; set it to `[]` to exclude all. -> **Workspaces apply only if the APIM instance uses workspaces.** Skip this -> type entirely if there are no workspaces. When a user wants SOME for -> `workspaces`, each workspace can also be narrowed by its own sub-resources -> (`apis`, `backends`, `diagnostics`, `groups`, `loggers`, `namedValues`, -> `policyFragments`, `products`, `schemas`, `subscriptions`, `tags`, -> `versionSets`). Omit a sub-filter to include all of that sub-type; set it to -> `[]` to exclude all. Only offer this depth if the user wants it. +> **Workspaces apply only if the APIM instance uses workspaces.** Skip this type entirely if there are no workspaces. When a user wants SOME for `workspaces`, each workspace can also be narrowed by its own sub-resources (`apis`, `backends`, `diagnostics`, `groups`, `loggers`, `namedValues`, `policyFragments`, `products`, `schemas`, `subscriptions`, `tags`, `versionSets`). Omit a sub-filter to include all of that sub-type; set it to `[]` to exclude all. Only offer this depth if the user wants it. -> **Service-level `policies` is effectively a single global policy.** For this -> type, ask only **include (ALL)** or **exclude (NONE)** — SOME does not apply. +> **Service-level `policies` is effectively a single global policy.** For this type, ask only **include (ALL)** or **exclude (NONE)** — SOME does not apply. -After all types are decided, summarize the per-type decisions and **STOP for -confirmation** before generating YAML. +After all types are decided, summarize the per-type decisions and **STOP for confirmation** before generating YAML. --- @@ -141,44 +78,30 @@ confirmation** before generating YAML. Based on the recorded decisions: -1. Recommend the smallest filter that safely captures the intended scope - (remember: omitted types are fully extracted, so only NONE/SOME types - appear in the file). +1. Recommend the smallest filter that safely captures the intended scope (remember: omitted types are fully extracted, so only NONE/SOME types appear in the file). 2. Explain any tradeoffs between broad and narrow filters. -3. Call out any risk of accidentally excluding required dependencies — for - example, excluding a named value or backend that an included API's policy - references. +3. Call out any risk of accidentally excluding required dependencies — for example, excluding a named value or backend that an included API's policy references. -If the user is unsure, recommend a conservative filter that is easy to refine, -then **STOP for confirmation**. +If the user is unsure, recommend a conservative filter that is easy to refine, then **STOP for confirmation**. --- ## Step 3 — Generate `configuration.extractor.yaml` -> **Note:** The file `configuration.extractor.yaml` may already exist if the -> user ran `apiops init`. If it exists, **update it in place** rather than -> overwriting unrelated content. +> **Note:** The file `configuration.extractor.yaml` may already exist if the user ran `apiops init`. If it exists, **update it in place** rather than overwriting unrelated content. Create the YAML file content reflecting the confirmed decisions. Requirements: -- **Preserve the existing schema comment.** If the file already has a - `# yaml-language-server: $schema=...` line (as `apiops init` generates), keep - it **exactly as-is** — it already points at the correct schema version. Only - if the file has **no** schema comment, add one referencing the current schema - version: +- **Preserve the existing schema comment.** If the file already has a `# yaml-language-server: $schema=...` line (as `apiops init` generates), keep it **exactly as-is** — it already points at the correct schema version. Only if the file has **no** schema comment, add one referencing the current schema version: `# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/apiops-cli/main/schemas/v1/extractor-config.schema.json` - Output valid YAML. -- Only include resource types the user chose to narrow (SOME) or exclude - (NONE). Leave ALL types out of the file. -- Use only names/patterns that exist in the artifacts or that the user - provided — do not invent names. +- Only include resource types the user chose to narrow (SOME) or exclude (NONE). Leave ALL types out of the file. +- Use only names/patterns that exist in the artifacts or that the user provided — do not invent names. - Add a short comment only when it explains a non-obvious choice. -Show the generated file and **STOP for confirmation** before treating it as -final. +Show the generated file and **STOP for confirmation** before treating it as final. --- @@ -187,9 +110,8 @@ final. Before finishing: 1. Review the generated YAML for syntax issues and schema validity. -2. Confirm the filters align with the user's intended extraction scope, and - that no type the user wanted is accidentally excluded or fully extracted. +2. Confirm the filters align with the user's intended extraction scope, and that no type the user wanted is accidentally excluded or fully extracted. 3. Remind the user to run the extractor and inspect the artifact output. -If the extractor output is too broad or too narrow, help the user refine the -filter file iteratively. +If the extractor output is too broad or too narrow, help the user refine the filter file iteratively. + diff --git a/src/templates/copilot/configure-overrides-prompt.md b/src/templates/copilot/configure-overrides-prompt.md index 7bbdedd8..545bbaed 100644 --- a/src/templates/copilot/configure-overrides-prompt.md +++ b/src/templates/copilot/configure-overrides-prompt.md @@ -5,14 +5,11 @@ description: 'Configure APIOps environment overrides' # Configure APIOps Environment Overrides -> **How to use:** Open this file in VS Code with GitHub Copilot and ask -> Copilot to help you create environment-specific APIOps override files. +> **How to use:** Open this file in VS Code with GitHub Copilot and ask Copilot to help you create environment-specific APIOps override files. ## Goal -Create one `configuration.{environment}.yaml` file per deployment environment -so APIOps publish runs can promote the same artifacts across environments with -environment-specific settings. +Create one `configuration.{environment}.yaml` file per deployment environment so APIOps publish runs can promote the same artifacts across environments with environment-specific settings. --- @@ -20,57 +17,31 @@ environment-specific settings. These rules apply to **every** step below. Follow them strictly: -1. **Confirm before proceeding.** At the end of every step, summarize what you - learned or propose, then **STOP and wait for the user to confirm** before - moving to the next step. Never chain steps together without an explicit - "yes" / "go ahead" from the user. - - **Hard stop rule:** When you ask for confirmation, end the response there. - Do **not** include the next question, next override, or any forward action - in the same message. - - This hard stop applies to **step boundaries** (Step 0, Step 1, Step 2, - Step 3, Step 5, Step 6). In Step 4, follow the single-setting cadence below. -2. **Never assume a value.** Do not invent backend URLs, service URLs, - resource IDs, instrumentation keys, secret names, Key Vault URLs, or token - names. If you don't know a value, **ask the user**. -3. **Do not tokenize everything.** A `{#[TOKEN_NAME]#}` placeholder is only for - values the user explicitly wants injected by the pipeline (see Step 4 for - how to classify each value). Many values are plain, non-sensitive settings - that should be written literally. -4. **Ask, don't guess, about pipeline tokens.** Only use a token after the user - has told you that token exists (or will be added) in their pipeline. -5. **The JSON schema is the source of structure.** To determine the valid - shape of an override entry and its nested properties (for example a Key - Vault named value's `keyVault.secretIdentifier` / `identityClientId`), - consult the `override-config` JSON schema referenced in each file's - `# yaml-language-server: $schema=...` comment (the public schema URL). Do - **not** rely on the `apiops-cli` source repository — end users only have - the built npm package and the published schema URL. +1. **Confirm before proceeding.** At the end of every step, summarize what you learned or propose, then **STOP and wait for the user to confirm** before moving to the next step. Never chain steps together without an explicit "yes" / "go ahead" from the user. + - **Hard stop rule:** When you ask for confirmation, end the response there. Do **not** include the next question, next override, or any forward action in the same message. + - This hard stop applies to **step boundaries** (Step 0, Step 1, Step 2, Step 3, Step 5, Step 6). In Step 4, follow the single-setting cadence below. +2. **Never assume a value.** Do not invent backend URLs, service URLs, resource IDs, instrumentation keys, secret names, Key Vault URLs, or token names. If you don't know a value, **ask the user**. +3. **Do not tokenize everything.** A `{#[TOKEN_NAME]#}` placeholder is only for values the user explicitly wants injected by the pipeline (see Step 4 for how to classify each value). Many values are plain, non-sensitive settings that should be written literally. +4. **Ask, don't guess, about pipeline tokens.** Only use a token after the user has told you that token exists (or will be added) in their pipeline. +5. **The JSON schema is the source of structure.** To determine the valid shape of an override entry and its nested properties (for example a Key Vault named value's `keyVault.secretIdentifier` / `identityClientId`), consult the `override-config` JSON schema referenced in each file's `# yaml-language-server: $schema=...` comment (the public schema URL). Do **not** rely on the `apiops-cli` source repository — end users only have the built npm package and the published schema URL. --- ## Step 0 — Detect and Confirm Environments -Before asking the user anything else, look for existing environment -configuration files in the repository: +Before asking the user anything else, look for existing environment configuration files in the repository: -1. Search for files matching `configuration.*.yaml` (excluding - `configuration.extractor.yaml`). The `*` portion is the environment name. -2. Also check CI/CD workflow files (`.github/workflows/` or - `.azdo/pipelines/`) for environment references. +1. Search for files matching `configuration.*.yaml` (excluding `configuration.extractor.yaml`). The `*` portion is the environment name. +2. Also check CI/CD workflow files (`.github/workflows/` or `.azdo/pipelines/`) for environment references. -Then **present the detected environments to the user** and ask which ones they -want override files for: +Then **present the detected environments to the user** and ask which ones they want override files for: -> "I found these environments: ``. Which of these do you want to create -> or update override files for? If you deploy to other environments I didn't -> detect, list them too." +> "I found these environments: ``. Which of these do you want to create or update override files for? If you deploy to other environments I didn't detect, list them too." If no config files are found, ask: -> "What environments do you deploy to? Common patterns include `dev, stage, prod` -> or `stage, prod` (if dev shares the same APIM instance as stage)." +> "What environments do you deploy to? Common patterns include `dev, stage, prod` or `stage, prod` (if dev shares the same APIM instance as stage)." -**STOP. Do not proceed until the user has explicitly confirmed the exact list -of environments to work on.** +**STOP. Do not proceed until the user has explicitly confirmed the exact list of environments to work on.** --- @@ -78,14 +49,11 @@ of environments to work on.** Once the environment list is confirmed, collect the following: -1. **Existing override config files** — If `configuration.{env}.yaml` files - already exist: +1. **Existing override config files** — If `configuration.{env}.yaml` files already exist: - Use those as the starting point. - Ask whether the user wants to update them or start fresh. -2. **APIM artifacts location** — Ask the user where the APIOps artifact - directory is (default: `./apim-artifacts`). You will inspect the artifacts - in the next step. +2. **APIM artifacts location** — Ask the user where the APIOps artifact directory is (default: `./apim-artifacts`). You will inspect the artifacts in the next step. Summarize what you've learned and **STOP for confirmation** before continuing. @@ -95,8 +63,7 @@ Summarize what you've learned and **STOP for confirmation** before continuing. Using the artifact directory confirmed in Step 1: -1. Scan the artifacts for references to **external resources** — the things - that typically differ between environments. Examples: +1. Scan the artifacts for references to **external resources** — the things that typically differ between environments. Examples: - API `serviceUrl` values - Backend service URLs and linked `resourceId`s - Named values (secrets and plain config values) @@ -104,32 +71,15 @@ Using the artifact directory confirmed in Step 1: - Diagnostic `loggerId` references - Gateway or VNet references - Policy fragment references to external endpoints - - Workspace-scoped resources (only if the APIM instance uses **workspaces**) - — workspaces can contain their own APIs, backends, named values, loggers, - etc. that may need per-environment overrides - - > **Note:** References to sub-resources of the same APIM instance (e.g., - > one API referencing another API's policy) are handled automatically by - > APIOps and do **not** need overrides. - -2. Produce a **plain list of override candidates** grouped by resource type - (e.g., "APIs needing a serviceUrl: `src-graphql-passthrough`, - `src-rest-versioned-v1`…"). Do **not** decide yet which are tokens versus - literals — that happens in Step 3. - -3. Present this list and ask the user to confirm which items actually need - per-environment overrides and which can be left as-is. - -4. Once the candidate list is confirmed, **create the stub override files** — - one `configuration.{env}.yaml` per confirmed environment — containing every - confirmed candidate as an entry with the correct `name` and structure but - **blank values** (e.g., empty strings `""` or empty `properties`). This - shows the shape of each file; the actual values are filled in during - Step 4. **Preserve any existing schema comment.** If a file already has a - `# yaml-language-server: $schema=...` line (as `apiops init` generates), - keep it **exactly as-is** — it already points at the correct schema version. - Only when creating a brand-new file with no schema comment, add one - referencing the current schema version: + - Workspace-scoped resources (only if the APIM instance uses **workspaces**) — workspaces can contain their own APIs, backends, named values, loggers, etc. that may need per-environment overrides + + > **Note:** References to sub-resources of the same APIM instance (e.g., one API referencing another API's policy) are handled automatically by APIOps and do **not** need overrides. + +2. Produce a **plain list of override candidates** grouped by resource type (e.g., "APIs needing a serviceUrl: `src-graphql-passthrough`, `src-rest-versioned-v1`…"). Do **not** decide yet which are tokens versus literals — that happens in Step 3. + +3. Present this list and ask the user to confirm which items actually need per-environment overrides and which can be left as-is. + +4. Once the candidate list is confirmed, **create the stub override files** — one `configuration.{env}.yaml` per confirmed environment — containing every confirmed candidate as an entry with the correct `name` and structure but **blank values** (e.g., empty strings `""` or empty `properties`). This shows the shape of each file; the actual values are filled in during Step 4. **Preserve any existing schema comment.** If a file already has a `# yaml-language-server: $schema=...` line (as `apiops init` generates), keep it **exactly as-is** — it already points at the correct schema version. Only when creating a brand-new file with no schema comment, add one referencing the current schema version: `# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/apiops-cli/main/schemas/v1/override-config.schema.json` **STOP for confirmation before continuing to Step 3.** @@ -138,43 +88,32 @@ Using the artifact directory confirmed in Step 1: ## Step 3 — Confirm Available Pipeline Tokens -Before filling in any value, **ask the user** which environment variables / -pipeline variables are available for this environment, and get the **exact, -case-sensitive** token names. +Before asking the user anything, **first inspect the pipeline files** in the repository (`.github/workflows/` or `.azdo/pipelines/`) to discover what environment variables and secrets are already defined. Look for `env:` blocks, `vars.*` and `secrets.*` references, and variable group entries. + +Then, try running `gh --version` to check if the `gh` CLI is available. If the command succeeds, ask the user for permission to query pipeline variables automatically: -**Known `apiops init` tokens.** If the user scaffolded the repo with -`apiops init`, the generated publish pipeline already wires up a standard set -of pipeline variables / secrets that are usable as `{#[...]#}` tokens. You may -**ask** whether the user has these (substitute `` with the uppercased -environment name, e.g. `STAGE`): +> "I can check your GitHub Actions secrets and variables automatically using the `gh` CLI. May I run `gh variable list` and `gh secret list` (names only — no values are revealed) to see what's available? Or would you prefer to list them manually?" + +If the user agrees, run the commands and use the results. If `gh` is not available or the user declines, fall back to asking manually. + +Regardless of how the variable list was obtained, **validate** that any token names the user provides actually appear in the pipeline files or in the queried variable list (if `gh` was used). Warn the user if a token name doesn't match anything found. + +**Known `apiops init` tokens.** If the user scaffolded the repo with `apiops init`, the generated publish pipeline already wires up a standard set of pipeline variables / secrets that are usable as `{#[...]#}` tokens. Cross-reference with any pipeline files you inspected or variables you queried. The standard tokens are (substitute `` with the uppercased environment name, e.g. `STAGE`): - `AZURE_SUBSCRIPTION_ID_` (e.g., `AZURE_SUBSCRIPTION_ID_STAGE`) - `APIM_RESOURCE_GROUP_` (e.g., `APIM_RESOURCE_GROUP_STAGE`) - `APIM_SERVICE_NAME_` (e.g., `APIM_SERVICE_NAME_STAGE`) -- GitHub Actions only: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` (used for login; - rarely needed inside override files) +- GitHub Actions only: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` (used for login; rarely needed inside override files) -> Note: some repos use `AZURE_SUBSCRIPTION_ID` (global, no env suffix) as the -> default init-generated name. Others customize to `AZURE_SUBSCRIPTION_ID_`. -> Ask the user which naming pattern exists in their pipeline. +> Note: some repos use `AZURE_SUBSCRIPTION_ID` (global, no env suffix) as the default init-generated name. Others customize to `AZURE_SUBSCRIPTION_ID_`. Check the pipeline files or queried variables to see which pattern is used. -Ask the user to **confirm which of these they actually have**, then ask whether -there are **any other** tokens: +After combining what you found from the pipeline inspection and any `gh` query, present the discovered token list to the user and ask them to confirm it is complete: -> "If you ran `apiops init`, you may already have these pipeline variables: -> `AZURE_SUBSCRIPTION_ID` (or `AZURE_SUBSCRIPTION_ID_STAGE`), -> `APIM_RESOURCE_GROUP_STAGE`, `APIM_SERVICE_NAME_STAGE`. -> Which of these do you have? And are there any other pipeline variables (with -> their exact, case-sensitive names) I should use as tokens?" +> "Based on the pipeline files and variables I found, the available tokens appear to be: ``. Are these correct? Are there any other pipeline variables (with their exact, case-sensitive names) I should use as tokens?" -> **Beyond the known `init` tokens, do NOT propose, guess, or pre-populate -> tokens.** Do not invent token names for secrets, URLs, or resource segments. -> Let the user tell you what else exists. +> **Beyond the confirmed tokens, do NOT propose, guess, or pre-populate tokens.** Do not invent token names for secrets, URLs, or resource segments. Let the user tell you what else exists. -Record this list of confirmed tokens. You may **only** use these token names -later. Never invent a token, and never wrap a value in `{#[TOKEN_NAME]#}` -unless its token is on this confirmed list. The publish step fails if a token -has no matching pipeline variable. +Record this list of confirmed tokens. You may **only** use these token names later. Never invent a token, and never wrap a value in `{#[TOKEN_NAME]#}` unless its token is on this confirmed list. The publish step fails if a token has no matching pipeline variable. **STOP and confirm the token list before continuing to Step 4.** @@ -182,23 +121,17 @@ has no matching pipeline variable. ## Step 4 — Fill In Each Override With the User -Walk through the stub entries created in Step 2 **one setting/property at a -time** (for example one `resourceId` or one `value` field), not one whole -override object at a time. Do not batch multiple settings in a single prompt. +Walk through the stub entries created in Step 2 **one setting/property at a time** (for example one `resourceId` or one `value` field), not one whole override object at a time. Do not batch multiple settings in a single prompt. **Single-setting cadence for Step 4:** - Ask for exactly one setting when information is missing. - If the user provides that setting unambiguously, write it immediately. - After writing it, proceed by asking for the next single missing setting. -- Only pause for confirmation when the user explicitly asks for confirmation, - or when the value is ambiguous and you need clarification. -- Do not ask the user to reconfirm a setting they just provided unless there is - a concrete ambiguity. +- Only pause for confirmation when the user explicitly asks for confirmation, or when the value is ambiguous and you need clarification. +- Do not ask the user to reconfirm a setting they just provided unless there is a concrete ambiguity. -For each override value, classify how it should be supplied using the confirmed -token list from Step 3. There are three kinds of values — do not default to -tokens: +For each override value, classify how it should be supplied using the confirmed token list from Step 3. There are three kinds of values — do not default to tokens: | Kind | When to use | How it's written | | --- | --- | --- | @@ -208,29 +141,18 @@ tokens: For each candidate value, ask the user something like: -> "For `.`, what is the value in ****? Is it a fixed -> value I can write directly, a secret your pipeline injects via a token, or a -> Key Vault secret?" +> "For `.`, what is the value in ****? Is it a fixed value I can write directly, a secret your pipeline injects via a token, or a Key Vault secret?" Concrete guidance to follow while classifying: -- **API service URLs and backend URLs** — Ask the user for the actual URL per - environment. These are usually plain literal values, **not tokens**, unless - the user specifically wants them injected by the pipeline. -- **Application Insights instrumentation keys** — These are **not secrets**. - Write the value the user provides directly, or leave the extracted value in - place. Do **not** wrap them in `{#[TOKEN_NAME]#}` unless the user asks. -- **Resource IDs (loggers, backends, diagnostics)** — Usually literal values. - Only the subscription ID / resource group / service name segments need - tokenizing if the user wants them injected; ask first. -- **Connection strings, API keys, passwords** — These are secrets. Use a - pipeline token or a Key Vault reference based on the user's preference. +- **API service URLs and backend URLs** — Ask the user for the actual URL per environment. These are usually plain literal values, **not tokens**, unless the user specifically wants them injected by the pipeline. +- **Application Insights instrumentation keys** — These are **not secrets**. Write the value the user provides directly, or leave the extracted value in place. Do **not** wrap them in `{#[TOKEN_NAME]#}` unless the user asks. +- **Resource IDs (loggers, backends, diagnostics)** — Usually literal values. Only the subscription ID / resource group / service name segments need tokenizing if the user wants them injected; ask first. +- **Connection strings, API keys, passwords** — These are secrets. Use a pipeline token or a Key Vault reference based on the user's preference. ### Key Vault reference — the correct pattern -A Key Vault-backed named value uses a **`keyVault.secretIdentifier`** that is a -**full secret URL**. Do **not** create a separate named value just to hold a -Key Vault base URL, and do **not** concatenate a token with a secret name. +A Key Vault-backed named value uses a **`keyVault.secretIdentifier`** that is a **full secret URL**. Do **not** create a separate named value just to hold a Key Vault base URL, and do **not** concatenate a token with a secret name. Correct — literal full secret identifier: @@ -243,8 +165,7 @@ namedValues: identityClientId: "{#[MANAGED_IDENTITY_CLIENT_ID]#}" ``` -Also acceptable — tokenize the whole secret identifier when the user wants the -pipeline to supply it: +Also acceptable — tokenize the whole secret identifier when the user wants the pipeline to supply it: ```yaml namedValues: @@ -257,10 +178,8 @@ namedValues: As you fill in each override, write it into the stub file using the right form: -- Write literal values directly; use `{#[TOKEN_NAME]#}` only for confirmed - tokens; use full `keyVault.secretIdentifier` URLs for Key Vault secrets. -- Never commit real secret values — those must be tokens or Key Vault - references. +- Write literal values directly; use `{#[TOKEN_NAME]#}` only for confirmed tokens; use full `keyVault.secretIdentifier` URLs for Key Vault secrets. +- Never commit real secret values — those must be tokens or Key Vault references. Continue setting-by-setting until there are no missing values. @@ -270,14 +189,14 @@ Continue setting-by-setting until there are no missing values. Once every stub override has been filled in across all environments: -- Re-read each `configuration.{env}.yaml` file and confirm it is valid YAML - with no leftover blank values from the stubs. +- Re-read each `configuration.{env}.yaml` file and confirm it is valid YAML with no leftover blank values from the stubs. - Confirm the schema comment is present as the first line of each file. -- Keep files easy to compare across environments and avoid duplicating - unchanged base configuration. +- **Validate each file against the schema** referenced in its `# yaml-language-server: $schema=...` comment. Perform structural validation: check that all top-level keys are recognized resource section names (e.g., `namedValues`, `backends`, `apis`, `diagnostics`, `loggers`, `policies`, `gateways`, `versionSets`, `groups`, `subscriptions`, `products`, `tags`, `policyFragments`, `workspaces`), that every list item has both `name` (string) and `properties` (object), and that no `properties` keys are obviously misspelled. If you can fetch the schema URL, use it to verify additional property constraints. +- Keep files easy to compare across environments and avoid duplicating unchanged base configuration. -Show the finalized files and **STOP for confirmation** before treating them as -final. +Ask the user to open and review each finalized file to confirm it looks correct before checking it in. Summarize any validation concerns you identified. + +**STOP for confirmation** before treating the files as final. --- @@ -286,14 +205,8 @@ final. Before finishing: 1. Verify every generated override file matches the intended environment. -2. Verify all **secrets** use either `{#[TOKEN_NAME]#}` or a Key Vault - reference — and that non-secrets (URLs, resource IDs, instrumentation keys) - are written as plain values, not tokens. -3. Confirm every `{#[TOKEN_NAME]#}` used corresponds to a token the user said - exists in their pipeline. -4. Remind the user to add any `{#[TOKEN_NAME]#}` tokens to their pipeline's - secret store (GitHub Actions Secrets or Azure DevOps variable groups). - Help with this if they ask. Note that the pipeline fails with an error if - any tokens are missing. -5. Remind the user to test publish for a lower environment before promoting - further. +2. Verify all **secrets** use either `{#[TOKEN_NAME]#}` or a Key Vault reference — and that non-secrets (URLs, resource IDs, instrumentation keys) are written as plain values, not tokens. +3. Confirm every `{#[TOKEN_NAME]#}` used corresponds to a token the user said exists in their pipeline. +4. Remind the user to add any `{#[TOKEN_NAME]#}` tokens to their pipeline's secret store (GitHub Actions Secrets or Azure DevOps variable groups). Help with this if they ask. Note that the pipeline fails with an error if any tokens are missing. +5. Remind the user to test publish for a lower environment before promoting further. +