diff --git a/.gitignore b/.gitignore index 700353b37dd8e751d23fb3aaf994d5db4f2ec619..dd0a0fbe4a5b88cd3a78b553b5b2fdc6dd3804de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .go-project.mk devtool .devtool.config +.vscode diff --git a/cmd/build.go b/cmd/build.go index d8dd4acbb843ce3976340c795e55c747e58210fc..073812c2747fdc9b7faaadc4e84280c745539d30 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -33,7 +33,11 @@ var buildCmd = &cobra.Command{ Use: "build", Short: "build the application", Run: func(cmd *cobra.Command, args []string) { - checkLocalSetup() + prereqsInstalled := checkLocalSetup() + if !prereqsInstalled { + color.Red("Please install the required applications and try again.\n") + return + } err := environment.Run(false, "minikube", "status") if err != nil { @@ -108,6 +112,13 @@ func init() { panic("invalid arg") } + buildCmd.Flags().Bool("dry-run", false, "Use the helm template command to output k8s objects") + viper.SetDefault("build.helm.dry.run", false) + err = viper.BindPFlag("build.helm.dry.run", buildCmd.Flags().Lookup("dry-run")) + if err != nil { + panic("invalid arg") + } + } func build(config config.Config) error { @@ -129,6 +140,8 @@ func build(config config.Config) error { fallthrough case "go": environment.RunRequired(true, "make", "build") + case "angular": + environment.RunRequired(true, "yarn", "build:prod") default: color.New(color.FgRed).Printf("devtool doesn't support the '%s' language yet.\n", lang) return errors.New("Language not supported") @@ -155,32 +168,35 @@ func buildDockerAndDeploy(config config.Config) error { color.Blue("Building Docker Containers") for _, deployable := range config.Deployables { - dockerfile := deployable.Dockerfile - image := deployable.Name - tag, err := uuid.NewRandom() - if err != nil { - color.Red("Failed to generate UUID for docker tag") - return err - } - - dockerTag := image + ":" + tag.String() - - 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 + var image string + var tag string + var err error + if deployable.Dockerfile != "" { + image, tag, err = buildDockerImage(deployable) + if err != nil { + color.New(color.FgRed).Printf("Failed to build docker image: %s", err.Error()) + return err + } + } else if deployable.Image != "" { + image = deployable.Image + tag = deployable.ImageTag + if tag == "" { + tag = "latest" + } + } else { + color.New(color.FgYellow).Printf("Deployable: %s does not have a valid Docker configuration", deployable.Name) + continue } color.Blue("Installing Chart") releaseName := deployable.Name + "-local" args := []string{"upgrade", releaseName, "--install", deployable.Chart, "--version", deployable.ChartVersion} + + if viper.GetBool("build.helm.dry.run") { + args = append(args, "--dry-run", "--debug") + } + if len(config.LocalEnvVars) > 0 { for _, key := range config.LocalEnvVars { args = append(args, "--set", "environmentVariables."+key+"="+os.Getenv(key)) @@ -197,7 +213,7 @@ func buildDockerAndDeploy(config config.Config) error { } args = append(args, "--set", "image.repository="+image) - args = append(args, "--set", "image.tag="+tag.String()) + args = append(args, "--set", "image.tag="+tag) args = append(args, "-f", deployable.LocalConfig) err = environment.Run(true, "helm", args...) @@ -215,3 +231,28 @@ func buildDockerAndDeploy(config config.Config) error { } return nil } + +func buildDockerImage(deployable config.Deployable) (string, string, error) { + dockerfile := deployable.Dockerfile + image := deployable.Name + tag, err := uuid.NewRandom() + if err != nil { + color.Red("Failed to generate UUID for docker tag") + return "", "", err + } + + dockerTag := image + ":" + tag.String() + + 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 + } + return image, tag.String(), nil +} diff --git a/cmd/ci.go b/cmd/ci.go index 4db1cd763cb9f02c749aff0c3a4eff2f6c2a4708..800ae41adb61588f0c974b26f5d71249aad35bef 100644 --- a/cmd/ci.go +++ b/cmd/ci.go @@ -18,6 +18,7 @@ import ( "os" "git.psu.edu/k8s/devtool/config" + "git.psu.edu/k8s/devtool/environment" "github.com/fatih/color" "github.com/spf13/cobra" @@ -30,9 +31,9 @@ var environmentSuffix string var ciCmd = &cobra.Command{ Use: "ci", Short: "Build tools for the CI system", - // Run: func(cmd *cobra.Command, args []string) { - // fmt.Println("config called") - // }, + Run: func(cmd *cobra.Command, args []string) { + cmd.HelpFunc()(cmd, args) + }, } func init() { @@ -69,3 +70,8 @@ func getCiConfiguration() config.Config { } return conf } + +func helmUpdate() { + environment.RunRequired(false, "helm", "repo", "update") + +} diff --git a/cmd/ci_build.go b/cmd/ci_build.go index ccdf9c9d8f3ffa7d4eb832190e483618068770ab..420c49299d34afc314a0fdde9d878fc8018fda41 100644 --- a/cmd/ci_build.go +++ b/cmd/ci_build.go @@ -83,6 +83,11 @@ func buildCiDocker(config config.Config) error { dockerfile := deployable.Dockerfile image := deployable.Name + if dockerfile == "" { + color.New(color.FgYellow).Printf("Deployable %s has no Dockerfile, nothing to build.", deployable.Name) + continue + } + dockerImage := dockerRegistry + "/" + dockerRegistryNamespace + "/" + image dockerTag := dockerImage + ":" + imageTag diff --git a/cmd/ci_deploy.go b/cmd/ci_deploy.go index 329ece014de402719c45792264e8e3206ce8d002..d59104759da682a2781923fad2e206311d329fed 100644 --- a/cmd/ci_deploy.go +++ b/cmd/ci_deploy.go @@ -59,6 +59,11 @@ func fluxDeployCi(config config.Config) error { for _, deployable := range config.Deployables { image := deployable.Name + if deployable.Dockerfile == "" { + color.New(color.FgYellow).Printf("Deployable %s has no Dockerfile, nothing to deploy.", deployable.Name) + continue + } + var releaseName string if environmentSuffix == "" { releaseName = deployable.Name diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000000000000000000000000000000000000..a3b4cbb7dde2a25407158d9f4017fce82c2251b4 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,60 @@ +// 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" + + "github.com/fatih/color" + "github.com/spf13/cobra" +) + +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion", + Short: "Generates bash completion scripts", + Long: `To load completion run + + source <(devtool completion) + + To configure your bash shell to load completions for each session add to your bashrc + + # ~/.bashrc or ~/.profile + source <(devtool completion) + `, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Do Nothing + }, + Run: func(cmd *cobra.Command, args []string) { + err := rootCmd.GenBashCompletion(os.Stdout) + if err != nil { + color.Red("Failed to generate bash completion script") + } + }, +} + +func init() { + rootCmd.AddCommand(completionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // completionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/config.go b/cmd/config.go index 313b7daa2e2863e743441e078d3214f5df5b9451..dbf2ea5af37c27364bc39f50756a2bcfa8dad712 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -22,9 +22,9 @@ import ( var configCmd = &cobra.Command{ Use: "config", Short: "Manage the projects build configuration", - // Run: func(cmd *cobra.Command, args []string) { - // fmt.Println("config called") - // }, + Run: func(cmd *cobra.Command, args []string) { + cmd.HelpFunc()(cmd, args) + }, } func init() { diff --git a/cmd/config_add.go b/cmd/config_add.go index 19e3a6ff9933cb657bf24f403898f9070c5716a3..75a65ad17b21cef7e12e8e887771d2af93bfd28c 100644 --- a/cmd/config_add.go +++ b/cmd/config_add.go @@ -15,7 +15,6 @@ package cmd import ( - "fmt" "strings" "git.psu.edu/k8s/devtool/config" @@ -29,18 +28,19 @@ var addCmd = &cobra.Command{ Use: "add", Short: "adds a deployable unit to the project configuration", Run: func(cmd *cobra.Command, args []string) { - conf, err := config.New(configFile) if err != nil { color.Red("Configuration doesn't exist, please run 'devtool config init' instead.") return } + helmUpdate() + deployables := conf.Deployables dockerfiles := findDockerfiles() - for _, file := range dockerfiles { - fmt.Printf("Found Dockerfile: %s\n", file) + foundAny := false + for _, file := range dockerfiles { found := false for _, deployable := range deployables { if deployable.Dockerfile == file { @@ -52,6 +52,8 @@ var addCmd = &cobra.Command{ if !found { color.New(color.FgYellow).Printf("Found Dockerfile: %s\n", file) + foundAny = true + prompt := promptui.Prompt{ Label: "Include in config", Default: "Yes", @@ -85,6 +87,9 @@ var addCmd = &cobra.Command{ } } } + if !foundAny { + color.Red("Failed to find any new Dockerfiles") + } }, } diff --git a/cmd/config_init.go b/cmd/config_init.go index 79e140c4e7d20e03af2796e98537fb6cd6c0aa69..7f35edd9f183c7b6ae7a5c66851778762b01abce 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -15,7 +15,6 @@ package cmd import ( - "fmt" "io/ioutil" "log" "os" @@ -52,15 +51,14 @@ var initCmd = &cobra.Command{ } conf.Deployables = []config.Deployable{} + helmUpdate() + defaultName := conf.Name if defaultName == "" { wd, _ := os.Getwd() - fmt.Println(wd) if idx := strings.LastIndex(wd, "/"); idx != -1 { - fmt.Println(idx) idx++ defaultName = wd[idx:] - fmt.Println(defaultName) } } diff --git a/cmd/root.go b/cmd/root.go index 908c93650d55234736622a82f4cf70756df54c91..90b58d64ddf8ac8c72d01352b37ebeef0cb6de5f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,7 +38,12 @@ var rootCmd = &cobra.Command{ Long: `A tool used by software engineering to build applications.`, // Uncomment the following line if your bare application // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Run: func(cmd *cobra.Command, args []string) { + cmd.HelpFunc()(cmd, args) + }, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + PrintLogo() + }, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -86,11 +91,11 @@ func initConfig() { viper.SetEnvKeyReplacer(replacer) // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } else { - color.Red(err.Error()) - } + // if err := viper.ReadInConfig(); err == nil { + // fmt.Println("Using config file:", viper.ConfigFileUsed()) + // } else { + // color.Red(err.Error()) + // } } func PrintLogo() { diff --git a/cmd/run.go b/cmd/run.go index b9f451945f66b5813930631af358a3a5621619f7..0dce6730fc6736f81a3909a4570fe320c17cb42f 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -26,6 +26,7 @@ import ( ) var command string +var envFile string // runCmd represents the run command var runCmd = &cobra.Command{ @@ -39,13 +40,21 @@ var runCmd = &cobra.Command{ color.New(color.FgYellow).Printf("WARNING: no project configuration exists: %s\n", configFile) } + checkEnvVars(conf) + localConfig := selectLocalConfig(conf.Deployables) + if _, err := os.Stat(localConfig); localConfig != "" && os.IsNotExist(err) { + color.New(color.FgYellow).Printf("WARN: Configuration file: '%s' doesn't exist\n", localConfig) + return + } + 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) + return } for k, v := range helmValues.EnvironmentVariables { os.Setenv(k, v) @@ -74,17 +83,25 @@ func init() { if err != nil { panic("invalid arg") } + + runCmd.Flags().StringVarP(&envFile, "env", "e", "", "Config YAML file with environmentVariables") + } func selectLocalConfig(deployables []config.Deployable) string { + if envFile != "" { + return envFile + } + if len(deployables) == 0 { + color.Yellow("WARN: This project has no deployables, please specify one with the --env param.") return "" } - //if len(deployables) == 1 { - return deployables[0].LocalConfig - //} - - //TODO add support for multiple deployables (prompt user and add arg) + if len(deployables) == 1 { + return deployables[0].LocalConfig + } + color.Yellow("WARN: This project has multiple deployables, please specify one with the --env param.") + return "" } diff --git a/config/config.go b/config/config.go index 9112c945184f5c4b53fd742e8adaa7253738f72f..255cf642121c60dba29cf524859c630e999d5eb6 100644 --- a/config/config.go +++ b/config/config.go @@ -18,6 +18,8 @@ type ( Deployable struct { Name string `json:"name"` Dockerfile string `json:"dockerfile"` + Image string `json:"image"` + ImageTag string `json:"imageTag"` Chart string `json:"chart"` ChartVersion string `json:"chartVersion"` LocalConfig string `json:"localConfig"` diff --git a/devtool.go b/devtool.go index ba3588e5d4a821abfbe1dacf63e0e1a80eaf1625..9d25080a9694c07c880355586b34740128fcbc98 100644 --- a/devtool.go +++ b/devtool.go @@ -3,6 +3,5 @@ package main import "git.psu.edu/k8s/devtool/cmd" func main() { - cmd.PrintLogo() cmd.Execute() }