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.
Click to show internal directories.
Click to hide internal directories.