Azure / azure-dev

A developer CLI that reduces the time it takes for you to get started on Azure. The Azure Developer CLI (azd) provides a set of developer-friendly commands that map to key stages in your workflow - code, build, deploy, monitor, repeat.
https://aka.ms/azd
MIT License
402 stars 195 forks source link

[feat] Setting env config #2358

Open heaths opened 1 year ago

heaths commented 1 year ago

Environments are useful to provision different resources with different settings e.g., dev vs. prod, Key Vault vs. Managed HSM, etc. Customizing these with environment variables (env vars) can be difficult, though. While most *nix shells e.g., bash let you pass them on the command line e.g., FOO=1 BAR="true" azd provision, shells typical of Windows e.g., cmd and pwsh, do not. Setting env vars can be dangerous, too. In my example above, what happens if we forget to re-set an env var for a dev connection string and it's left as the prod connection string? What if we provision an expensive Managed HSM resource (uptime cost) when we thought we were provisioning a Key Vault (per-call cost)?

After some internal discussion about adding a --parameters parameter vs. other means, it seems we've settled on making persistent parameters via an environment's config.json file. This is already used for retaining selections when prompted, but doesn't seem to work if you just edit it by hand (likely due to a bifurcation of global vs. provider-based parameters).

To make this a first class feature of environments, instead we could have a command group like azd env (-e name) config with typical set, get-values, etc., commands to get, set, clear, etc., parameters. These could be restricted to only those in main.parameters.json, though I think allowing them for any parameters in the main.bicep (or main.json) template would be useful. Maintaining a separate main.parameters.json seems better if it's only to default to env var replacements.

These parameters would persist past an azd down. Customers should be able to deprovision resources to reduce costs during the dev inner loop, for example.

heaths commented 1 year ago

On a related note, the support for infra.parameters.* doesn't seem to actually override any parameters from main.parameters.json. Presumably, this is because of separate parameter structs where I do override (based on command line args, but could work here) in PR #2360.

wbreza commented 1 year ago

@heaths Does the azd env set of commands help here? Here you can set get/set new key value pairs for each environment.

image
heaths commented 1 year ago

No, because anything variable related updates the .env, but if you want a persistent environment "parameter" - a default to pass to bicep - that is also exposed as an environment variable, azd down clears it.

Case in point: my bicep template has a managedHsm boolean parameter that defaults to false because those things are expensive to provision. I have two environments: one for Key Vault (cheap, fast) and one for Managed HSM. Instead of having to change env vars (again, harder on Windows) or pass a parameter every time (per #2352) - which is easy to forget - I want to set the default in the environment's config.json. Maybe there's another way, but it seems this should actually already work...

When you make a selection from parameter prompts, it's supposed to be set in config.json as "infra.parameters.{name}". So that should also be read in according to the code, though it appears to be broken: I translated "infra.parameters.{name}" to be a JSON hierarchy per the typical config API translation, though maybe that's not actually true and that's why it didn't work.

However this does work (or is supposed to work), given users the ability to set persistent parameter values per-environment would be very useful. Right now, I feel as if the env var-based mechanics are geared too much to pipelines where you would typically set env vars (or secrets) per environment e.g., in GitHub Actions you'd have dev, test, and prod environments with different env vars. That's great and should be supported, but what about the inner dev loop? I have different environments I want to test my code against, and I don't want to have to remember to pass certain parameters every time or have to change my env vars which is easily forgettable.

heaths commented 1 year ago

Talking with @vhvb1989 I suppose I don't need this particular env var as output to roundtrip, so in this instance azd env set could work; however, I worry about this constraint being well understood or even discoverable, and still think persistent parameter values per environment is worth considering e.g., in an environment's config.json.

vhvb1989 commented 1 year ago

My recommendation is to:

  1. Create the azd-env for dev (non hsm):

    azd env new dev-no-hsm
    azd env set AZD_MANAGED_HSM false
  2. Create azd-env for prod (hsm):

    azd env new prod-hsm
    azd env set AZD_MANAGED_HSM true
  3. Create the mapping for the bicep input param in main.parameters.json // Note: Ideally set default false within the mapping as well. That would help in case you create a new env and forgot to set the AZD_MANAGED_HSM (or just not to worry about adding this var to all non-managed-hsm azd-env)

    "managedHsm": {
      "value": "${AZD_MANAGED_HSM=false}"
    }
  4. Make sure your bicep file doesn't have an output like AZD_MANAGED_HSM, as that would be deleted after azd down

  5. Select the azd-env you want to use, like azd env select dev-non-hsm . Now you can run provision + down multiple times, with a deterministic result.

vhvb1989 commented 1 year ago

Independently from my suggestion, I think the feature that @heaths is describing here would be a good feature to have.

We would first need to define all the config-scopes azd can support. azd has currently azd config set command, which applies to all azd-projects in the system. The configuration is persisted within the ~/user folder.

We have talked in the past (@weikanglim @ellismg) about making azd to support something similar to git, where you can set configuration for the folder, workspace or global. In the same way, azd could support:

Then, azd should automatically resolve hierarchy, where --env is the last overriding option for a user, and all azd-config key/paths are supported on every level (alpha-features, persisted-deployment-outputs (to-be-added), defaults, etc).

This way, user can choose if a persisted-bicep-output is required for all the projects within the system, or just for one simple azd-env.

weikanglim commented 1 year ago
  1. As @heaths correctly points out, configuration != state. azd currently stores a mix bag of state and configuration in an .env file for an environment. This offers a simple environment management mechanism, with the following negative tradeoffs:
    • Configuration in .env is treated ephemerally. This can be somewhat mitigated with a "state store", even in the azd down case, but personally, configuration that aren't secrets should always be checked into source control and be reshaped across team members.
    • (Storage format) Configuration in .env is dynamically typed. This may be important to avoid time-consuming development mistakes.
    • Depending on how you view it, round tripping state/configuration in .env can be viewed as powerful or unmanageable.

I suspect that the first bullet point here will push us towards making separating configuration vs. state, but any additional anecdotes here will help. We'll probably still want to expose state update operations for advanced scenarios that are in-use today.

  1. As to the configuration hierarchy, two things come to mind:
    • azd configuration != infra configuration. I'd prefer not having azd config manage both infra and azd configuration.
    • System wide configuration defaults are scary for infrastructure provisioning configuration. I'm not sure what a use-case justifies this, so I'd always start out with environment-level configuration, followed by project-level configuration. Both are checked in to source for visibility and maintainability.