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

Improve support for helm release operations when cluster is unreachable #1886

Merged
merged 3 commits into from
Jan 26, 2022
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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
## HEAD (Unreleased)
- [Helm/Release] Remove beta warnings (https://github.com/pulumi/pulumi-kubernetes/pull/1885)

- [Helm/Release] Handle partial failure during create/update (https://github.com/pulumi/pulumi-kubernetes/pull/1880)

- [Helm/Release] Improve support for helm release operations when cluster is unreachable (https://github.com/pulumi/pulumi-kubernetes/pull/1886)
- Fix detailed diff for server-side apply (https://github.com/pulumi/pulumi-kubernetes/pull/1873)

## 3.14.1 (January 18, 2022)
Expand Down
95 changes: 39 additions & 56 deletions provider/pkg/provider/helm_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"net/url"
"os"
"reflect"
Expand Down Expand Up @@ -168,15 +167,17 @@ type ReleaseStatus struct {
}

type helmReleaseProvider struct {
host *provider.HostClient
helmDriver string
apiConfig *api.Config
defaultOverrides *clientcmd.ConfigOverrides
restConfig *rest.Config
defaultNamespace string
enableSecrets bool
name string
settings *cli.EnvSettings
host *provider.HostClient
helmDriver string
apiConfig *api.Config
defaultOverrides *clientcmd.ConfigOverrides
restConfig *rest.Config
defaultNamespace string
enableSecrets bool
clusterUnreachable bool
clusterUnreachableReason string
name string
settings *cli.EnvSettings
}

func newHelmReleaseProvider(
Expand All @@ -191,6 +192,8 @@ func newHelmReleaseProvider(
registryConfigPath,
repositoryConfigPath,
repositoryCache string,
clusterUnreachable bool,
clusterUnreachableReason string,
) (customResourceProvider, error) {
settings := cli.New()
settings.PluginsDirectory = pluginsDirectory
Expand All @@ -200,15 +203,17 @@ func newHelmReleaseProvider(
settings.Debug = true

return &helmReleaseProvider{
host: host,
apiConfig: apiConfig,
defaultOverrides: defaultOverrides,
restConfig: restConfig,
helmDriver: helmDriver,
defaultNamespace: namespace,
enableSecrets: enableSecrets,
name: "kubernetes:helmrelease",
settings: settings,
host: host,
apiConfig: apiConfig,
defaultOverrides: defaultOverrides,
restConfig: restConfig,
helmDriver: helmDriver,
defaultNamespace: namespace,
enableSecrets: enableSecrets,
clusterUnreachable: clusterUnreachable,
clusterUnreachableReason: clusterUnreachableReason,
name: "kubernetes:helmrelease",
settings: settings,
}, nil
}

Expand All @@ -234,7 +239,7 @@ func (r *helmReleaseProvider) getActionConfig(namespace string) (*action.Configu
clientConfig = clientcmd.NewDefaultClientConfig(*r.apiConfig, &overrides)
} else {
// Use client-go to resolve the final configuration values for the client. Typically these
// values would would reside in the $KUBECONFIG file, but can also be altered in several
// values would reside in the $KUBECONFIG file, but can also be altered in several
// places, including in env variables, client-go default values, and (if we allowed it) CLI
// flags.
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
Expand Down Expand Up @@ -333,39 +338,11 @@ func (r *helmReleaseProvider) Check(ctx context.Context, req *pulumirpc.CheckReq
assignNameIfAutonameable(new, news, urn.Name())
r.setDefaults(new)

conf, err := r.getActionConfig(new.Namespace)
resourceNames, err := computeResourceNames(new)
if err != nil {
return nil, err
}
rel, exists, err := resourceReleaseLookup(new.Name, conf)
if err != nil {
return nil, err
}

haveResourceNames := true
if len(olds.Mappable()) > 0 {
haveResourceNames = false
} else {
if !exists {
haveResourceNames = false
}
}

if !haveResourceNames {
resourceNames, err := computeResourceNames(new)
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

logger.V(9).Infof("New: %+v", new)
autonamed := resource.NewPropertyMap(new)
Expand Down Expand Up @@ -804,6 +781,9 @@ func (r *helmReleaseProvider) Create(ctx context.Context, req *pulumirpc.CreateR

var creationError error
if !req.GetPreview() {
if r.clusterUnreachable {
return nil, fmt.Errorf("can't create Helm Release with unreachable cluster: %s", r.clusterUnreachableReason)
}
id = fqName(newRelease.Namespace, newRelease.Name)
if err := r.helmCreate(ctx, urn, newRelease); err != nil {
var failedErr *releaseFailedError
Expand All @@ -815,7 +795,7 @@ func (r *helmReleaseProvider) Create(ctx context.Context, req *pulumirpc.CreateR
}
}

obj := checkpointRelease(news, newRelease)
obj := checkpointRelease(news, newRelease, fmt.Sprintf("%s.news", label))
inputsAndComputed, err := plugin.MarshalProperties(
obj, plugin.MarshalOptions{
Label: fmt.Sprintf("%s.inputsAndComputed", label),
Expand Down Expand Up @@ -921,7 +901,7 @@ func (r *helmReleaseProvider) Read(ctx context.Context, req *pulumirpc.ReadReque

// Return a new "checkpoint object".
state, err := plugin.MarshalProperties(
checkpointRelease(oldInputs, existingRelease), plugin.MarshalOptions{
checkpointRelease(oldInputs, existingRelease, fmt.Sprintf("%s.olds", label)), plugin.MarshalOptions{
Label: fmt.Sprintf("%s.state", label),
KeepUnknowns: true,
SkipNulls: true,
Expand Down Expand Up @@ -990,6 +970,9 @@ func (r *helmReleaseProvider) Update(ctx context.Context, req *pulumirpc.UpdateR

var updateError error
if !req.GetPreview() {
if r.clusterUnreachable {
return nil, fmt.Errorf("can't update Helm Release with unreachable cluster: %s", r.clusterUnreachableReason)
}
if err = r.helmUpdate(ctx, urn, newResInputs, newRelease, oldRelease); err != nil {
var failedErr *releaseFailedError
if errors.As(err, &failedErr) {
Expand All @@ -1000,7 +983,7 @@ func (r *helmReleaseProvider) Update(ctx context.Context, req *pulumirpc.UpdateR
}
}

checkpointed := checkpointRelease(newResInputs, newRelease)
checkpointed := checkpointRelease(newResInputs, newRelease, fmt.Sprintf("%s.news", label))
inputsAndComputed, err := plugin.MarshalProperties(
checkpointed, plugin.MarshalOptions{
Label: fmt.Sprintf("%s.inputsAndComputed", label),
Expand Down Expand Up @@ -1113,7 +1096,9 @@ func computeResourceNames(r *Release) (map[string][]string, error) {
return resourceNames, nil
}

func checkpointRelease(inputs resource.PropertyMap, outputs *Release) resource.PropertyMap {
func checkpointRelease(inputs resource.PropertyMap, outputs *Release, label string) resource.PropertyMap {
logger.V(9).Infof("[%s] Checkpointing outputs: %#v", label, outputs)
logger.V(9).Infof("[%s] Checkpointing inputs: %#v", label, inputs)
object := resource.NewPropertyMap(outputs)
object["__inputs"] = resource.MakeSecret(resource.NewObjectProperty(inputs))

Expand Down Expand Up @@ -1360,14 +1345,12 @@ func checkChartDependencies(c *helmchart.Chart, path, keyring string, settings *
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
}
log.Println("[DEBUG] Downloading chart dependencies...")
return true, man.Update()
}
return false, err
}
return false, err
}
log.Println("[DEBUG] Chart dependencies are up to date.")
return false, nil
}

Expand Down
68 changes: 29 additions & 39 deletions provider/pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,26 +656,6 @@ func (k *kubeProvider) Configure(_ context.Context, req *pulumirpc.ConfigureRequ
warningConfig.WarningHandler = rest.NoWarnings{}
k.config = warningConfig
k.kubeconfig = kubeconfig

namespace := "default"
if k.defaultNamespace != "" {
namespace = k.defaultNamespace
}
k.helmReleaseProvider, err = newHelmReleaseProvider(
k.host,
apiConfig,
overrides,
k.config,
k.helmDriver,
namespace,
k.enableSecrets,
k.helmPluginsPath,
k.helmRegistryConfigPath,
k.helmRepositoryConfigPath,
k.helmRepositoryCache)
if err != nil {
return nil, err
}
}
}

Expand All @@ -702,6 +682,29 @@ func (k *kubeProvider) Configure(_ context.Context, req *pulumirpc.ConfigureRequ
}
}

var err error
namespace := "default"
if k.defaultNamespace != "" {
namespace = k.defaultNamespace
}
k.helmReleaseProvider, err = newHelmReleaseProvider(
k.host,
apiConfig,
overrides,
k.config,
k.helmDriver,
namespace,
k.enableSecrets,
k.helmPluginsPath,
k.helmRegistryConfigPath,
k.helmRepositoryConfigPath,
k.helmRepositoryCache,
k.clusterUnreachable,
k.clusterUnreachableReason)
if err != nil {
return nil, err
}

return &pulumirpc.ConfigureResponse{
AcceptSecrets: true,
SupportsPreview: true,
Expand Down Expand Up @@ -1221,10 +1224,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (
newInputs = annotatedInputs

if isHelmRelease(urn) && !hasComputedValue(newInputs) {
if !k.clusterUnreachable {
return k.helmReleaseProvider.Check(ctx, req)
}
return nil, fmt.Errorf("can't use Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
return k.helmReleaseProvider.Check(ctx, req)
}

// Adopt name from old object if appropriate.
Expand Down Expand Up @@ -1434,10 +1434,7 @@ func (k *kubeProvider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*p
}

if isHelmRelease(urn) && !hasComputedValue(newInputs) {
if !k.clusterUnreachable {
return k.helmReleaseProvider.Diff(ctx, req)
}
return nil, fmt.Errorf("can't use Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
return k.helmReleaseProvider.Diff(ctx, req)
}

namespacedKind, err := clients.IsNamespacedKind(gvk, k.clientSet)
Expand Down Expand Up @@ -1605,10 +1602,7 @@ func (k *kubeProvider) Create(
urn := resource.URN(req.GetUrn())

if isHelmRelease(urn) && !req.GetPreview() {
if !k.clusterUnreachable {
return k.helmReleaseProvider.Create(ctx, req)
}
return nil, fmt.Errorf("can't create Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
return k.helmReleaseProvider.Create(ctx, req)
}

label := fmt.Sprintf("%s.Create(%s)", k.label(), urn)
Expand Down Expand Up @@ -2110,10 +2104,6 @@ func (k *kubeProvider) Update(
}

if isHelmRelease(urn) {
if k.clusterUnreachable {
return nil, fmt.Errorf("can't update Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
}

return k.helmReleaseProvider.Update(ctx, req)
}
// Ignore old state; we'll get it from Kubernetes later.
Expand Down Expand Up @@ -2256,10 +2246,10 @@ func (k *kubeProvider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest)
}

if isHelmRelease(urn) {
if !k.clusterUnreachable {
return k.helmReleaseProvider.Delete(ctx, req)
if k.clusterUnreachable {
return nil, fmt.Errorf("can't delete Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
}
return nil, fmt.Errorf("can't delete Helm Release with unreachable cluster. Reason: %q", k.clusterUnreachableReason)
return k.helmReleaseProvider.Delete(ctx, req)
}

_, current := parseCheckpointObject(oldState)
Expand Down