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

Fix import handling for Helm Release #1818

Merged
merged 9 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
@@ -1,4 +1,5 @@
## HEAD (Unreleased)
- Helm Release: Helm Release imports support (https://github.com/pulumi/pulumi-kubernetes/pull/1818)

## 3.11.0 (December 6, 2021)

Expand Down
178 changes: 103 additions & 75 deletions provider/pkg/provider/helm_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func newHelmReleaseProvider(
settings.RegistryConfig = registryConfigPath
settings.RepositoryConfig = repositoryConfigPath
settings.RepositoryCache = repositoryCache
settings.Debug = true

return &helmReleaseProvider{
host: host,
Expand All @@ -212,7 +213,7 @@ func newHelmReleaseProvider(
}

func debug(format string, a ...interface{}) {
logger.V(9).Infof("[DEBUG] %s", fmt.Sprintf(format, a...))
logger.V(3).Infof("[DEBUG] %s", fmt.Sprintf(format, a...))
}

func (r *helmReleaseProvider) getActionConfig(namespace string) (*action.Configuration, error) {
Expand Down Expand Up @@ -240,7 +241,7 @@ func (r *helmReleaseProvider) getActionConfig(namespace string) (*action.Configu
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
clientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &overrides)
}
kc := newKubeConfig(r.restConfig, clientConfig)
kc := NewKubeConfig(r.restConfig, clientConfig)
if err := conf.Init(kc, namespace, r.helmDriver, debug); err != nil {
return nil, err
}
Expand Down Expand Up @@ -299,44 +300,44 @@ func (r *helmReleaseProvider) Check(ctx context.Context, req *pulumirpc.CheckReq
return nil, err
}

if new.Namespace == "" {
new.Namespace = r.defaultNamespace
if len(olds.Mappable()) > 0 {
adoptOldNameIfUnnamed(new, old)
}
assignNameIfAutonameable(new, news, "release")
r.setDefaults(new)

if !new.SkipAwait && new.Timeout == 0 {
new.Timeout = defaultTimeoutSeconds
conf, err := r.getActionConfig(new.Namespace)
if err != nil {
return nil, err
}

if new.Keyring == "" {
new.Keyring = os.ExpandEnv("$HOME/.gnupg/pubring.gpg")
rel, exists, err := resourceReleaseLookup(new.Name, conf)
if err != nil {
return nil, err
}

haveResourceNames := true
if len(olds.Mappable()) > 0 {
adoptOldNameIfUnnamed(new, old)
haveResourceNames = false
} else {
assignNameIfAutonameable(new, news, "release")
conf, err := r.getActionConfig(new.Namespace)
if err != nil {
return nil, err
}
exists, err := resourceReleaseExists(new.Name, conf)
if err != nil {
return nil, err
}
if !exists {
haveResourceNames = false
}
// If resource exists, we are likely doing an import. We will just pass the inputs through.
}

if !haveResourceNames {
resourceNames, err := computeResourceNames(new)
if err != nil {
return nil, err
if err != nil && exists {
logger.V(9).Infof("Failed to compute resource names, assuming no resource names known: %v", err)
_, resourceNames, err = convertYAMLManifestToJSON(rel.Manifest)
if err != nil {
return nil, err
}
new.ResourceNames = resourceNames
} else if err != nil && !exists {
return nil, fmt.Errorf("release not found: %q: %w", new.Name, err)
} else {
new.ResourceNames = resourceNames
}
new.ResourceNames = resourceNames
}

autonamed := resource.NewPropertyMap(new)
Expand All @@ -355,6 +356,20 @@ func (r *helmReleaseProvider) Check(ctx context.Context, req *pulumirpc.CheckReq
return &pulumirpc.CheckResponse{Inputs: autonamedInputs}, nil
}

func (r *helmReleaseProvider) setDefaults(new *Release) {
if new.Namespace == "" {
new.Namespace = r.defaultNamespace
}

if !new.SkipAwait && new.Timeout == 0 {
new.Timeout = defaultTimeoutSeconds
}

if new.Keyring == "" {
new.Keyring = os.ExpandEnv("$HOME/.gnupg/pubring.gpg")
}
}

func (r *helmReleaseProvider) helmCreate(ctx context.Context, urn resource.URN, newRelease *Release) error {
conf, err := r.getActionConfig(newRelease.Namespace)
if err != nil {
Expand Down Expand Up @@ -435,12 +450,10 @@ func (r *helmReleaseProvider) helmCreate(ctx context.Context, urn resource.URN,
}

if err != nil && rel != nil {
exists, existsErr := resourceReleaseExists(newRelease.Name, conf)

_, exists, existsErr := resourceReleaseLookup(newRelease.Name, conf)
if existsErr != nil {
return err
}

if !exists {
return err
}
Expand Down Expand Up @@ -546,6 +559,9 @@ func adoptOldNameIfUnnamed(new, old *Release) {
}

func assignNameIfAutonameable(release *Release, pm resource.PropertyMap, base tokens.QName) {
if release.Name != "" {
return
}
if name, ok := pm["name"]; ok && name.IsComputed() {
return
}
Expand Down Expand Up @@ -776,9 +792,7 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque
if err != nil {
return nil, err
}
oldInputs, err := plugin.UnmarshalProperties(req.GetInputs(), plugin.MarshalOptions{
Label: fmt.Sprintf("%s.oldInputs", label), KeepUnknowns: true, SkipNulls: true, KeepSecrets: true,
})

if err != nil {
return nil, err
}
Expand All @@ -803,7 +817,7 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque
if err != nil {
return nil, err
}
exists, err := resourceReleaseExists(name, actionConfig)
liveObj, exists, err := resourceReleaseLookup(name, actionConfig)
if err != nil {
return nil, err
}
Expand All @@ -813,27 +827,36 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque
return deleteResponse, nil
}

liveObj, err := getRelease(actionConfig, name)
if err != nil {
return nil, err
}

err = setReleaseAttributes(existingRelease, liveObj, false)
if err != nil {
return nil, err
}

logger.V(9).Infof("Trying to get chart: %q, settings: %#v", existingRelease.Chart, r.settings)

// Helm itself doesn't store any information about where the Chart was downloaded from.
// We need the user to ensure the chart is downloadable by using `helm repo add` etc.
_, _, err = getChart(r.settings, existingRelease)
if err != nil {
return nil, err
if existingRelease.RepositoryOpts != nil {
_, chartName, err := chartPathOptions(existingRelease)
if err != nil {
return nil, err
}

logger.V(9).Infof("Trying to get chart: %q, settings: %#v", chartName, r.settings)

_, _, err = getChart(r.settings, existingRelease)
if err != nil {
return nil, err
}
}

logger.V(9).Infof("%s Found release %s/%s", label, namespace, name)

oldInputs, _ := parseCheckpointRelease(oldState)
if oldInputs == nil {
// No old inputs suggests this is an import. Hydrate the imports from the current live object
r.setDefaults(existingRelease)
oldInputs = r.serializeImportInputs(existingRelease)
}

// Return a new "checkpoint object".
state, err := plugin.MarshalProperties(
checkpointRelease(oldInputs, existingRelease), plugin.MarshalOptions{
Expand All @@ -846,7 +869,7 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque
return nil, err
}

liveInputsPM := resource.NewPropertyMap(existingRelease)
liveInputsPM := r.serializeImportInputs(existingRelease)

inputs, err := plugin.MarshalProperties(liveInputsPM, plugin.MarshalOptions{
Label: label + ".inputs", KeepUnknowns: true, SkipNulls: true, KeepSecrets: r.enableSecrets,
Expand All @@ -863,6 +886,12 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque
return &pulumirpc.ReadResponse{Id: id, Properties: state, Inputs: inputs}, nil
}

func (r *helmReleaseProvider) serializeImportInputs(release *Release) resource.PropertyMap {
inputs := resource.NewPropertyMap(release)
delete(inputs, resource.PropertyKey("status"))
return inputs
}

func (r *helmReleaseProvider) Update(ctx context.Context, req *pulumirpc.UpdateRequest) (*pulumirpc.UpdateResponse, error) {
urn := resource.URN(req.GetUrn())
label := fmt.Sprintf("Provider[%s].Update(%s)", r.name, urn)
Expand Down Expand Up @@ -963,6 +992,7 @@ func (r *helmReleaseProvider) Delete(ctx context.Context, req *pulumirpc.DeleteR
}

func computeResourceNames(r *Release) (map[string][]string, error) {
logger.V(9).Infof("Looking up resource names for release: %q: %#v", r.Name, r)
helmHome := os.Getenv("HELM_HOME")

helmChartOpts := HelmChartOpts{
Expand Down Expand Up @@ -1036,18 +1066,12 @@ func setReleaseAttributes(release *Release, r *release.Release, isPreview bool)
if release.Namespace == "" {
release.Namespace = r.Namespace
}
if len(release.Values) == 0 {
release.Values = r.Config
}
if release.Version == "" {
release.Version = r.Chart.Metadata.Version
}
if release.Chart == "" {
release.Chart = r.Chart.Metadata.Name
}
if release.Description == "" {
release.Description = r.Info.Description
}
logger.V(9).Infof("Setting release values: %+v", r.Config)
release.Values = r.Config
release.Version = r.Chart.Metadata.Version

_, resources, err := convertYAMLManifestToJSON(r.Manifest)
if err != nil {
Expand Down Expand Up @@ -1080,21 +1104,20 @@ func setReleaseAttributes(release *Release, r *release.Release, isPreview bool)
return nil
}

func resourceReleaseExists(name string, actionConfig *action.Configuration) (bool, error) {
logger.V(9).Infof("[resourceReleaseExists: %s]", name)
_, err := getRelease(actionConfig, name)

logger.V(9).Infof("[resourceReleaseExists: %s] Done", name)
func resourceReleaseLookup(name string, actionConfig *action.Configuration) (*release.Release, bool, error) {
logger.V(9).Infof("[resourceReleaseLookup: %s]", name)
release, err := getRelease(actionConfig, name)
logger.V(9).Infof("[resourceReleaseLookup: %s] Done", name)

if err == nil {
return true, nil
return release, true, nil
}

if err == errReleaseNotFound {
return false, nil
return nil, false, nil
}

return false, err
return nil, false, err
}

func getRelease(cfg *action.Configuration, name string) (*release.Release, error) {
Expand All @@ -1116,7 +1139,7 @@ func getRelease(cfg *action.Configuration, name string) (*release.Release, error
return nil, err
}

logger.V(9).Infof("%s getRelease done", name)
logger.V(9).Infof("%s getRelease done: %+v", name, res)

return res, nil
}
Expand Down Expand Up @@ -1244,24 +1267,29 @@ func checkChartDependencies(c *helmchart.Chart, path, keyring string, settings *
func chartPathOptions(release *Release) (*action.ChartPathOptions, string, error) {
chartName := release.Chart

repository := release.RepositoryOpts.Repo
repositoryURL, chartName, err := resolveChartName(repository, strings.TrimSpace(chartName))
if err != nil {
return nil, "", err
}
version := getVersion(release)
cpo := &action.ChartPathOptions{
Keyring: release.Keyring,
Verify: release.Verify,
Version: version,
}
if release.RepositoryOpts != nil {
var repositoryURL string
var err error
repository := release.RepositoryOpts.Repo
repositoryURL, chartName, err = resolveChartName(repository, strings.TrimSpace(chartName))
if err != nil {
return nil, "", err
}
cpo.CertFile = release.RepositoryOpts.CertFile
cpo.CaFile = release.RepositoryOpts.CAFile
cpo.KeyFile = release.RepositoryOpts.KeyFile
cpo.Username = release.RepositoryOpts.Username
cpo.Password = release.RepositoryOpts.Password
cpo.RepoURL = repositoryURL
}

return &action.ChartPathOptions{
CaFile: release.RepositoryOpts.CAFile,
CertFile: release.RepositoryOpts.CertFile,
KeyFile: release.RepositoryOpts.KeyFile,
Keyring: release.Keyring,
RepoURL: repositoryURL,
Verify: release.Verify,
Version: version,
Username: release.RepositoryOpts.Username,
Password: release.RepositoryOpts.Password, // TODO: This should already be resolved.
}, chartName, nil
return cpo, chartName, nil
}

func getVersion(release *Release) (version string) {
Expand Down
2 changes: 1 addition & 1 deletion provider/pkg/provider/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ func (k *KubeConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return k.clientConfig
}

func newKubeConfig(config *rest.Config, clientConfig clientcmd.ClientConfig) *KubeConfig {
func NewKubeConfig(config *rest.Config, clientConfig clientcmd.ClientConfig) *KubeConfig {
return &KubeConfig{restConfig: config, clientConfig: clientConfig}
}
18 changes: 8 additions & 10 deletions provider/pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,21 +647,19 @@ func (k *kubeProvider) Configure(_ context.Context, req *pulumirpc.ConfigureRequ
// Attempt to load the configuration from the provided kubeconfig. If this fails, mark the cluster as unreachable.
if !k.clusterUnreachable {
config, err := kubeconfig.ClientConfig()

if kubeClientSettings.Burst != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into some panics here in tests which this change fixed.

config.Burst = *kubeClientSettings.Burst
logger.V(9).Infof("kube client burst set to %v", config.Burst)
}
if kubeClientSettings.QPS != nil {
config.QPS = float32(*kubeClientSettings.QPS)
logger.V(9).Infof("kube client QPS set to %v", config.QPS)
}

if err != nil {
k.clusterUnreachable = true
k.clusterUnreachableReason = fmt.Sprintf(
"unable to load Kubernetes client configuration from kubeconfig file: %v", err)
} else {
if kubeClientSettings.Burst != nil {
config.Burst = *kubeClientSettings.Burst
logger.V(9).Infof("kube client burst set to %v", config.Burst)
}
if kubeClientSettings.QPS != nil {
config.QPS = float32(*kubeClientSettings.QPS)
logger.V(9).Infof("kube client QPS set to %v", config.QPS)
}
warningConfig := rest.CopyConfig(config)
warningConfig.WarningHandler = rest.NoWarnings{}
k.config = warningConfig
Expand Down
2 changes: 2 additions & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ require (
github.com/pulumi/pulumi/pkg/v3 v3.19.0
github.com/pulumi/pulumi/sdk/v3 v3.19.0
github.com/stretchr/testify v1.7.0
helm.sh/helm/v3 v3.7.1
k8s.io/client-go v0.22.1
)
Loading