Skip to content

Commit

Permalink
feat(product_enablement): add products command (#1036)
Browse files Browse the repository at this point in the history
* build: bump go version to 1.21

* doc(vcl/condition): fix typo

* feat(product_enablement): add products command

* test(product_enablement): add tests for products command

* feat(product_enablement): support json output

* Update pkg/commands/products/root.go

Co-authored-by: Adam Williams <awilliams@fastly.com>

* fix(product_enablement): reference correct variables

* fix(product_enablement): check for ProductUndefined

* refactor(product_enablement): define new type for inline struct

---------

Co-authored-by: Adam Williams <awilliams@fastly.com>
  • Loading branch information
Integralist and awilliams-fastly authored Oct 10, 2023
1 parent beed704 commit 8ef3ca7
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 3 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/fastly/cli

go 1.20
go 1.21

require (
github.com/Masterminds/semver/v3 v3.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
Expand Down Expand Up @@ -83,6 +84,7 @@ github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
Expand Down
4 changes: 4 additions & 0 deletions pkg/api/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ type Interface interface {
GetCondition(i *fastly.GetConditionInput) (*fastly.Condition, error)
ListConditions(i *fastly.ListConditionsInput) ([]*fastly.Condition, error)
UpdateCondition(i *fastly.UpdateConditionInput) (*fastly.Condition, error)

GetProduct(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error)
EnableProduct(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error)
DisableProduct(i *fastly.ProductEnablementInput) error
}

// RealtimeStatsInterface is the subset of go-fastly's realtime stats API used here.
Expand Down
3 changes: 3 additions & 0 deletions pkg/app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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/products"
"github.com/fastly/cli/pkg/commands/profile"
"github.com/fastly/cli/pkg/commands/purge"
"github.com/fastly/cli/pkg/commands/ratelimit"
Expand Down Expand Up @@ -329,6 +330,7 @@ func defineCommands(
loggingSyslogList := syslog.NewListCommand(loggingSyslogCmdRoot.CmdClause, g, m)
loggingSyslogUpdate := syslog.NewUpdateCommand(loggingSyslogCmdRoot.CmdClause, g, m)
popCmdRoot := pop.NewRootCommand(app, g)
productsCmdRoot := products.NewRootCommand(app, g, m)
profileCmdRoot := profile.NewRootCommand(app, g)
profileCreate := profile.NewCreateCommand(profileCmdRoot.CmdClause, profile.APIClientFactory(opts.APIClient), g)
profileDelete := profile.NewDeleteCommand(profileCmdRoot.CmdClause, g)
Expand Down Expand Up @@ -691,6 +693,7 @@ func defineCommands(
loggingSyslogList,
loggingSyslogUpdate,
popCmdRoot,
productsCmdRoot,
profileCmdRoot,
profileCreate,
profileDelete,
Expand Down
1 change: 1 addition & 0 deletions pkg/app/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ kv-store-entry
log-tail
logging
pops
products
profile
purge
rate-limit
Expand Down
2 changes: 2 additions & 0 deletions pkg/commands/products/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package products contains commands to inspect and manipulate Fastly products.
package products
143 changes: 143 additions & 0 deletions pkg/commands/products/products_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package products_test

import (
"bytes"
"testing"

"github.com/fastly/go-fastly/v8/fastly"

"github.com/fastly/cli/pkg/app"
"github.com/fastly/cli/pkg/mock"
"github.com/fastly/cli/pkg/testutil"
)

func TestProductEnablement(t *testing.T) {
args := testutil.Args
scenarios := []testutil.TestScenario{
{
Name: "validate missing Service ID",
Args: args("products"),
WantError: "failed to identify Service ID: error reading service: no service ID found",
},
{
Name: "validate invalid enable/disable flag combo",
Args: args("products --enable fanout --disable fanout"),
WantError: "invalid flag combination: --enable and --disable",
},
{
Name: "validate API error for product status",
API: mock.API{
GetProductFn: func(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error) {
return nil, testutil.Err
},
},
Args: args("products --service-id 123"),
WantOutput: `PRODUCT ENABLED
Brotli Compression false
Domain Inspector false
Fanout false
Image Optimizer false
Origin Inspector false
Web Sockets false
`,
},
{
Name: "validate API success for product status",
API: mock.API{
GetProductFn: func(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error) {
return nil, nil
},
},
Args: args("products --service-id 123"),
WantOutput: `PRODUCT ENABLED
Brotli Compression true
Domain Inspector true
Fanout true
Image Optimizer true
Origin Inspector true
Web Sockets true
`,
},
{
Name: "validate flag parsing error for enabling product",
Args: args("products --service-id 123 --enable foo"),
WantError: "error parsing arguments: enum value must be one of brotli_compression,domain_inspector,fanout,image_optimizer,origin_inspector,websockets, got 'foo'",
},
{
Name: "validate flag parsing error for disabling product",
Args: args("products --service-id 123 --disable foo"),
WantError: "error parsing arguments: enum value must be one of brotli_compression,domain_inspector,fanout,image_optimizer,origin_inspector,websockets, got 'foo'",
},
{
Name: "validate success for enabling product",
API: mock.API{
EnableProductFn: func(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error) {
return nil, nil
},
},
Args: args("products --service-id 123 --enable brotli_compression"),
WantOutput: "SUCCESS: Successfully enabled product 'brotli_compression'",
},
{
Name: "validate success for disabling product",
API: mock.API{
DisableProductFn: func(i *fastly.ProductEnablementInput) error {
return nil
},
},
Args: args("products --service-id 123 --disable brotli_compression"),
WantOutput: "SUCCESS: Successfully disabled product 'brotli_compression'",
},
{
Name: "validate invalid json/verbose flag combo",
Args: args("products --service-id 123 --json --verbose"),
WantError: "invalid flag combination, --verbose and --json",
},
{
Name: "validate API error for product status with --json output",
API: mock.API{
GetProductFn: func(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error) {
return nil, testutil.Err
},
},
Args: args("products --service-id 123 --json"),
WantOutput: `{
"brotli_compression": false,
"domain_inspector": false,
"fanout": false,
"image_optimizer": false,
"origin_inspector": false,
"websockets": false
}`,
},
{
Name: "validate API success for product status with --json output",
API: mock.API{
GetProductFn: func(i *fastly.ProductEnablementInput) (*fastly.ProductEnablement, error) {
return nil, nil
},
},
Args: args("products --service-id 123 --json"),
WantOutput: `{
"brotli_compression": true,
"domain_inspector": true,
"fanout": true,
"image_optimizer": true,
"origin_inspector": true,
"websockets": true
}`,
},
}

for testcaseIdx := range scenarios {
testcase := &scenarios[testcaseIdx]
t.Run(testcase.Name, func(t *testing.T) {
var stdout bytes.Buffer
opts := testutil.NewRunOpts(testcase.Args, &stdout)
opts.APIClient = mock.APIClient(testcase.API)
err := app.Run(opts)
testutil.AssertErrorContains(t, err, testcase.WantError)
testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput)
})
}
}
Loading

0 comments on commit 8ef3ca7

Please sign in to comment.