GithubHelp home page GithubHelp logo

pulumi / pulumi-kubernetes-operator Goto Github PK

View Code? Open in Web Editor NEW
212.0 25.0 53.0 1.75 MB

A Kubernetes Operator that automates the deployment of Pulumi Stacks

License: Apache License 2.0

Dockerfile 0.27% Shell 0.64% Go 85.40% Makefile 0.98% TypeScript 2.57% C# 4.50% Python 4.94% Smarty 0.71%
kubernetes pulumi

pulumi-kubernetes-operator's Introduction

Pulumi Kubernetes Operator Artifact Hub

Pulumi Kubernetes Operator

A Kubernetes operator that provides a CI/CD workflow for Pulumi stacks using Kubernetes primitives. To learn more about the Pulumi Kubernetes Operator visit the Pulumi documentation.

Overview

What is Pulumi?

Pulumi is an open source infrastructure-as-code tool for creating, deploying, and managing cloud infrastructure in the programming language of your choice. If you are new to Pulumi, please consider visiting the getting started first to familiarize yourself with Pulumi and concepts such as Pulumi stacks and backends.

When To Use the Pulumi Kubernetes Operator?

The Pulumi Kubernetes Operator enables Kubernetes users to create a Pulumi Stack as a first-class Kubernetes API resource, and use the StackController to drive the updates. It allows users to adopt a GitOps workflow for managing their cloud infrastructure using Pulumi. This infrastructure includes Kubernetes resources in addition to over 60 cloud providers including AWS, Azure, and Google Cloud. The operator provides an alternative to Pulumi's other CI/CD integrations such as Github Actions, Gitlab CI, Jenkins etc. See the full list of Pulumi's CI/CD integrations here. Since the Pulumi Kubernetes Operator can be deployed on any Kubernetes cluster, it provides turnkey GitOps functionality for Pulumi users running in self-hosted or restricted settings. The Kubernetes Operator pattern, lends itself nicely to automation scenarios by driving to the specified state and automatically retrying if transient failures are encountered.

Prerequisites

The following steps should be completed before starting on Pulumi:

Install Pulumi CLI

Follow the Pulumi installation instructions for your OS. For instance, on Mac OS, the easiest way to install Pulumi CLI is from Homebrew:

$ brew install pulumi

Login to Your Chosen State Backend

The operator stores additional metadata about provisioned resources. By default, Pulumi (and the Pulumi Kubernetes Operator) uses the Pulumi managed SaaS backend to store this state and manage concurrency. However, in addition to the managed backend, Pulumi also readily integrates with a variety of state backends, like S3, Azure Blob Storage, Google Cloud Storage, etc. See here for a detailed discussion on choosing a state backend.

Login to Pulumi using your chosen state backend. For simplicity we will only cover the Pulumi managed SaaS state backend and AWS S3 here:

Pulumi SaaS Backend
$ pulumi login

This will display a prompt that asks for you to provide an access token or automatically request an access token:

Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens
    or hit <ENTER> to log in using your browser                   :

In order to configure the Pulumi Kubernetes Operator to use Stacks with state stored on the SaaS backend, you will also need to manually generate access tokens. This can be done by accessing the Access Tokens page. Setting the environment variable PULUMI_ACCESS_TOKEN to the manually generated token will obviate the need for a pulumi login.

At this point your pulumi CLI is configured to work with the Pulumi SaaS backend.

AWS S3 Backend
  1. First, you will need to create an S3 bucket manually, either through the AWS CLI or the AWS Console.
  2. If you have already configured the AWS CLI to use credential files, single sign-on etc., Pulumi will automatically respect and use these settings. Alternatively you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables to the access key and secret access key respectively.
  3. To use the AWS S3 backend, pass the s3://<bucket-name> as your <backend-url> to pulumi login, i.e.:
    $ pulumi login s3://<bucket-name>
    
    For additional options, refer to the Pulumi documentation.
  4. You will need the AWS credentials when configuring Stack CRs for stacks you wish to be backed by the S3 bucket.
  5. Lastly you will need to create an AWS Key Management Service (KMS) key. This key will be used by Pulumi to encrypt secret configuration values or outputs associated with stacks. Pulumi ensures all secrets are stored encrypted in transit and at rest. By default, the SaaS backend creates per-stack encryption keys to do this, however, Pulumi can leverage KMS as one of several supported encryption providers instead, thus allowing users to self-manage their encryption keys.

Deploy the Operator

Deploy the operator to a Kubernetes cluster.

You can use an existing cluster, or get started by creating a new managed Kubernetes cluster. We will assume that your target Kubernetes cluster is already created and you have configured kubectl to point to it. Note that Pulumi doesn't actually use kubectl but for convenience can use the same mechanism to authenticate against clusters.

Using kubectl

First, download the latest release source code tar ball and expand it locally.

Deploy the CustomResourceDefinitions (CRDs) for the operator.

kubectl apply -f deploy/crds/

Deploy the API resources for the operator.

kubectl apply -f deploy/yaml

This will deploy the operator to the default namespace (which depends on your configuration, and is usually "default"). To deploy to a different namespace:

kubectl apply -n <namespace> -f deploy/yaml

You can deploy to several namespaces by repeating the above command.

Using Pulumi

First, make sure you have reviewed and performed the tasks identified in the prerequisite section.

We will create a Pulumi project to deploy the operator by using a template, then customize it if necessary, then use pulumi up to run it. There is a choice of template, related to the programming language and environment you wish to use:

  • deploy/deploy-operator-cs (.NET)
  • deploy/deploy-operator-go (Go)
  • deploy/deploy-operator-py (Python)
  • deploy/deploy-operator-ts (TypeScript/NodeJS)

Pick one of those, then create a new project in a fresh directory:

TEMPLATE=deploy/deploy-operator-ts # for example
mkdir deploy-operator
cd deploy-operator
pulumi new https://github.com/pulumi/pulumi-kubernetes-operator/$TEMPLATE
# If using the S3 state backend, you may wish to set the secrets provider here
pulumi stack change-secrets-provider "awskms:///arn:aws:kms:...?region=<region>"

You can then set the namespace, or namespaces, in which to deploy the operator:

pulumi config set namespace ns1
# OR deploy to multiple namespaces
pulumi set config --path namespaces[0] ns1
pulumi set config --path namespaces[1] ns2

And finally, run the program:

pulumi up

Upgrading the operator

For patch and minor version releases, you can just bump the version in your stack config and rerun pulumi up. For example, if the new version is v1.10.2, you would do this:

pulumi config set operator-version v1.10.2
pulumi up

Create Pulumi Stack CustomResources

The following are examples to create Pulumi Stacks in Kubernetes that are managed and run by the operator.

Using kubectl

Check out Create Pulumi Stacks using kubectl for YAML examples.

Using Pulumi

Check out Create Pulumi Stacks using Pulumi for Typescript, Python, Go, and .NET examples.

Extended Examples

If you'd like to use your own Pulumi Stack, ensure that you have an existing Pulumi program in a git repo, and update the CR with:

  • An existing github project and/or commit,
  • A Pulumi stack name that exists and will be selected, or a new stack that will be created and selected.
  • A Kubernetes Secret for your Pulumi API accessToken,
  • A Kubernetes Secret for other sensitive settings like cloud provider credentials, and
  • Environment variables and stack config as needed.

Stack CR Documentation

Detailed documentation on Stack Custom Resource is available here.

Prometheus Metrics Integration

Details on metrics emitted by the Pulumi Kubernetes Operator as instructions on getting them to flow to Prometheus are available here.

Development

Check out docs/build.md for more details on building and working with the operator locally.

pulumi-kubernetes-operator's People

Contributors

adrienzieba avatar blampe avatar bob-bins avatar dependabot[bot] avatar dirien avatar elsesiy avatar eronwright avatar g00pix avatar justinvp avatar kenji-fukasawa avatar komalali avatar kris686 avatar lblackstone avatar liamawhite avatar lukehoban avatar m1l1j0n avatar metral avatar mikhailshilkov avatar mikolajprzybysz avatar mjeffryes avatar pulumi-bot avatar rawkode avatar robbiemckinstry avatar rquitales avatar squaremo avatar stack72 avatar stevesloka avatar thomas11 avatar v-yarotsky avatar viveklak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pulumi-kubernetes-operator's Issues

Support Python projects without `virtualenv` configuration

The initial implementation of Python support in #77 did not enable support for Python projects that do not have a virtualenv configuration, as installing requirements for these projects would require global changes, which are not safe in the operator until reconciliation is performed in separate Jobs. Once we support stack update isolation, we can bring back support for non-virtualenv Python projects.

Add support for publishing Stack status updates to the event system

Problem description

Support adding Stack events to k8s event system, such that kubectl describe will show a populated Events field, along with kubectl get events to return events in the namespace.

$ kubectl describe stack my-stack-xq5sis2q
Name:         my-stack-xq5sis2q
Namespace:    default
Labels:       app.kubernetes.io/managed-by=pulumi
Annotations:  pulumi.com/autonamed: true
API Version:  pulumi.com/v1alpha1
Kind:         Stack
Metadata:
  Creation Timestamp:  2020-08-10T22:06:18Z
  Finalizers:
    finalizer.stack.pulumi.com
  Generation:        1
  Resource Version:  14697165
  Self Link:         /apis/pulumi.com/v1alpha1/namespaces/default/stacks/my-stack-xq5sis2q
  UID:               9b1ee8f2-d0b9-4b79-9bfb-ee118084302d
Spec:
  Access Token Secret:  accesstoken-1fwvxnra
  Commit:               bd1edfac28577d62068b7ace0586df595bda33be
  Config:
    aws:region:         us-west-2
  Destroy On Finalize:  true
  Env Secrets:
    aws-creds-0fipbnea
  Init On Create:  true
  Project Repo:    https://github.com/metral/test-s3-op-project
  Stack:           metral/s3-op-project/dev
Status:
  Last Update:
    Permalink:  https://app.pulumi.com/metral/s3-op-project/dev/updates/1
    State:      bd1edfac28577d62068b7ace0586df595bda33be
  Outputs:
    Bucket Names:
      my-bucket-0-4d9ee8e
      my-bucket-1-3946b0d
Events:  <none>

Add support for storage backend

Pulumi support storing the state in a backend similar to Terraform - AWS S3, GCS etc
The suggestion is to add support for this - allow to set in the secret, instead of access token, the relevant backend:

stringData:
  account: "gs://pulumi-state"

Looking at the code, this is relatively simple to add - unless there is a reason it is not there from the beginning :)
This could also support on-prem deployments of pulumi - by setting the account key to the relevant server URL.

SSH agent requested but SSH_AUTH_SOCK not-specified

Problem description

v0.0.6 was just cut, but it introduced an error w.r.t. GitAuth and SSH sockets in a container image.

As a result, we've reverted v0.0.6 for now from the commits and docs until this issue is fixed.

Errors & Logs

SSH agent requested but SSH_AUTH_SOCK not-specified

Affected product version(s)

Reproducing the issue

Run the operator using v0.0.6 and deploy a stack that uses git authentication e.g. https://github.com/pulumi/pulumi-kubernetes-operator/blob/master/stack-examples/yaml/s3_bucket_stack_access_token.yaml

Suggestions for a fix

Installing openssh-server in the Dockefile didn't work but it appears that it is related to this.

Implement observability on garbage collection needs of failed Stack runs

In the scenarios that stacks can be stuck in a failure, resources in flight could be in a pending or failed state.
We should provide the information listed in the pending_operations.

  • Log the resources orphaned in the update
  • Reflect the log of orphaned resources in CR spec.status field

Get Outputs working

The current implementation aims to have support for publishing Outputs, and @joeduffy's video showed them working, but when I run the operator, I do not see any outputs getting published into the CR.

Use envtest to parallelize tests by giving each test its own etcd & kube-apiserver to use

The operator-sdk supports MaxConcurrentReconcilations, which is the number of workers a given operator can spawn to handle reconciliation loops. By default this is set to 1, but we set it to 10 (an arbitrary value greater than the default).

While this handles concurrency within a single operator to work with the APIserver, it does not handle concurrency across multiple instances/replicas of an operator. This means competing operators will fight to process the same Stack CRs, and causes concurrency issues that ultimately lead to extraneous reconciliation loops, and indeterministic stack update sequences.

The operator will need to be configured with leader election to settle contention between multiple Operator instances by using an active-passive setup.

Related: operator-framework/operator-sdk#3585

Enhance Kubernetes Stack CRD API Type

Enhance the current Stack CRD type beyond what currently exists, to reflect a user experience that aims to cover most use cases we expect of initial users.

  • Use private git repos for the Pulumi project source (opt-in)
  • Use git repo commit or branch
  • Change project directories (opt-in) if Pulumi.yaml is not in top-level project repo root
  • Create new stack, with optional secrets provider (opt-in)
  • Refresh stack (opt-in)
  • Expect refresh changes (opt-in)
  • Use config secrets (opt-in)

Extra reconciliation loops can cause harmless errors due to stale objects

Issue

Kubernetes uses optimistic concurrency, which can lead to invalid operations if an object becomes stale. This a feature in k8s, not a bug.

Working with GETs and UPDATES for CustomResources like the Stack means that we will occasionally hit stale data during operations. Here are some examples w.r.t the finalizer being added and executed, and hitting outdated objects. When this happens, we requeue the request iff the step is required for the run -- setting a finalizer is one of these steps.

2020-07-22T18:38:00.022Z        INFO    controller_stack        Adding Finalizer for the Stack  {"Request.Namespace": "default", "Request.Name": "stack-test-aws-s3-commit-change-mmit4b", "Stack.Name": "stack-test-aws-s3-commit-change-mmit4b"}
2020-07-22T18:38:00.846Z        ERROR   controller-runtime.controller   Reconciler error        {"controller": "stack-controller", "request": "default/stack-test-aws-s3-commit-change-mmit4b", 
"error": "Operation cannot be fulfilled on stacks.pulumi.com \"stack-test-aws-s3-commit-change-mmit4b\": the object has been modified; please apply your changes to the latest version and try again"}
Failed to add Pulumi finalizer  {"Request.Namespace": "default", "Request.Name": "stack-test-aws-s3-6itteb", "Stack.Name": "metral/s3-op-project/dev-zvei3i",
error": "Operation cannot be fulfilled on stacks.pulumi.com \"stack-test-aws-s3-6itteb\": the object has been modified; please apply your changes to the latest version and try againโ€}
2020-07-22T18:39:44.171Z        ERROR   controller_stack        Failed to run Pulumi finalizer  {"Request.Namespace": "default", "Request.Name": "stack-test-aws-s3-commit-change-mmit4b", "Stack.Name": "metral/s3-op-project/dev-commit-change-autkr6", "error": "destroying resources for stack 'metral/s3-op-project/dev-commit-change-autkr6': exit status 255", "error

We can also see how requeued requests may fail if another loop got further along, e.g. update conflicts or destroy conflicts. We mitigate update conflicts by default by not using the RetryOnUpdateConflict option in the StackSpec, which dismisses conflicted update loops. Destroys (running the finalizer) are left as-is as these repeating themselves is not harmful if the intent to destroy was registered.

2020-07-22T22:12:38.273Z        INFO    controller_stack        Conflict with another concurrent update -- NOT retrying {"Request.Namespace": "default", "Request.Name": "stack-test-aws-s3-g37qr3", "Stack.Name": "metral/s3-op-project/dev-la4p4f", 
"Err:": "exit status 255"}

Extensive testing, use of retries on APIserver conflicts, and hardening of the reconcile loop has turned these extra loop errors mostly into warnings, and in most cases can be ignored.

Suggestions for a fix

  • Identify and elide extra AddFinalizer invocation. We only invoke if not set, but some unidentified event is leading to 2 finalizer registration attempts per test. Favorably, only one loop ever succeeds.
  • Permutations of predicates have not proved effective beyond the resourceGeneration, which ignores events for an Update if the generation number of the API object does not change -- no generation changes is only true for updates to spec.status and metadata changes. Disabling predicates can create extra reconcile loops and an inconsistent stack update activity, so turning them off is not a path forward, however, identifying if there is anything else that can be done here to lower the total number of reconciliation loops would be beneficial.

Support other languages in InstallProjectDependencies

Currently nodejs is the only runtime supported when setting up a Stack's dependencies in the operator. This means Pulumi programs written in other runtimes are not yet runnable.

func (sess *reconcileStackSession) InstallProjectDependencies(runtime string) error {
switch runtime {
case "nodejs":
npm, _ := exec.LookPath("npm")
if npm == "" {
npm, _ = exec.LookPath("yarn")
}
if npm == "" {
return errors.New("did not find 'npm' or 'yarn' on the PATH; can't install project dependencies")
}
cmd := exec.Command(npm, "install")
_, _, err := sess.runCmd("NPM/Yarn", cmd)
return err
default:
return errors.Errorf("unsupported project runtime: %s", runtime)
}
}

Add support for other runtimes:

  • Python
  • .NET
  • Go

Support branch as desired state by resolving it to a given commit

Currently, a Stack's desired state is based on the git checkout and deployment of a given Pulumi program commit.

if instance.Status.LastUpdate == nil {
instance.Status.LastUpdate = &pulumiv1alpha1.StackUpdateState{
State: instance.Spec.Commit,
Permalink: permalink,
}

The StackSpec supports a Branch field, but it is currently not used if set.

To continue with the desired state model using a commit SHA, we should resolve a branch if supplied to its current commit.

Design Kubernetes StackController Interface

Design an Interface for the StackController operations needed to reflect a typical use case for most users, that can be pluggable for alternative implementations.

  • Fetch project source
  • Set relative working dir in source repo
  • Log into the Pulumi service
  • Install Node, Python, Go, .NET dependencies
  • Stack init and --secrets-provider configuration (both opt-in)
  • Stack config (will need to be updated for Stack CRD API and StackController Interface)
  • Stack config --secret (opt-in)
  • Stack refresh (opt-in)
  • Stack update (will need to be updated for Stack CRD API and StackController Interface)
  • Get all Stack output (will need to be updated for Stack CRD API and StackController Interface)
  • Stack destroy (opt-in)
  • Stack remove (opt-in)
  • Update stack status

Add basic tests to validate the CI build process

The operator-sdk is moving away from subcommands like operator-sdk test, operator-sdk run operator-sdk generate to kubebuilder per changelog-a and changelog-b.

Tests should follow operator-sdk recommendations of moving to kube-builder style projects and testing, which is a WIP in redefining:

Support for private git repos

The operator seems to currently only support public repos. Is there a way to pass a token in the Stack resource so that private repos can be used?

GetStackOutputs occasionally hits unexpected end of JSON input

The operator is occasionally hitting this error where the stack outputs cannot be unmarshaled to the map in GetStackOutputs(). It happens rarely, but does seem to be tied to how we're working with stdout and stack export --json.

Error: "error":"unmarshaling stack outputs (to map): unexpected end of JSON input"

// Step 5. Capture outputs onto the resulting status object.
outs, err := sess.GetStackOutputs()
if err != nil {
reqLogger.Error(err, "Failed to get Stack outputs", "Stack.Name", stack.Stack)
return reconcile.Result{}, err
}

// Parse the JSON to ensure it's valid and then encode it as a raw message.
var outs map[string]interface{}
if err = json.Unmarshal([]byte(stdout), &outs); err != nil {
return nil, errors.Wrap(err, "unmarshaling stack outputs (to map)")
}

Full logs.

Log Line:

{
   "level":"error",
   "ts":1594371934.4927378,
   "logger":"controller_stack",
   "msg":"Failed to get Stack outputs",
   "Request.Namespace":"default",
   "Request.Name":"s3-bucket-stack-01",
   "Stack.Name":"metral/s3-op-project/dev-123",
   "error":"unmarshaling stack outputs (to map): unexpected end of JSON input",
   "errorVerbose":"unexpected end of JSON input\nunmarshaling stack outputs (to map)\ngithub.com/pulumi/pulumi-kubernetes-operator/pkg/controller/stack.(*reconcileStackSession).GetStackOutputs\n\tpulumi-kubernetes-operator/pkg/controller/stack/stack_controller.go:634\ngithub.com/pulumi/pulumi-kubernetes-operator/pkg/controller/stack.(*ReconcileStack).Reconcile\n\tpulumi-kubernetes-operator/pkg/controller/stack/stack_controller.go:233\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:256\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:232\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).worker\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:211\nk8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:155\nk8s.io/apimachinery/pkg/util/wait.BackoffUntil\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:156\nk8s.io/apimachinery/pkg/util/wait.JitterUntil\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:133\nk8s.io/apimachinery/pkg/util/wait.Until\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:90\nruntime.goexit\n\t/home/metral/.gvm/gos/go1.14.2/src/runtime/asm_amd64.s:1373",
   "stacktrace":"github.com/go-logr/zapr.(*zapLogger).Error\n\t/home/metral/go/pkg/mod/github.com/go-logr/[email protected]/zapr.go:128\ngithub.com/pulumi/pulumi-kubernetes-operator/pkg/controller/stack.(*ReconcileStack).Reconcile\n\tpulumi-kubernetes-operator/pkg/controller/stack/stack_controller.go:235\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:256\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:232\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).worker\n\t/home/metral/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:211\nk8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:155\nk8s.io/apimachinery/pkg/util/wait.BackoffUntil\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:156\nk8s.io/apimachinery/pkg/util/wait.JitterUntil\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:133\nk8s.io/apimachinery/pkg/util/wait.Until\n\t/home/metral/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:90"
}

Handle stack pending_operations

  • Test pending_operations scenario
  • Export stack state in the event a pending_operations scenario occurs
  • Create any supporting documentation needed to debug this scenario.
  • Log potentially leaked resources in the update
  • Reflect the log of potentially leaked resources in CR spec.status field

Stack Outputs are fetched from the stack but are not being stored in spec.status

Stack outputs are currently fetched from a stack using pulumi stack output --json, which returns a JSON blob.
StackOutputs implements outputs, and this object is set to be stored in the stack's spec.status, but after a run, the result is never set and remains empty: spec.status.outputs = {}.

We represent the JSON blob using json.RawMessage and implement simple marshaling & unmarshaling on the Outputs per the recommendation in kubernetes-sigs/controller-tools#155. The outputs data is visible when using printing debugging, but they are not getting stored in the CR spec.

Current implementation:

// StackOutputs is an opaque JSON blob, since Pulumi stack outputs can contain arbitrary JSON maps and objects.
// Due to a limitation in controller-tools code generation, we need to do this to trick it to generate a JSON
// object instead of byte array. See https://github.com/kubernetes-sigs/controller-tools/issues/155.
type StackOutputs struct {
// Raw JSON representation of the remote status as a byte array.
Raw json.RawMessage `json:"raw,omitempty"`
}
// MarshalJSON returns the JSON encoding of the StackOutputs.
func (s StackOutputs) MarshalJSON() ([]byte, error) {
return s.Raw.MarshalJSON()
}
// UnmarshalJSON sets the StackOutputs to a copy of data.
func (s *StackOutputs) UnmarshalJSON(data []byte) error {
return s.Raw.UnmarshalJSON(data)
}

kubernetes-sigs/controller-tools#155 and kubernetes-sigs/controller-tools#126 (comment) recommends the use of json.RawMessage these days to represent JSON/YAML blobs in the CR spec, and we use this approach.

However, as kubernetes-sigs/controller-tools#126 (comment) points out using a json.RawMessage does not work because when the API is codegen'd using operator-sdk generate crds, the rendered OpenAPI spec for the StackOutputs is given type: string and format: byte, so the input JSON object would need to be serialized in the spec (not ideal). We see the same rendering type and format used in our codegen types, but this approach still does not resolve the issue of the outputs not being set in spec.status.outputs.

The API shape and marshaling of StackOutputs combined with the operator-sdk API codegen seem to be at the core of the issue for why the outputs are not being set.

Consider adding preview

Consider thinking through if pulumi preview is useful and needed in an operator, and how it would work.

e.g. Do we store preview links in CR statuses, or hook up GitHub PR webhooks to provide the Pulumi GitHub app preview-in-a-PR capability, or is this not needed at all for an operator?

ProjectRepoAccessTokenSecret in the StackSpec should be a k8s Secret

Currently to use a Git AccessToken to pull private repos or to not be rate-limited is configurable via ProjectRepoAccessTokenSecret. However, this is a raw field that should be stored in a k8s Secret like done for the Pulumi API token and any other cloud provider credentials.

// Step 1. Clone the repo.
workdir, err := sess.FetchProjectSource(stack.ProjectRepo, &pulumiv1alpha1.ProjectSourceOptions{
AccessToken: stack.ProjectRepoAccessTokenSecret,
Commit: stack.Commit,
Branch: stack.Branch,
})

Should store the AccessToken in a newly created k8s Secret e.g. how the pulumi API access token is done:

// Fetch the API token from the named secret.
secret := &corev1.Secret{}
if err = r.client.Get(context.TODO(),
types.NamespacedName{Name: stack.AccessTokenSecret, Namespace: request.Namespace}, secret); err != nil {
reqLogger.Error(err, "Could not find secret for Pulumi API access",
"Namespace", request.Namespace, "Stack.AccessTokenSecret", stack.AccessTokenSecret)
return reconcile.Result{}, err
}
accessToken := string(secret.Data["accessToken"])
if accessToken == "" {
err = errors.New("Secret accessToken data is empty")
reqLogger.Error(err, "Illegal empty secret accessToken data for Pulumi API access",
"Namespace", request.Namespace, "Stack.AccessTokenSecret", stack.AccessTokenSecret)
return reconcile.Result{}, err
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.