From d3c6c2c3bf7a831a73fb984eaf648187a27b4ef1 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 9 Dec 2024 17:20:54 -0500 Subject: [PATCH 1/2] feat(product): Add 'product' commands for each Fastly product. Adds 'enable', 'disable', and 'status' commands for each product previously supported by the 'products' command (which is now deprecated). Also adds 'configure' commands for products which support configuration (DDoS Protection and Next-Gen WAF). --- go.mod | 5 +- go.sum | 10 +- pkg/commands/commands.go | 12 ++ .../product/bot_management/disable.go | 77 +++++++++++ pkg/commands/product/bot_management/doc.go | 3 + pkg/commands/product/bot_management/enable.go | 77 +++++++++++ .../product/bot_management/product_test.go | 125 ++++++++++++++++++ pkg/commands/product/bot_management/root.go | 31 +++++ pkg/commands/product/bot_management/status.go | 112 ++++++++++++++++ pkg/commands/product/doc.go | 3 + pkg/commands/product/root.go | 31 +++++ 11 files changed, 481 insertions(+), 5 deletions(-) create mode 100644 pkg/commands/product/bot_management/disable.go create mode 100644 pkg/commands/product/bot_management/doc.go create mode 100644 pkg/commands/product/bot_management/enable.go create mode 100644 pkg/commands/product/bot_management/product_test.go create mode 100644 pkg/commands/product/bot_management/root.go create mode 100644 pkg/commands/product/bot_management/status.go create mode 100644 pkg/commands/product/doc.go create mode 100644 pkg/commands/product/root.go 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/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") +} From 80860f73c9df43559ce10c0d3cc72e52d9c46b33 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 17 Dec 2024 13:43:08 -0500 Subject: [PATCH 2/2] Fix shell-completion test. --- pkg/app/run_test.go | 1 + 1 file changed, 1 insertion(+) 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