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

Add Helm support for Go SDK #1105

Merged
merged 46 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a65269a
Add YAML support for Go SDK
lblackstone May 1, 2020
f8e3cc9
gofmt generated file
lblackstone May 1, 2020
892be30
Handle list types
lblackstone May 1, 2020
6731332
Update templates
lblackstone May 1, 2020
f482724
WIP testing
lblackstone May 1, 2020
fd2c1e0
Update test
lblackstone May 1, 2020
1db54b7
changelog
lblackstone May 1, 2020
e8ae1dd
Fix test path
lblackstone May 1, 2020
a4d96e6
Appease the linter
lblackstone May 4, 2020
7399f3c
Update comments
lblackstone May 4, 2020
7b18f6e
Update template
lblackstone May 4, 2020
369e65b
Fix tests
lblackstone May 4, 2020
6d37aa0
More feedback
lblackstone May 4, 2020
f2d4c46
Fixed output resources
lblackstone May 4, 2020
ebca09e
Test getResource
lblackstone May 5, 2020
ed67459
Clean up codegen
lblackstone May 5, 2020
431c72a
Update get methods
lblackstone May 5, 2020
f1b53d3
Don't reuse opts
lblackstone May 5, 2020
84252ac
Remove pointer type
lblackstone May 5, 2020
cb4b325
Transformation support
lblackstone May 5, 2020
9aa3928
Bump deps
lblackstone May 6, 2020
c812a68
Simplify
lblackstone May 6, 2020
adbda26
Move templates to subfolder
lblackstone May 6, 2020
bd29096
Even simpler
lblackstone May 6, 2020
efbd1a9
Merge branch 'master' into lblackstone/go-yaml
lblackstone May 6, 2020
e1f6dd0
Fix test
lblackstone May 6, 2020
c22c74e
Add Helm support to Go SDK
lblackstone May 7, 2020
0867c41
Changelog
lblackstone May 7, 2020
0397db7
Add types
lblackstone May 7, 2020
beed284
Untested WIP
lblackstone May 7, 2020
512c32c
Working!
lblackstone May 7, 2020
b10bec7
Fix fetch
lblackstone May 7, 2020
34d410c
Fix GetResource
lblackstone May 7, 2020
70b68b1
Address feedback
lblackstone May 7, 2020
1866928
Merge branch 'lblackstone/go-yaml' into lblackstone/go-helm
lblackstone May 7, 2020
f98455d
Add tests
lblackstone May 7, 2020
d5e2822
Better error messages
lblackstone May 7, 2020
080f018
Merge branch 'master' into lblackstone/go-helm
lblackstone May 7, 2020
e9dafa4
Fix values override
lblackstone May 7, 2020
cae3cc6
Update tests
lblackstone May 7, 2020
da58564
Update sdk/go/kubernetes/helm/v2/types.go
lblackstone May 7, 2020
7a65cec
Feedback
lblackstone May 7, 2020
cac4050
gofmt
lblackstone May 8, 2020
abe43b4
Linting
lblackstone May 8, 2020
686d2b2
Mod paths
lblackstone May 8, 2020
9c494a5
Try lowering test parallelism
lblackstone May 8, 2020
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 @@
### Improvements

- Add YAML support to Go SDK. (https://github.com/pulumi/pulumi-kubernetes/pull/1093).
- Add Helm support to Go SDK. (https://github.com/pulumi/pulumi-kubernetes/pull/1105).

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ else
DOTNET_VERSION := $(strip ${DOTNET_PREFIX})-$(strip ${DOTNET_SUFFIX})
endif

TESTPARALLELISM := 10
TESTPARALLELISM := 1

$(OPENAPI_FILE)::
@mkdir -p $(OPENAPI_DIR)
Expand Down
4 changes: 2 additions & 2 deletions provider/pkg/gen/go-templates/yaml/yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func parseDecodeYamlFiles(ctx *pulumi.Context, args *ConfigGroupArgs, glob bool,
}

// Now process the resulting list of Kubernetes objects.
return parseYamlObjects(ctx, objs, args.Transformations, args.ResourcePrefix, opts...)
return ParseYamlObjects(ctx, objs, args.Transformations, args.ResourcePrefix, opts...)
}

// yamlDecode invokes the function to decode a single YAML file and decompose it into object structures.
Expand All @@ -106,7 +106,7 @@ func yamlDecode(ctx *pulumi.Context, text string, opts ...pulumi.ResourceOption)
return ret.Result, nil
}

func parseYamlObjects(ctx *pulumi.Context, objs []map[string]interface{}, transformations []Transformation,
func ParseYamlObjects(ctx *pulumi.Context, objs []map[string]interface{}, transformations []Transformation,
resourcePrefix string, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {
var intermediates []resourceTuple
Expand Down
281 changes: 281 additions & 0 deletions sdk/go/kubernetes/helm/v2/chart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
// Copyright 2016-2020, 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.

// *** WARNING: this file was generated by pulumigen. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***

package helm

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"

"github.com/pkg/errors"
"github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/yaml"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)

// Chart is a component representing a collection of resources described by an arbitrary Helm
// Chart. The Chart can be fetched from any source that is accessible to the `helm` command
// line. Values in the `values.yml` file can be overridden using `ChartOpts.values` (equivalent
// to `--set` or having multiple `values.yml` files). Objects can be transformed arbitrarily by
// supplying callbacks to `ChartOpts.transformations`.
//
// `Chart` does not use Tiller. The Chart specified is copied and expanded locally; the semantics
// are equivalent to running `helm template` and then using Pulumi to manage the resulting YAML
// manifests. Any values that would be retrieved in-cluster are assigned fake values, and
// none of Tiller's server-side validity testing is executed.
type Chart struct {
pulumi.ResourceState

Resources pulumi.Output
}

// NewChart registers a new resource with the given unique name, arguments, and options.
func NewChart(ctx *pulumi.Context,
name string, args ChartArgs, opts ...pulumi.ResourceOption) (*Chart, error) {

// Register the resulting resource state.
chart := &Chart{}
err := ctx.RegisterComponentResource("kubernetes:helm.sh/v2:Chart", name, chart, opts...)
if err != nil {
return nil, err
}

// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
}

resources := args.ToChartArgsOutput().ApplyT(func(args chartArgs) (map[string]pulumi.Resource, error) {
return parseChart(ctx, name, args, pulumi.Parent(chart))
})
chart.Resources = resources

// Finally, register all of the resources found.
// Note: Go requires that we "pull" on our futures in order to get them scheduled for execution. Here, we use
// the engine's RegisterResourceOutputs to wait for the resolution of all resources that this Helm chart created.
err = ctx.RegisterResourceOutputs(chart, pulumi.Map{"resources": resources})
if err != nil {
return nil, errors.Wrap(err, "registering child resources")
}

return chart, nil
}

func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {

// Create temporary directory and file to hold chart data and override values.
chartDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, errors.Wrap(err, "creating temp directory for chart")
}
defer os.RemoveAll(chartDir)
overrides, err := ioutil.TempFile("", "values.*.yaml")
if err != nil {
return nil, errors.Wrap(err, "creating temp file for chart values")
}
defer os.Remove(overrides.Name())

var chart string
if args.Path != "" { // Local Chart
chart = args.Path
} else { // Remote Chart
if strings.HasPrefix(args.Repo, "http") {
return nil, fmt.Errorf("`repo` specifies the name of the Helm chart repo. Use FetchOpts.Repo" +
"to specify a URL")
}

chartToFetch := args.Chart
if len(args.Repo) > 0 {
chartToFetch = fmt.Sprintf("%s/%s", args.Repo, chartToFetch)
}

// Fetch the Chart.
if len(args.FetchArgs.Destination) == 0 {
args.FetchArgs.Destination = chartDir
}
if len(args.FetchArgs.Version) == 0 {
args.FetchArgs.Version = args.Version
}
err = fetch(chartToFetch, args.FetchArgs)
if err != nil {
return nil, err
}

// Get the path to the fetched Chart.
files, err := ioutil.ReadDir(chartDir)
if err != nil {
return nil, errors.Wrap(err, "failed to read chart directory")
}
if len(files) == 0 {
return nil, errors.New("chart directory was empty")
}
sort.Slice(files, func(i, j int) bool {
return files[i].Name() < files[j].Name()
})
fetchedChartName := files[0].Name()

chart = filepath.Join(chartDir, fetchedChartName)
}

defaultVals := filepath.Join(chart, "values.yaml")

helmArgs := []string{"template", chart, "--name-template", name, "--values", defaultVals}
// Write overrides file if Values set.
if args.Values != nil {
b, err := json.Marshal(args.Values)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal overrides file")
}
_, err = overrides.Write(b)
if err != nil {
return nil, errors.Wrap(err, "failed to write overrides file")
}
helmArgs = append(helmArgs, "--values", overrides.Name())
}
if len(args.Namespace) > 0 {
helmArgs = append(helmArgs, "--namespace", args.Namespace)
}

helmCmd := exec.Command("helm", helmArgs...)
var stderr bytes.Buffer
helmCmd.Stderr = &stderr
yamlBytes, err := helmCmd.Output()
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to run helm template: %s", stderr.String()))
}
objs, err := yamlDecode(ctx, string(yamlBytes), args.Namespace)
if err != nil {
return nil, err
}

resources, err := yaml.ParseYamlObjects(ctx, objs, args.Transformations, args.ResourcePrefix, opts...)
if err != nil {
return nil, err
}
return resources, nil
}

// yamlDecode invokes the function to decode a single YAML file and decompose it into object structures.
func yamlDecode(ctx *pulumi.Context, text, namespace string) ([]map[string]interface{}, error) {
args := struct {
Text string `pulumi:"text"`
DefaultNamespace string `pulumi:"defaultNamespace"`
}{Text: text, DefaultNamespace: namespace}
var ret struct {
Result []map[string]interface{} `pulumi:"result"`
}
if err := ctx.Invoke("kubernetes:yaml:decode", &args, &ret); err != nil {
return nil, errors.Wrap(err, "failed to decode YAML")
}
return ret.Result, nil
}

func fetch(name string, args fetchArgs) error {
helmArgs := []string{"fetch", name}

// Untar by default.
if args.Untar == nil || !*args.Untar {
helmArgs = append(helmArgs, "--untar")
}

env := os.Environ()
// Helm v3 removed the `--home` flag, so we must use an env var instead.
if len(args.Home) > 0 {
found := false
for i, v := range env {
if strings.HasPrefix(v, "HELM_HOME=") {
env[i] = fmt.Sprintf("HELM_HOME=%s", args.Home)
found = true
break
}
}
if !found {
env = append(env, fmt.Sprintf("HELM_HOME=%s", args.Home))
}
}

if len(args.Version) > 0 {
helmArgs = append(helmArgs, "--version", args.Version)
}
if len(args.CAFile) > 0 {
helmArgs = append(helmArgs, "--ca-file", args.CAFile)
}
if len(args.CertFile) > 0 {
helmArgs = append(helmArgs, "--cert-file", args.CertFile)
}
if len(args.KeyFile) > 0 {
helmArgs = append(helmArgs, "--key-file", args.KeyFile)
}
if len(args.Destination) > 0 {
helmArgs = append(helmArgs, "--destination", args.Destination)
}
if len(args.Keyring) > 0 {
helmArgs = append(helmArgs, "--keyring", args.Keyring)
}
if len(args.Password) > 0 {
helmArgs = append(helmArgs, "--password", args.Password)
}
if len(args.Repo) > 0 {
helmArgs = append(helmArgs, "--repo", args.Repo)
}
if len(args.UntarDir) > 0 {
helmArgs = append(helmArgs, "--untardir", args.UntarDir)
}
if len(args.Username) > 0 {
helmArgs = append(helmArgs, "--username", args.Username)
}
if args.Devel != nil && *args.Devel {
helmArgs = append(helmArgs, "--devel")
}
if args.Prov != nil && *args.Prov {
helmArgs = append(helmArgs, "--prov")
}
if args.Verify != nil && *args.Verify {
helmArgs = append(helmArgs, "--verify")
}

helmCmd := exec.Command("helm", helmArgs...)
var stderr bytes.Buffer
helmCmd.Stderr = &stderr
err := helmCmd.Run()
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to fetch Helm chart: %s", stderr.String()))
}

return nil
}

// GetResource returns a resource defined by a built-in Kubernetes group/version/kind, name and namespace.
// For example, GetResource("v1/Pod", "foo", "") would return a Pod called "foo" from the "default" namespace.
func (c *Chart) GetResource(gvk, name, namespace string) pulumi.AnyOutput {
id := name
if len(namespace) > 0 && namespace != "default" {
id = fmt.Sprintf("%s/%s", namespace, name)
}
key := fmt.Sprintf("%s::%s", gvk, id)
return c.Resources.ApplyT(func(x interface{}) interface{} {
resources := x.(map[string]pulumi.Resource)
return resources[key]
}).(pulumi.AnyOutput)
}
Loading