diff --git a/cmd/build.go b/cmd/build.go index 7aaac70e0eaecf45e4d01e5bea97732698f6f062..07bc520f046a3c5a14077b4beaabd983bcf1c4aa 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -16,7 +16,9 @@ package cmd import ( "errors" + "fmt" "os" + "strings" "git.psu.edu/k8s/devtool/config" "git.psu.edu/k8s/devtool/environment" @@ -44,7 +46,7 @@ var buildCmd = &cobra.Command{ conf, err := config.New(configFile) if err != nil { - color.New(color.FgRed).PrintFunc()("Error reading configuration file: %s", configFile) + color.New(color.FgRed).Printf("Error reading configuration file: %s\n", configFile) return } @@ -100,7 +102,7 @@ func build(config config.Config) error { case "go": environment.RunRequired(true, "make", "build") default: - color.New(color.FgRed).PrintFunc()("devtool doesn't support the '%s' language yet.", lang) + color.New(color.FgRed).Printf("devtool doesn't support the '%s' language yet.\n", lang) return errors.New("Language not supported") } return nil @@ -120,8 +122,14 @@ func buildDockerAndDeploy(config config.Config) error { dockerTag := image + ":" + tag.String() - color.New(color.FgGreen).PrintFunc()("Building ", dockerTag) - err = environment.Run(true, "docker", "build", "-t", dockerTag, "-f", dockerfile, ".") + dockerRootDir := "." + if idx := strings.LastIndex(dockerfile, "/"); idx != -1 { + dockerRootDir = dockerfile[:idx] + fmt.Println(dockerRootDir) + } + + color.New(color.FgGreen).Printf("Building: %s\n", dockerTag) + err = environment.Run(true, "docker", "build", "-t", dockerTag, "-f", dockerfile, dockerRootDir) if err != nil { return err } @@ -146,12 +154,15 @@ func buildDockerAndDeploy(config config.Config) error { err = environment.Run(true, "helm", args...) if err != nil { - color.New(color.FgRed).PrintFunc()("helm error. try running 'helm del --purge %s'", releaseName) + color.New(color.FgRed).Printf("helm error. try running 'helm del --purge %s'\n", releaseName) return err } if openServices { - environment.Run(true, "minikube", "service", releaseName) + err = environment.Run(true, "minikube", "service", releaseName) + if err != nil { + return err + } } } return nil diff --git a/cmd/ci.go b/cmd/ci.go index 833b01bcba66ac3b4cd0c4c512c2fa98c105262c..26a561d139ce4294a2e88795d766ce6d67a8496e 100644 --- a/cmd/ci.go +++ b/cmd/ci.go @@ -41,7 +41,10 @@ func init() { ciCmd.PersistentFlags().StringVarP(&imageTag, "image-tag", "i", "", "image tag for docker") ciCmd.PersistentFlags().StringVarP(&environmentSuffix, "environment", "e", "", "environment suffix to append to helm release name") - ciCmd.MarkPersistentFlagRequired("image-tag") + err := ciCmd.MarkPersistentFlagRequired("image-tag") + if err != nil { + panic("invalid arg") + } // Cobra supports local flags which will only run when this command // is called directly, e.g.: diff --git a/cmd/ci_build.go b/cmd/ci_build.go index 3e09c9182f3fc005b5447473a5d994e3ad9aeeac..2b710495c40947a956f2122cca9fcb4484ec3f06 100644 --- a/cmd/ci_build.go +++ b/cmd/ci_build.go @@ -31,7 +31,7 @@ var ciBuildCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { conf, err := config.New(configFile) if err != nil { - color.New(color.FgRed).PrintFunc()("Error reading configuration file: %s", configFile) + color.New(color.FgRed).Printf("Error reading configuration file: %s\n", configFile) return err } @@ -59,8 +59,14 @@ func init() { ciBuildCmd.Flags().StringVarP(&dockerRegistry, "registry", "r", "", "url of the docker registry") ciBuildCmd.Flags().StringVarP(&dockerRegistryNamespace, "registry-namespace", "n", "", "namespace in the docker registry") - ciBuildCmd.MarkFlagRequired("registry") - ciBuildCmd.MarkFlagRequired("registry-namespace") + err := ciBuildCmd.MarkFlagRequired("registry") + if err != nil { + panic("invalid arg") + } + err = ciBuildCmd.MarkFlagRequired("registry-namespace") + if err != nil { + panic("invalid arg") + } } func buildCiDocker(config config.Config) error { @@ -73,13 +79,13 @@ func buildCiDocker(config config.Config) error { dockerImage := dockerRegistry + "/" + dockerRegistryNamespace + "/" + image dockerTag := dockerImage + ":" + imageTag - color.New(color.FgGreen).PrintFunc()("Building: ", dockerTag) + color.New(color.FgGreen).Printf("Building: %s\n", dockerTag) err := environment.Run(true, "docker", "build", "-t", dockerTag, "-f", dockerfile, ".") if err != nil { return err } - color.New(color.FgGreen).PrintFunc()("Pushing Docker image: ", dockerTag) + color.New(color.FgGreen).Printf("Pushing Docker image: %s\n", dockerTag) err = environment.Run(true, "docker", "push", dockerImage) if err != nil { return err diff --git a/cmd/ci_deploy.go b/cmd/ci_deploy.go index e5bc1cd2fcde397fbfcbe992af9782aabbbeed30..50e52277989577cd1f5d015436bcec801fe60257 100644 --- a/cmd/ci_deploy.go +++ b/cmd/ci_deploy.go @@ -30,7 +30,7 @@ var ciDeployCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { conf, err := config.New(configFile) if err != nil { - color.New(color.FgRed).PrintFunc()("Error reading configuration file: %s", configFile) + color.New(color.FgRed).Printf("Error reading configuration file: %s\n", configFile) return err } @@ -69,14 +69,14 @@ func fluxDeployCi(config config.Config) error { } else { releaseName = deployable.Name + "-" + environmentSuffix } - color.Blue("Updating Flux Release:", releaseName) + color.New(color.FgBlue).Printf("Updating Flux Release: %s\n", releaseName) os.Setenv("KUBE_SERVICE_NAME", releaseName) os.Setenv("CI_PROJECT_NAME", image) err := environment.Run(true, "fluxhelmrelease") if err != nil { - color.Red("fluxhelmrelease error.") + color.Red("fluxhelmrelease error: %s", err) return err } } diff --git a/cmd/config_init.go b/cmd/config_init.go index 0de207c2b72bc2b49c8f0545d6fba90969aabfe0..fbc4f65c7ec4c49e615c2b8a160cef70afa3ee38 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -16,11 +16,14 @@ package cmd import ( "fmt" + "io/ioutil" "log" "os" "path/filepath" "strings" + "git.psu.edu/k8s/devtool/environment" + "git.psu.edu/k8s/devtool/config" "github.com/fatih/color" @@ -85,7 +88,7 @@ var initCmd = &cobra.Command{ dockerfiles := findDockerfiles() for _, file := range dockerfiles { - color.Yellow("Found Dockerfile: %s", file) + color.New(color.FgYellow).Printf("Found Dockerfile: %s\n", file) prompt := promptui.Prompt{ Label: "Include in config", Default: "Yes", @@ -121,6 +124,7 @@ var initCmd = &cobra.Command{ } deployable.Name = response + // Ask for Chart selectPrompt := promptui.SelectWithAdd{ Label: "Chart", Items: []string{"cm/eio-swe-service", "cm/eio-swe-cronjob", "cm/angular-client", "cm/eio-swe-jms-processor"}, @@ -132,31 +136,53 @@ var initCmd = &cobra.Command{ } deployable.Chart = response - prompt = promptui.Prompt{ - Label: "Chart Version", + // Ask for Chart Version + availableChartVersions := findChartVersions(response) + + selectPrompt = promptui.SelectWithAdd{ + Label: "Chart Version", + Items: availableChartVersions, + AddLabel: "Other", } - response, err = prompt.Run() + _, response, err = selectPrompt.Run() if err != nil { return } deployable.ChartVersion = response - prompt = promptui.Prompt{ - Label: "Local Config File", + // Ask for Local Config + availableLocalConfigFiles := findLocalConfigs() + + selectPrompt = promptui.SelectWithAdd{ + Label: "Local Config File", + Items: availableLocalConfigFiles, + AddLabel: "Other", } - response, err = prompt.Run() + _, response, err = selectPrompt.Run() if err != nil { return } deployable.LocalConfig = response + // Create the file if it doesn't exist already + _, err = os.OpenFile(response, os.O_RDONLY|os.O_CREATE, 0666) + if err != nil { + color.Yellow("Unable to create config file") + } + conf.Deployables = append(conf.Deployables, deployable) color.Green("Adding Deployment") } + conf.LocalEnvVars = []string{"OAUTH_CLIENT_ID"} + conf.LocalEnvVars = []string{"OAUTH_CLIENT_SECRET", "OAUTH_JWK"} + color.Yellow("Writing configuration") - conf.Write(configFile) + err = conf.Write(configFile) + if err != nil { + color.New(color.FgRed).Printf("Error writing configuration file: %s\n", configFile) + } }, } @@ -195,3 +221,32 @@ func findDockerfiles() []string { } return dockerfiles } + +func findChartVersions(chartName string) []string { + var versions []string + output := environment.RunAndGetOutputRequired("helm", "search", chartName, "--versions") + for i, line := range output { + if i == 0 { + continue + } + fields := strings.Fields(line) + if len(fields) < 4 { + continue + } + versions = append(versions, fields[1]) + } + return versions +} + +func findLocalConfigs() []string { + files, err := ioutil.ReadDir("config/") + if err != nil { + log.Println(err) + } + + var filenames []string + for _, f := range files { + filenames = append(filenames, "config/"+f.Name()) + } + return filenames +} diff --git a/cmd/config_list.go b/cmd/config_list.go index 8df3e96c090335909bd442c5a36d5addf9eece33..a538d9da0f7a8f8ef576c33c8f5979b7493d32a8 100644 --- a/cmd/config_list.go +++ b/cmd/config_list.go @@ -29,7 +29,7 @@ var listCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { conf, err := config.New(configFile) if err != nil { - color.New(color.FgRed).PrintFunc()("Error reading configuration file: ", configFile, "\n") + color.New(color.FgRed).Printf("Error reading configuration file: %s\n", configFile) return } fmt.Println("Project Configuration") diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000000000000000000000000000000000000..b9f451945f66b5813930631af358a3a5621619f7 --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,90 @@ +// Copyright © 2019 NAME HERE <EMAIL ADDRESS> +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "os" + "strings" + + "git.psu.edu/k8s/devtool/environment" + + "git.psu.edu/k8s/devtool/config" + "github.com/fatih/color" + "github.com/spf13/cobra" +) + +var command string + +// runCmd represents the run command +var runCmd = &cobra.Command{ + Use: "run", + Short: "run the application", + Run: func(cmd *cobra.Command, args []string) { + color.Blue("Running your application") + + conf, err := config.New(configFile) + if err != nil { + color.New(color.FgYellow).Printf("WARNING: no project configuration exists: %s\n", configFile) + } + + localConfig := selectLocalConfig(conf.Deployables) + + if localConfig != "" { + var helmValues config.HelmValues + helmValues, err = config.ReadHelmValues(localConfig) + if err != nil { + color.New(color.FgRed).Printf("Error reading local config file: %s\n", localConfig) + } + for k, v := range helmValues.EnvironmentVariables { + os.Setenv(k, v) + } + } + + commandSplit := strings.Fields(command) + environment.RunRequired(true, commandSplit[0], commandSplit[1:]...) + }, +} + +func init() { + rootCmd.AddCommand(runCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // runCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + runCmd.Flags().StringVarP(&command, "command", "c", "", "The command to run") + + err := runCmd.MarkFlagRequired("command") + if err != nil { + panic("invalid arg") + } +} + +func selectLocalConfig(deployables []config.Deployable) string { + if len(deployables) == 0 { + return "" + } + + //if len(deployables) == 1 { + return deployables[0].LocalConfig + //} + + //TODO add support for multiple deployables (prompt user and add arg) + +} diff --git a/cmd/start.go b/cmd/start.go index 246f367e29691c46db62cbe9303758f8e1b6221b..8edfc381270ea05b1aa19839348f52551abac2d0 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -16,7 +16,6 @@ package cmd import ( "fmt" - "os" "os/exec" "strings" "time" @@ -78,15 +77,9 @@ func start(memory, cpu string) { environment.RunRequired(true, "minikube", "start", "--memory", memory, "--cpus", cpu) } - checkInstallTiller() - - environment.EvalDockerEnv() + fixKubectlContext() - color.Blue("Environment Variables:") - - for _, e := range os.Environ() { - fmt.Println(e) - } + checkInstallTiller() } @@ -113,13 +106,31 @@ func checkLocalSetup() bool { func checkInstalled(app string) error { path, err := exec.LookPath(app) if err != nil { - color.New(color.FgYellow).PrintfFunc()("%s is not installed\n", app) + color.New(color.FgYellow).Printf("%s is not installed\n", app) } else { fmt.Printf("%s: %s\n", app, path) } return err } +func fixKubectlContext() { + color.Blue("setting kubectl context.") + + _, err := exec.LookPath("kubectx") + if err != nil { + color.New(color.FgYellow).Printf("kubectx is not installed") + } else { + environment.RunRequired(true, "kubectx", "minikube") + } + + _, err = exec.LookPath("kubens") + if err != nil { + color.New(color.FgYellow).Printf("kubens is not installed") + } else { + environment.RunRequired(true, "kubens", "default") + } +} + func checkInstallTiller() { color.Blue("Checking if Tiller is installed.") @@ -133,15 +144,18 @@ func checkInstallTiller() { time.Sleep(10 * time.Second) serverVersionOutput, err = environment.RunAndGetOutput("helm", "version", "--server", "--short") + if err != nil { + color.Red("Cannot determin helm server version") + } } clientVersion := strings.TrimPrefix(clientVersionOutput[0], "Client: ") serverVersion := strings.TrimPrefix(serverVersionOutput[0], "Server: ") if clientVersion != serverVersion { - warn := color.New(color.FgYellow).PrintFunc() - warn("Helm version mismatch between client and server") - warn("Client Version: %s\n", clientVersion) - warn("Server Version: %s\n", serverVersion) + warn := color.New(color.FgYellow).PrintfFunc() + warn("Helm version mismatch between client and server\n") + warn(" Client Version: %s\n", clientVersion) + warn(" Server Version: %s\n", serverVersion) } } diff --git a/config/config.go b/config/config.go index 5e6423278cf7c2ebea75d008bdb8e9e5fcfb0750..9112c945184f5c4b53fd742e8adaa7253738f72f 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,8 @@ package config import ( "encoding/json" "io/ioutil" + + "gopkg.in/yaml.v2" ) type ( @@ -20,6 +22,9 @@ type ( ChartVersion string `json:"chartVersion"` LocalConfig string `json:"localConfig"` } + HelmValues struct { + EnvironmentVariables map[string]string `yaml:"environmentVariables"` + } ) func (config Config) Write(filename string) error { @@ -40,3 +45,14 @@ func New(filename string) (Config, error) { err = json.Unmarshal(data, &config) return config, err } + +func ReadHelmValues(filename string) (HelmValues, error) { + data, err := ioutil.ReadFile(filename) + var helmValues HelmValues + if err != nil { + return helmValues, err + } + + err = yaml.Unmarshal([]byte(data), &helmValues) + return helmValues, err +} diff --git a/go.mod b/go.mod index ca513bdab48a0f9a50b3f5d3029aa34ea43bcbb3..88d1010e2edfd9f673bd9562fc34dd2e6c9e862c 100644 --- a/go.mod +++ b/go.mod @@ -17,4 +17,5 @@ require ( github.com/spf13/viper v1.3.2 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect + gopkg.in/yaml.v2 v2.2.2 )