diff --git a/cmd/event.go b/cmd/event.go deleted file mode 100644 index f956914..0000000 --- a/cmd/event.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmd - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" -) - -func init() { - eventCmd.Flags().StringVarP(&environment, "environment", "e", "", "Environmnent UUID") - eventCmd.Flags().StringVarP(&service, "service", "s", "", "Service UUID") - eventCmd.Flags().StringVarP(&payload, "payload", "p", "", "Any additional metadata to include in the change event") - eventCmd.Flags().StringVarP(&identities, "identities", "i", "", "Change identities to allow for explicitly linking change events, one or more k=v, comma separated") - eventCmd.Flags().StringVarP(&revision, "revision", "r", "", "Git revision") -} - -var eventCmd = &cobra.Command{ - Use: "event", - Short: "Submit change event data", - Long: `Submit change events and other metadata to the FireHydrant API.`, - RunE: event, -} - -func event(cmd *cobra.Command, args []string) error { - setupClient() - ce := NewChangeEvent() - - ce.Summary = strings.Join(args, " ") - ce.Environment = environment - ce.Service = service - ce.RawIdentities = identities - - id, err := ce.Submit() - if err != nil { - return err - } - - fmt.Println(fmt.Sprintf("Created event %s", id)) - - return nil -} - -func init() { - rootCmd.AddCommand(eventCmd) -} diff --git a/cmd/execute.go b/cmd/execute.go deleted file mode 100644 index 5e994b5..0000000 --- a/cmd/execute.go +++ /dev/null @@ -1,75 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/spf13/cobra" -) - -func init() { - executeCmd.Flags().StringVarP(&environment, "environment", "e", "", "Environmnent UUID") - executeCmd.Flags().StringVarP(&service, "service", "s", "", "Service UUID") - executeCmd.Flags().StringVarP(&payload, "payload", "p", "", "Any additional metadata to include in the change execute") - executeCmd.Flags().StringVarP(&identities, "identities", "i", "", "Change identities to allow for explicitly linking change executes, one or more k=v, comma separated") - executeCmd.Flags().StringVarP(&revision, "revision", "r", "", "Git revision") -} - -var executeCmd = &cobra.Command{ - Use: "execute [flags] [command]", - Short: "Execute command and submit change data", - Long: `Executes a command, monitors its duration and submits the change event to the FireHydrant API.`, - RunE: execute, -} - -func execute(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("No command passed") - } - - setupClient() - - ce := NewChangeEvent() - ce.Summary = strings.Join(args, " ") - ce.Environment = environment - ce.Service = service - ce.RawIdentities = identities - - command, flags := args[0], args[1:] - - fmt.Println(fmt.Sprintf("Executing command %s with args %s", command, strings.Join(flags, " "))) - cmdExec := exec.Command(command, flags...) - - // We don't actually want to capture this; pass it through to the calling shell - cmdExec.Stdout = os.Stdout - cmdExec.Stderr = os.Stderr - ce.StartsAt = time.Now() - err := cmdExec.Run() - duration := time.Since(ce.StartsAt) / time.Millisecond - ce.EndsAt = time.Now() - - // getting the exit code is a pita so i'm not doing it yet - if err != nil { - ce.Identities["error"] = err.Error() - fmt.Println(fmt.Sprintf("Executed command, duration %dms, error: %s", duration, err)) - } else { - fmt.Println(fmt.Sprintf("Executed command, duration %dms", duration)) - } - - id, err := ce.Submit() - if err != nil { - return err - } - - fmt.Println(fmt.Sprintf("Created event %s", id)) - - return nil -} - -func init() { - rootCmd.AddCommand(executeCmd) -} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..9e1dda7 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "log" + "os" + + "github.com/firehydrant/fhcli/pkg/cli" +) + +func main() { + app := cli.NewApp() + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } + +} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 898c3c2..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,74 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - fhclient "github.com/firehydrant/api-client-go/client" - runtime "github.com/go-openapi/runtime" - transport "github.com/go-openapi/runtime/client" - - "github.com/go-openapi/strfmt" - "github.com/spf13/cobra" -) - -type apiClient struct { - transport *transport.Runtime - client *fhclient.FireHydrant - auth runtime.ClientAuthInfoWriter -} - -var apiKey string -var apiHost string -var debug bool -var ignoreErrors bool -var client apiClient - -var environment string -var service string -var payload string -var identities string -var revision string - -func init() { - rootCmd.PersistentFlags().StringVar(&apiKey, "api-key", "", "API key") - rootCmd.PersistentFlags().StringVar(&apiHost, "api-host", "", "API hostname (default: api.firehydrant.io") - rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Debug HTTP requests") - rootCmd.PersistentFlags().BoolVarP(&ignoreErrors, "ignore-errors", "x", true, "Ignore errors from FireHydrant API, exit 0") -} - -var rootCmd = &cobra.Command{ - Use: "fhcli", - Short: "fhcli is the command line client for the FireHydrant API", - Long: `Submit change events and other metadata to the FireHydrant API. - -API Documentation: https://apidocs.firehydrant.io`, - Run: func(cmd *cobra.Command, args []string) {}, -} - -// Execute the base command -func Execute() { - - rootCmd.SilenceUsage = ignoreErrors - rootCmd.SilenceErrors = ignoreErrors - - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - - if ignoreErrors { - os.Exit(0) - } else { - os.Exit(1) - } - } -} - -func setupClient() error { - client.transport = transport.New(apiHost, "", []string{"https"}) - client.transport.Debug = debug - - client.client = fhclient.New(client.transport, strfmt.Default) - client.auth = transport.BearerToken(apiKey) - - return nil -} diff --git a/cmd/version.go b/cmd/version.go deleted file mode 100644 index d3fb692..0000000 --- a/cmd/version.go +++ /dev/null @@ -1,23 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var cliRevision string -var releaseVersion string - -func init() { - rootCmd.AddCommand(versionCmd) -} - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of FireHydrant CLI", - Run: func(cmd *cobra.Command, args []string) { - versionString := fmt.Sprintf("FireHydrant API CLI v%s - %s", releaseVersion, cliRevision) - fmt.Println(versionString) - }, -} diff --git a/go.mod b/go.mod index dcdba09..f218a12 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,5 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.3.1 + github.com/urfave/cli v1.20.0 ) diff --git a/go.sum b/go.sum index ec7b6e0..a22ea7d 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,8 @@ github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/main.go b/main.go deleted file mode 100644 index aefbeb2..0000000 --- a/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/firehydrant/fhcli/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/pkg/api_client/main.go b/pkg/api_client/main.go new file mode 100644 index 0000000..84b245f --- /dev/null +++ b/pkg/api_client/main.go @@ -0,0 +1,33 @@ +package api_client + +import ( + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + fhclient "github.com/firehydrant/api-client-go/client" + transport "github.com/go-openapi/runtime/client" +) + +type Config struct { + ApiHost string + ApiKey string + Debug bool +} + +type ApiClient struct { + transport *transport.Runtime + Client *fhclient.FireHydrant + Auth runtime.ClientAuthInfoWriter +} + +func NewApiClient(c Config) ApiClient { + client := ApiClient{} + + client.transport = transport.New(c.ApiHost, "", []string{"http"}) + client.transport.Debug = c.Debug + + client.Client = fhclient.New(client.transport, strfmt.Default) + client.Auth = transport.BearerToken(c.ApiKey) + + return client +} diff --git a/cmd/change_event.go b/pkg/change_events/main.go similarity index 75% rename from cmd/change_event.go rename to pkg/change_events/main.go index b4ca175..dd41177 100644 --- a/cmd/change_event.go +++ b/pkg/change_events/main.go @@ -1,9 +1,11 @@ -package cmd +package events import ( "strings" "time" + apiclient "github.com/firehydrant/fhcli/pkg/api_client" + "github.com/firehydrant/api-client-go/client/changes" "github.com/firehydrant/api-client-go/models" "github.com/go-openapi/strfmt" @@ -13,7 +15,9 @@ type ChangeEvent struct { Environment string Service string RawIdentities string + RawLabels string Identities map[string]string + Labels map[string]string StartsAt time.Time EndsAt time.Time Summary string @@ -22,6 +26,7 @@ type ChangeEvent struct { func NewChangeEvent() ChangeEvent { return ChangeEvent{ Identities: make(map[string]string), + Labels: make(map[string]string), StartsAt: time.Now(), EndsAt: time.Now(), } @@ -49,9 +54,19 @@ func (ce *ChangeEvent) parseIdentities() { } } -func (ce *ChangeEvent) Submit() (string, error) { +func (ce *ChangeEvent) parseLabels() { + for _, label := range strings.Split(ce.RawLabels, ",") { + lParts := strings.Split(label, "=") + if len(lParts) > 1 { + ce.Labels[lParts[0]] = lParts[1] + } + } +} + +func (ce *ChangeEvent) Submit(client apiclient.ApiClient) (string, error) { c := changes.NewPostV1ChangesEventsParams() ce.parseIdentities() + ce.parseLabels() envList := []string{} if len(ce.Environment) > 0 { @@ -70,9 +85,10 @@ func (ce *ChangeEvent) Submit() (string, error) { EndsAt: strfmt.DateTime(ce.EndsAt), Summary: &ce.Summary, ChangeIdentities: ce.identitiesToAPI(), + Labels: ce.Labels, } - resp, err := client.client.Changes.PostV1ChangesEvents(c, client.auth) + resp, err := client.Client.Changes.PostV1ChangesEvents(c, client.Auth) if err != nil { return "", err diff --git a/pkg/cli/event.go b/pkg/cli/event.go new file mode 100644 index 0000000..a1ef6af --- /dev/null +++ b/pkg/cli/event.go @@ -0,0 +1,30 @@ +package cli + +import ( + "fmt" + "strings" + + changeevents "github.com/firehydrant/fhcli/pkg/change_events" + "github.com/urfave/cli" +) + +func eventCmd(c *cli.Context) error { + client := NewApiClient(c) + + ce := changeevents.NewChangeEvent() + + ce.Summary = strings.Join(c.Args(), " ") + ce.Environment = c.String("environment") + ce.Service = c.String("service") + ce.RawIdentities = c.String("identities") + ce.RawLabels = c.String("labels") + + id, err := ce.Submit(client) + if err != nil { + return err + } + + fmt.Println(fmt.Sprintf("Created event %s", id)) + + return nil +} diff --git a/pkg/cli/execute.go b/pkg/cli/execute.go new file mode 100644 index 0000000..2f9e6d9 --- /dev/null +++ b/pkg/cli/execute.go @@ -0,0 +1,59 @@ +package cli + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + "time" + + changeevents "github.com/firehydrant/fhcli/pkg/change_events" + "github.com/urfave/cli" +) + +func executeCmd(c *cli.Context) error { + client := NewApiClient(c) + + if len(c.Args()) < 1 { + return errors.New("No command passed") + } + + ce := changeevents.NewChangeEvent() + + ce.Summary = strings.Join(c.Args(), " ") + ce.Environment = c.String("environment") + ce.Service = c.String("service") + ce.RawIdentities = c.String("identities") + ce.RawLabels = c.String("labels") + + command, flags := c.Args()[0], c.Args()[1:] + + fmt.Println(fmt.Sprintf("Executing command %s with args %s", command, strings.Join(flags, " "))) + cmdExec := exec.Command(command, flags...) + + // We don't actually want to capture this; pass it through to the calling shell + cmdExec.Stdout = os.Stdout + cmdExec.Stderr = os.Stderr + ce.StartsAt = time.Now() + err := cmdExec.Run() + duration := time.Since(ce.StartsAt) / time.Millisecond + ce.EndsAt = time.Now() + + // getting the exit code is a pita so i'm not doing it yet + if err != nil { + ce.Identities["error"] = err.Error() + fmt.Println(fmt.Sprintf("Executed command, duration %dms, error: %s", duration, err)) + } else { + fmt.Println(fmt.Sprintf("Executed command, duration %dms", duration)) + } + + id, err := ce.Submit(client) + if err != nil { + return err + } + + fmt.Println(fmt.Sprintf("Created event %s", id)) + + return nil +} diff --git a/pkg/cli/main.go b/pkg/cli/main.go new file mode 100644 index 0000000..650b205 --- /dev/null +++ b/pkg/cli/main.go @@ -0,0 +1,82 @@ +package cli + +import ( + apiclient "github.com/firehydrant/fhcli/pkg/api_client" + "github.com/urfave/cli" +) + +var sharedFlags = []cli.Flag{ + cli.StringFlag{ + Name: "identities, i", + Usage: "identities for the event, comma separated. example: image=us.gcr.io/firehydrant/rails:abcdef0", + EnvVar: "FH_IDENTITIES", + }, + cli.StringFlag{ + Name: "labels", + Usage: "labels for the event, comma separated. example: executor=jenkins", + EnvVar: "FH_LABELS", + }, + cli.StringFlag{ + Name: "environment", + Usage: "environment for the event, example: production", + EnvVar: "FH_ENVIRONMENT", + }, + cli.StringFlag{ + Name: "service", + Usage: "service for the event, example: rails-monolith", + EnvVar: "FH_SERVICE", + }, +} + +func NewApp() *cli.App { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "api-key, k", + Usage: "firehydrant.io API Key", + EnvVar: "FH_API_KEY", + }, + cli.StringFlag{ + Name: "api-host, a", + Usage: "firehydrant.io API hostname", + Value: "api.firehydrant.io", + EnvVar: "FH_API_HOST", + }, + cli.BoolTFlag{ + Name: "debug, d", + Usage: "Enable debug output for API calls", + }, + cli.BoolTFlag{ + Name: "ignore-errors, x", + Usage: "exit 0 on errors from FH API (default)", + }, + } + + app.Commands = []cli.Command{ + { + Name: "event", + Usage: "submit an event to the FireHydrant API", + Action: eventCmd, + Flags: sharedFlags, + }, + { + Name: "execute", + Usage: "execute a command and submit metrics to the FireHydrant API", + Action: executeCmd, + Flags: sharedFlags, + }, + } + + return app +} + +func NewApiClient(c *cli.Context) apiclient.ApiClient { + config := apiclient.Config{ + ApiHost: c.GlobalString("api-host"), + ApiKey: c.GlobalString("api-key"), + Debug: c.GlobalBool("debug"), + } + + return apiclient.NewApiClient(config) +}