Skip to content

Commit

Permalink
Add support for performing server-side applies (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
KiaFarhang authored May 17, 2023
1 parent 90001de commit 768788e
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 28 deletions.
23 changes: 23 additions & 0 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
63 changes: 44 additions & 19 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -54,7 +57,7 @@ metadata:
name: %s
`
var invalidNameRegex = regexp.MustCompile(`[^a-z0-9\.\-]+`)
var dryRunFlag = dryRunFlagDefault
var dryRunFlag = clientSideDryRunFlagDefault

func main() {
err := wrapMain()
Expand All @@ -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",
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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))
}
Expand All @@ -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",
}
Expand All @@ -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)

Expand Down
62 changes: 53 additions & 9 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -736,6 +740,7 @@ func TestSetDryRunFlag(t *testing.T) {
name string
versionCommandOutput string
explicitVersion string
isServerSide bool

expectedFlag string
}{
Expand All @@ -755,7 +760,7 @@ func TestSetDryRunFlag(t *testing.T) {
}
}`,
explicitVersion: "",
expectedFlag: dryRunFlagPre118,
expectedFlag: clientSideDryRunFlagPre118,
},
{
name: "kubectl-1.15",
Expand All @@ -773,7 +778,7 @@ func TestSetDryRunFlag(t *testing.T) {
}
}`,
explicitVersion: "1.15",
expectedFlag: dryRunFlagPre118,
expectedFlag: clientSideDryRunFlagPre118,
},
{
name: "kubectl-1.16",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -827,7 +869,8 @@ func TestSetDryRunFlag(t *testing.T) {
}
}`,
explicitVersion: "1.18",
expectedFlag: dryRunFlagDefault,
isServerSide: true,
expectedFlag: serverSideDryRunFlagDefault,
},
{
name: "kubectl-1.19",
Expand All @@ -845,14 +888,15 @@ func TestSetDryRunFlag(t *testing.T) {
}
}`,
explicitVersion: "1.19",
expectedFlag: dryRunFlagDefault,
expectedFlag: clientSideDryRunFlagDefault,
},
}
for _, test := range tests {
t.Run(test.name, func(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(),
Expand All @@ -874,7 +918,7 @@ func TestSetDryRunFlag(t *testing.T) {
}

// Run
setDryRunFlag(testRunner, buf)
setDryRunFlag(testRunner, buf, ctx)

// Check
if dryRunFlag != test.expectedFlag {
Expand Down

0 comments on commit 768788e

Please sign in to comment.