Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[yaml/v2] Support for resource ordering (implicit and explicit) #2894

Merged
merged 8 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions provider/cmd/pulumi-resource-kubernetes/schema.json

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions provider/pkg/gen/examples/overlays/configFileV2.md
Original file line number Diff line number Diff line change
@@ -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 | `<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.

{{% examples %}}
## Example Usage
{{% example %}}
Expand Down
34 changes: 34 additions & 0 deletions provider/pkg/gen/examples/overlays/configGroupV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | `<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.

{{% examples %}}
## Example Usage
{{% example %}}
Expand Down
12 changes: 9 additions & 3 deletions provider/pkg/provider/yaml/v2/configfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
EronWright marked this conversation as resolved.
Show resolved Hide resolved
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"),
)),
Expand All @@ -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"),
)),
Expand All @@ -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"),
)),
Expand Down
6 changes: 5 additions & 1 deletion provider/pkg/provider/yaml/v2/configgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
53 changes: 41 additions & 12 deletions provider/pkg/provider/yaml/v2/configgroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)),
Expand All @@ -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"),
)),
Expand All @@ -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"),
)),
Expand All @@ -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() {
Expand Down
Loading
Loading