Author : MD TAREQ HASSAN | Updated : 2021/10/10
About Stack Configuration
- Pulumi offers a configuration system for managing stack specific values (resource parameters)
- In many cases, different stacks for a single project will need differing values (i.e. different AKS cluster for Dev and Prod environments)
- Configurations (key-value pairs) for any given stack are stored in “
Pulumi.<stack-name>.yaml
” - “
Pulumi.<stack-name>.yaml
” is called “stack settings file”
Managing Stack Specific Configuration
- Pulumi CLI: CLI offers a
config
command withset
andget
subcommands for managing key-value pairs - Programitically: programming model offers a
Config
object with various getters and setters for retrieving values - https://www.pulumi.com/docs/reference/cli/pulumi_config/
Stack Settings File
- Stack settings file is typically resides in the root of the project directory
- When a new project is created, stack settings file “
Pulumi.<stack-name>.yaml
” will be created automatically - After creating project, when new stack is added using CLI command (
pulumi stack init <stack-name>
), stack settings file will also be added automatically in the project root folder
Setting and Getting Configurations
- Configuration keys format:
<namespace>:<key-name>
- Namespace, colon (delimiting) & the actual key name
- Example:
azure-native:location
(namespace: azure-native, key-name: location) - Simple key (without namespace & colon): Pulumi automatically uses the current project name as namespace (A Pulumi project is any folder which contains a
Pulumi.yaml
file)
pulumi config
commands can be used to set and get configuration values using Pulumi CLI- If
[value]
is not specified when setting a configuration key, the CLI will prompt for it interactively - Example command (Pulumi CLI)
- Set:
pulumi config set azure-native:region japaneast
- Get:
pulumi config get azure-native:region
- List:
pulumi config
(will give all configurations in the current stack)
- Set:
- If
- If namespace is skipped (CLI command and in Code)
- Project name will be used are namespace
- Example:
pulumi config set MyKey MyVal
will result in “xxx:MyKey: MyVal
”- Key:
<project-name>:MyKey
- Value:
MyVal
- Key:
- By deafult, values are saved as plaintext
Set configuration
#pulumi config set <namespace>:<key> <value> [flags]
pulumi config set xyz XyzValue
#
# flag: --plaintext
#
pulumi config set ns:xyz XyzValue --plaintext
# for secrets ("--secrets"), see secret section
Pulumi.dev.yaml
config:
ns:xyz: XyzValue
# ... ... ...
With json
pulumi config set ns:xyz '{"key1": "val1", "key2": "val2" }'
pulumi config set ns:xyz '["val1", "val2"]'
Get configuration
pulumi config get xyz
Nested or Structured Configuration
Use --path
flag
pulumi config set --path 'ns:data.active' true
pulumi config set --path 'ns:data.nums[0]' 1
pulumi config set --path 'ns:data.nums[1]' 2
pulumi config set --path 'ns:data.nums[2]' 3
Pulumi.dev.yaml
config:
proj:data:
active: true
nums:
- 1
- 2
- 3
Accessing Configuration from Code
config.Get("key")
: null if key not foundconfig.Require("key")
: exception if key not foundconfig.RequireObject<JsonElement>("key")
: get as json objectconfig.RequireObject<Type<T>>("key")
List<T>
(config.RequireObject<List<string>>("key")
)Dictionary<string, string>
(config.RequireObject<Dictionary<string, string>>("key")
)
- Values extracted from
config
are optional. Do either of followings:- use “
!
” (null forgiving) - Null coalaseing:
var x = config.Get("X") ?? "DefaultX";
- use “
Pulumi.dev.yaml
config:
ns:ResourceGroupNameKey: yyy-rg
ns:TagsKey:
CreatedBy: Pulumi
Scope: Demo
ns:ItemsListKey:
- item1
- item2
- item3
ns:data:
active: true
IntVal: 2020
nums:
- 1
- 2
- 3
C# Code
var configNamespace = "ns"
var config = new Config(configNamespace);
var resourceGroupName = config.Require("ResourceGroupNameKey")!;
var Tags = config.RequireObject<Dictionary<string, string>>("TagsKey")!;
var items = config.RequireObject<List<string>>("ItemsListKey")!;
var jsonObject = config.RequireObject<JsonElement>("data")!; // jsonObject -> instance of JsonElement class
//Get typed value
var isActive = jsonObject.GetProperty("active").GetBoolean()!; // true
var intVal = jsonObject.GetProperty("IntVal").GetInt32() ?? 2021;
Secrets
- To encrypt/decrypt secrets, Pulumi uses either of the followings
- pulumi.com service managed key
- logged into pulumi service backend
pulumi stack init <name>
- Key from cloud secret management service (i.e. Azure KeyVault)
- Specifying secret provider when creating stack:
--secrets-provider="<provider>://<provider-settings>"
(Example:pulumi stack init my-stack --secrets-provider="azurekeyvault://xxx.vault.azure.net/keys/yyy"
) - Required environment variables are set. See Use Azure KeyVault Key for Pulumi Secrets
- Specifying secret provider when creating stack:
- Passphrase (locally)
- Neither logged into pulumi service backend, nor specified secret provider
pulumi stack init <name>
(Pulumi CLI will ask for passphrase)
- pulumi.com service managed key
- Use pulumi CLI to set secrets
- Pulumi will encrypt secret using key
- Pulumi will decrypt secret during stack deployment (
pulumi up ...
), so that code gets decrypted value
Set secrets using Pulumi cli
pulumi config set --secret dbPassword xxx
#
# Nested secret
#
pulumi config set --secret --path 'ns:MainManagedInstanceArgs.AdministratorLoginPassword' <strong-password-here>
Pulumi.dev.yaml
config:
# ... ... ...
ns:dbPassword: xxx
ns:MainManagedInstanceArgs:
# ... ... ...
AdministratorLoginPassword:
secure: v1:xxxxxxx
Get secrets using Pulumi cli
#
# Pulumi will automatically decrypt and provide value as plain text
#
pulumi config get ns:dbPassword
pulumi config get --path 'ns:MainManagedInstanceArgs.AdministratorLoginPassword'
Get secrets in code
var config = new Config("ns");
//
// Simple
//
var dbPassword = config.RequireSecret("dbPassword"); //key -> ns:dbPassword
//
// Complex: secret from complex object
//
var mainManagedInstanceArgs = config.RequireObject<JsonElement>("MainManagedInstanceArgs");
var miAdminPassword = mainManagedInstanceArgs.GetString("AdministratorLoginPassword");
var mainManagedInstance = new ManagedInstance("MainManagedInstance", new ManagedInstanceArgs {
// ... ... ...
AdministratorLoginPassword = miAdminPassword, // miAdminPassword will be decrypted by Pulumi during runtime
// ... .... ...
});
Default Location
- About
location
configuration in stack settings file- The location is a provider argument i.e.
azure-native:location
for Azure Native - The reason location is a provider argument is because every resource requires location when it is created
- The location is a provider argument i.e.
- To set stack level default location:
- Use namespace
azure-native
(key ->azure-native:location
) - Example:
azure-native:location: japaneast
- Use namespace
- When
azure-native:location: japaneast
is used in stack settings file (Pulumi.XxxDev.yaml
)- Resource group location is set automatically
- Lcation for resources is derived from resource group location
Pulumi.XxxDev.yaml
config:
azure-native:location: japaneast
# ... ... ...
MyStack
var mainRg = new ResourceGroup("DemoRg", new ResourceGroupArgs
{
ResourceGroupName = "DemoRG"
//Location = # will be set automatically from azure-native:location in stack settings file
});
// ... ... ...
Best Practices for Using Configuration in Code
- The key in stack settings file (i.e.
Pulumi.dev.yml
) will be same as target resource argument class name. For example, for resource group:- Argument class:
ResourceGroupArgs
- Key in yaml
xxx:ResourceGroupArgs:
- Argument class:
- To avoid confusion in case of multiple instances of same type resource, all instances would not have prefix
Xxx
whereXxx
indicates purpose of an instance- Example: 2 resource groups -> Main and Disaster Recovery (DR)
- Main resource group:
xxx:MainResourceGroupArgs:
- DR resource group:
xxx:DrResourceGroupArgs:
- Create constant classes
StringLiterals
:- Generic literal strings
- To extract values from json element (i.e.
const string ResourceGroupName = "ResourceGroupName"
,const string Tags = "Tags"
)
StackConfig
: to get ResourceArgs object as json element (config.RequireObject<JsonElement>(StackConfig.Xxx)
)PulumiResourceUniqueNames
: Pulumi resource unique names (const string MainResourceGroup = "ResourceGroup.Main"
)
Pulumi.XxxDev.yml
config:
azure-native:location: japaneast # default location
xxx:ResourceGroupArgs:
ResourceGroupName: DemoRG
Tags:
CreatedBy: Pulumi
Scope: Demo
StackConfig.cs
namespace PulumiDemo.Constants
{
internal static class StackConfig
{
internal const string CONFIG_NAMESPACE = "xxx";
internal const string KEY_RESOURCE_GROUP_ARGS = "ResourceGroupArgs";
// ... ... ...
}
}
StringLiterals.cs
namespace PulumiDemo.Constants
{
internal static class StringLiterals
{
internal const string NAME = "Name";
internal const string TAGS = "Tags";
internal const string RESOURCE_GROUP_NAME = "ResourceGroupName";
//
// Unique names for Pulumi resources
//
internal const string RESOURCE_GROUP_MAIN = "ResourceGroupMain";
// ... ... ...
}
}
MyStack.cs
using System.Threading.Tasks;
using Pulumi;
using Pulumi.AzureNative.Resources;
using Pulumi.AzureNative.Storage;
using static PulumiDemo.Constants.StackConfig;
using static PulumiDemo.Constants.StringLiterals;
using System.Collections.Generic;
using System.Text.Json;
namespace PulumiDemo
{
class MyStack : Stack
{
public MyStack()
{
var config = new Config(CONFIG_NAMESPACE);
//
// Use Pulumi config to get ResourceGroupArgs from yaml (as json element)
// Then exract configuration values from json element
//
var mainRgArgs = config.RequireObject<JsonElement>(KEY_RESOURCE_GROUP_ARGS);
var mainRgName = mainRgArgs.GetProperty(RESOURCE_GROUP_NAME).GetString()!;
var mainRgTags = mainRgArgs.GetProperty(TAGS).Deserialize<Dictionary<string, string>>()!; // .Deserialize -> available only in .Net 6.0+
var mainRg = new ResourceGroup(RESOURCE_GROUP_MAIN, new ResourceGroupArgs
{
ResourceGroupName = mainRgName,
Tags = mainRgTags
});
// ... ... ...
}
}
}