commands

package
v0.0.0-...-dfe815e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 23, 2019 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DeployCommand = &cobra.Command{
	Use:   "deploy",
	Short: "Deploy the project to Kubernetes",
	Long: `Deploy the project to Kubernetes cluster by building a
Docker image and deploying the image to a Kubernetes Deployment object.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		spin := ui.ShowSpinner(1, "Reading configuration...")

		cwd, err := executable.GetCwd()
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command.")
			return err
		}

		cp, err := config.GetPath(cwd)
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Couldn't find kubecli YAML file in the project root. Try running 'kube-cli init' to create one.")
			return err
		}

		cfg, err := config.Read(cp)
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Couldn't read kubecli YAML file. Try running 'kube-cli validate' to make sure the file is valid.")
			return err
		}
		ui.SpinnerSuccess(1, "Successfully read configuration for project.", spin)
		spin = ui.ShowSpinner(2, "Packing project into archive...")

		df := filepath.Join(cwd, "Dockerfile")
		if !filesystem.FileExists(df) {
			ui.SpinnerFail(2, "There was a problem packing a project into archive.", spin)
			ui.FailMessage("Couldn't find Dockerfile in the project root. See https://docs.docker.com/engine/reference/builder/ for further info.")
			return errors.New("missing Dockerfile")
		}

		pf, err := filesystem.Glob(cwd)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem packing a project into archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command as an administrator.")
			return err
		}

		files, err := filterProjectFiles(pf, cwd)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem packing a project into archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command as an administrator.")
			return err
		}

		tmp, err := filesystem.CreateTemp()
		if err != nil {
			ui.SpinnerFail(2, "There was a problem packing a project into archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command as an administrator.")
			return err
		}
		defer os.Remove(tmp)

		err = tar.Archive(files, tmp, &cwd)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem packing a project into archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command as an administrator.")
			return err
		}
		ui.SpinnerSuccess(2, "Project packing successfull.", spin)
		spin = ui.ShowSpinner(3, "Uploading archive...")

		bName := cfg.Gke.Project + "-cloudbuild"
		timestamp := fmt.Sprintf("%v", time.Now().Unix())
		oName := fmt.Sprintf("%v-%v.tar.gz", cfg.Docker.Name, timestamp)
		_ = web.CreateBucket(bName, cfg.Gke.Project)
		sz, err := web.StorageUpload(bName, oName, tmp)
		if err != nil {
			ui.SpinnerFail(3, "There was a problem uploading archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy'. Make sure you have an active internet connection and 'Storage Admin' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}
		ui.SpinnerSuccess(3, fmt.Sprintf("Uploaded archive %s.", humanize.Bytes(uint64(sz))), spin)
		spin = ui.ShowSpinner(4, "Building project...")

		tags := []string{
			cfg.Docker.Tag,
			timestamp,
		}
		bld, err := web.CreateBuild(cfg.Gke.Project, cfg.Docker.Name, bName, oName, tags)
		if err != nil {
			ui.SpinnerFail(4, "There was a problem building the project.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy'. Make sure you have an active internet connection and 'Cloud Build Service Account' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}

		running := true
		timeout := 1
		maxTimeout := 60
		for running {
			b, err := web.GetBuild(cfg.Gke.Project, bld.ID)
			if err != nil {
				ui.SpinnerFail(4, "There was a problem building the project.", spin)
				ui.FailMessage("Please, retry 'kube-cli deploy'. Make sure you have an active internet connection and 'Cloud Build Service Account' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
				return err
			}

			if b.Status == web.SuccessBuildStatus {
				running = false
				break
			}

			if b.Status == web.QueuedBuildStatus || b.Status == web.WorkingBuildStatus {
				timeout *= 2
				if timeout > maxTimeout {
					timeout = maxTimeout
				}
				time.Sleep(time.Duration(timeout) * time.Second)
				continue
			}

			ui.SpinnerFail(4, "There was a problem building the project.", spin)
			ui.FailMessage(fmt.Sprintf("There was a problem building the project, fix the issue and rerun the command. More info available at %v.", b.LogURL))
			return fmt.Errorf("visit %v to learn more", b.LogURL)
		}
		ui.SpinnerSuccess(4, "Building project succeeded.", spin)
		spin = ui.ShowSpinner(5, "Deploying project...")

		cls, err := web.GetGKECluster(cfg.Gke.Project, cfg.Gke.Zone, cfg.Gke.Cluster)
		if err != nil {
			ui.SpinnerFail(5, "There was a problem deploying the project.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy'. Make sure you have an active internet connection and 'Kubernetes Engine Admin' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}

		di := fmt.Sprintf("gcr.io/%v/%v:%v", cfg.Gke.Project, cfg.Docker.Name, timestamp)
		err = web.UpdateDeployment(cfg.Deployment.Namespace, cfg.Deployment.Name, cfg.Deployment.Container.Name, di, cls)
		if err != nil {
			ui.SpinnerFail(5, "There was a problem deploying the project.", spin)
			if err.Error() == fmt.Sprintf("deployments.apps \"%v\" not found", cfg.Deployment.Name) {
				ui.FailMessage(fmt.Sprintf("Couldn't find deployment '%v' in '%v' namespace in cluster '%v'. Make sure you've created a deployment beforehand and rerun the command.", cfg.Deployment.Name, cfg.Deployment.Namespace, cfg.Gke.Cluster))
				return err
			}
			ui.FailMessage("Please, retry 'kube-cli deploy'. Make sure you have an active internet connection and 'Kubernetes Engine Admin' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}
		if asyncDeploy {
			ui.SpinnerSuccess(5, "Successfully started the rolling deployment. You can keep track of the progress at https://console.cloud.google.com/kubernetes/workload.", spin)
			return nil
		}

		running = true
		timeout = 1
		for running {
			cnt, err := web.UnavailableReplicas(cfg.Deployment.Namespace, cfg.Deployment.Name, cls)
			if err != nil {
				ui.SpinnerFail(5, "There was a problem deploying the project.", spin)
				ui.FailMessage("Something unexpected happened. Please check on the status of the deployment on the Google Cloud Console https://console.cloud.google.com/kubernetes/workload.")
				return err
			}
			if cnt == 0 {
				running = false
				break
			}
			timeout *= 2
			if timeout > maxTimeout {
				timeout = maxTimeout
			}
			time.Sleep(time.Duration(timeout) * time.Second)
		}
		ui.SpinnerSuccess(5, "Deploying project succeeded.", spin)
		return nil
	},
}

DeployCommand executes a multi step workflow that builds the project using GCP Cloud Build and than deploys the docker image to a GKE deployment.

View Source
var InitCommand = &cobra.Command{
	Use:   "init",
	Short: "Initialize the project config",
	Long: `Initialize the project YAML config by answering
some questions.`,
	RunE: func(cmd *cobra.Command, args []string) error {

		cwd, err := executable.GetCwd()
		if err != nil {
			ui.FailMessage("Please, retry 'kube-cli init' command.")
			return err
		}

		df := filepath.Join(cwd, "Dockerfile")
		if !filesystem.FileExists(df) {
			ui.WarnMessage("Couldn't find Dockerfile in the project root. See https://docs.docker.com/engine/reference/builder/ for further info.")
		}

		ip := filepath.Join(cwd, ".kubecliignore")
		if !filesystem.FileExists(ip) {
			create, err := ui.Confirm(".kubecliignore was not found in the project root. Would you like to create a generic one?")
			if err != nil {
				ui.FailMessage("Command canceled by user. No changes made.")
				return err
			}
			if create {
				err = ioutil.WriteFile(ip, []byte(".git/**/*\n.kubecliignore\nkubecli.yaml\nkubecli.yml"), 0644)
				if err != nil {
					ui.FailMessage("Please, retry 'kube-cli init' command. Try running it as an administrator.")
					return err
				}
				ui.SuccessMessage("Created a generic .kubecliignore file.")
			}
		}
		// Load existing YAML config, if it exists
		var cfg config.Data
		cp, err := config.GetPath(cwd)
		if err == nil {

			cfg, err = config.Read(cp)
			if err != nil {
				ui.FailMessage("Couldn't read kubecli YAML file. Try running 'kube-cli lint' to make sure the file is valid.")
				return err
			}
			cont, err := ui.Confirm("Continuing will override the current YAML file. Are you sure?")
			if err != nil {
				ui.FailMessage("Command canceled by user. The configuration hasn't been modified.")
				return err
			}
			if !cont {
				ui.Message("The kubecli configuration hasn't been changed.")
				return nil
			}
		}

		ui.Message("Provide the following variables to build the project config file:")
		cfg.Gke.Project, err = ui.Ask("GKE Project", "Name of the GCP project where the Kubernetes cluster is hosted.", cfg.Gke.Project, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Gke.Cluster, err = ui.Ask("GKE Cluster", "Name of the GKE cluster.", cfg.Gke.Cluster, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Gke.Zone, err = ui.Choose("GKE Zone", "GCP zone of the Kubernetes cluster.", cfg.Gke.Zone, genZones())
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Docker.Name, err = ui.Ask("Docker Name", "Name of the Docker image, without gcr.io/...", cfg.Docker.Name, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Docker.Tag, err = ui.Ask("Docker Tag", "Name of a Docker tag that will be applied as a default.", cfg.Docker.Tag, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Deployment.Name, err = ui.Ask("Deployment Name", "Name of the Kubernetes deployment where the project is deployed.", cfg.Deployment.Name, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Deployment.Namespace, err = ui.Ask("Deployment Namespace", "Kubernetes namespace where the deplyment resides.", cfg.Deployment.Namespace, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}
		cfg.Deployment.Container.Name, err = ui.Ask("Container Name", "Container name used in the Kubernetes deployment.", cfg.Deployment.Container.Name, validDashName)
		if err != nil {
			ui.FailMessage("Command canceled by user. No changes made.")
			return err
		}

		err = config.Write(cp, cfg)
		if err != nil {
			ui.FailMessage("Couldn't save YAML config file. Please rerun the 'kube-cli init' command as an administrator.")
			return err
		}
		ui.SuccessMessage("Project is configured. You can now run 'kube-cli deploy' to deploy the project to Kubernetes.")
		return nil
	},
}

InitCommand generates a YAML config used by other commands to properly deploy the project to Kubernetes.

View Source
var RollbackCommand = &cobra.Command{
	Use:   "rollback",
	Short: "Rollback deployment",
	Long:  `Rollback deployment to a previous state.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		spin := ui.ShowSpinner(1, "Reading configuration...")

		cwd, err := executable.GetCwd()
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Please, retry 'kube-cli deploy' command.")
			return err
		}

		cp, err := config.GetPath(cwd)
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Couldn't find kubecli YAML file in the project root. Try running 'kube-cli init' to create one.")
			return err
		}

		cfg, err := config.Read(cp)
		if err != nil {
			ui.SpinnerFail(1, "There was a problem reading configuration.", spin)
			ui.FailMessage("Couldn't read kubecli YAML file. Try running 'kube-cli validate' to make sure the file is valid.")
			return err
		}
		ui.SpinnerSuccess(1, "Successfully read configuration for project.", spin)
		spin = ui.ShowSpinner(2, "Rolling back deployment...")

		cls, err := web.GetGKECluster(cfg.Gke.Project, cfg.Gke.Zone, cfg.Gke.Cluster)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem rolling back the deployment.", spin)
			ui.FailMessage("Please, retry 'kube-cli rollback'. Make sure you have an active internet connection and 'Kubernetes Engine Admin' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}

		err = web.RollbackDeployment(cfg.Deployment.Namespace, cfg.Deployment.Name, cls)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem rolling back the deployment.", spin)
			ui.FailMessage("Please, retry 'kube-cli rollback'. Make sure you have an active internet connection and 'Kubernetes Engine Admin' permissions on GCP Service Account defined in GOOGLE_APPLICATION_CREDENTIALS.")
			return err
		}
		if asyncRollback {
			ui.SpinnerSuccess(2, "Successfully started the rollback of the deployment. You can keep track of the progress at https://console.cloud.google.com/kubernetes/workload.", spin)
			return nil
		}

		running := true
		timeout := 1
		maxTimeout := 60
		for running {
			cnt, err := web.UnavailableReplicas(cfg.Deployment.Namespace, cfg.Deployment.Name, cls)
			if err != nil {
				ui.SpinnerFail(2, "There was a problem rolling back the deployment.", spin)
				ui.FailMessage("Something unexpected happened. Please check on the status of the rollback on the Google Cloud Console https://console.cloud.google.com/kubernetes/workload.")
				return err
			}
			if cnt == 0 {
				running = false
				break
			}
			timeout *= 2
			if timeout > maxTimeout {
				timeout = maxTimeout
			}
			time.Sleep(time.Duration(timeout) * time.Second)
		}
		ui.SpinnerSuccess(2, "Successfully rolled back deployment.", spin)
		return nil
	},
}

RollbackCommand rolls back a Kubernetes deployment to a previous state.

View Source
var UpdateCommand = &cobra.Command{
	Use:   "update",
	Short: "Update the command line tool",
	Long: `Update the command line tool by pulling the latest
version from the web. Make sure you have an active web connection.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		spin := ui.ShowSpinner(1, "Retrieving latest version info...")

		info, err := executable.GetInfo()
		if err != nil {
			ui.SpinnerFail(1, "There was a problem retrieving the latest version info.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command.")
			return err
		}

		shaName := info.Name + "_" + info.OS + "_" + info.Arch + ".sha512"
		tarName := info.Name + "_" + info.OS + "_" + info.Arch + ".tar.gz"
		release, err := web.GetLatestRelease(shaName, tarName)
		if err != nil {
			ui.SpinnerFail(1, "There was a problem retrieving the latest version info.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command. Make sure you have an active internet connection.")
			return err
		}
		ui.SpinnerSuccess(1, fmt.Sprintf("Retrieved latest version is %v.", release.Version), spin)
		if info.Version == release.Version {
			ui.SuccessMessage("The CLI tool is already updated to the latest version.")
			return nil
		}
		spin = ui.ShowSpinner(2, "Downloading CLI archive...")

		tarTemp, err := filesystem.CreateTemp()
		if err != nil {
			ui.SpinnerFail(2, "There was a problem downloading CLI archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command as an administrator.")
			return err
		}

		sz, err := web.DownloadFile(tarTemp, release.TarURL)
		if err != nil {
			ui.SpinnerFail(2, "There was a problem downloading CLI archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command as an administrator.")
			return err
		}
		defer os.Remove(tarTemp)
		ui.SpinnerSuccess(2, fmt.Sprintf("Downloaded CLI archive %s.", humanize.Bytes(uint64(sz))), spin)

		spin = ui.ShowSpinner(3, "Verifying downloaded archive...")

		shaTemp, err := filesystem.CreateTemp()
		if err != nil {
			ui.SpinnerFail(3, "There was a problem verifying the downloaded archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command as an administrator.")
			return err
		}

		sz, err = web.DownloadFile(shaTemp, release.ShaURL)
		if err != nil {
			ui.SpinnerFail(3, "There was a problem verifying the downloaded archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command as an administrator.")
			return err
		}
		defer os.Remove(shaTemp)

		dSum, err := extractSum(shaTemp)
		if err != nil {
			ui.SpinnerFail(3, "There was a problem verifying the downloaded archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command.")
			return err
		}

		cSum, err := hash.Sum(tarTemp)
		if err != nil {
			ui.SpinnerFail(3, "There was a problem verifying the downloaded archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command.")
			return err
		}

		if dSum != cSum {
			ui.SpinnerFail(3, "There was a problem verifying the downloaded archive.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command. The downloaded archive was corrupt.")
			return errors.New("update failed, SHA512 sum missmatch")
		}
		ui.SpinnerSuccess(3, "Verified downloaded archive.", spin)
		spin = ui.ShowSpinner(4, "Updating CLI binaries...")

		err = tar.Unarchive(tarTemp, filepath.Dir(info.Path))
		if err != nil {
			ui.SpinnerFail(4, "There was a problem updating CLI binaries.", spin)
			ui.FailMessage("Please, retry 'kube-cli update' command as an administrator.")
			return err
		}
		ui.SpinnerSuccess(4, fmt.Sprintf("Updated CLI binaries from %v to %v.", info.Version, release.Version), spin)
		return nil
	},
}

UpdateCommand executes CLI update workflow, which downloads latest CLI tool, verifies the download and replaces the old binary.

View Source
var ValidateCommand = &cobra.Command{
	Use:   "validate",
	Short: "Validate YAML config",
	Long: `Validate YAML config in kubecli.yaml to make sure
the properties and structure is correct.`,
	RunE: func(cmd *cobra.Command, args []string) error {

		cwd, err := executable.GetCwd()
		if err != nil {
			ui.FailMessage("Please, retry 'kube-cli init' command.")
			return err
		}

		df := filepath.Join(cwd, "Dockerfile")
		if !filesystem.FileExists(df) {
			ui.WarnMessage("Couldn't find Dockerfile in the project root. See https://docs.docker.com/engine/reference/builder/ for further info.")
		}

		cp, err := config.GetPath(cwd)
		if err != nil {
			ui.FailMessage("Couldn't find kubecli YAML file in the project root. Try running 'kube-cli init' to create one.")
			return err
		}

		cfg, err := config.Read(cp)
		if err != nil {
			ui.FailMessage(strings.Replace(err.Error(), "yaml:", "YAML sytnax is incorrect on", 1))
			return err
		}

		err = validDashName(cfg.Gke.Project)
		hasInvalid := false
		if err != nil {
			ui.FailMessage(fmt.Sprintf("GKE Project %v", err.Error()))
			hasInvalid = true
		}
		err = validDashName(cfg.Gke.Cluster)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("GKE Cluster %v", err.Error()))
			hasInvalid = true
		}
		if !linearSearch(cfg.Gke.Zone, genZones()) {
			ui.FailMessage("GKE Zone is not a valid zone string. See https://cloud.google.com/compute/docs/regions-zones/ for more info.")
			hasInvalid = true
		}
		err = validDashName(cfg.Docker.Name)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("Docker Name %v", err.Error()))
			hasInvalid = true
		}
		err = validDashName(cfg.Docker.Tag)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("Docker Tag %v", err.Error()))
			hasInvalid = true
		}
		err = validDashName(cfg.Deployment.Name)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("Deployment Name %v", err.Error()))
			hasInvalid = true
		}
		err = validDashName(cfg.Deployment.Namespace)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("Deployment Namespace %v", err.Error()))
			hasInvalid = true
		}
		err = validDashName(cfg.Deployment.Container.Name)
		if err != nil {
			ui.FailMessage(fmt.Sprintf("Container Name %v", err.Error()))
			hasInvalid = true
		}
		if hasInvalid {
			ui.FailMessage("YAML configuration is invalid. Try running 'kube-cli init' to fix it.")
			return err
		}
		ui.SuccessMessage("YAML configuration is valid.")
		return nil
	},
}

ValidateCommand checks the validity of kubecli.yaml project config.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL