diff --git a/go.mod b/go.mod index ecfe1adda..731b90b77 100644 --- a/go.mod +++ b/go.mod @@ -38,10 +38,11 @@ require ( ) require ( + github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) @@ -77,3 +78,5 @@ require ( ) require 4d63.com/optional v0.2.0 + +replace github.com/fastly/go-fastly/v9 => github.com/kpfleming/go-fastly/v9 v9.12.1-0.20241217164724-8f6ebe66851a diff --git a/go.sum b/go.sum index 9d988a5f0..54392cc23 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj6 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 h1:S6Dco8FtAhEI/qkg/00H6RdEGC+MCy5GPiQ+xweNRFE= github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc= -github.com/fastly/go-fastly/v9 v9.12.0 h1:NUR4l+3LrSCux91sgKduFV8d/eejoGpQNlHa0ftKrFo= -github.com/fastly/go-fastly/v9 v9.12.0/go.mod h1:5w2jgJBZqQEebOwM/rRg7wutAcpDTziiMYWb/6qdM7U= github.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible h1:FhrXlfhgGCS+uc6YwyiFUt04alnjpoX7vgDKJxS6Qbk= github.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible/go.mod h1:U8UynVoU1SQaqD2I4ZqgYd5lx3A1ipQYn4aSt2Y5h6c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -67,6 +65,8 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/kpfleming/go-fastly/v9 v9.12.1-0.20241217164724-8f6ebe66851a h1:4NkDjmddTs2qY+41phkmIqV07XX4vDUZhLuOfZJtcXo= +github.com/kpfleming/go-fastly/v9 v9.12.1-0.20241217164724-8f6ebe66851a/go.mod h1:rB3T7CBBYBw+/W4rpzmZPev8BbARin6vriirVCY0yaw= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -91,6 +91,7 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/nicksnyder/go-i18n v1.10.3 h1:0U60fnLBNrLBVt8vb8Q67yKNs+gykbQuLsIkiesJL+w= github.com/nicksnyder/go-i18n v1.10.3/go.mod h1:hvLG5HTlZ4UfSuVLSRuX7JRUomIaoKQM19hm6f+no7o= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= @@ -128,8 +129,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4= github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= @@ -209,6 +210,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/app/run_test.go b/pkg/app/run_test.go index e5c83d8cd..42cfc3a54 100644 --- a/pkg/app/run_test.go +++ b/pkg/app/run_test.go @@ -79,6 +79,7 @@ kv-store-entry log-tail logging pops +product products profile purge diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index a9079d7aa..eae699054 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -51,6 +51,8 @@ import ( "github.com/fastly/cli/pkg/commands/logging/syslog" "github.com/fastly/cli/pkg/commands/logtail" "github.com/fastly/cli/pkg/commands/pop" + "github.com/fastly/cli/pkg/commands/product" + "github.com/fastly/cli/pkg/commands/product/bot_management" "github.com/fastly/cli/pkg/commands/products" "github.com/fastly/cli/pkg/commands/profile" "github.com/fastly/cli/pkg/commands/purge" @@ -356,6 +358,11 @@ func Define( loggingSyslogList := syslog.NewListCommand(loggingSyslogCmdRoot.CmdClause, data) loggingSyslogUpdate := syslog.NewUpdateCommand(loggingSyslogCmdRoot.CmdClause, data) popCmdRoot := pop.NewRootCommand(app, data) + productCmdRoot := product.NewRootCommand(app, data) + productBotManagementCmdRoot := bot_management.NewRootCommand(productCmdRoot.CmdClause, data) + productBotManagementDisable := bot_management.NewDisableCommand(productBotManagementCmdRoot.CmdClause, data) + productBotManagementEnable := bot_management.NewEnableCommand(productBotManagementCmdRoot.CmdClause, data) + productBotManagementStatus := bot_management.NewStatusCommand(productBotManagementCmdRoot.CmdClause, data) productsCmdRoot := products.NewRootCommand(app, data) profileCmdRoot := profile.NewRootCommand(app, data) profileCreate := profile.NewCreateCommand(profileCmdRoot.CmdClause, data, ssoCmdRoot) @@ -735,6 +742,11 @@ func Define( loggingSyslogList, loggingSyslogUpdate, popCmdRoot, + productCmdRoot, + productBotManagementCmdRoot, + productBotManagementDisable, + productBotManagementEnable, + productBotManagementStatus, productsCmdRoot, profileCmdRoot, profileCreate, diff --git a/pkg/commands/product/bot_management/disable.go b/pkg/commands/product/bot_management/disable.go new file mode 100644 index 000000000..e67dacc33 --- /dev/null +++ b/pkg/commands/product/bot_management/disable.go @@ -0,0 +1,77 @@ +package bot_management + +import ( + "io" + + "github.com/fastly/go-fastly/v9/fastly" + "github.com/fastly/go-fastly/v9/fastly/products/bot_management" + + "github.com/fastly/cli/pkg/api" + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/manifest" + "github.com/fastly/cli/pkg/text" +) + +// DisableFn is a dependency-injection point for unit tests to provide +// a mock implementation of the API operation. +var DisableFn = func(client api.Interface, serviceID string) error { + return bot_management.Disable(client.(*fastly.Client), serviceID) +} + +// DisableCommand calls the Fastly API to disable the product. +type DisableCommand struct { + argparser.Base + Manifest manifest.Data + + serviceName argparser.OptionalServiceNameID +} + +// NewDisableCommand returns a usable command registered under the parent. +func NewDisableCommand(parent argparser.Registerer, g *global.Data) *DisableCommand { + c := DisableCommand{ + Base: argparser.Base{ + Globals: g, + }, + } + c.CmdClause = parent.Command("disable", "Disable the "+bot_management.ProductName+" product") + + // Optional. + c.RegisterFlag(argparser.StringFlagOpts{ + Name: argparser.FlagServiceIDName, + Description: argparser.FlagServiceIDDesc, + Dst: &g.Manifest.Flag.ServiceID, + Short: 's', + }) + c.RegisterFlag(argparser.StringFlagOpts{ + Action: c.serviceName.Set, + Name: argparser.FlagServiceName, + Description: argparser.FlagServiceNameDesc, + Dst: &c.serviceName.Value, + }) + return &c +} + +// Exec invokes the application logic for the command. +func (c *DisableCommand) Exec(_ io.Reader, out io.Writer) error { + serviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog) + if err != nil { + c.Globals.ErrLog.Add(err) + return err + } + + if c.Globals.Verbose() { + argparser.DisplayServiceID(serviceID, flag, source, out) + } + + err = DisableFn(c.Globals.APIClient, serviceID) + if err != nil { + c.Globals.ErrLog.Add(err) + return err + } + + text.Success(out, + "Disabled "+bot_management.ProductName+" on service %s", serviceID) + + return nil +} diff --git a/pkg/commands/product/bot_management/doc.go b/pkg/commands/product/bot_management/doc.go new file mode 100644 index 000000000..90a800a0c --- /dev/null +++ b/pkg/commands/product/bot_management/doc.go @@ -0,0 +1,3 @@ +// Package bot_management contains commands to enable and disable the +// Fastly Bot Management product. +package bot_management diff --git a/pkg/commands/product/bot_management/enable.go b/pkg/commands/product/bot_management/enable.go new file mode 100644 index 000000000..b93a4a342 --- /dev/null +++ b/pkg/commands/product/bot_management/enable.go @@ -0,0 +1,77 @@ +package bot_management + +import ( + "io" + + "github.com/fastly/go-fastly/v9/fastly" + "github.com/fastly/go-fastly/v9/fastly/products/bot_management" + + "github.com/fastly/cli/pkg/api" + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/manifest" + "github.com/fastly/cli/pkg/text" +) + +// EnableFn is a dependency-injection point for unit tests to provide +// a mock implementation of the API operation. +var EnableFn = func(client api.Interface, serviceID string) (*bot_management.EnableOutput, error) { + return bot_management.Enable(client.(*fastly.Client), serviceID) +} + +// EnableCommand calls the Fastly API to enable the product. +type EnableCommand struct { + argparser.Base + Manifest manifest.Data + + serviceName argparser.OptionalServiceNameID +} + +// NewEnableCommand returns a usable command registered under the parent. +func NewEnableCommand(parent argparser.Registerer, g *global.Data) *EnableCommand { + c := EnableCommand{ + Base: argparser.Base{ + Globals: g, + }, + } + c.CmdClause = parent.Command("enable", "Enable the "+bot_management.ProductName+" product") + + // Optional. + c.RegisterFlag(argparser.StringFlagOpts{ + Name: argparser.FlagServiceIDName, + Description: argparser.FlagServiceIDDesc, + Dst: &g.Manifest.Flag.ServiceID, + Short: 's', + }) + c.RegisterFlag(argparser.StringFlagOpts{ + Action: c.serviceName.Set, + Name: argparser.FlagServiceName, + Description: argparser.FlagServiceNameDesc, + Dst: &c.serviceName.Value, + }) + return &c +} + +// Exec invokes the application logic for the command. +func (c *EnableCommand) Exec(_ io.Reader, out io.Writer) error { + serviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog) + if err != nil { + c.Globals.ErrLog.Add(err) + return err + } + + if c.Globals.Verbose() { + argparser.DisplayServiceID(serviceID, flag, source, out) + } + + _, err = EnableFn(c.Globals.APIClient, serviceID) + if err != nil { + c.Globals.ErrLog.Add(err) + return err + } + + text.Success(out, + "Enabled "+bot_management.ProductName+" on service %s", serviceID) + + return nil +} diff --git a/pkg/commands/product/bot_management/product_test.go b/pkg/commands/product/bot_management/product_test.go new file mode 100644 index 000000000..55f107c7c --- /dev/null +++ b/pkg/commands/product/bot_management/product_test.go @@ -0,0 +1,125 @@ +package bot_management_test + +import ( + "testing" + + "github.com/fastly/go-fastly/v9/fastly" + "github.com/fastly/go-fastly/v9/fastly/products/bot_management" + + "github.com/fastly/cli/pkg/api" + root "github.com/fastly/cli/pkg/commands/product" + sub "github.com/fastly/cli/pkg/commands/product/bot_management" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/testutil" +) + +func TestProductEnablement(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Name: "validate missing Service ID: enable", + Args: "enable", + WantError: "error reading service: no service ID found", + }, + { + Name: "validate missing Service ID: disable", + Args: "enable", + WantError: "error reading service: no service ID found", + }, + { + Name: "validate missing Service ID: status", + Args: "enable", + WantError: "error reading service: no service ID found", + }, + { + Name: "validate invalid json/verbose flag combo: status", + Args: "status --service-id 123 --json --verbose", + WantError: "invalid flag combination, --verbose and --json", + }, + { + Name: "validate success for enabling product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.EnableFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + return nil, nil + } + }, + Args: "enable --service-id 123", + WantOutput: "SUCCESS: Enabled " + bot_management.ProductName + " on service 123", + }, + { + Name: "validate failure for enabling product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.EnableFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + return nil, testutil.Err + } + }, + Args: "enable --service-id 123", + WantError: "test error", + }, + { + Name: "validate success for disabling product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.DisableFn = func(_ api.Interface, _ string) error { + return nil + } + }, + Args: "disable --service-id 123", + WantOutput: "SUCCESS: Disabled " + bot_management.ProductName + " on service 123", + }, + { + Name: "validate failure for disabling product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.DisableFn = func(_ api.Interface, _ string) error { + return testutil.Err + } + }, + Args: "disable --service-id 123", + WantError: "test error", + }, + { + Name: "validate regular status output for enabled product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.GetFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + return nil, nil + } + }, + Args: "status --service-id 123", + WantOutput: "INFO: " + bot_management.ProductName + " is enabled on service 123", + }, + { + Name: "validate JSON status output for enabled product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.GetFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + return nil, nil + } + }, + Args: "status --service-id 123 --json", + WantOutput: "{\n \"enabled\": true\n}", + }, + { + Name: "validate regular status output for disabled product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.GetFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + // The API returns a 'Bad Request' error when the + // product has not been enabled on the service + return nil, &fastly.HTTPError{StatusCode: 400} + } + }, + Args: "status --service-id 123", + WantOutput: "INFO: " + bot_management.ProductName + " is disabled on service 123", + }, + { + Name: "validate JSON status output for disabled product", + Setup: func(t *testing.T, scenario *testutil.CLIScenario, opts *global.Data) { + sub.GetFn = func(_ api.Interface, _ string) (*bot_management.EnableOutput, error) { + // The API returns a 'Bad Request' error when the + // product has not been enabled on the service + return nil, &fastly.HTTPError{StatusCode: 400} + } + }, + Args: "status --service-id 123 --json", + WantOutput: "{\n \"enabled\": false\n}", + }, + } + + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName}, scenarios) +} diff --git a/pkg/commands/product/bot_management/root.go b/pkg/commands/product/bot_management/root.go new file mode 100644 index 000000000..20437c2ad --- /dev/null +++ b/pkg/commands/product/bot_management/root.go @@ -0,0 +1,31 @@ +package bot_management + +import ( + "io" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" +) + +// RootCommand is the parent command for all subcommands in this package. +// It should be installed under the primary root command. +type RootCommand struct { + argparser.Base + // no flags +} + +// CommandName is the string to be used to invoke this command +const CommandName = "bot_management" + +// NewRootCommand returns a new command registered in the parent. +func NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand { + var c RootCommand + c.Globals = g + c.CmdClause = parent.Command(CommandName, "Enable and disable the Fastly Bot Management product") + return &c +} + +// Exec implements the command interface. +func (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error { + panic("unreachable") +} diff --git a/pkg/commands/product/bot_management/status.go b/pkg/commands/product/bot_management/status.go new file mode 100644 index 000000000..8202696d8 --- /dev/null +++ b/pkg/commands/product/bot_management/status.go @@ -0,0 +1,112 @@ +package bot_management + +import ( + "errors" + "io" + + fsterr "github.com/fastly/cli/pkg/errors" + + "github.com/fastly/go-fastly/v9/fastly" + "github.com/fastly/go-fastly/v9/fastly/products/bot_management" + + "github.com/fastly/cli/pkg/api" + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/manifest" + "github.com/fastly/cli/pkg/text" +) + +// GetFn is a dependency-injection point for unit tests to provide +// a mock implementation of the API operation. +var GetFn = func(client api.Interface, serviceID string) (*bot_management.EnableOutput, error) { + return bot_management.Get(client.(*fastly.Client), serviceID) +} + +// StatusCommand calls the Fastly API to get the enablement status of the product. +type StatusCommand struct { + argparser.Base + argparser.JSONOutput + Manifest manifest.Data + + serviceName argparser.OptionalServiceNameID +} + +// NewStatusCommand returns a usable command registered under the parent. +func NewStatusCommand(parent argparser.Registerer, g *global.Data) *StatusCommand { + c := StatusCommand{ + Base: argparser.Base{ + Globals: g, + }, + } + c.CmdClause = parent.Command("status", "Get the enablement status of the "+bot_management.ProductName+" product") + + // Optional. + c.RegisterFlagBool(c.JSONFlag()) // --json + c.RegisterFlag(argparser.StringFlagOpts{ + Name: argparser.FlagServiceIDName, + Description: argparser.FlagServiceIDDesc, + Dst: &g.Manifest.Flag.ServiceID, + Short: 's', + }) + c.RegisterFlag(argparser.StringFlagOpts{ + Action: c.serviceName.Set, + Name: argparser.FlagServiceName, + Description: argparser.FlagServiceNameDesc, + Dst: &c.serviceName.Value, + }) + return &c +} + +type status struct { + Enabled bool `json:"enabled"` +} + +// Exec invokes the application logic for the command. +func (c *StatusCommand) Exec(_ io.Reader, out io.Writer) error { + if c.Globals.Verbose() && c.JSONOutput.Enabled { + return fsterr.ErrInvalidVerboseJSONCombo + } + + serviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog) + if err != nil { + c.Globals.ErrLog.Add(err) + return err + } + + if c.Globals.Verbose() { + argparser.DisplayServiceID(serviceID, flag, source, out) + } + + var s status + + _, err = GetFn(c.Globals.APIClient, serviceID) + if err != nil { + var herr *fastly.HTTPError + + // The API returns a 'Bad Request' error when the + // product has not been enabled on the service; any + // other error should be reported + if !errors.As(err, &herr) || !herr.IsBadRequest() { + c.Globals.ErrLog.Add(err) + return err + } + } else { + s.Enabled = true + } + + if ok, err := c.WriteJSON(out, s); ok { + return err + } + + var msg string + if s.Enabled { + msg = "enabled" + } else { + msg = "disabled" + } + + text.Info(out, + bot_management.ProductName+" is %s on service %s", msg, serviceID) + + return nil +} diff --git a/pkg/commands/product/doc.go b/pkg/commands/product/doc.go new file mode 100644 index 000000000..e35f0ab32 --- /dev/null +++ b/pkg/commands/product/doc.go @@ -0,0 +1,3 @@ +// Package product contains commands to enable, disable, and configure +// Fastly products. +package product diff --git a/pkg/commands/product/root.go b/pkg/commands/product/root.go new file mode 100644 index 000000000..dcda72a2f --- /dev/null +++ b/pkg/commands/product/root.go @@ -0,0 +1,31 @@ +package product + +import ( + "io" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" +) + +// RootCommand is the parent command for all subcommands in this package. +// It should be installed under the primary root command. +type RootCommand struct { + argparser.Base + // no flags +} + +// CommandName is the string to be used to invoke this command +const CommandName = "product" + +// NewRootCommand returns a new command registered in the parent. +func NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand { + var c RootCommand + c.Globals = g + c.CmdClause = parent.Command(CommandName, "Enable, disable, and configure Fastly products") + return &c +} + +// Exec implements the command interface. +func (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error { + panic("unreachable") +}