From 768788ebbb6565ef1199a4b07f0596b931cb3f85 Mon Sep 17 00:00:00 2001 From: Kia Farhang <17127565+KiaFarhang@users.noreply.github.com> Date: Wed, 17 May 2023 11:18:38 -0500 Subject: [PATCH] Add support for performing server-side applies (#191) --- DOCS.md | 23 +++++++++++++++++++ Makefile | 1 + main.go | 63 ++++++++++++++++++++++++++++++++++++---------------- main_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 121 insertions(+), 28 deletions(-) diff --git a/DOCS.md b/DOCS.md index fae1c1c..e8f61e7 100644 --- a/DOCS.md +++ b/DOCS.md @@ -565,6 +565,29 @@ steps: # ... ``` +### `server_side` + +_**type**_ `bool` + +_**default**_ `false` + +_**description**_ Perform a Kubernetes [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) + +_**example**_ + +```yaml +# .drone.yml +--- +kind: pipeline +# ... +steps: + - name: deploy-gke + image: nytimes/drone-gke + settings: + server_side: true + # ... +``` + ### `verbose` _**type**_ `bool` diff --git a/Makefile b/Makefile index 2f9f646..5dc9767 100644 --- a/Makefile +++ b/Makefile @@ -201,6 +201,7 @@ run : @$(docker) run \ --env PLUGIN_CLUSTER \ --env PLUGIN_DRY_RUN \ + --env PLUGIN_SERVER_SIDE \ --env PLUGIN_EXPAND_ENV_VARS \ --env PLUGIN_KUBECTL_VERSION \ --env PLUGIN_NAMESPACE \ diff --git a/main.go b/main.go index 9100b1e..06f6f51 100644 --- a/main.go +++ b/main.go @@ -39,8 +39,11 @@ const ( nsPath = "/tmp/namespace.json" templateBasePath = "/tmp" - dryRunFlagPre118 = "--dry-run=true" - dryRunFlagDefault = "--dry-run=client" + clientSideDryRunFlagPre118 = "--dry-run=true" + clientSideDryRunFlagDefault = "--dry-run=client" + serverSideDryRunFlagPre118 = "--server-dry-run=true" + serverSideDryRunFlagDefault = "--dry-run=server" + serverSideApplyFlag = "--server-side" ) // default to kubectlCmdName, can be overriden via kubectl-version param @@ -54,7 +57,7 @@ metadata: name: %s ` var invalidNameRegex = regexp.MustCompile(`[^a-z0-9\.\-]+`) -var dryRunFlag = dryRunFlagDefault +var dryRunFlag = clientSideDryRunFlagDefault func main() { err := wrapMain() @@ -71,6 +74,11 @@ func getAppFlags() []cli.Flag { Usage: "do not apply the Kubernetes manifests to the API server", EnvVars: []string{"PLUGIN_DRY_RUN"}, }, + &cli.BoolFlag{ + Name: "server-side", + Usage: "perform a server-side apply", + EnvVars: []string{"PLUGIN_SERVER_SIDE"}, + }, &cli.BoolFlag{ Name: "verbose", Usage: "dump available vars and the generated Kubernetes manifest, keeping secrets hidden", @@ -248,7 +256,7 @@ func run(c *cli.Context) error { // Parse and adjust the dry-run flag if needed var dryRunBuffer bytes.Buffer dryRunRunner := NewBasicRunner("/", []string{}, &dryRunBuffer, &dryRunBuffer) - if err := setDryRunFlag(dryRunRunner, &dryRunBuffer); err != nil { + if err := setDryRunFlag(dryRunRunner, &dryRunBuffer, c); err != nil { return err } @@ -337,7 +345,7 @@ func run(c *cli.Context) error { } // Wait for jobs to finish - if err:= waitForJobs(c, runner); err != nil { + if err := waitForJobs(c, runner); err != nil { return fmt.Errorf("Error: %s\n", err) } @@ -438,18 +446,31 @@ func parseSkips(c *cli.Context) error { return nil } -// setDryRunFlag sets the value of the dry-run flag for the version of kubectl -// that is being used -func setDryRunFlag(runner Runner, output io.Reader) error { - dryRunFlag = dryRunFlagDefault +// setDryRunFlag sets the value of the dry-run flag based on the version of kubectl being +// used and whether the apply should be client-side or server-side +func setDryRunFlag(runner Runner, output io.Reader, c *cli.Context) error { + dryRunFlag = clientSideDryRunFlagDefault + version, err := getMinorVersion(runner, output) if err != nil { return fmt.Errorf("Error determining which kubectl version is running: %v", err) } - // default is the >= 1.18 flag - if version < 18 { - dryRunFlag = dryRunFlagPre118 + + isServerSideApply := c.Bool("server-side") + + // Default is the >= 1.18 flag for both server- and client-side dry runs + if isServerSideApply { + if version < 18 { + dryRunFlag = serverSideDryRunFlagPre118 + } else { + dryRunFlag = serverSideDryRunFlagDefault + } + } else { + if version < 18 { + dryRunFlag = clientSideDryRunFlagPre118 + } } + return nil } @@ -746,7 +767,7 @@ func setNamespace(c *cli.Context, project string, runner Runner) error { // Ensure the namespace exists, without errors (unlike `kubectl create namespace`). log("Ensuring the %s namespace exists\n", namespace) - nsArgs := applyArgs(c.Bool("dry-run"), nsPath) + nsArgs := applyArgs(c.Bool("dry-run"), c.Bool("server-side"), nsPath) if err := runner.Run(kubectlCmd, nsArgs...); err != nil { return fmt.Errorf("Error: %s\n", err) } @@ -764,13 +785,13 @@ func applyManifests(c *cli.Context, manifestPaths map[string]string, runner Runn log("Validating Kubernetes manifests with a dry-run\n") if !c.Bool("dry-run") { - args := applyArgs(true, manifests) + args := applyArgs(true, c.Bool("server-side"), manifests) if err := runner.Run(kubectlCmd, args...); err != nil { return fmt.Errorf("Error: %s\n", err) } if len(manifestsSecret) > 0 { - argsSecret := applyArgs(true, manifestsSecret) + argsSecret := applyArgs(true, c.Bool("server-side"), manifestsSecret) if err := runnerSecret.Run(kubectlCmd, argsSecret...); err != nil { return fmt.Errorf("Error: %s\n", err) } @@ -780,14 +801,14 @@ func applyManifests(c *cli.Context, manifestPaths map[string]string, runner Runn } // Actually apply Kubernetes manifests. - args := applyArgs(c.Bool("dry-run"), manifests) + args := applyArgs(c.Bool("dry-run"), c.Bool("server-side"), manifests) if err := runner.Run(kubectlCmd, args...); err != nil { return fmt.Errorf("Error: %s\n", err) } // Apply Kubernetes secrets manifests if len(manifestsSecret) > 0 { - argsSecret := applyArgs(c.Bool("dry-run"), manifestsSecret) + argsSecret := applyArgs(c.Bool("dry-run"), c.Bool("server-side"), manifestsSecret) if err := runnerSecret.Run(kubectlCmd, argsSecret...); err != nil { return fmt.Errorf("Error: %s\n", err) } @@ -868,7 +889,7 @@ func waitForJobs(c *cli.Context, runner Runner) error { log(fmt.Sprintf("Waiting until job completes for %s%s\n", job, counterProgress)) command := []string{"wait", "--for=condition=complete", job} - + if waitSeconds != 0 { command = append(command, fmt.Sprintf("--timeout=%ds", waitSeconds)) } @@ -888,7 +909,7 @@ func waitForJobs(c *cli.Context, runner Runner) error { } // applyArgs creates args slice for kubectl apply command -func applyArgs(dryrun bool, file string) []string { +func applyArgs(dryrun bool, serverSide bool, file string) []string { args := []string{ "apply", } @@ -897,6 +918,10 @@ func applyArgs(dryrun bool, file string) []string { args = append(args, dryRunFlag) } + if serverSide { + args = append(args, serverSideApplyFlag) + } + args = append(args, "--filename") args = append(args, file) diff --git a/main_test.go b/main_test.go index 0726994..d47585d 100644 --- a/main_test.go +++ b/main_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "strconv" "strings" "testing" @@ -632,11 +633,14 @@ func TestWaitForJobs(t *testing.T) { } func TestApplyArgs(t *testing.T) { - args := applyArgs(false, "/path/to/file/1") + args := applyArgs(false, false, "/path/to/file/1") assert.Equal(t, []string{"apply", "--filename", "/path/to/file/1"}, args) - args = applyArgs(true, "/path/to/file/2") + args = applyArgs(true, false, "/path/to/file/2") assert.Equal(t, []string{"apply", "--dry-run=client", "--filename", "/path/to/file/2"}, args) + + args = applyArgs(false, true, "/path/to/file/3") + assert.Equal(t, []string{"apply", "--server-side", "--filename", "/path/to/file/3"}, args) } func TestPrintTrimmedError(t *testing.T) { @@ -736,6 +740,7 @@ func TestSetDryRunFlag(t *testing.T) { name string versionCommandOutput string explicitVersion string + isServerSide bool expectedFlag string }{ @@ -755,7 +760,7 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "", - expectedFlag: dryRunFlagPre118, + expectedFlag: clientSideDryRunFlagPre118, }, { name: "kubectl-1.15", @@ -773,7 +778,7 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "1.15", - expectedFlag: dryRunFlagPre118, + expectedFlag: clientSideDryRunFlagPre118, }, { name: "kubectl-1.16", @@ -791,7 +796,25 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "1.16", - expectedFlag: dryRunFlagPre118, + expectedFlag: clientSideDryRunFlagPre118, + }, + { + name: "kubectl-1.17", + versionCommandOutput: `{ + "clientVersion": { + "major": "1", + "minor": "17", + "gitVersion": "v1.17.17", + "gitCommit": "f3abc15296f3a3f54e4ee42e830c61047b13895f", + "gitTreeState": "clean", + "buildDate": "2021-01-13T13:21:12Z", + "goVersion": "go1.13.15", + "compiler": "gc", + "platform": "linux/amd64" + } + }`, + explicitVersion: "1.17", + expectedFlag: clientSideDryRunFlagPre118, }, { name: "kubectl-1.17", @@ -809,7 +832,26 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "1.17", - expectedFlag: dryRunFlagPre118, + isServerSide: true, + expectedFlag: serverSideDryRunFlagPre118, + }, + { + name: "kubectl-1.18", + versionCommandOutput: `{ + "clientVersion": { + "major": "1", + "minor": "18", + "gitVersion": "v1.18.15", + "gitCommit": "73dd5c840662bb066a146d0871216333181f4b64", + "gitTreeState": "clean", + "buildDate": "2021-01-13T13:22:41Z", + "goVersion": "go1.13.15", + "compiler": "gc", + "platform": "linux/amd64" + } + }`, + explicitVersion: "1.18", + expectedFlag: clientSideDryRunFlagDefault, }, { name: "kubectl-1.18", @@ -827,7 +869,8 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "1.18", - expectedFlag: dryRunFlagDefault, + isServerSide: true, + expectedFlag: serverSideDryRunFlagDefault, }, { name: "kubectl-1.19", @@ -845,7 +888,7 @@ func TestSetDryRunFlag(t *testing.T) { } }`, explicitVersion: "1.19", - expectedFlag: dryRunFlagDefault, + expectedFlag: clientSideDryRunFlagDefault, }, } for _, test := range tests { @@ -853,6 +896,7 @@ func TestSetDryRunFlag(t *testing.T) { os.Clearenv() os.Setenv("PLUGIN_KUBECTL_VERSION", test.explicitVersion) + os.Setenv("PLUGIN_SERVER_SIDE", strconv.FormatBool(test.isServerSide)) err := (&cli.App{ Flags: getAppFlags(), @@ -874,7 +918,7 @@ func TestSetDryRunFlag(t *testing.T) { } // Run - setDryRunFlag(testRunner, buf) + setDryRunFlag(testRunner, buf, ctx) // Check if dryRunFlag != test.expectedFlag {