Skip to content

Commit

Permalink
Refactor config loading
Browse files Browse the repository at this point in the history
  • Loading branch information
viveklak committed Aug 31, 2021
1 parent 791d0bc commit b6836e7
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 39 deletions.
8 changes: 4 additions & 4 deletions pkg/apis/pulumi/v1alpha1/stack_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ type StackSpec struct {
// and randomized activity timeline for the stack in the Pulumi Service.
RetryOnUpdateConflict bool `json:"retryOnUpdateConflict,omitempty"`

// (optional) UseLocalStackOnly can be set to true to prevent the operator to
// create stacks that do not exist in the tracking git repo.
// (optional) UseLocalStackOnly can be set to true to prevent the operator from
// creating stacks that do not exist in the tracking git repo.
// The default behavior is to create a stack if it doesn't exist.
UseLocalStackOnly bool `json:"useLocalStackOnly,omitempty"`
}
Expand Down Expand Up @@ -357,10 +357,10 @@ type StackController interface {
InstallProjectDependencies(ctx context.Context, workspace auto.Workspace) error
// SetEnvs populates the environment of the stack run with values
// from an array of Kubernetes ConfigMaps in a Namespace.
SetEnvs(configMapNames []string, namespace string) error
SetEnvs(w auto.Workspace, configMapNames []string, namespace string) error
// SetSecretEnvs populates the environment of the stack run with values
// from an array of Kubernetes Secrets in a Namespace.
SetSecretEnvs(secretNames []string, namespace string) error
SetSecretEnvs(w auto.Workspace, secretNames []string, namespace string) error

// Lifecycle:

Expand Down
81 changes: 50 additions & 31 deletions pkg/controller/stack/stack_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"strings"
"time"

"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"

"github.com/pulumi/pulumi-kubernetes-operator/pkg/logging"
"github.com/pulumi/pulumi-kubernetes-operator/version"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
Expand Down Expand Up @@ -182,16 +184,6 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result,
// Delete the working directory after the reconciliation is completed (regardless of success or failure).
defer sess.CleanupPulumiWorkdir()

// Step 2. If there are extra environment variables, read them in now and use them for subsequent commands.
if err = sess.SetEnvs(stack.Envs, request.Namespace); err != nil {
reqLogger.Error(err, "Could not find ConfigMap for Envs")
return reconcile.Result{}, err
}
if err = sess.SetSecretEnvs(stack.SecretEnvs, request.Namespace); err != nil {
reqLogger.Error(err, "Could not find Secret for SecretEnvs")
return reconcile.Result{}, err
}

// Check if the Stack instance is marked to be deleted, which is
// indicated by the deletion timestamp being set.
isStackMarkedToBeDeleted = instance.GetDeletionTimestamp() != nil
Expand Down Expand Up @@ -232,7 +224,7 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result,
"Last commit", instance.Status.LastUpdate.LastSuccessfulCommit)
}

// Step 3. If a stack refresh is requested, run it now.
// Step 2. If a stack refresh is requested, run it now.
if sess.stack.Refresh {
permalink, err := sess.RefreshStack(sess.stack.ExpectNoRefreshChanges)
if err != nil {
Expand All @@ -257,7 +249,7 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result,
reqLogger.Info("Successfully refreshed Stack", "Stack.Name", stack.Stack)
}

// Step 4. Run a `pulumi up --skip-preview`.
// Step 3. Run a `pulumi up --skip-preview`.
// TODO: is it possible to support a --dry-run with a preview?
status, permalink, result, err := sess.UpdateStack()
switch status {
Expand Down Expand Up @@ -289,7 +281,7 @@ func (r *ReconcileStack) Reconcile(request reconcile.Request) (reconcile.Result,
}
}

// Step 5. Capture outputs onto the resulting status object.
// Step 4. Capture outputs onto the resulting status object.
outs, err := sess.GetStackOutputs(result.Outputs)
if err != nil {
reqLogger.Error(err, "Failed to get Stack outputs", "Stack.Name", stack.Stack)
Expand Down Expand Up @@ -412,13 +404,13 @@ func newReconcileStackSession(

// SetEnvs populates the environment the stack run with values
// from an array of Kubernetes ConfigMaps in a Namespace.
func (sess *reconcileStackSession) SetEnvs(configMapNames []string, namespace string) error {
func (sess *reconcileStackSession) SetEnvs(w auto.Workspace, configMapNames []string, namespace string) error {
for _, env := range configMapNames {
config := &corev1.ConfigMap{}
if err := sess.getLatestResource(config, types.NamespacedName{Name: env, Namespace: namespace}); err != nil {
return errors.Wrapf(err, "Namespace=%s Name=%s", namespace, env)
}
if err := sess.autoStack.Workspace().SetEnvVars(config.Data); err != nil {
if err := w.SetEnvVars(config.Data); err != nil {
return errors.Wrapf(err, "Namespace=%s Name=%s", namespace, env)
}
}
Expand All @@ -427,7 +419,7 @@ func (sess *reconcileStackSession) SetEnvs(configMapNames []string, namespace st

// SetSecretEnvs populates the environment of the stack run with values
// from an array of Kubernetes Secrets in a Namespace.
func (sess *reconcileStackSession) SetSecretEnvs(secrets []string, namespace string) error {
func (sess *reconcileStackSession) SetSecretEnvs(w auto.Workspace, secrets []string, namespace string) error {
for _, env := range secrets {
config := &corev1.Secret{}
if err := sess.getLatestResource(config, types.NamespacedName{Name: env, Namespace: namespace}); err != nil {
Expand All @@ -437,7 +429,7 @@ func (sess *reconcileStackSession) SetSecretEnvs(secrets []string, namespace str
for k, v := range config.Data {
envvars[k] = string(v)
}
if err := sess.autoStack.Workspace().SetEnvVars(envvars); err != nil {
if err := w.SetEnvVars(envvars); err != nil {
return errors.Wrapf(err, "Namespace=%s Name=%s", namespace, env)
}
}
Expand Down Expand Up @@ -604,32 +596,49 @@ func (sess *reconcileStackSession) SetupPulumiWorkdir(gitAuth *auto.GitAuth) err
return errors.Wrap(err, "failed to create local workspace")
}

sess.workdir = w.WorkDir()

if sess.stack.Backend != "" {
w.SetEnvVar("PULUMI_BACKEND_URL", sess.stack.Backend)
}
if accessToken, found := sess.lookupPulumiAccessToken(); found {
w.SetEnvVar("PULUMI_ACCESS_TOKEN", accessToken)
}

if err = sess.SetEnvs(w, sess.stack.Envs, sess.namespace); err != nil {
return errors.Wrap(err, "could not find ConfigMap for Envs")
}
if err = sess.SetSecretEnvs(w, sess.stack.SecretEnvs, sess.namespace); err != nil {
return errors.Wrap(err, "could not find Secret for SecretEnvs")
}
if err = sess.SetEnvRefsForWorkspace(w); err != nil {
return err
}
sess.workdir = w.WorkDir()

ctx := context.Background()
var a auto.Stack

if sess.stack.UseLocalStackOnly {
sess.logger.Info("Using local stack", "stack", sess.stack.Stack)
a, err = auto.SelectStack(ctx, sess.stack.Stack, w)
} else {
sess.logger.Info("Upserting stack", "stack", sess.stack.Stack, "worskspace", w)
a, err = auto.UpsertStack(ctx, sess.stack.Stack, w)
}
if err != nil {
return errors.Wrapf(err, "failed to create and/or select stack: %s", sess.stack.Stack)
}
sess.autoStack = &a
sess.logger.Debug("Setting autostack", "autostack", sess.autoStack)

c, err := sess.autoStack.GetAllConfig(ctx)
if err != nil {
return err
}
sess.logger.Debug("Initial autostack config", "config", c)

// Ensure stack settings file in workspace is populated appropriately.
if err = sess.ensureStackSettings(context.Background(), w); err != nil {
if err = sess.ensureStackSettings(context.Background()); err != nil {
return err
}

Expand All @@ -648,20 +657,23 @@ func (sess *reconcileStackSession) SetupPulumiWorkdir(gitAuth *auto.GitAuth) err
return nil
}

func (sess *reconcileStackSession) ensureStackSettings(ctx context.Context, w auto.Workspace) error {
func (sess *reconcileStackSession) ensureStackSettings(ctx context.Context) error {
// We may have a project stack file already checked-in. Try and read that first
// since we don't want to clobber it unnecessarily.
// If not found, stackConfig will be a pointer to a zeroed-out workspace.ProjectStack.
stackConfig, err := w.StackSettings(ctx, sess.stack.Stack)
stackConfig, err := sess.autoStack.Workspace().StackSettings(ctx, sess.stack.Stack)
if err != nil {
return errors.Wrap(err, "failed to load stack config")
sess.logger.Info("Missing stack config file. Will assume no stack config checked-in.", "Cause", err)
stackConfig = &workspace.ProjectStack{}
}

sess.logger.Debug("stackConfig loaded", "stack", sess.autoStack, "stackConfig", stackConfig)

// We must always make sure the secret provider is initialized in the workspace
// before we set any configs. Otherwise secret provider will mysteriously reset.
// https://github.com/pulumi/pulumi-kubernetes-operator/issues/135
stackConfig.SecretsProvider = sess.stack.SecretsProvider
if err := w.SaveStackSettings(context.Background(), sess.stack.Stack, stackConfig); err != nil {
if err := sess.autoStack.Workspace().SaveStackSettings(context.Background(), sess.stack.Stack, stackConfig); err != nil {
return errors.Wrap(err, "failed to save stack settings.")
}
return nil
Expand Down Expand Up @@ -755,20 +767,20 @@ func (sess *reconcileStackSession) InstallProjectDependencies(ctx context.Contex
}

func (sess *reconcileStackSession) UpdateConfig(ctx context.Context) error {
m, err := sess.autoStack.GetAllConfig(ctx)
if err != nil {
return err
}
for k, v := range sess.stack.Config {
m[k] = auto.ConfigValue{
if err := sess.autoStack.SetConfig(ctx, k, auto.ConfigValue{
Value: v,
Secret: false,
}); err != nil {
return err
}
}
for k, v := range sess.stack.Secrets {
m[k] = auto.ConfigValue{
if err := sess.autoStack.SetConfig(ctx, k, auto.ConfigValue{
Value: v,
Secret: true,
}); err != nil {
return err
}
}

Expand All @@ -777,12 +789,19 @@ func (sess *reconcileStackSession) UpdateConfig(ctx context.Context) error {
if err != nil {
return errors.Wrapf(err, "updating secretRef for: %q", k)
}
m[k] = auto.ConfigValue{
if err := sess.autoStack.SetConfig(ctx, k, auto.ConfigValue{
Value: resolved,
Secret: true,
}); err != nil {
return err
}
}
return sess.autoStack.SetAllConfig(context.Background(), m)
m, err := sess.autoStack.GetAllConfig(ctx)
if err != nil {
return err
}
sess.logger.Debug("Updated stack config", "Stack.Name", sess.stack.Stack, "config", m)
return nil
}

func (sess *reconcileStackSession) RefreshStack(expectNoChanges bool) (pulumiv1alpha1.Permalink, error) {
Expand Down
14 changes: 10 additions & 4 deletions test/stack_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ var _ = Describe("Stack Controller", func() {
}
})

var toDelete []string

defer func() {
for _, d := range toDelete {
_ = os.RemoveAll(d)
}
}()

// Tip: avoid adding tests for vanilla CRUD operations because they would
// test the Kubernetes API server, which isn't the goal here.

Expand All @@ -198,9 +206,7 @@ var _ = Describe("Stack Controller", func() {
// local state for the sake of this test.
backendDir, err := ioutil.TempDir("", "local-state")
Ω(err).ShouldNot(HaveOccurred())
defer func() {
_ = os.RemoveAll(backendDir)
}()
toDelete = append(toDelete, backendDir)

// Define the stack spec
localSpec := pulumiv1alpha1.StackSpec{
Expand Down Expand Up @@ -239,7 +245,7 @@ var _ = Describe("Stack Controller", func() {
}, timeout, interval).Should(BeTrue())
// Validate outputs.
Expect(fetched.Status.Outputs).Should(BeEquivalentTo(pulumiv1alpha1.StackOutputs{
"region": v1.JSON{Raw: []byte(`us-west-2`)},
"region": v1.JSON{Raw: []byte(`"us-west-2"`)},
"notSoSecret": v1.JSON{Raw: []byte(`"safe"`)},
"secretVal": v1.JSON{Raw: []byte(`"[secret]"`)},
}))
Expand Down

0 comments on commit b6836e7

Please sign in to comment.