func parseSecrets() (map[string]string, error) {
	secrets := make(map[string]string)
	for _, e := range os.Environ() {
		if !strings.HasPrefix(e, "SECRET_") {
			continue
		}
		pair := strings.SplitN(e, "=", 2)
		if len(pair) != 2 {
			return nil, fmt.Errorf("Error: missing secret value")
		}
		k := pair[0]
		v := pair[1]
		if _, ok := secrets[k]; ok {
			return nil, fmt.Errorf("Error: secret var %q shadows existing secret\n", k)
		}
		if v == "" {
			return nil, fmt.Errorf("Error: secret var %q is an empty string\n", k)
		}
		if strings.HasPrefix(k, "SECRET_BASE64_") {
			secrets[k] = v
		} else {
			secrets[k] = base64.StdEncoding.EncodeToString([]byte(v))
		}
		os.Unsetenv(k)
	}
	return secrets, nil
}
parseSecrets parses secrets from environment variables (beginning with "SECRET_"),
clears them and returns a map
package main
import (
	"bufio"
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"github.com/urfave/cli/v2"
)
type token struct {
	ProjectID string `json:"project_id"`
}
var (
	version string
	rev string
)
const (
	gcloudCmd      = "gcloud"
	kubectlCmdName = "kubectl"
	timeoutCmd     = "timeout"
	keyPath          = "/tmp/gcloud.json"
	nsPath           = "/tmp/namespace.json"
	templateBasePath = "/tmp"
	dryRunFlagPre118  = "--dry-run=true"
	dryRunFlagDefault = "--dry-run=client"
)
var kubectlCmd = kubectlCmdName
var extraKubectlVersions = strings.Split(os.Getenv("EXTRA_KUBECTL_VERSIONS"), " ")
var nsTemplate = `
---
apiVersion: v1
kind: Namespace
metadata:
  name: %s
`
var invalidNameRegex = regexp.MustCompile(`[^a-z0-9\.\-]+`)
var dryRunFlag = dryRunFlagDefault
func main() {
	err := wrapMain()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
func getAppFlags() []cli.Flag {
	return []cli.Flag{
		&cli.BoolFlag{
			Name:    "dry-run",
			Usage:   "do not apply the Kubernetes manifests to the API server",
			EnvVars: []string{"PLUGIN_DRY_RUN"},
		},
		&cli.BoolFlag{
			Name:    "verbose",
			Usage:   "dump available vars and the generated Kubernetes manifest, keeping secrets hidden",
			EnvVars: []string{"PLUGIN_VERBOSE"},
		},
		&cli.StringFlag{
			Name:    "token",
			Usage:   "service account's `JSON` credentials",
			EnvVars: []string{"PLUGIN_TOKEN", "TOKEN"},
		},
		&cli.StringFlag{
			Name:    "project",
			Usage:   "GCP project name (default: interpreted from JSON credentials)",
			EnvVars: []string{"PLUGIN_PROJECT"},
		},
		&cli.StringFlag{
			Name:    "zone",
			Usage:   "zone of the container cluster",
			EnvVars: []string{"PLUGIN_ZONE"},
		},
		&cli.StringFlag{
			Name:    "region",
			Usage:   "region of the container cluster",
			EnvVars: []string{"PLUGIN_REGION"},
		},
		&cli.StringFlag{
			Name:    "cluster",
			Usage:   "name of the container cluster",
			EnvVars: []string{"PLUGIN_CLUSTER"},
		},
		&cli.StringFlag{
			Name:    "namespace",
			Usage:   "Kubernetes namespace to operate in",
			EnvVars: []string{"PLUGIN_NAMESPACE"},
		},
		&cli.StringFlag{
			Name:    "kube-template",
			Usage:   "template for Kubernetes resources, e.g. Deployments",
			EnvVars: []string{"PLUGIN_TEMPLATE"},
			Value:   ".kube.yml",
		},
		&cli.BoolFlag{
			Name:    "skip-template",
			Usage:   "do not parse or apply the Kubernetes template",
			EnvVars: []string{"PLUGIN_SKIP_TEMPLATE"},
		},
		&cli.StringFlag{
			Name:    "secret-template",
			Usage:   "template for Kubernetes Secret resources",
			EnvVars: []string{"PLUGIN_SECRET_TEMPLATE"},
			Value:   ".kube.sec.yml",
		},
		&cli.BoolFlag{
			Name:    "skip-secret-template",
			Usage:   "do not parse or apply the Kubernetes Secret template",
			EnvVars: []string{"PLUGIN_SKIP_SECRET_TEMPLATE"},
		},
		&cli.StringFlag{
			Name:    "vars",
			Usage:   "variables to use while templating manifests in `JSON` format",
			EnvVars: []string{"PLUGIN_VARS"},
		},
		&cli.BoolFlag{
			Name:    "expand-env-vars",
			Usage:   "expand environment variables contents on vars",
			EnvVars: []string{"PLUGIN_EXPAND_ENV_VARS"},
		},
		&cli.StringFlag{
			Name:    "drone-build-number",
			Usage:   "Drone build number",
			EnvVars: []string{"DRONE_BUILD_NUMBER"},
		},
		&cli.StringFlag{
			Name:    "drone-commit",
			Usage:   "Git commit hash",
			EnvVars: []string{"DRONE_COMMIT"},
		},
		&cli.StringFlag{
			Name:    "drone-branch",
			Usage:   "Git branch",
			EnvVars: []string{"DRONE_BRANCH"},
		},
		&cli.StringFlag{
			Name:    "drone-tag",
			Usage:   "Git tag",
			EnvVars: []string{"DRONE_TAG"},
		},
		&cli.StringSliceFlag{
			Name:    "wait-deployments",
			Usage:   "list of Deployments to wait for successful rollout using kubectl rollout status in `JSON` format",
			EnvVars: []string{"PLUGIN_WAIT_DEPLOYMENTS"},
		},
		&cli.IntFlag{
			Name:    "wait-seconds",
			Usage:   "if wait-deployments is set, number of seconds to wait before failing the build",
			EnvVars: []string{"PLUGIN_WAIT_SECONDS"},
			Value:   0,
		},
		&cli.StringFlag{
			Name:    "kubectl-version",
			Usage:   "optional - version of kubectl binary to use, e.g. 1.14",
			EnvVars: []string{"PLUGIN_KUBECTL_VERSION"},
		},
	}
}
func wrapMain() error {
	if version == "" {
		version = "x.x.x"
	}
	if rev == "" {
		rev = "[unknown]"
	}
	fmt.Printf("Drone GKE Plugin built from %s\n", rev)
	app := cli.NewApp()
	app.Name = "gke plugin"
	app.Usage = "gke plugin"
	app.Action = run
	app.Version = fmt.Sprintf("%s-%s", version, rev)
	app.Flags = getAppFlags()
	return app.Run(os.Args)
}
func run(c *cli.Context) error {
	if err := checkParams(c); err != nil {
		return err
	}
	token := decodeToken(c.String("token"))
	project := c.String("project")
	if project == "" {
		log("Parsing Project ID from credentials\n")
		project = getProjectFromToken(token)
		if project == "" {
			return fmt.Errorf("Missing required param: project")
		}
	}
	err := parseSkips(c)
	if err != nil {
		return err
	}
	kubectlVersion := c.String("kubectl-version")
	if kubectlVersion != "" {
		kubectlCmd = fmt.Sprintf("%s.%s", kubectlCmdName, kubectlVersion)
	}
	var dryRunBuffer bytes.Buffer
	dryRunRunner := NewBasicRunner("/", []string{}, &dryRunBuffer, &dryRunBuffer)
	if err := setDryRunFlag(dryRunRunner, &dryRunBuffer); err != nil {
		return err
	}
	vars, err := parseVars(c)
	if err != nil {
		return err
	}
	secrets, err := parseSecrets()
	if err != nil {
		return err
	}
	environ := os.Environ()
	environ = append(environ, fmt.Sprintf("GOOGLE_APPLICATION_CREDENTIALS=%s", keyPath))
	runner := NewBasicRunner("", environ, os.Stdout, os.Stderr)
	if err := fetchCredentials(c, token, project, runner); err != nil {
		return err
	}
	defer func() {
		err := os.Remove(keyPath)
		if err != nil {
			log("Warning: error removing token file: %s\n", err)
		}
	}()
	templateData, secretsData, secretsDataRedacted, err := templateData(c, project, vars, secrets)
	if err != nil {
		return err
	}
	if c.Bool("verbose") {
		dumpData(os.Stdout, "VARIABLES AVAILABLE FOR ALL TEMPLATES", templateData)
		dumpData(os.Stdout, "ADDITIONAL SECRET VARIABLES AVAILABLE FOR .sec.yml TEMPLATES", secretsDataRedacted)
	}
	manifestPaths, err := renderTemplates(c, templateData, secretsData)
	if err != nil {
		return err
	}
	if c.Bool("verbose") {
		dumpFile(os.Stdout, "RENDERED MANIFEST (Secret Manifest Omitted)", manifestPaths[c.String("kube-template")])
	}
	if err := printKubectlVersion(runner); err != nil {
		return fmt.Errorf("Error: %s\n", err)
	}
	if err := setNamespace(c, project, runner); err != nil {
		return fmt.Errorf("Error: %s\n", err)
	}
	var secretStderr bytes.Buffer
	runnerSecret := NewBasicRunner("", environ, os.Stdout, &secretStderr)
	if err := applyManifests(c, manifestPaths, runner, runnerSecret); err != nil {
		return fmt.Errorf("Error (kubectl output redacted): %s\n", err)
	}
	if c.Bool("dry-run") {
		log("Not waiting for rollout, this was a dry-run\n")
		return nil
	}
	if err := waitForRollout(c, runner); err != nil {
		return fmt.Errorf("Error: %s\n", err)
	}
	return nil
}
func decodeToken(token string) string {
	if decodedToken, err := base64.StdEncoding.DecodeString(token); err == nil {
		token = string(decodedToken)
	} else {
		fmt.Println("info: skipping base64 credentials decode")
	}
	return token
}
func checkParams(c *cli.Context) error {
	if c.String("token") == "" {
		return fmt.Errorf("Missing required param: token")
	}
	if c.String("zone") == "" && c.String("region") == "" {
		return fmt.Errorf("Missing required param: at least one of region or zone must be specified")
	}
	if c.String("zone") != "" && c.String("region") != "" {
		return fmt.Errorf("Invalid params: at most one of region or zone may be specified")
	}
	if c.String("cluster") == "" {
		return fmt.Errorf("Missing required param: cluster")
	}
	if err := validateKubectlVersion(c, extraKubectlVersions); err != nil {
		return err
	}
	return nil
}
func validateKubectlVersion(c *cli.Context, availableVersions []string) error {
	kubectlVersionParam := c.String("kubectl-version")
	if kubectlVersionParam == "" {
		return nil
	}
	if len(availableVersions) == 0 {
		return fmt.Errorf("Invalid param: kubectl-version was set to %s but no extra kubectl versions are available", kubectlVersionParam)
	}
	for _, availableVersion := range availableVersions {
		if kubectlVersionParam == availableVersion {
			return nil
		}
	}
	return fmt.Errorf("Invalid param kubectl-version: %s must be one of %s", kubectlVersionParam, strings.Join(availableVersions, ", "))
}
func getProjectFromToken(j string) string {
	t := token{}
	err := json.Unmarshal([]byte(j), &t)
	if err != nil {
		return ""
	}
	return t.ProjectID
}
func parseSkips(c *cli.Context) error {
	if c.Bool("skip-template") {
		log("Warning: skipping kube-template because it was set to be ignored\n")
		if err := c.Set("kube-template", ""); err != nil {
			return err
		}
	}
	if c.Bool("skip-secret-template") {
		log("Warning: skipping secret-template because it was set to be ignored\n")
		if err := c.Set("secret-template", ""); err != nil {
			return err
		}
	}
	if c.Bool("skip-template") && c.Bool("skip-secret-template") {
		return fmt.Errorf("Error: skipping both templates ends the plugin execution\n")
	}
	return nil
}
func setDryRunFlag(runner Runner, output io.Reader) error {
	dryRunFlag = dryRunFlagDefault
	version, err := getMinorVersion(runner, output)
	if err != nil {
		return fmt.Errorf("Error determining which kubectl version is running: %v", err)
	}
	if version < 18 {
		dryRunFlag = dryRunFlagPre118
	}
	return nil
}
func getMinorVersion(runner Runner, output io.Reader) (int64, error) {
	runner.Run(kubectlCmd, "version", "--client", "-o=json")
	data, err := ioutil.ReadAll(output)
	if err != nil {
		return 0, fmt.Errorf("Error reading kubectl version: %v", err)
	}
	var versionOutput struct {
		ClientVersion struct {
			Minor string
		}
	}
	err = json.Unmarshal(data, &versionOutput)
	if err != nil {
		return 0, fmt.Errorf("Error reading kubectl version: %v", err)
	}
	versionString, err := strings.Replace(versionOutput.ClientVersion.Minor, "+", "", 1), nil
	if err != nil {
		return 0, fmt.Errorf("Error removing extra '+' from version string: %v", err)
	}
	versionInt, err := strconv.ParseInt(versionString, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("Couldn't parse minor version from string: %v", err)
	}
	return versionInt, nil
}
func parseVars(c *cli.Context) (map[string]interface{}, error) {
	vars := make(map[string]interface{})
	varsJSON := c.String("vars")
	if varsJSON != "" {
		if err := json.Unmarshal([]byte(varsJSON), &vars); err != nil {
			return nil, fmt.Errorf("Error parsing vars: %s\n", err)
		}
	}
	return vars, nil
}