diff --git a/app.go b/app.go new file mode 100644 index 0000000..fe5b504 --- /dev/null +++ b/app.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/cidverse/normalizeci/cmd" + "github.com/rs/zerolog/log" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" + status = "clean" +) + +// Init Hook +func init() { + // Set Version Information + cmd.Version = version + cmd.CommitHash = commit + cmd.BuildAt = date + cmd.RepositoryStatus = status +} + +// CLI Main Entrypoint +func main() { + rootCommand := cmd.RootCmd() + cmdErr := rootCommand.Execute() + if cmdErr != nil { + log.Fatal().Err(cmdErr).Msg("cli error") + } +} diff --git a/cmd/denormalize.go b/cmd/denormalize.go new file mode 100644 index 0000000..947867c --- /dev/null +++ b/cmd/denormalize.go @@ -0,0 +1,80 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/cidverse/normalizeci/pkg/normalizer" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +func denormalizeCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "denormalize", + Short: "denormalizes information about the current CI environment", + Run: func(cmd *cobra.Command, args []string) { + format, _ := cmd.Flags().GetString("format") + outputFile, _ := cmd.Flags().GetString("output") + strict, _ := cmd.Flags().GetBool("strict") + targets, _ := cmd.Flags().GetStringArray("target") + + // run normalization + var normalized, err = normalizer.Normalize() + if err != nil { + log.Fatal().Err(err).Msg("normalization failed") + } + + // output + outputEnv := make(map[string]string) + + // targets + if len(targets) > 0 { + for _, target := range targets { + denormalized, err := normalizer.Denormalize(target, normalized) + if err != nil { + log.Fatal().Err(err).Str("target", target).Msg("denormalization failed") + } + + for key, value := range denormalized { + outputEnv[key] = value + } + } + } + + // set process env + normalizer.SetProcessEnvironment(outputEnv) + + // content? + content, err := normalizer.FormatEnvironment(outputEnv, format) + if err != nil { + log.Fatal().Str("format", format).Str("supported", "export,powershell,cmd").Msg("unsupported format!") + } + + // validate? + if strict { + errors := normalized.Validate() + if len(errors) > 0 { + for _, line := range errors { + fmt.Printf("%s: %s [%s]\n", line.Field, line.Description, line.Value) + } + os.Exit(1) + } + } + + // output + if len(outputFile) > 0 { + fileOutput(outputFile, content) + } else { + consoleOutput(content) + } + }, + } + + cmd.PersistentFlags().StringP("format", "f", normalizer.GetDefaultFormat(), "The format in which to store the normalized variables. (export, powershell, cmd)") + cmd.PersistentFlags().StringP("output", "o", "", "Write output to this file instead of writing it to stdout.") + cmd.PersistentFlags().Bool("strict", false, "Validate the generated variables against the spec and fail on errors?") + cmd.PersistentFlags().StringArrayP("target", "t", []string{}, "Additionally generates the environment for the target ci services") + + return cmd +} diff --git a/cmd/normalize.go b/cmd/normalize.go new file mode 100644 index 0000000..57b88e4 --- /dev/null +++ b/cmd/normalize.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/cidverse/normalizeci/pkg/envstruct" + "github.com/cidverse/normalizeci/pkg/normalizer" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +func normalizeCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "normalize", + Short: "normalizes information about the current CI environment", + Run: func(cmd *cobra.Command, args []string) { + format, _ := cmd.Flags().GetString("format") + outputFile, _ := cmd.Flags().GetString("output") + strict, _ := cmd.Flags().GetBool("strict") + targets, _ := cmd.Flags().GetStringArray("target") + + // run normalization + var normalized, err = normalizer.Normalize() + if err != nil { + log.Fatal().Err(err).Msg("normalization failed") + } + + // transform to map + outputEnv := envstruct.StructToEnvMap(normalized) + + // targets + if len(targets) > 0 { + for _, target := range targets { + denormalized, err := normalizer.Denormalize(target, normalized) + if err != nil { + log.Fatal().Err(err).Str("target", target).Msg("denormalization failed") + } + + for key, value := range denormalized { + outputEnv[key] = value + } + } + } + + // set process env + normalizer.SetProcessEnvironment(outputEnv) + + // format content + content, err := normalizer.FormatEnvironment(outputEnv, format) + if err != nil { + log.Fatal().Str("format", format).Str("supported", "export,powershell,cmd").Msg("unsupported format!") + } + + // validate? + if strict { + errors := normalized.Validate() + if len(errors) > 0 { + for _, line := range errors { + fmt.Printf("%s: %s [%s]\n", line.Field, line.Description, line.Value) + } + os.Exit(1) + } + } + + // output + if len(outputFile) > 0 { + fileOutput(outputFile, content) + } else { + consoleOutput(content) + } + }, + } + + cmd.PersistentFlags().StringP("format", "f", normalizer.GetDefaultFormat(), "The format in which to store the normalized variables. (export, powershell, cmd)") + cmd.PersistentFlags().StringP("output", "o", "", "Write output to this file instead of writing it to stdout.") + cmd.PersistentFlags().Bool("strict", false, "Validate the generated variables against the spec and fail on errors?") + cmd.PersistentFlags().BoolP("version", "v", false, "all software has versions, this prints version information for normalizeci") + cmd.PersistentFlags().StringArrayP("target", "t", []string{}, "Additionally generates the environment for the target ci services") + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..657364e --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "os" + "strings" + + "github.com/cidverse/cidverseutils/zerologconfig" + "github.com/spf13/cobra" +) + +var cfg zerologconfig.LogConfig + +func RootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: `normalizeci`, + Short: `normalizeci provides a foundation for platform-agnostic CI-CD processes.`, + Long: `normalizeci provides a foundation for platform-agnostic CI-CD processes.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if cfg.LogLevel == "" { + if os.Getenv("NCI_LOG_LEVEL") != "" { + cfg.LogLevel = os.Getenv("NCI_LOG_LEVEL") + } else if os.Getenv("NCI_DEBUG") == "true" { // legacy 0.x toggle for debug mode + cfg.LogLevel = "trace" + } + } + + zerologconfig.Configure(cfg) + }, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + os.Exit(0) + }, + } + + cmd.PersistentFlags().StringVar(&cfg.LogLevel, "log-level", "info", "log level - allowed: "+strings.Join(zerologconfig.ValidLogLevels, ",")) + cmd.PersistentFlags().StringVar(&cfg.LogFormat, "log-format", "color", "log format - allowed: "+strings.Join(zerologconfig.ValidLogFormats, ",")) + cmd.PersistentFlags().BoolVar(&cfg.LogCaller, "log-caller", false, "include caller in log functions") + + cmd.AddCommand(versionCmd()) + cmd.AddCommand(normalizeCmd()) + cmd.AddCommand(denormalizeCmd()) + + return cmd +} diff --git a/cmd/util.go b/cmd/util.go new file mode 100644 index 0000000..c32b977 --- /dev/null +++ b/cmd/util.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "io" + "os" + + "github.com/rs/zerolog/log" +) + +func fileOutput(file string, content string) { + contentByteArray := []byte(content) + err := os.WriteFile(file, contentByteArray, 0644) + if err != nil { + log.Err(err).Str("file", file).Msg("failed to generate file") + } +} + +func consoleOutput(content string) { + _, err := io.WriteString(os.Stdout, content) + if err != nil { + log.Err(err).Msg("failed to write content to stdout") + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..b923435 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + "os" + "runtime" + + "github.com/spf13/cobra" +) + +// Version will be set at build time +var Version string + +// RepositoryStatus will be set at build time +var RepositoryStatus string + +// CommitHash will be set at build time +var CommitHash string + +// BuildAt will be set at build time +var BuildAt string + +func versionCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: "print version information", + Run: func(cmd *cobra.Command, args []string) { + _, _ = fmt.Fprintf(os.Stdout, "GitVersion: %s\n", Version) + _, _ = fmt.Fprintf(os.Stdout, "GitCommit: %s\n", CommitHash) + _, _ = fmt.Fprintf(os.Stdout, "GitTreeState: %s\n", RepositoryStatus) + _, _ = fmt.Fprintf(os.Stdout, "BuildDate: %s\n", BuildAt) + _, _ = fmt.Fprintf(os.Stdout, "GoVersion: %s\n", runtime.Version()) + _, _ = fmt.Fprintf(os.Stdout, "Compiler: %s\n", runtime.Compiler) + _, _ = fmt.Fprintf(os.Stdout, "Platform: %s\n", runtime.GOOS+"/"+runtime.GOARCH) + }, + } + + return cmd +} diff --git a/go.mod b/go.mod index a82498b..08196e1 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,15 @@ go 1.21 require ( github.com/bwmarrin/snowflake v0.3.0 - github.com/cidverse/go-vcs v0.0.0-20240628195958-27702663f242 + github.com/cidverse/cidverseutils/zerologconfig v0.1.0 + github.com/cidverse/go-vcs v0.0.0-20240727232508-6da7d96dca90 github.com/go-playground/validator/v10 v10.22.0 github.com/google/go-github/v63 v63.0.0 github.com/gosimple/slug v1.14.0 github.com/jarcoal/httpmock v1.3.1 github.com/rs/zerolog v1.33.0 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/xanzy/go-gitlab v0.107.0 golang.org/x/oauth2 v0.21.0 @@ -25,7 +27,7 @@ require ( github.com/cyphar/filepath-securejoin v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect @@ -36,6 +38,7 @@ require ( github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -45,6 +48,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.25.0 // indirect diff --git a/go.sum b/go.sum index a94bee8..a453434 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,15 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/cidverse/go-vcs v0.0.0-20240628195958-27702663f242 h1:WbROpjwLzkYze8lcbFQ8Zga9fC3RxLedss8h4uXyOvM= -github.com/cidverse/go-vcs v0.0.0-20240628195958-27702663f242/go.mod h1:Cvbn1Zx80b6rlt/t3CCEuQczd0t+jJjjR97MIG5SQr4= +github.com/cidverse/cidverseutils/zerologconfig v0.1.0 h1:iUJ9ANUCHH6IsM8MoeOv5Q8QvCd+iDl4X8QL/nfAdVw= +github.com/cidverse/cidverseutils/zerologconfig v0.1.0/go.mod h1:X3mGXDU1BtdGk818+a0UL6vzhU9lLb8/oEslSnVQzuA= +github.com/cidverse/go-vcs v0.0.0-20240727232508-6da7d96dca90 h1:BaGKGTOGrwU2Cyf9/pkxphKBbf8bkP7Lb6xV4Y2IwQ8= +github.com/cidverse/go-vcs v0.0.0-20240727232508-6da7d96dca90/go.mod h1:Cvbn1Zx80b6rlt/t3CCEuQczd0t+jJjjR97MIG5SQr4= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE= github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE= github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -31,8 +34,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -71,6 +74,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -107,6 +112,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -114,6 +120,10 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=