From e0064102a939887e7fcf1da94446b3fc590f3ae8 Mon Sep 17 00:00:00 2001 From: Eron Wright Date: Thu, 21 Mar 2024 14:44:26 -0700 Subject: [PATCH] [yaml/v2] Support for resource ordering (implicit and explicit) (#2894) ### Proposed changes This PR adds support for resource ordering within a `ConfigGroup` or `ConfigFile`. Two approaches are supported (and work in combination): 1. implicit dependencies: the provider uses heuristics to install CRDs and namespaces first. 2. explicit dependencies: the provider understands the `config.kubernetes.io/depends-on` annotation to explicitly declare a dependency on a given resource. The implementation is based on [kubernetes-sigs/cli-utils](https://github.com/kubernetes-sigs/cli-utils) and its support for resource ordering ([documentation](https://github.com/kubernetes-sigs/cli-utils?tab=readme-ov-file#resource-ordering)). To be clear, ordering _across_ `ConfigGroup` resources is supported already, simply using the `dependsOn` option. This PR adds a more granular ordering _within_ the group. ### Testing New test cases were added to verify the new behavior. Many tests rely on a common manifest file, and it is updated to include a CRD and a namespace. Manual testing was performed with the following well-known manifests: 1. knative-serving-core ([ref](https://github.com/knative/serving/releases/download/knative-v1.13.1/serving-core.yaml)) 2. cert-manager ([ref](https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml)) ### Example Here's an example that installs cert-manager then provisions a certificate issuer and a TLS certificate. An explicit dependency is drawn between the `Certificate` and `Issuer`. ```yaml name: issue-2881-cert-manager runtime: yaml description: Installs cert-manager. See https://cert-manager.io/docs/installation/kubectl/ for details. variables: {} resources: install: type: kubernetes:yaml/v2:ConfigGroup properties: files: - https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml test: type: kubernetes:yaml/v2:ConfigGroup options: dependsOn: - ${install} properties: yaml: | apiVersion: v1 kind: Namespace metadata: name: cert-manager-test --- apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: test-selfsigned namespace: cert-manager-test spec: selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: selfsigned-cert namespace: cert-manager-test annotations: config.kubernetes.io/depends-on: cert-manager.io/namespaces/cert-manager-test/Issuer/test-selfsigned spec: dnsNames: - example.com secretName: selfsigned-cert-tls issuerRef: name: test-selfsigned ``` Within the stack state, one sees dependencies, e.g. on the `Certificate` resource. ```json { "urn": "urn:pulumi:dev::issue-2881-cert-manager::kubernetes:yaml/v2:ConfigGroup$kubernetes:cert-manager.io/v1:Certificate::test-cert-manager-test/selfsigned-cert", "id": "cert-manager-test/selfsigned-cert", "type": "kubernetes:cert-manager.io/v1:Certificate", "dependencies": [ "urn:pulumi:dev::issue-2881-cert-manager::kubernetes:yaml/v2:ConfigGroup$kubernetes:cert-manager.io/v1:Issuer::test-cert-manager-test/test-selfsigned", "urn:pulumi:dev::issue-2881-cert-manager::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:Namespace::test-cert-manager-test" ] } ``` ### Related issues (optional) Closes #2881 --- CHANGELOG.md | 1 + .../pulumi-resource-kubernetes/schema.json | 4 +- .../pkg/gen/examples/overlays/configFileV2.md | 34 +++ .../gen/examples/overlays/configGroupV2.md | 34 +++ .../pkg/provider/yaml/v2/configfile_test.go | 12 +- provider/pkg/provider/yaml/v2/configgroup.go | 6 +- .../pkg/provider/yaml/v2/configgroup_test.go | 53 ++++- provider/pkg/provider/yaml/v2/yaml.go | 135 +++++++---- provider/pkg/provider/yaml/v2/yaml_test.go | 225 ++++++++++++++---- sdk/dotnet/Yaml/V2/ConfigFile.cs | 34 +++ sdk/dotnet/Yaml/V2/ConfigGroup.cs | 34 +++ sdk/go/kubernetes/yaml/v2/configFile.go | 34 +++ sdk/go/kubernetes/yaml/v2/configGroup.go | 34 +++ .../pulumi/kubernetes/yaml/v2/ConfigFile.java | 34 +++ .../kubernetes/yaml/v2/ConfigGroup.java | 34 +++ sdk/nodejs/yaml/v2/configFile.ts | 34 +++ sdk/nodejs/yaml/v2/configGroup.ts | 34 +++ .../pulumi_kubernetes/yaml/v2/ConfigFile.py | 68 ++++++ .../pulumi_kubernetes/yaml/v2/ConfigGroup.py | 68 ++++++ 19 files changed, 812 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a702384fe3..a68a520509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - ConfigGroup V2 (https://github.com/pulumi/pulumi-kubernetes/pull/2844) - ConfigFile V2 (https://github.com/pulumi/pulumi-kubernetes/pull/2862) - Bugfix for ambiguous kinds (https://github.com/pulumi/pulumi-kubernetes/pull/2889) +- [yaml/v2] Support for resource ordering (https://github.com/pulumi/pulumi-kubernetes/pull/2894) ### New Features diff --git a/provider/cmd/pulumi-resource-kubernetes/schema.json b/provider/cmd/pulumi-resource-kubernetes/schema.json index 7b872169af..8d4d187c8a 100644 --- a/provider/cmd/pulumi-resource-kubernetes/schema.json +++ b/provider/cmd/pulumi-resource-kubernetes/schema.json @@ -95188,7 +95188,7 @@ ] }, "kubernetes:yaml/v2:ConfigFile": { - "description": "ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Local File\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigFile(\"example\", {\n file: \"foo.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigFile\n\nexample = ConfigFile(\n \"example\",\n file=\"foo.yaml\",\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigFile(\"example\", new ConfigFileArgs\n {\n File = \"foo.yaml\",\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n yamlv2 \"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n \"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n pulumi.Run(func(ctx *pulumi.Context) error {\n _, err := yamlv2.NewConfigFile(ctx, \"example\",\n &yamlv2.ConfigFileArgs{\n File: \"foo.yaml\",\n },\n )\n if err != nil {\n return err\n }\n\n return nil\n })\n}\n```\n{{% /example %}}\n{% /examples %}}\n", + "description": "ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file.\n\n## Dependency ordering\nSometimes resources must be applied in a specific order. For example, a namespace resource must be\ncreated before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed.\n\nPulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also\nwaits for each object to be fully reconciled, unless `skipAwait` is enabled.\n\n### Explicit Dependency Ordering\nPulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource.\nThe annotation accepts a list of resource references, delimited by commas. \n\nNote that references to resources outside the ConfigFile aren't supported.\n\n**Resource reference**\n\nA resource reference is a string that uniquely identifies a resource.\n\nIt consists of the group, kind, name, and optionally the namespace, delimited by forward slashes.\n\n| Resource Scope | Format |\n| :--------------- | :--------------------------------------------- |\n| namespace-scoped | `/namespaces///` |\n| cluster-scoped | `//` |\n\nFor resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`).\n\n### Ordering across ConfigFiles\nThe `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources.\nUse it on another resource to make it dependent on the ConfigFile and to wait for the resources within\nthe group to be deployed.\n\nA best practice is to deploy each application using its own ConfigFile, especially when that application\ninstalls custom resource definitions.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Local File\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigFile(\"example\", {\n file: \"foo.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigFile\n\nexample = ConfigFile(\n \"example\",\n file=\"foo.yaml\",\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigFile(\"example\", new ConfigFileArgs\n {\n File = \"foo.yaml\",\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n yamlv2 \"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n \"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n pulumi.Run(func(ctx *pulumi.Context) error {\n _, err := yamlv2.NewConfigFile(ctx, \"example\",\n &yamlv2.ConfigFileArgs{\n File: \"foo.yaml\",\n },\n )\n if err != nil {\n return err\n }\n\n return nil\n })\n}\n```\n{{% /example %}}\n{% /examples %}}\n", "properties": { "resources": { "type": "array", @@ -95219,7 +95219,7 @@ "isComponent": true }, "kubernetes:yaml/v2:ConfigGroup": { - "description": "ConfigGroup creates a set of Kubernetes resources from Kubernetes YAML text. The YAML text\nmay be supplied using any of the following methods:\n\n1. Using a filename or a list of filenames:\n2. Using a file pattern or a list of file patterns:\n3. Using a literal string containing YAML, or a list of such strings:\n4. Any combination of files, patterns, or YAML strings:\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Local File\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: \"foo.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"foo.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Multiple Local Files\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: [\"foo.yaml\", \"bar.yaml\"],\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo.yaml\", \"bar.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo.yaml\", \"bar.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"foo.yaml\", \"bar.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Local File Pattern\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: \"yaml/*.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"yaml/*.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"yaml/*.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"yaml/*.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Multiple Local File Patterns\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: [\"foo/*.yaml\", \"bar/*.yaml\"],\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo/*.yaml\", \"bar/*.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo/*.yaml\", \"bar/*.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"yaml/*.yaml\", \"bar/*.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Literal YAML String\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n yaml: `\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n`,\n})\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n yaml=['''\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n''']\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Yaml = @\"\n apiVersion: v1\n kind: Namespace\n metadata:\n name: foo\n \",\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tYAML: []string{\n\t\t\t\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n`,\n\t\t\t\t},\n\t\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{% /examples %}}\n", + "description": "ConfigGroup creates a set of Kubernetes resources from Kubernetes YAML text. The YAML text\nmay be supplied using any of the following methods:\n\n1. Using a filename or a list of filenames:\n2. Using a file pattern or a list of file patterns:\n3. Using a literal string containing YAML, or a list of such strings:\n4. Any combination of files, patterns, or YAML strings:\n\n## Dependency ordering\nSometimes resources must be applied in a specific order. For example, a namespace resource must be\ncreated before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed.\n\nPulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also\nwaits for each object to be fully reconciled, unless `skipAwait` is enabled.\n\n### Explicit Dependency Ordering\nPulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource.\nThe annotation accepts a list of resource references, delimited by commas. \n\nNote that references to resources outside the ConfigGroup aren't supported.\n\n**Resource reference**\n\nA resource reference is a string that uniquely identifies a resource.\n\nIt consists of the group, kind, name, and optionally the namespace, delimited by forward slashes.\n\n| Resource Scope | Format |\n| :--------------- | :--------------------------------------------- |\n| namespace-scoped | `/namespaces///` |\n| cluster-scoped | `//` |\n\nFor resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`).\n\n### Ordering across ConfigGroups\nThe `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources.\nUse it on another resource to make it dependent on the ConfigGroup and to wait for the resources within\nthe group to be deployed.\n\nA best practice is to deploy each application using its own ConfigGroup, especially when that application\ninstalls custom resource definitions.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Local File\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: \"foo.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"foo.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Multiple Local Files\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: [\"foo.yaml\", \"bar.yaml\"],\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo.yaml\", \"bar.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo.yaml\", \"bar.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"foo.yaml\", \"bar.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Local File Pattern\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: \"yaml/*.yaml\",\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"yaml/*.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"yaml/*.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"yaml/*.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Multiple Local File Patterns\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n files: [\"foo/*.yaml\", \"bar/*.yaml\"],\n});\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n files=[\"foo/*.yaml\", \"bar/*.yaml\"],\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Files = new[] { \"foo/*.yaml\", \"bar/*.yaml\" }\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tFiles: []string{\"yaml/*.yaml\", \"bar/*.yaml\"},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{{% example %}}\n### Literal YAML String\n\n```typescript\nimport * as k8s from \"@pulumi/kubernetes\";\n\nconst example = new k8s.yaml.v2.ConfigGroup(\"example\", {\n yaml: `\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n`,\n})\n```\n```python\nfrom pulumi_kubernetes.yaml.v2 import ConfigGroup\n\nexample = ConfigGroup(\n \"example\",\n yaml=['''\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n''']\n)\n```\n```csharp\nusing System.Threading.Tasks;\nusing Pulumi;\nusing Pulumi.Kubernetes.Yaml.V2;\n\nclass YamlStack : Stack\n{\n public YamlStack()\n {\n var helloWorld = new ConfigGroup(\"example\", new ConfigGroupArgs\n {\n Yaml = @\"\n apiVersion: v1\n kind: Namespace\n metadata:\n name: foo\n \",\n });\n }\n}\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := yaml.NewConfigGroup(ctx, \"example\",\n\t\t\t&yaml.ConfigGroupArgs{\n\t\t\t\tYAML: []string{\n\t\t\t\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n name: foo\n`,\n\t\t\t\t},\n\t\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n{{% /example %}}\n{% /examples %}}\n", "properties": { "resources": { "type": "array", diff --git a/provider/pkg/gen/examples/overlays/configFileV2.md b/provider/pkg/gen/examples/overlays/configFileV2.md index 4f28485e93..ac1d52de90 100644 --- a/provider/pkg/gen/examples/overlays/configFileV2.md +++ b/provider/pkg/gen/examples/overlays/configFileV2.md @@ -1,5 +1,39 @@ ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. +## Dependency ordering +Sometimes resources must be applied in a specific order. For example, a namespace resource must be +created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + +Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also +waits for each object to be fully reconciled, unless `skipAwait` is enabled. + +### Explicit Dependency Ordering +Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. +The annotation accepts a list of resource references, delimited by commas. + +Note that references to resources outside the ConfigFile aren't supported. + +**Resource reference** + +A resource reference is a string that uniquely identifies a resource. + +It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + +| Resource Scope | Format | +| :--------------- | :--------------------------------------------- | +| namespace-scoped | `/namespaces///` | +| cluster-scoped | `//` | + +For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + +### Ordering across ConfigFiles +The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. +Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within +the group to be deployed. + +A best practice is to deploy each application using its own ConfigFile, especially when that application +installs custom resource definitions. + {{% examples %}} ## Example Usage {{% example %}} diff --git a/provider/pkg/gen/examples/overlays/configGroupV2.md b/provider/pkg/gen/examples/overlays/configGroupV2.md index 7b014b2e13..9a0af90278 100644 --- a/provider/pkg/gen/examples/overlays/configGroupV2.md +++ b/provider/pkg/gen/examples/overlays/configGroupV2.md @@ -6,6 +6,40 @@ may be supplied using any of the following methods: 3. Using a literal string containing YAML, or a list of such strings: 4. Any combination of files, patterns, or YAML strings: +## Dependency ordering +Sometimes resources must be applied in a specific order. For example, a namespace resource must be +created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + +Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also +waits for each object to be fully reconciled, unless `skipAwait` is enabled. + +### Explicit Dependency Ordering +Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. +The annotation accepts a list of resource references, delimited by commas. + +Note that references to resources outside the ConfigGroup aren't supported. + +**Resource reference** + +A resource reference is a string that uniquely identifies a resource. + +It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + +| Resource Scope | Format | +| :--------------- | :--------------------------------------------- | +| namespace-scoped | `/namespaces///` | +| cluster-scoped | `//` | + +For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + +### Ordering across ConfigGroups +The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. +Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within +the group to be deployed. + +A best practice is to deploy each application using its own ConfigGroup, especially when that application +installs custom resource definitions. + {{% examples %}} ## Example Usage {{% example %}} diff --git a/provider/pkg/provider/yaml/v2/configfile_test.go b/provider/pkg/provider/yaml/v2/configfile_test.go index 000cc0a0b1..938cc4d44c 100644 --- a/provider/pkg/provider/yaml/v2/configfile_test.go +++ b/provider/pkg/provider/yaml/v2/configfile_test.go @@ -88,7 +88,9 @@ var _ = Describe("ConfigFile.Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:Namespace::test-my-namespace", "test-my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::test-crontabs.stable.example.com", "test-crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:ConfigMap::test-my-map", "test-my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:stable.example.com/v1:CronTab::test-my-new-cron-object", "test-my-new-cron-object"), )), @@ -104,7 +106,9 @@ var _ = Describe("ConfigFile.Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:Namespace::prefixed-my-namespace", "prefixed-my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::prefixed-crontabs.stable.example.com", "prefixed-crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:ConfigMap::prefixed-my-map", "prefixed-my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:stable.example.com/v1:CronTab::prefixed-my-new-cron-object", "prefixed-my-new-cron-object"), )), @@ -121,7 +125,9 @@ var _ = Describe("ConfigFile.Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:Namespace::my-namespace", "my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com", "crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:core/v1:ConfigMap::my-map", "my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigFile$kubernetes:stable.example.com/v1:CronTab::my-new-cron-object", "my-new-cron-object"), )), diff --git a/provider/pkg/provider/yaml/v2/configgroup.go b/provider/pkg/provider/yaml/v2/configgroup.go index 1f73bca840..52a9bc93d8 100644 --- a/provider/pkg/provider/yaml/v2/configgroup.go +++ b/provider/pkg/provider/yaml/v2/configgroup.go @@ -102,7 +102,11 @@ func (k *ConfigGroupProvider) Construct(ctx *pulumi.Context, typ, name string, i return pulumi.ArrayOutput{}, err } for _, obj := range objects { - objs = append(objs, unstructured.Unstructured{Object: obj}) + expanded, err := Expand([]unstructured.Unstructured{{Object: obj}}) + if err != nil { + return pulumi.ArrayOutput{}, err + } + objs = append(objs, expanded...) } // Register the objects as Pulumi resources. diff --git a/provider/pkg/provider/yaml/v2/configgroup_test.go b/provider/pkg/provider/yaml/v2/configgroup_test.go index 396b870cef..bb510866e6 100644 --- a/provider/pkg/provider/yaml/v2/configgroup_test.go +++ b/provider/pkg/provider/yaml/v2/configgroup_test.go @@ -78,7 +78,9 @@ var _ = Describe("Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:Namespace::test-my-namespace", "test-my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::test-crontabs.stable.example.com", "test-crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::test-my-map", "test-my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:stable.example.com/v1:CronTab::test-my-new-cron-object", "test-my-new-cron-object"), )), @@ -94,7 +96,9 @@ var _ = Describe("Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:Namespace::prefixed-my-namespace", "prefixed-my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::prefixed-crontabs.stable.example.com", "prefixed-crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::prefixed-my-map", "prefixed-my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:stable.example.com/v1:CronTab::prefixed-my-new-cron-object", "prefixed-my-new-cron-object"), )), @@ -111,7 +115,9 @@ var _ = Describe("Construct", func() { Expect(err).ShouldNot(HaveOccurred()) outputs := unmarshalProperties(GinkgoTB(), resp.State) Expect(outputs).To(MatchProps(IgnoreExtras, Props{ - "resources": MatchArrayValue(HaveExactElements( + "resources": MatchArrayValue(ConsistOf( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:Namespace::my-namespace", "my-namespace"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com", "crontabs.stable.example.com"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::my-map", "my-map"), MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:stable.example.com/v1:CronTab::my-new-cron-object", "my-new-cron-object"), )), @@ -130,19 +136,42 @@ var _ = Describe("Construct", func() { }) Describe("objs", func() { - Context("when the input is a valid object", func() { + decodeObjects := func(manifest string) []resource.PropertyValue { + // decode the manifest to Unstructured objects, then convert to input properties + resources, err := yamlDecode(manifest, nil) + Expect(err).ShouldNot(HaveOccurred()) + var objs []resource.PropertyValue + for _, res := range resources { + objs = append(objs, resource.NewPropertyValue(res.Object)) + } + return objs + } + + Context("when the input is a valid object literal", func() { BeforeEach(func() { - // decode the manifest to Unstructured objects, then convert to input properties - resources, err := yamlDecode(manifest, nil) - Expect(err).ShouldNot(HaveOccurred()) - var objs []resource.PropertyValue - for _, res := range resources { - objs = append(objs, resource.NewPropertyValue(res.Object)) - } - inputs["objs"] = resource.NewArrayProperty(objs) + inputs["objs"] = resource.NewArrayProperty(decodeObjects(manifest)) }) commonAssertions() }) + + Context("when the object is a list", func() { + BeforeEach(func() { + inputs["objs"] = resource.NewArrayProperty(decodeObjects(list)) + }) + + It("should expand the list", func(ctx context.Context) { + resp, err := pulumiprovider.Construct(ctx, req, tc.EngineConn(), k.Construct) + Expect(err).ShouldNot(HaveOccurred()) + outputs := unmarshalProperties(GinkgoTB(), resp.State) + Expect(outputs).To(MatchProps(IgnoreExtras, Props{ + "resources": MatchArrayValue(HaveExactElements( + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::test-map-1", "test-map-1"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::test-map-2", "test-map-2"), + MatchResourceReferenceValue("urn:pulumi:stack::project::kubernetes:yaml/v2:ConfigGroup$kubernetes:core/v1:ConfigMap::test-map-3", "test-map-3"), + )), + })) + }) + }) }) Describe("files", func() { diff --git a/provider/pkg/provider/yaml/v2/yaml.go b/provider/pkg/provider/yaml/v2/yaml.go index cde4d4da61..85d50a06cf 100644 --- a/provider/pkg/provider/yaml/v2/yaml.go +++ b/provider/pkg/provider/yaml/v2/yaml.go @@ -26,6 +26,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" "github.com/pkg/errors" @@ -34,8 +35,9 @@ import ( yamlv2 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/yaml" + cliutilsobject "sigs.k8s.io/cli-utils/pkg/object" + cliutilsgraph "sigs.k8s.io/cli-utils/pkg/object/graph" ) type ParseOptions struct { @@ -77,6 +79,7 @@ func Parse(ctx context.Context, clientSet *clients.DynamicClientSet, opts ParseO if err != nil { return nil, errors.Wrapf(err, "expanding glob") } + sort.Strings(files) } else { files = []string{file} } @@ -99,6 +102,10 @@ func Parse(ctx context.Context, clientSet *clients.DynamicClientSet, opts ParseO if err != nil { return nil, errors.Wrapf(err, "decoding YAML") } + dec, err = Expand(dec) + if err != nil { + return nil, err + } objs = append(objs, dec...) } @@ -130,6 +137,41 @@ func yamlDecode(text string, _ *clients.DynamicClientSet) ([]unstructured.Unstru return resources, nil } +func Expand(objs []unstructured.Unstructured) ([]unstructured.Unstructured, error) { + result := make([]unstructured.Unstructured, 0, len(objs)) + for { + if len(objs) == 0 { + break + } + var obj unstructured.Unstructured + obj, objs = objs[0], objs[1:] + + // Recursively traverse built-in Kubernetes list types into a single set of "naked" resource + // definitions that we can register with the Pulumi engine. + // + // Kubernetes does not instantiate list types like `v1.List`. When the API server receives + // a list, it will recursively traverse it and perform the necessary operations on + // each "instantiable" resource it finds. For example, `kubectl apply` on a + // `v1.ConfigMapList` will cause the API server to traverse the list, and `apply` each + // `v1.ConfigMap` it finds. + // + // Since Kubernetes does not instantiate list types directly, Pulumi also traverses lists + // for resource definitions that can be managed by Kubernetes, and registers those with the + // engine instead. + if obj.IsList() { + list, err := obj.ToList() + if err != nil { + return nil, fmt.Errorf("YAML object is invalid: `%s`: %w", printUnstructured(&obj), err) + } + objs = append(list.Items, objs...) + continue + } + + result = append(result, obj) + } + return result, nil +} + type RegisterOptions struct { Objects []unstructured.Unstructured ResourcePrefix string @@ -140,14 +182,54 @@ type RegisterOptions struct { // Register registers the given Kubernetes objects as resources with the Pulumi engine. // Returns an array of the resources that were registered. func Register(ctx *pulumi.Context, opts RegisterOptions) (pulumi.ArrayOutput, error) { - resources := pulumi.Array{} + objs := make(cliutilsobject.UnstructuredSet, 0, len(opts.Objects)) for _, obj := range opts.Objects { - rs, err := register(ctx, &obj, opts) - if err != nil { - return pulumi.ArrayOutput{}, err - } - for _, r := range rs { + obj := obj + objs = append(objs, &obj) + } + + // sort the objects using heuristics about Kubernetes object kinds and their dependencies. + g, err := cliutilsgraph.DependencyGraph(objs) + if err != nil { + return pulumi.ArrayOutput{}, err + } + + // process the resources in topological order, meaning that we first process the resources that have no dependencies, + // then process the resources that depend on those, and so on. + sorted, err := g.Sort() + if err != nil { + return pulumi.ArrayOutput{}, err + } + subsets := cliutilsgraph.HydrateSetList(sorted, objs) + + resources := pulumi.Array{} + objToResource := map[cliutilsobject.ObjMetadata]pulumi.Resource{} + for _, subset := range subsets { + for _, obj := range subset { + var resourceOptions []pulumi.ResourceOption + resourceOptions = append(resourceOptions, opts.ResourceOptions...) + + // Depend on the explicit dependencies. + // The subsets are ordered such that the dependency is guaranteed to be registered before the dependent. + dependsOn := []pulumi.Resource{} + dependents := g.Dependencies(cliutilsobject.UnstructuredToObjMetadata(obj)) + for _, dep := range dependents { + if r, ok := objToResource[dep]; ok { + dependsOn = append(dependsOn, r) + } + } + if len(dependsOn) > 0 { + resourceOptions = append(resourceOptions, pulumi.DependsOn(dependsOn)) + } + + // Register the resource with the Pulumi engine. + // Note that the RPC call is made asynchronously, and is awaited by the resource output. + r, err := register(ctx, obj, opts, resourceOptions) + if err != nil { + return pulumi.ArrayOutput{}, err + } resources = append(resources, pulumi.NewResourceOutput(r)) + objToResource[cliutilsobject.UnstructuredToObjMetadata(obj)] = r } } return resources.ToArrayOutputWithContext(ctx.Context()), nil @@ -157,7 +239,8 @@ func register( ctx *pulumi.Context, obj *unstructured.Unstructured, opts RegisterOptions, -) ([]pulumi.CustomResource, error) { + resourceOpts []pulumi.ResourceOption, +) (pulumi.CustomResource, error) { // Ensure there is a kind and API version. kind := obj.GetKind() @@ -167,37 +250,7 @@ func register( } fullKind := fmt.Sprintf("%s/%s", apiVersion, kind) - // Recursively traverse built-in Kubernetes list types into a single set of "naked" resource - // definitions that we can register with the Pulumi engine. - // - // Kubernetes does not instantiate list types like `v1.List`. When the API server receives - // a list, it will recursively traverse it and perform the necessary operations on - // each "instantiable" resource it finds. For example, `kubectl apply` on a - // `v1.ConfigMapList` will cause the API server to traverse the list, and `apply` each - // `v1.ConfigMap` it finds. - // - // Since Kubernetes does not instantiate list types directly, Pulumi also traverses lists - // for resource definitions that can be managed by Kubernetes, and registers those with the - // engine instead. - if obj.IsList() { - var resources []pulumi.CustomResource - err := obj.EachListItem(func(o runtime.Object) error { - obj := o.(*unstructured.Unstructured) - rs, err := register(ctx, obj, opts) - if err != nil { - return err - } - resources = append(resources, rs...) - return nil - }) - if err != nil { - return nil, fmt.Errorf("YAML object is invalid: `%s`: %w", printUnstructured(obj), err) - } - return resources, nil - } - - // If we got here, it's not a recursively traversed type, so process it directly. - // First, validate that it has the requisite metadata and name properties. + // Validate that it has the requisite metadata and name properties. if obj.GetName() == "" { return nil, fmt.Errorf("YAML object does not have a .metadata.name: `%s`", printUnstructured(obj)) } @@ -228,11 +281,11 @@ func register( } // Finally allocate a resource of the correct type. - res, err := yamlv2.RegisterResource(ctx, apiVersion, kind, resourceName, kubernetes.UntypedArgs(obj.Object), opts.ResourceOptions...) + res, err := yamlv2.RegisterResource(ctx, apiVersion, kind, resourceName, kubernetes.UntypedArgs(obj.Object), resourceOpts...) if err != nil { return nil, err } - return []pulumi.CustomResource{res}, nil + return res, nil } func printUnstructured(obj *unstructured.Unstructured) string { diff --git a/provider/pkg/provider/yaml/v2/yaml_test.go b/provider/pkg/provider/yaml/v2/yaml_test.go index 91d41fd5a6..e13e2ef3bc 100644 --- a/provider/pkg/provider/yaml/v2/yaml_test.go +++ b/provider/pkg/provider/yaml/v2/yaml_test.go @@ -36,6 +36,40 @@ import ( const ( manifest = ` +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: crontabs.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + cronSpec: + type: string + image: + type: string + replicas: + type: integer + scope: Namespaced + names: + plural: crontabs + singular: crontab + kind: CronTab +--- +apiVersion: v1 +kind: Namespace +metadata: + name: my-namespace +--- apiVersion: v1 kind: ConfigMap metadata: @@ -51,6 +85,25 @@ spec: cronSpec: "* * * * */5" image: my-awesome-cron-image ` + + list = ` +apiVersion: v1 +kind: List +items: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: map-1 +- apiVersion: v1 + kind: ConfigMap + metadata: + name: map-2 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: map-3 +` ) var _ = Describe("Register", func() { @@ -82,7 +135,21 @@ var _ = Describe("Register", func() { _, err := register(ctx) Expect(err).ShouldNot(HaveOccurred()) - Expect(tc.monitor.Resources()).To(MatchKeys(IgnoreExtras, Keys{ + Expect(tc.monitor.Resources()).To(MatchAllKeys(Keys{ + "urn:pulumi:stack::project::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "name": MatchValue("crontabs.stable.example.com"), + }), + }), + }), + "urn:pulumi:stack::project::kubernetes:core/v1:Namespace::my-namespace": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "name": MatchValue("my-namespace"), + }), + }), + }), "urn:pulumi:stack::project::kubernetes:core/v1:ConfigMap::my-map": MatchProps(IgnoreExtras, Props{ "state": MatchObject(IgnoreExtras, Props{ "metadata": MatchObject(IgnoreExtras, Props{ @@ -106,7 +173,7 @@ var _ = Describe("Register", func() { resourceArray, err := internals.UnsafeAwaitOutput(ctx, resources) Expect(err).ShouldNot(HaveOccurred()) - Expect(resourceArray.Value).To(HaveLen(2)) + Expect(resourceArray.Value).To(HaveLen(4)) }) Context("when a prefix is configured", func() { @@ -117,7 +184,21 @@ var _ = Describe("Register", func() { _, err := register(ctx) Expect(err).ShouldNot(HaveOccurred()) - Expect(tc.monitor.Resources()).To(MatchKeys(IgnoreExtras, Keys{ + Expect(tc.monitor.Resources()).To(MatchAllKeys(Keys{ + "urn:pulumi:stack::project::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::prefixed-crontabs.stable.example.com": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "name": MatchValue("crontabs.stable.example.com"), + }), + }), + }), + "urn:pulumi:stack::project::kubernetes:core/v1:Namespace::prefixed-my-namespace": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "name": MatchValue("my-namespace"), + }), + }), + }), "urn:pulumi:stack::project::kubernetes:core/v1:ConfigMap::prefixed-my-map": MatchProps(IgnoreExtras, Props{ "state": MatchObject(IgnoreExtras, Props{ "metadata": MatchObject(IgnoreExtras, Props{ @@ -144,7 +225,25 @@ var _ = Describe("Register", func() { _, err := register(ctx) Expect(err).ShouldNot(HaveOccurred()) - Expect(tc.monitor.Resources()).To(MatchKeys(IgnoreExtras, Keys{ + Expect(tc.monitor.Resources()).To(MatchAllKeys(Keys{ + "urn:pulumi:stack::project::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "annotations": MatchObject(IgnoreExtras, Props{ + "pulumi.com/skipAwait": MatchValue("true"), + }), + }), + }), + }), + "urn:pulumi:stack::project::kubernetes:core/v1:Namespace::my-namespace": MatchProps(IgnoreExtras, Props{ + "state": MatchObject(IgnoreExtras, Props{ + "metadata": MatchObject(IgnoreExtras, Props{ + "annotations": MatchObject(IgnoreExtras, Props{ + "pulumi.com/skipAwait": MatchValue("true"), + }), + }), + }), + }), "urn:pulumi:stack::project::kubernetes:core/v1:ConfigMap::my-map": MatchProps(IgnoreExtras, Props{ "state": MatchObject(IgnoreExtras, Props{ "metadata": MatchObject(IgnoreExtras, Props{ @@ -166,6 +265,67 @@ var _ = Describe("Register", func() { })) }) }) + + Describe("Ordering", func() { + Context("implicit dependencies", func() { + It("should apply a DependsOn option on the dependents", func(ctx context.Context) { + _, err := register(ctx) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(tc.monitor.Registrations()).To(MatchAllKeys(Keys{ + "urn:pulumi:stack::project::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com": MatchFields(IgnoreExtras, Fields{ + "Request": MatchFields(IgnoreExtras, Fields{ + "Dependencies": BeEmpty(), + }), + }), + "urn:pulumi:stack::project::kubernetes:core/v1:Namespace::my-namespace": MatchFields(IgnoreExtras, Fields{ + "Request": MatchFields(IgnoreExtras, Fields{ + "Dependencies": BeEmpty(), + }), + }), + "urn:pulumi:stack::project::kubernetes:core/v1:ConfigMap::my-map": MatchFields(IgnoreExtras, Fields{ + "Request": MatchFields(IgnoreExtras, Fields{ + "Dependencies": BeEmpty(), + }), + }), + "urn:pulumi:stack::project::kubernetes:stable.example.com/v1:CronTab::my-new-cron-object": MatchFields(IgnoreExtras, Fields{ + "Request": MatchFields(IgnoreExtras, Fields{ + "Dependencies": ConsistOf("urn:pulumi:stack::project::kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition::crontabs.stable.example.com"), + }), + }), + })) + }) + }) + + Context("explicit dependencies (config.kubernetes.io/depends-on annotation)", func() { + BeforeEach(func() { + registerOpts.Objects = append(registerOpts.Objects, unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]any{ + "name": "my-pod", + "annotations": map[string]any{ + "config.kubernetes.io/depends-on": "/Namespace/my-namespace", + }, + }, + }, + }) + }) + It("should apply a DependsOn option on the dependents", func(ctx context.Context) { + _, err := register(ctx) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(tc.monitor.Registrations()).To(MatchKeys(IgnoreExtras, Keys{ + "urn:pulumi:stack::project::kubernetes:core/v1:Pod::my-pod": MatchFields(IgnoreExtras, Fields{ + "Request": MatchFields(IgnoreExtras, Fields{ + "Dependencies": ConsistOf("urn:pulumi:stack::project::kubernetes:core/v1:Namespace::my-namespace"), + }), + }), + })) + }) + }) + }) }) Describe("Kubernetes object specifics", func() { @@ -200,32 +360,6 @@ var _ = Describe("Register", func() { }) }) - Context("when the object is a list", func() { - BeforeEach(func() { - registerOpts.Objects = []unstructured.Unstructured{{ - Object: map[string]any{ - "apiVersion": "v1", - "kind": "List", - "items": []any{ - map[string]any{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]any{ - "name": "my-secret", - }, - }, - }, - }, - }} - }) - It("should flatten the list", func(ctx context.Context) { - _, err := register(ctx) - Expect(err).ShouldNot(HaveOccurred()) - - Expect(tc.monitor.Resources()).To(HaveKey("urn:pulumi:stack::project::kubernetes:core/v1:Secret::my-secret")) - }) - }) - Context("when the object is a Secret", func() { BeforeEach(func() { registerOpts.Objects = []unstructured.Unstructured{{ @@ -288,17 +422,11 @@ var _ = Describe("Parse", func() { It("should produce the objects in the manifest", func(ctx context.Context) { objs, err := parse(ctx) Expect(err).ShouldNot(HaveOccurred()) - Expect(objs).To(HaveExactElements( - matchUnstructured(Keys{ - "metadata": MatchKeys(IgnoreExtras, Keys{ - "name": Equal("my-map"), - }), - }), - matchUnstructured(Keys{ - "metadata": MatchKeys(IgnoreExtras, Keys{ - "name": Equal("my-new-cron-object"), - }), - }), + Expect(objs).To(ConsistOf( + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("my-namespace")})}), + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("crontabs.stable.example.com")})}), + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("my-map")})}), + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("my-new-cron-object")})}), )) }) } @@ -320,6 +448,21 @@ var _ = Describe("Parse", func() { Expect(err).ShouldNot(HaveOccurred()) }) }) + + Context("when the object is a list", func() { + BeforeEach(func() { + args.YAML = list + }) + It("should flatten the list", func(ctx context.Context) { + objs, err := parse(ctx) + Expect(err).ShouldNot(HaveOccurred()) + Expect(objs).To(HaveExactElements( + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("map-1")})}), + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("map-2")})}), + matchUnstructured(Keys{"metadata": MatchKeys(IgnoreExtras, Keys{"name": Equal("map-3")})}), + )) + }) + }) }) Describe("files", func() { diff --git a/sdk/dotnet/Yaml/V2/ConfigFile.cs b/sdk/dotnet/Yaml/V2/ConfigFile.cs index 8af6a160cf..2f7b62bbb0 100644 --- a/sdk/dotnet/Yaml/V2/ConfigFile.cs +++ b/sdk/dotnet/Yaml/V2/ConfigFile.cs @@ -12,6 +12,40 @@ namespace Pulumi.Kubernetes.Yaml.V2 /// /// ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. /// + /// ## Dependency ordering + /// Sometimes resources must be applied in a specific order. For example, a namespace resource must be + /// created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + /// + /// Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also + /// waits for each object to be fully reconciled, unless `skipAwait` is enabled. + /// + /// ### Explicit Dependency Ordering + /// Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + /// The annotation accepts a list of resource references, delimited by commas. + /// + /// Note that references to resources outside the ConfigFile aren't supported. + /// + /// **Resource reference** + /// + /// A resource reference is a string that uniquely identifies a resource. + /// + /// It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + /// + /// | Resource Scope | Format | + /// | :--------------- | :--------------------------------------------- | + /// | namespace-scoped | `<group>/namespaces/<namespace>/<kind>/<name>` | + /// | cluster-scoped | `<group>/<kind>/<name>` | + /// + /// For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + /// + /// ### Ordering across ConfigFiles + /// The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + /// Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within + /// the group to be deployed. + /// + /// A best practice is to deploy each application using its own ConfigFile, especially when that application + /// installs custom resource definitions. + /// /// ## Example Usage /// ### Local File /// ```csharp diff --git a/sdk/dotnet/Yaml/V2/ConfigGroup.cs b/sdk/dotnet/Yaml/V2/ConfigGroup.cs index b948ffe377..c0d8492d84 100644 --- a/sdk/dotnet/Yaml/V2/ConfigGroup.cs +++ b/sdk/dotnet/Yaml/V2/ConfigGroup.cs @@ -18,6 +18,40 @@ namespace Pulumi.Kubernetes.Yaml.V2 /// 3. Using a literal string containing YAML, or a list of such strings: /// 4. Any combination of files, patterns, or YAML strings: /// + /// ## Dependency ordering + /// Sometimes resources must be applied in a specific order. For example, a namespace resource must be + /// created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + /// + /// Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also + /// waits for each object to be fully reconciled, unless `skipAwait` is enabled. + /// + /// ### Explicit Dependency Ordering + /// Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + /// The annotation accepts a list of resource references, delimited by commas. + /// + /// Note that references to resources outside the ConfigGroup aren't supported. + /// + /// **Resource reference** + /// + /// A resource reference is a string that uniquely identifies a resource. + /// + /// It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + /// + /// | Resource Scope | Format | + /// | :--------------- | :--------------------------------------------- | + /// | namespace-scoped | `<group>/namespaces/<namespace>/<kind>/<name>` | + /// | cluster-scoped | `<group>/<kind>/<name>` | + /// + /// For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + /// + /// ### Ordering across ConfigGroups + /// The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + /// Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within + /// the group to be deployed. + /// + /// A best practice is to deploy each application using its own ConfigGroup, especially when that application + /// installs custom resource definitions. + /// /// ## Example Usage /// ### Local File /// ```csharp diff --git a/sdk/go/kubernetes/yaml/v2/configFile.go b/sdk/go/kubernetes/yaml/v2/configFile.go index 2ad3275830..11f7110133 100644 --- a/sdk/go/kubernetes/yaml/v2/configFile.go +++ b/sdk/go/kubernetes/yaml/v2/configFile.go @@ -14,6 +14,40 @@ import ( // ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. // +// ## Dependency ordering +// Sometimes resources must be applied in a specific order. For example, a namespace resource must be +// created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. +// +// Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also +// waits for each object to be fully reconciled, unless `skipAwait` is enabled. +// +// ### Explicit Dependency Ordering +// Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. +// The annotation accepts a list of resource references, delimited by commas. +// +// Note that references to resources outside the ConfigFile aren't supported. +// +// **Resource reference** +// +// A resource reference is a string that uniquely identifies a resource. +// +// It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. +// +// | Resource Scope | Format | +// | :--------------- | :--------------------------------------------- | +// | namespace-scoped | `/namespaces///` | +// | cluster-scoped | `//` | +// +// For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). +// +// ### Ordering across ConfigFiles +// The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. +// Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within +// the group to be deployed. +// +// A best practice is to deploy each application using its own ConfigFile, especially when that application +// installs custom resource definitions. +// // ## Example Usage // ### Local File // ```go diff --git a/sdk/go/kubernetes/yaml/v2/configGroup.go b/sdk/go/kubernetes/yaml/v2/configGroup.go index 8e2cdf777d..3eec4a876c 100644 --- a/sdk/go/kubernetes/yaml/v2/configGroup.go +++ b/sdk/go/kubernetes/yaml/v2/configGroup.go @@ -19,6 +19,40 @@ import ( // 3. Using a literal string containing YAML, or a list of such strings: // 4. Any combination of files, patterns, or YAML strings: // +// ## Dependency ordering +// Sometimes resources must be applied in a specific order. For example, a namespace resource must be +// created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. +// +// Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also +// waits for each object to be fully reconciled, unless `skipAwait` is enabled. +// +// ### Explicit Dependency Ordering +// Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. +// The annotation accepts a list of resource references, delimited by commas. +// +// Note that references to resources outside the ConfigGroup aren't supported. +// +// **Resource reference** +// +// A resource reference is a string that uniquely identifies a resource. +// +// It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. +// +// | Resource Scope | Format | +// | :--------------- | :--------------------------------------------- | +// | namespace-scoped | `/namespaces///` | +// | cluster-scoped | `//` | +// +// For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). +// +// ### Ordering across ConfigGroups +// The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. +// Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within +// the group to be deployed. +// +// A best practice is to deploy each application using its own ConfigGroup, especially when that application +// installs custom resource definitions. +// // ## Example Usage // ### Local File // ```go diff --git a/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigFile.java b/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigFile.java index ceb6f2881c..7cbb685da5 100644 --- a/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigFile.java +++ b/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigFile.java @@ -17,6 +17,40 @@ /** * ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. * + * ## Dependency ordering + * Sometimes resources must be applied in a specific order. For example, a namespace resource must be + * created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + * + * Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also + * waits for each object to be fully reconciled, unless `skipAwait` is enabled. + * + * ### Explicit Dependency Ordering + * Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + * The annotation accepts a list of resource references, delimited by commas. + * + * Note that references to resources outside the ConfigFile aren't supported. + * + * **Resource reference** + * + * A resource reference is a string that uniquely identifies a resource. + * + * It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + * + * | Resource Scope | Format | + * | :--------------- | :--------------------------------------------- | + * | namespace-scoped | `<group>/namespaces/<namespace>/<kind>/<name>` | + * | cluster-scoped | `<group>/<kind>/<name>` | + * + * For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + * + * ### Ordering across ConfigFiles + * The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + * Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within + * the group to be deployed. + * + * A best practice is to deploy each application using its own ConfigFile, especially when that application + * installs custom resource definitions. + * * ## Example Usage * {% /examples %}} * diff --git a/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigGroup.java b/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigGroup.java index f370b51be0..b553821f7a 100644 --- a/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigGroup.java +++ b/sdk/java/src/main/java/com/pulumi/kubernetes/yaml/v2/ConfigGroup.java @@ -23,6 +23,40 @@ * 3. Using a literal string containing YAML, or a list of such strings: * 4. Any combination of files, patterns, or YAML strings: * + * ## Dependency ordering + * Sometimes resources must be applied in a specific order. For example, a namespace resource must be + * created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + * + * Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also + * waits for each object to be fully reconciled, unless `skipAwait` is enabled. + * + * ### Explicit Dependency Ordering + * Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + * The annotation accepts a list of resource references, delimited by commas. + * + * Note that references to resources outside the ConfigGroup aren't supported. + * + * **Resource reference** + * + * A resource reference is a string that uniquely identifies a resource. + * + * It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + * + * | Resource Scope | Format | + * | :--------------- | :--------------------------------------------- | + * | namespace-scoped | `<group>/namespaces/<namespace>/<kind>/<name>` | + * | cluster-scoped | `<group>/<kind>/<name>` | + * + * For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + * + * ### Ordering across ConfigGroups + * The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + * Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within + * the group to be deployed. + * + * A best practice is to deploy each application using its own ConfigGroup, especially when that application + * installs custom resource definitions. + * * ## Example Usage * {% /examples %}} * diff --git a/sdk/nodejs/yaml/v2/configFile.ts b/sdk/nodejs/yaml/v2/configFile.ts index db0512e28f..f9a0f71847 100644 --- a/sdk/nodejs/yaml/v2/configFile.ts +++ b/sdk/nodejs/yaml/v2/configFile.ts @@ -7,6 +7,40 @@ import * as utilities from "../../utilities"; /** * ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. * + * ## Dependency ordering + * Sometimes resources must be applied in a specific order. For example, a namespace resource must be + * created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + * + * Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also + * waits for each object to be fully reconciled, unless `skipAwait` is enabled. + * + * ### Explicit Dependency Ordering + * Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + * The annotation accepts a list of resource references, delimited by commas. + * + * Note that references to resources outside the ConfigFile aren't supported. + * + * **Resource reference** + * + * A resource reference is a string that uniquely identifies a resource. + * + * It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + * + * | Resource Scope | Format | + * | :--------------- | :--------------------------------------------- | + * | namespace-scoped | `/namespaces///` | + * | cluster-scoped | `//` | + * + * For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + * + * ### Ordering across ConfigFiles + * The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + * Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within + * the group to be deployed. + * + * A best practice is to deploy each application using its own ConfigFile, especially when that application + * installs custom resource definitions. + * * ## Example Usage * ### Local File * diff --git a/sdk/nodejs/yaml/v2/configGroup.ts b/sdk/nodejs/yaml/v2/configGroup.ts index be25598b8e..1da564b685 100644 --- a/sdk/nodejs/yaml/v2/configGroup.ts +++ b/sdk/nodejs/yaml/v2/configGroup.ts @@ -13,6 +13,40 @@ import * as utilities from "../../utilities"; * 3. Using a literal string containing YAML, or a list of such strings: * 4. Any combination of files, patterns, or YAML strings: * + * ## Dependency ordering + * Sometimes resources must be applied in a specific order. For example, a namespace resource must be + * created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + * + * Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also + * waits for each object to be fully reconciled, unless `skipAwait` is enabled. + * + * ### Explicit Dependency Ordering + * Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + * The annotation accepts a list of resource references, delimited by commas. + * + * Note that references to resources outside the ConfigGroup aren't supported. + * + * **Resource reference** + * + * A resource reference is a string that uniquely identifies a resource. + * + * It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + * + * | Resource Scope | Format | + * | :--------------- | :--------------------------------------------- | + * | namespace-scoped | `/namespaces///` | + * | cluster-scoped | `//` | + * + * For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + * + * ### Ordering across ConfigGroups + * The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + * Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within + * the group to be deployed. + * + * A best practice is to deploy each application using its own ConfigGroup, especially when that application + * installs custom resource definitions. + * * ## Example Usage * ### Local File * diff --git a/sdk/python/pulumi_kubernetes/yaml/v2/ConfigFile.py b/sdk/python/pulumi_kubernetes/yaml/v2/ConfigFile.py index 07e2f361fe..42fa173ad5 100644 --- a/sdk/python/pulumi_kubernetes/yaml/v2/ConfigFile.py +++ b/sdk/python/pulumi_kubernetes/yaml/v2/ConfigFile.py @@ -78,6 +78,40 @@ def __init__(__self__, """ ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. + ## Dependency ordering + Sometimes resources must be applied in a specific order. For example, a namespace resource must be + created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + + Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also + waits for each object to be fully reconciled, unless `skipAwait` is enabled. + + ### Explicit Dependency Ordering + Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + The annotation accepts a list of resource references, delimited by commas. + + Note that references to resources outside the ConfigFile aren't supported. + + **Resource reference** + + A resource reference is a string that uniquely identifies a resource. + + It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + + | Resource Scope | Format | + | :--------------- | :--------------------------------------------- | + | namespace-scoped | `/namespaces///` | + | cluster-scoped | `//` | + + For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + + ### Ordering across ConfigFiles + The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within + the group to be deployed. + + A best practice is to deploy each application using its own ConfigFile, especially when that application + installs custom resource definitions. + ## Example Usage ### Local File ```python @@ -105,6 +139,40 @@ def __init__(__self__, """ ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file. + ## Dependency ordering + Sometimes resources must be applied in a specific order. For example, a namespace resource must be + created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + + Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigFile. Pulumi also + waits for each object to be fully reconciled, unless `skipAwait` is enabled. + + ### Explicit Dependency Ordering + Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + The annotation accepts a list of resource references, delimited by commas. + + Note that references to resources outside the ConfigFile aren't supported. + + **Resource reference** + + A resource reference is a string that uniquely identifies a resource. + + It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + + | Resource Scope | Format | + | :--------------- | :--------------------------------------------- | + | namespace-scoped | `/namespaces///` | + | cluster-scoped | `//` | + + For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + + ### Ordering across ConfigFiles + The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + Use it on another resource to make it dependent on the ConfigFile and to wait for the resources within + the group to be deployed. + + A best practice is to deploy each application using its own ConfigFile, especially when that application + installs custom resource definitions. + ## Example Usage ### Local File ```python diff --git a/sdk/python/pulumi_kubernetes/yaml/v2/ConfigGroup.py b/sdk/python/pulumi_kubernetes/yaml/v2/ConfigGroup.py index 3fd02d979a..e4cfc0c224 100644 --- a/sdk/python/pulumi_kubernetes/yaml/v2/ConfigGroup.py +++ b/sdk/python/pulumi_kubernetes/yaml/v2/ConfigGroup.py @@ -119,6 +119,40 @@ def __init__(__self__, 3. Using a literal string containing YAML, or a list of such strings: 4. Any combination of files, patterns, or YAML strings: + ## Dependency ordering + Sometimes resources must be applied in a specific order. For example, a namespace resource must be + created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + + Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also + waits for each object to be fully reconciled, unless `skipAwait` is enabled. + + ### Explicit Dependency Ordering + Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + The annotation accepts a list of resource references, delimited by commas. + + Note that references to resources outside the ConfigGroup aren't supported. + + **Resource reference** + + A resource reference is a string that uniquely identifies a resource. + + It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + + | Resource Scope | Format | + | :--------------- | :--------------------------------------------- | + | namespace-scoped | `/namespaces///` | + | cluster-scoped | `//` | + + For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + + ### Ordering across ConfigGroups + The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within + the group to be deployed. + + A best practice is to deploy each application using its own ConfigGroup, especially when that application + installs custom resource definitions. + ## Example Usage ### Local File ```python @@ -195,6 +229,40 @@ def __init__(__self__, 3. Using a literal string containing YAML, or a list of such strings: 4. Any combination of files, patterns, or YAML strings: + ## Dependency ordering + Sometimes resources must be applied in a specific order. For example, a namespace resource must be + created before any namespaced resources, or a Custom Resource Definition (CRD) must be pre-installed. + + Pulumi uses heuristics to determine which order to apply and delete objects within the ConfigGroup. Pulumi also + waits for each object to be fully reconciled, unless `skipAwait` is enabled. + + ### Explicit Dependency Ordering + Pulumi supports the `config.kubernetes.io/depends-on` annotation to declare an explicit dependency on a given resource. + The annotation accepts a list of resource references, delimited by commas. + + Note that references to resources outside the ConfigGroup aren't supported. + + **Resource reference** + + A resource reference is a string that uniquely identifies a resource. + + It consists of the group, kind, name, and optionally the namespace, delimited by forward slashes. + + | Resource Scope | Format | + | :--------------- | :--------------------------------------------- | + | namespace-scoped | `/namespaces///` | + | cluster-scoped | `//` | + + For resources in the “core” group, the empty string is used instead (for example: `/namespaces/test/Pod/pod-a`). + + ### Ordering across ConfigGroups + The `dependsOn` resource option creates a list of explicit dependencies between Pulumi resources. + Use it on another resource to make it dependent on the ConfigGroup and to wait for the resources within + the group to be deployed. + + A best practice is to deploy each application using its own ConfigGroup, especially when that application + installs custom resource definitions. + ## Example Usage ### Local File ```python