Skip to content

Commit

Permalink
ConfigFile V2 (Component) (#2862)
Browse files Browse the repository at this point in the history
This PR implements a new `ConfigFile` resource (v2) as a Pulumi
multi-language component (MLC). The new resource is offered side-by-side
with the existing (v1) resource; it is not a drop-in replacement at this
time.

Some notable differences with respect to the previous (overlay)
implementations:
1. Implemented as an MLC to have a consistent implementation across SDKs
and to extend support for YAML and Java.
2. The component name is used as a prefix for the child resource names.
Use the `resourcePrefix` property to override. Note that the
`resourcePrefix` is not applied to the Kubernetes object names.
4. Transformations aren't supported (yet). Support for Pulumi-style
transformation is being tracked
[here](pulumi/pulumi#12996), and
Kubernetes-style transformation may come in future.
5. The `file` property is required and the file must exist.

Closes #2785
  • Loading branch information
EronWright authored Mar 8, 2024
1 parent 6951ed5 commit c59ddff
Show file tree
Hide file tree
Showing 22 changed files with 1,236 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Use async invokes to avoid hangs/stalls in Python `helm`, `kustomize`, and `yaml` components (https://github.com/pulumi/pulumi-kubernetes/pull/2863)
- ConfigGroup V2 (https://github.com/pulumi/pulumi-kubernetes/pull/2844)
- ConfigFile V2 (https://github.com/pulumi/pulumi-kubernetes/pull/2862)

### New Features

Expand Down
1 change: 1 addition & 0 deletions provider/cmd/pulumi-gen-kubernetes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func generateSchema(swaggerPath string) schema.PackageSpec {
// This is to mostly filter resources from the spec.
var resourcesToFilterFromTemplate = codegen.NewStringSet(
"kubernetes:helm.sh/v3:Release",
"kubernetes:yaml/v2:ConfigFile",
"kubernetes:yaml/v2:ConfigGroup",
)

Expand Down
37 changes: 35 additions & 2 deletions provider/cmd/pulumi-resource-kubernetes/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
"storage.k8s.io/v1": "Storage.V1",
"storage.k8s.io/v1alpha1": "Storage.V1Alpha1",
"storage.k8s.io/v1beta1": "Storage.V1Beta1",
"yaml": "Yaml"
"yaml": "Yaml",
"yaml/v2": "Yaml.V2"
},
"packageReferences": {
"Glob": "1.1.5",
Expand Down Expand Up @@ -291,7 +292,8 @@
"settings.k8s.io/v1alpha1": "settings.v1alpha1",
"storage.k8s.io/v1": "storage.v1",
"storage.k8s.io/v1alpha1": "storage.v1alpha1",
"storage.k8s.io/v1beta1": "storage.v1beta1"
"storage.k8s.io/v1beta1": "storage.v1beta1",
"yaml/v2": "yaml.v2"
}
},
"nodejs": {
Expand Down Expand Up @@ -95185,6 +95187,37 @@
}
]
},
"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",
"properties": {
"resources": {
"type": "array",
"items": {
"$ref": "pulumi.json#/Any"
},
"description": "Resources created by the ConfigFile."
}
},
"type": "object",
"inputProperties": {
"file": {
"type": "string",
"description": "Path or URL to a Kubernetes manifest file. File must exist."
},
"resourcePrefix": {
"type": "string",
"description": "A prefix for the auto-generated resource names. Defaults to the name of the ConfigFile. Example: A resource created with resourcePrefix=\"foo\" would produce a resource named \"foo-resourceName\"."
},
"skipAwait": {
"type": "boolean",
"description": "Indicates that child resources should skip the await logic."
}
},
"requiredInputs": [
"file"
],
"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",
"properties": {
Expand Down
63 changes: 63 additions & 0 deletions provider/pkg/gen/examples/overlays/configFileV2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
ConfigFile creates a set of Kubernetes resources from a Kubernetes YAML file.

{{% examples %}}
## Example Usage
{{% example %}}
### Local File

```typescript
import * as k8s from "@pulumi/kubernetes";

const example = new k8s.yaml.v2.ConfigFile("example", {
file: "foo.yaml",
});
```
```python
from pulumi_kubernetes.yaml.v2 import ConfigFile

example = ConfigFile(
"example",
file="foo.yaml",
)
```
```csharp
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Kubernetes.Yaml.V2;

class YamlStack : Stack
{
public YamlStack()
{
var helloWorld = new ConfigFile("example", new ConfigFileArgs
{
File = "foo.yaml",
});
}
}
```
```go
package main

import (
yamlv2 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml/v2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := yamlv2.NewConfigFile(ctx, "example",
&yamlv2.ConfigFileArgs{
File: "foo.yaml",
},
)
if err != nil {
return err
}

return nil
})
}
```
{{% /example %}}
{% /examples %}}
47 changes: 47 additions & 0 deletions provider/pkg/gen/overlays.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,52 @@ var yamlConfigFileResource = pschema.ResourceSpec{
},
}

//go:embed examples/overlays/configFileV2.md
var configFileV2MD string

var yamlConfigFileV2Resource = pschema.ResourceSpec{
IsComponent: true,
ObjectTypeSpec: pschema.ObjectTypeSpec{
IsOverlay: false,
Description: configFileV2MD,
Properties: map[string]pschema.PropertySpec{
"resources": {
TypeSpec: pschema.TypeSpec{
Type: "array",
Items: &pschema.TypeSpec{
Ref: "pulumi.json#/Any",
},
},
Description: "Resources created by the ConfigFile.",
},
},
Type: "object",
},
InputProperties: map[string]pschema.PropertySpec{
"file": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
Description: "Path or URL to a Kubernetes manifest file. File must exist.",
},
"resourcePrefix": {
TypeSpec: pschema.TypeSpec{
Type: "string",
},
Description: "A prefix for the auto-generated resource names. Defaults to the name of the ConfigFile. Example: A resource created with resourcePrefix=\"foo\" would produce a resource named \"foo-resourceName\".",
},
"skipAwait": {
TypeSpec: pschema.TypeSpec{
Type: "boolean",
},
Description: "Indicates that child resources should skip the await logic.",
},
},
RequiredInputs: []string{
"file",
},
}

//go:embed examples/overlays/configGroup.md
var configGroupMD string

Expand Down Expand Up @@ -1352,6 +1398,7 @@ func init() {
resourceOverlays["kubernetes:helm.sh/v3:Release"] = helmV3ReleaseResource
resourceOverlays["kubernetes:kustomize:Directory"] = kustomizeDirectoryResource
resourceOverlays["kubernetes:yaml:ConfigFile"] = yamlConfigFileResource
resourceOverlays["kubernetes:yaml/v2:ConfigFile"] = yamlConfigFileV2Resource
resourceOverlays["kubernetes:yaml:ConfigGroup"] = yamlConfigGroupResource
resourceOverlays["kubernetes:yaml/v2:ConfigGroup"] = yamlConfigGroupV2Resource
}
2 changes: 2 additions & 0 deletions provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,13 @@ func PulumiSchema(swagger map[string]any) pschema.PackageSpec {
"helm.sh/v2": "Helm.V2",
"helm.sh/v3": "Helm.V3",
"yaml": "Yaml",
"yaml/v2": "Yaml.V2",
"": "Provider",
}
javaPackages := map[string]string{
"helm.sh/v2": "helm.v2",
"helm.sh/v3": "helm.v3",
"yaml/v2": "yaml.v2",
}
modToPkg := map[string]string{
"apiextensions.k8s.io": "apiextensions",
Expand Down
1 change: 1 addition & 0 deletions provider/pkg/provider/provider_construct.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

// resourceProviders contains factories for component resource providers.
var resourceProviders = map[string]providerresource.ResourceProviderFactory{
"kubernetes:yaml/v2:ConfigFile": provideryamlv2.NewConfigFileProvider,
"kubernetes:yaml/v2:ConfigGroup": provideryamlv2.NewConfigGroupProvider,
}

Expand Down
99 changes: 99 additions & 0 deletions provider/pkg/provider/yaml/v2/configfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v2

import (
"context"
"fmt"

"github.com/pulumi/pulumi-kubernetes/provider/v4/pkg/clients"
providerresource "github.com/pulumi/pulumi-kubernetes/provider/v4/pkg/provider/resource"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/internals"
pulumiprovider "github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider"
)

type ConfigFileProvider struct {
clientSet *clients.DynamicClientSet
}

type ConfigFileArgs struct {
File pulumi.StringInput `pulumi:"file"`
ResourcePrefix pulumi.StringInput `pulumi:"resourcePrefix,optional"`
SkipAwait pulumi.BoolInput `pulumi:"skipAwait,optional"`
}

type ConfigFileState struct {
pulumi.ResourceState
Resources pulumi.ArrayOutput `pulumi:"resources"`
}

var _ providerresource.ResourceProvider = &ConfigFileProvider{}

func NewConfigFileProvider(opts *providerresource.ResourceProviderOptions) providerresource.ResourceProvider {
return &ConfigFileProvider{
clientSet: opts.ClientSet,
}
}

func (k *ConfigFileProvider) Construct(ctx *pulumi.Context, typ, name string, inputs pulumiprovider.ConstructInputs, options pulumi.ResourceOption) (*pulumiprovider.ConstructResult, error) {
comp := &ConfigFileState{}
err := ctx.RegisterComponentResource(typ, name, comp, options)
if err != nil {
return nil, err
}

args := &ConfigFileArgs{}
if err := inputs.CopyTo(args); err != nil {
return nil, fmt.Errorf("setting args: %w", err)
}

// Check if all the required args are known, and print a warning if not.
result, err := internals.UnsafeAwaitOutput(ctx.Context(), pulumi.All(
args.File, args.ResourcePrefix, args.SkipAwait))
if err != nil {
return nil, err
}
if !result.Known {
msg := fmt.Sprintf("%s:%s -- Required input properties have unknown values. Preview is incomplete.\n", typ, name)
_ = ctx.Log.Warn(msg, nil)
}

// Parse the manifest(s) and register the resources.

comp.Resources = pulumi.All(args.File, args.ResourcePrefix, args.SkipAwait).ApplyTWithContext(ctx.Context(), func(_ context.Context, args []any) (pulumi.ArrayOutput, error) {
// make type assertions to get each value (or the zero value)
file, _ := args[0].(string)
resourcePrefix, hasResourcePrefix := args[1].(string)
skipAwait, _ := args[2].(bool)

if !hasResourcePrefix {
// use the name of the ConfigFile as the resource prefix to ensure uniqueness
// across multiple instances of the component resource.
resourcePrefix = name
}

return ParseDecodeYamlFiles(ctx, &ParseArgs{
Files: []string{file},
ResourcePrefix: resourcePrefix,
SkipAwait: skipAwait,
}, false, k.clientSet, pulumi.Parent(comp))
}).(pulumi.ArrayOutput)

// issue: https://github.com/pulumi/pulumi/issues/15527
_, _ = internals.UnsafeAwaitOutput(ctx.Context(), comp.Resources)

return pulumiprovider.NewConstructResult(comp)
}
Loading

0 comments on commit c59ddff

Please sign in to comment.