Skip to content

Commit

Permalink
feat(config-store): Add Config Store commands
Browse files Browse the repository at this point in the history
Related `go-fastly` change: fastly/go-fastly#398
  • Loading branch information
awilliams-fastly committed Feb 16, 2023
1 parent f10a150 commit 87875ea
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 2 deletions.
3 changes: 3 additions & 0 deletions pkg/api/interface.go
Expand Up @@ -328,6 +328,9 @@ type Interface interface {
UpdateServiceAuthorization(i *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)
DeleteServiceAuthorization(i *fastly.DeleteServiceAuthorizationInput) error

CreateConfigStore(i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error)
DeleteConfigStore(i *fastly.DeleteConfigStoreInput) error

CreateObjectStore(i *fastly.CreateObjectStoreInput) (*fastly.ObjectStore, error)
ListObjectStores(i *fastly.ListObjectStoresInput) (*fastly.ListObjectStoresResponse, error)
DeleteObjectStore(i *fastly.DeleteObjectStoreInput) error
Expand Down
7 changes: 7 additions & 0 deletions pkg/app/commands.go
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/fastly/cli/pkg/commands/backend"
"github.com/fastly/cli/pkg/commands/compute"
"github.com/fastly/cli/pkg/commands/config"
"github.com/fastly/cli/pkg/commands/configstore"
"github.com/fastly/cli/pkg/commands/dictionary"
"github.com/fastly/cli/pkg/commands/dictionaryentry"
"github.com/fastly/cli/pkg/commands/domain"
Expand Down Expand Up @@ -115,6 +116,9 @@ func defineCommands(
computeUpdate := compute.NewUpdateCommand(computeCmdRoot.CmdClause, g, m)
computeValidate := compute.NewValidateCommand(computeCmdRoot.CmdClause, g, m)
configCmdRoot := config.NewRootCommand(app, g)
configstoreCmdRoot := configstore.NewRootCommand(app, g)
configstoreCreate := configstore.NewCreateCommand(configstoreCmdRoot.CmdClause, g, m)
configstoreDelete := configstore.NewDeleteCommand(configstoreCmdRoot.CmdClause, g, m)
dictionaryCmdRoot := dictionary.NewRootCommand(app, g)
dictionaryCreate := dictionary.NewCreateCommand(dictionaryCmdRoot.CmdClause, g, m)
dictionaryDelete := dictionary.NewDeleteCommand(dictionaryCmdRoot.CmdClause, g, m)
Expand Down Expand Up @@ -447,6 +451,9 @@ func defineCommands(
computeUpdate,
computeValidate,
configCmdRoot,
configstoreCmdRoot,
configstoreCreate,
configstoreDelete,
dictionaryCmdRoot,
dictionaryCreate,
dictionaryDelete,
Expand Down
183 changes: 183 additions & 0 deletions pkg/commands/configstore/configstore_test.go
@@ -0,0 +1,183 @@
package configstore_test

import (
"bytes"
"errors"
"fmt"
"testing"
"time"

"github.com/fastly/cli/pkg/app"
"github.com/fastly/cli/pkg/commands/configstore"
fstfmt "github.com/fastly/cli/pkg/fmt"
"github.com/fastly/cli/pkg/mock"
"github.com/fastly/cli/pkg/testutil"
"github.com/fastly/go-fastly/v7/fastly"
)

func TestCreateStoreCommand(t *testing.T) {
const (
storeName = "test123"
storeID = "store-id-123"
)
now := time.Now()

scenarios := []struct {
args string
api mock.API
wantAPIInvoked bool
wantError string
wantOutput string
}{
{
args: "create",
wantError: "error parsing arguments: required flag --name not provided",
},
{
args: fmt.Sprintf("create --name %s", storeName),
api: mock.API{
CreateConfigStoreFn: func(i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {
return nil, errors.New("invalid request")
},
},
wantAPIInvoked: true,
wantError: "invalid request",
},
{
args: fmt.Sprintf("create --name %s", storeName),
api: mock.API{
CreateConfigStoreFn: func(i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {
return &fastly.ConfigStore{
ID: storeID,
Name: i.Name,
}, nil
},
},
wantAPIInvoked: true,
wantOutput: fstfmt.Success("Created config store %s (name %s)", storeID, storeName),
},
{
args: fmt.Sprintf("create --name %s --json", storeName),
api: mock.API{
CreateConfigStoreFn: func(i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {
return &fastly.ConfigStore{
ID: storeID,
Name: i.Name,
CreatedAt: &now,
UpdatedAt: &now,
DeletedAt: nil,
}, nil
},
},
wantAPIInvoked: true,
wantOutput: fstfmt.JSON(`{"name": %q, "id": %q, "created_at": %q, "updated_at": %q, "deleted_at": null}`, storeName, storeID, now.Format(time.RFC3339Nano), now.Format(time.RFC3339Nano)),
},
}

for _, testcase := range scenarios {
testcase := testcase
t.Run(testcase.args, func(t *testing.T) {
var stdout bytes.Buffer
opts := testutil.NewRunOpts(testutil.Args(configstore.RootName+" "+testcase.args), &stdout)

f := testcase.api.CreateConfigStoreFn
var apiInvoked bool
testcase.api.CreateConfigStoreFn = func(i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {
apiInvoked = true
return f(i)
}

opts.APIClient = mock.APIClient(testcase.api)

err := app.Run(opts)

testutil.AssertErrorContains(t, err, testcase.wantError)
testutil.AssertString(t, testcase.wantOutput, stdout.String())
if apiInvoked != testcase.wantAPIInvoked {
t.Fatalf("API CreateConfigStore invoked = %v, want %v", apiInvoked, testcase.wantAPIInvoked)
}
})
}
}

func TestDeleteStoreCommand(t *testing.T) {
const storeID = "test123"
errStoreNotFound := errors.New("store not found")

scenarios := []struct {
args string
api mock.API
wantAPIInvoked bool
wantError string
wantOutput string
}{
{
args: "delete",
wantError: "error parsing arguments: required flag --store-id not provided",
},
{
args: "delete --store-id DOES-NOT-EXIST",
api: mock.API{
DeleteConfigStoreFn: func(i *fastly.DeleteConfigStoreInput) error {
if i.ID != storeID {
return errStoreNotFound
}
return nil
},
},
wantAPIInvoked: true,
wantError: errStoreNotFound.Error(),
},
{
args: fmt.Sprintf("delete --store-id %s", storeID),
api: mock.API{
DeleteConfigStoreFn: func(i *fastly.DeleteConfigStoreInput) error {
if i.ID != storeID {
return errStoreNotFound
}
return nil
},
},
wantAPIInvoked: true,
wantOutput: fstfmt.Success("Deleted config store %s\n", storeID),
},
{
args: fmt.Sprintf("delete --store-id %s --json", storeID),
api: mock.API{
DeleteConfigStoreFn: func(i *fastly.DeleteConfigStoreInput) error {
if i.ID != storeID {
return errStoreNotFound
}
return nil
},
},
wantAPIInvoked: true,
wantOutput: fstfmt.JSON(`{"id": %q, "deleted": true}`, storeID),
},
}

for _, testcase := range scenarios {
testcase := testcase
t.Run(testcase.args, func(t *testing.T) {
var stdout bytes.Buffer
opts := testutil.NewRunOpts(testutil.Args(configstore.RootName+" "+testcase.args), &stdout)

f := testcase.api.DeleteConfigStoreFn
var apiInvoked bool
testcase.api.DeleteConfigStoreFn = func(i *fastly.DeleteConfigStoreInput) error {
apiInvoked = true
return f(i)
}

opts.APIClient = mock.APIClient(testcase.api)

err := app.Run(opts)

testutil.AssertErrorContains(t, err, testcase.wantError)
testutil.AssertString(t, testcase.wantOutput, stdout.String())
if apiInvoked != testcase.wantAPIInvoked {
t.Fatalf("API DeleteConfigStore invoked = %v, want %v", apiInvoked, testcase.wantAPIInvoked)
}
})
}
}
68 changes: 68 additions & 0 deletions pkg/commands/configstore/create.go
@@ -0,0 +1,68 @@
package configstore

import (
"io"

"github.com/fastly/cli/pkg/cmd"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/global"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/text"
"github.com/fastly/go-fastly/v7/fastly"
)

// NewCreateCommand returns a usable command registered under the parent.
func NewCreateCommand(parent cmd.Registerer, g *global.Data, m manifest.Data) *CreateCommand {
c := CreateCommand{
Base: cmd.Base{
Globals: g,
},
manifest: m,
}

c.CmdClause = parent.Command("create", "Create a new config store")

// Required.
c.RegisterFlag(cmd.StringFlagOpts{
Name: "name",
Short: 'n',
Description: "Store name",
Dst: &c.input.Name,
Required: true,
})

// Optional.
c.RegisterFlagBool(c.JSONFlag()) // --json

return &c
}

// CreateCommand calls the Fastly API to create an appropriate resource.
type CreateCommand struct {
cmd.Base
cmd.JSONOutput

input fastly.CreateConfigStoreInput
manifest manifest.Data
}

// Exec invokes the application logic for the command.
func (cmd *CreateCommand) Exec(_ io.Reader, out io.Writer) error {
if cmd.Globals.Verbose() && cmd.JSONOutput.Enabled {
return fsterr.ErrInvalidVerboseJSONCombo
}

o, err := cmd.Globals.APIClient.CreateConfigStore(&cmd.input)
if err != nil {
cmd.Globals.ErrLog.Add(err)
return err
}

if ok, err := cmd.WriteJSON(out, o); ok {
return err
}

text.Success(out, "Created config store %s (name %s)", o.ID, o.Name)

return nil
}
76 changes: 76 additions & 0 deletions pkg/commands/configstore/delete.go
@@ -0,0 +1,76 @@
package configstore

import (
"io"

"github.com/fastly/cli/pkg/cmd"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/global"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/text"
"github.com/fastly/go-fastly/v7/fastly"
)

// NewDeleteCommand returns a usable command registered under the parent.
func NewDeleteCommand(parent cmd.Registerer, g *global.Data, m manifest.Data) *DeleteCommand {
c := DeleteCommand{
Base: cmd.Base{
Globals: g,
},
manifest: m,
}

c.CmdClause = parent.Command("delete", "Delete a config store")

// Required.
c.RegisterFlag(cmd.StringFlagOpts{
Name: "store-id",
Short: 's',
Description: "Store ID",
Dst: &c.input.ID,
Required: true,
})

// Optional.
c.RegisterFlagBool(c.JSONFlag()) // --json

return &c
}

// DeleteCommand calls the Fastly API to delete an appropriate resource.
type DeleteCommand struct {
cmd.Base
cmd.JSONOutput

input fastly.DeleteConfigStoreInput
manifest manifest.Data
}

// Exec invokes the application logic for the command.
func (cmd *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {
if cmd.Globals.Verbose() && cmd.JSONOutput.Enabled {
return fsterr.ErrInvalidVerboseJSONCombo
}

err := cmd.Globals.APIClient.DeleteConfigStore(&cmd.input)
if err != nil {
cmd.Globals.ErrLog.Add(err)
return err
}

if cmd.JSONOutput.Enabled {
o := struct {
ID string `json:"id"`
Deleted bool `json:"deleted"`
}{
cmd.input.ID,
true,
}
_, err := cmd.WriteJSON(out, o)
return err
}

text.Success(out, "Deleted config store %s", cmd.input.ID)

return nil
}
5 changes: 5 additions & 0 deletions pkg/commands/configstore/doc.go
@@ -0,0 +1,5 @@
// Package configstore contains commands to inspect and manipulate Fastly edge
// config stores.
//
// https://developer.fastly.com/reference/api/services/resources/config-store/
package configstore

0 comments on commit 87875ea

Please sign in to comment.