-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!--Thanks for your contribution. See [CONTRIBUTING](CONTRIBUTING.md) for Pulumi's contribution guidelines. Help us merge your changes more quickly by adding more details such as labels, milestones, and reviewers.--> ### Proposed changes **Epic Link**: #606 **Demo Video Link**: https://pulumi.slack.com/archives/C07DQSV84DC/p1722636430008649 Implements an agent consisting of two commands: - `init` - fetches a flux source into the given directory, intended for use in an init container. - `serve` - starts an RPC server providing an automation API to perform stack updates over the given workspace. ### Overview The RPC server assumes that the project source code has been checked out to a local working directory, called the "workspace" directory. This generally corresponds to a sub-directory within a git repository, e.g. [examples/random-yaml](https://github.com/pulumi/examples/tree/master/random-yaml). At startup, the server opens the workspace using [auto.NewLocalWorkspace](https://github.com/pulumi/pulumi/blob/5651750bb254f73da5ef0fa503818c5a38755ea8/sdk/go/auto/local_workspace.go#L848). All RPC operations are applied to this same workspace, usually one-at-a-time. Some operations cause state changes, e.g. stack selection, that may affect subsequent operations. Some operations produce `PreconditionFailed` if a stack hasn't been selected. At startup, the server optionally runs `pulumi install` to install dependencies and plugins for the project, based on pulumi/pulumi#16782. Note that PKOv1 has some code to install plugins, now believed to be obsolete (see [discussion](pulumi/pulumi#16782 (comment))). The supported operations are: - `WhoAmI` - returns current user info. - `Install` - runs `pulumi install` in the workspace. - `SelectStack` - select (and optionally create) a stack, for use in subsequent operations. - `Info` - a summary of the current stack. - `SetAllConfig` - set multiple configuration values on the current stack, based on literals, environment variables, and file references. It is expected that the server's pod would have ConfigMaps and Secrets mounted accordingly. - `Preview` runs the preview operation for the current stack. - `Up` runs the up operation for the current stack. - `Destroy` runs the destroy operation for the current stack. - `Refresh` runs the refresh operation for the current stack. The deployment operations have streaming responses, consisting of a series of engine events and a final result. The agent uses zap for logging, because it supports structured logging, implements `io.Writer` to capture Pulumi console output, and integrates well with grpc-go. ### Follow-ups - [x] Write RPC server tests - [ ] Rename 'init' to 'fetch' for clarity - [ ] lock the workspace during an operation? Or rely on locking within the Pulumi CLI? ### Related issues (optional) <!--Refer to related PRs or issues: #1234, or 'Fixes #1234' or 'Closes #1234'. Or link to full URLs to issues or pull requests in other GitHub repositories. --> Closes #610 #611
- Loading branch information
1 parent
13b81b8
commit 5b5d8a7
Showing
25 changed files
with
6,160 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
name: Pulumi Kubernetes Operator PR Builds | ||
on: | ||
pull_request: | ||
branches: | ||
- v2 | ||
env: | ||
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
name: Build | ||
steps: | ||
- name: Check out code | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- name: Install Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: 1.22.x | ||
- name: Run GoReleaser | ||
uses: goreleaser/goreleaser-action@v6 | ||
with: | ||
distribution: goreleaser-pro | ||
# 'latest', 'nightly', or a semver | ||
version: '~> v2' | ||
args: release --snapshot --clean --skip=docker | ||
|
||
agent-integration-tests: | ||
runs-on: ubuntu-latest | ||
name: Integration Testing | ||
if: github.event_name == 'repository_dispatch' || github.event.pull_request.head.repo.full_name == github.repository | ||
steps: | ||
- name: Check out code | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- name: Install Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: 1.22.x | ||
- name: Install Pulumi | ||
uses: pulumi/actions@v5 | ||
- name: Run Tests | ||
run: make -C agent test | ||
- name: Upload coverage reports to Codecov | ||
uses: codecov/codecov-action@v4 | ||
with: | ||
file: agent/coverage.out | ||
env: | ||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pulumi-kubernetes-agent | ||
coverage.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Build the agent binary | ||
FROM golang:1.22 AS builder | ||
ARG TARGETOS | ||
ARG TARGETARCH | ||
ARG VERSION | ||
|
||
WORKDIR /workspace | ||
# Copy the Go Modules manifests | ||
COPY go.mod go.mod | ||
COPY go.sum go.sum | ||
|
||
# cache deps before building and copying source so that we don't need to re-download as much | ||
# and so that source changes don't invalidate our downloaded layer | ||
RUN go mod download | ||
|
||
# Copy the go source | ||
COPY main.go main.go | ||
COPY cmd/ cmd/ | ||
COPY pkg/ pkg/ | ||
COPY version/ version/ | ||
|
||
# Build | ||
# the GOARCH has not a default value to allow the binary be built according to the host where the command | ||
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO | ||
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, | ||
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. | ||
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "-X github.com/pulumi/pulumi-kubernetes-operator/agent/version.Version=${VERSION}" -a -o agent main.go | ||
|
||
# runtime image | ||
FROM gcr.io/distroless/static-debian12:debug-nonroot | ||
|
||
# install agent binary | ||
WORKDIR / | ||
COPY --from=builder /workspace/agent agent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
VERSION ?= $(shell git describe --tags --always --dirty) | ||
|
||
all: protoc agent | ||
|
||
ensure: | ||
go mod tidy | ||
|
||
protoc: | ||
@echo "Generating Go files" | ||
cd pkg/proto && protoc --go_out=. --go_opt=paths=source_relative \ | ||
--go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto | ||
|
||
test: | ||
go test -covermode=atomic -coverprofile=coverage.out -v ./... | ||
|
||
agent: protoc | ||
@echo "Building agent" | ||
go build -o pulumi-kubernetes-agent \ | ||
-ldflags "-X github.com/pulumi/pulumi-kubernetes-operator/agent/version.Version=${VERSION}" \ | ||
github.com/pulumi/pulumi-kubernetes-operator/agent | ||
|
||
image: agent | ||
docker build --build-arg="VERSION=$(VERSION)" -t pulumi/pulumi-kubernetes-agent:latest . | ||
|
||
.PHONY: agent protoc image ensure test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
Copyright © 2024 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. | ||
*/ | ||
package cmd | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/fluxcd/pkg/http/fetch" | ||
"github.com/spf13/cobra" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
DefaultFluxRetries = 3 | ||
) | ||
|
||
var ( | ||
TargetDir string | ||
FluxUrl string | ||
FluxDigest string | ||
) | ||
|
||
// initCmd represents the init command | ||
var initCmd = &cobra.Command{ | ||
Use: "init", | ||
Short: "Initialize a Pulumi workspace", | ||
Long: `Initialize a working directory to contain project sources. | ||
For Flux sources: | ||
pulumi-kubernetes-agent init --flux-fetch-url URL | ||
`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
ctx := cmd.Context() | ||
log.Debugw("executing init command", "TargetDir", TargetDir) | ||
|
||
err := os.MkdirAll(TargetDir, 0777) | ||
if err != nil { | ||
log.Errorw("fatal: unable to make target directory", zap.Error(err)) | ||
os.Exit(1) | ||
} | ||
log.Debugw("target directory created", "dir", TargetDir) | ||
|
||
// fetch the configured flux source | ||
if FluxUrl != "" { | ||
// https://github.com/fluxcd/kustomize-controller/blob/a1a33f2adda783dd2a17234f5d8e84caca4e24e2/internal/controller/kustomization_controller.go#L328 | ||
fetcher := fetch.New( | ||
fetch.WithRetries(DefaultFluxRetries), | ||
fetch.WithHostnameOverwrite(os.Getenv("SOURCE_CONTROLLER_LOCALHOST")), | ||
fetch.WithUntar()) | ||
|
||
log.Infow("flux source fetching", "url", FluxUrl, "digest", FluxDigest) | ||
err := fetcher.FetchWithContext(ctx, FluxUrl, FluxDigest, TargetDir) | ||
if err != nil { | ||
log.Errorw("fatal: unable to fetch flux source", zap.Error(err)) | ||
os.Exit(2) | ||
} | ||
log.Infow("flux source fetched", "dir", TargetDir) | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
rootCmd.AddCommand(initCmd) | ||
initCmd.Flags().StringVarP(&TargetDir, "target-dir", "t", "", "The target directory to initialize") | ||
initCmd.MarkFlagRequired("target-dir") | ||
|
||
initCmd.Flags().StringVar(&FluxUrl, "flux-url", "", "Flux archive URL") | ||
initCmd.Flags().StringVar(&FluxDigest, "flux-digest", "", "Flux digest") | ||
initCmd.MarkFlagsRequiredTogether("flux-url", "flux-digest") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
Copyright © 2024 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. | ||
*/ | ||
package cmd | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
"go.uber.org/zap" | ||
) | ||
|
||
var verbose bool | ||
|
||
// a command-specific logger | ||
var log *zap.SugaredLogger | ||
|
||
// rootCmd represents the base command when called without any subcommands | ||
var rootCmd = &cobra.Command{ | ||
Use: "agent", | ||
Short: "Pulumi Kubernetes Operator Agent", | ||
Long: `Provides tooling and a gRPC service for the Pulumi Kubernetes Operator | ||
to use to perform stack operations.`, | ||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { | ||
var err error | ||
|
||
// initialize the global logger | ||
zc := zap.NewDevelopmentConfig() | ||
zc.DisableCaller = true | ||
if !verbose { | ||
zc.Level.SetLevel(zap.InfoLevel) | ||
} | ||
zapLog, err := zc.Build() | ||
if err != nil { | ||
return err | ||
} | ||
zap.ReplaceGlobals(zapLog) | ||
|
||
// initialize a command-specific logger | ||
log = zap.L().Named("cmd").Named(cmd.Name()).Sugar() | ||
return nil | ||
}, | ||
PersistentPostRun: func(cmd *cobra.Command, args []string) { | ||
// ignore sync errors: https://github.com/uber-go/zap/pull/347 | ||
_ = zap.L().Sync() | ||
}, | ||
} | ||
|
||
// Execute adds all child commands to the root command and sets flags appropriately. | ||
// This is called by main.main(). It only needs to happen once to the rootCmd. | ||
func Execute() { | ||
err := rootCmd.Execute() | ||
if err != nil { | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func init() { | ||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") | ||
} |
Oops, something went wrong.