Skip to content

Commit

Permalink
feat(service-resource): Add Service Resource commands
Browse files Browse the repository at this point in the history
https://developer.fastly.com/reference/api/services/resource/

This adds a `service-resource` sub-command which has associated create,
delete, list, and update commands.

Some of the names in the API may be confusing. I've done my best to add
clarification whenever possible. Specifically:

- A resource, as named by the API, is truly a _link_ between a service
  and a resource (e.g. an Object Store), not the resource itself
- In some places, the API mixes `id` and `resource_id` which both refer
  to the ID of the resource _link_, and not the resource itself.
- It's possible to provide a resource link name. If set, this is an alias
  for the resource. Otherwise, whatever name was used when creating the
  resource itself will be the name to reference from the service for the
  resource.
  • Loading branch information
awilliams-fastly committed Feb 7, 2023
1 parent 894a1f3 commit ac4b804
Show file tree
Hide file tree
Showing 14 changed files with 1,345 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pkg/api/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ type Interface interface {
ListSecrets(i *fastly.ListSecretsInput) (*fastly.Secrets, error)

CreateResource(i *fastly.CreateResourceInput) (*fastly.Resource, error)
DeleteResource(i *fastly.DeleteResourceInput) error
GetResource(i *fastly.GetResourceInput) (*fastly.Resource, error)
ListResources(i *fastly.ListResourcesInput) ([]*fastly.Resource, error)
UpdateResource(i *fastly.UpdateResourceInput) (*fastly.Resource, error)
}

// RealtimeStatsInterface is the subset of go-fastly's realtime stats API used here.
Expand Down
13 changes: 13 additions & 0 deletions pkg/app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"github.com/fastly/cli/pkg/commands/secretstoreentry"
"github.com/fastly/cli/pkg/commands/service"
"github.com/fastly/cli/pkg/commands/serviceauth"
"github.com/fastly/cli/pkg/commands/serviceresource"
"github.com/fastly/cli/pkg/commands/serviceversion"
"github.com/fastly/cli/pkg/commands/shellcomplete"
"github.com/fastly/cli/pkg/commands/stats"
Expand Down Expand Up @@ -341,6 +342,12 @@ func defineCommands(
serviceauthDescribe := serviceauth.NewDescribeCommand(serviceauthCmdRoot.CmdClause, g, m)
serviceauthList := serviceauth.NewListCommand(serviceauthCmdRoot.CmdClause, g)
serviceauthUpdate := serviceauth.NewUpdateCommand(serviceauthCmdRoot.CmdClause, g, m)
serviceresourceCmdRoot := serviceresource.NewRootCommand(app, g)
serviceresourceCreate := serviceresource.NewCreateCommand(serviceresourceCmdRoot.CmdClause, g, m)
serviceresourceDelete := serviceresource.NewDeleteCommand(serviceresourceCmdRoot.CmdClause, g, m)
serviceresourceDescribe := serviceresource.NewDescribeCommand(serviceresourceCmdRoot.CmdClause, g, m)
serviceresourceList := serviceresource.NewListCommand(serviceresourceCmdRoot.CmdClause, g, m)
serviceresourceUpdate := serviceresource.NewUpdateCommand(serviceresourceCmdRoot.CmdClause, g, m)
serviceVersionCmdRoot := serviceversion.NewRootCommand(app, g)
serviceVersionActivate := serviceversion.NewActivateCommand(serviceVersionCmdRoot.CmdClause, g, m)
serviceVersionClone := serviceversion.NewCloneCommand(serviceVersionCmdRoot.CmdClause, g, m)
Expand Down Expand Up @@ -669,6 +676,12 @@ func defineCommands(
serviceauthDescribe,
serviceauthList,
serviceauthUpdate,
serviceresourceCmdRoot,
serviceresourceCreate,
serviceresourceDelete,
serviceresourceDescribe,
serviceresourceList,
serviceresourceUpdate,
serviceVersionActivate,
serviceVersionClone,
serviceVersionCmdRoot,
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 @@ -79,6 +79,7 @@ secret-store
secret-store-entry
service
service-auth
service-resource
service-version
stats
tls-config
Expand Down
120 changes: 120 additions & 0 deletions pkg/commands/serviceresource/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package serviceresource

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"
)

// CreateCommand calls the Fastly API to create a resource link.
type CreateCommand struct {
cmd.Base
jsonOutput

autoClone cmd.OptionalAutoClone
input fastly.CreateResourceInput
manifest manifest.Data
serviceVersion cmd.OptionalServiceVersion
}

// NewCreateCommand returns a usable command registered under the parent.
func NewCreateCommand(parent cmd.Registerer, globals *global.Data, data manifest.Data) *CreateCommand {
c := CreateCommand{
Base: cmd.Base{
Globals: globals,
},
manifest: data,
input: fastly.CreateResourceInput{
// Kingpin requires the following to be initialized.
ResourceID: new(string),
Name: new(string),
},
}
c.CmdClause = parent.Command("create", "Create a Fastly service resource link").Alias("link")

// Required.
c.RegisterFlag(cmd.StringFlagOpts{
Name: "resource-id",
Short: 'r',
Description: "Resource ID",
Dst: c.input.ResourceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagServiceIDName,
Short: 's',
Description: cmd.FlagServiceIDDesc,
Dst: &c.manifest.Flag.ServiceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagVersionName,
Description: cmd.FlagVersionDesc,
Dst: &c.serviceVersion.Value,
Required: true,
})

// Optional.
c.RegisterAutoCloneFlag(cmd.AutoCloneFlagOpts{
Action: c.autoClone.Set,
Dst: &c.autoClone.Value,
})
c.RegisterFlagBool(c.jsonFlag()) // --json
c.RegisterFlag(cmd.StringFlagOpts{
Name: "name",
Short: 'n',
Description: "Resource alias. Defaults to name of resource",
Dst: c.input.Name,
})

return &c
}

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

serviceID, serviceVersion, err := cmd.ServiceDetails(cmd.ServiceDetailsOpts{
AutoCloneFlag: c.autoClone,
APIClient: c.Globals.APIClient,
Manifest: c.manifest,
Out: out,
ServiceNameFlag: cmd.OptionalServiceNameID{}, // ServiceID flag is required, no need to lookup service by name.
ServiceVersionFlag: c.serviceVersion,
VerboseMode: c.Globals.Flags.Verbose,
})
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"Service ID": c.manifest.Flag.ServiceID,
"Service Version": fsterr.ServiceVersion(serviceVersion),
})
return err
}

c.input.ServiceID = serviceID
c.input.ServiceVersion = serviceVersion.Number

resource, err := c.Globals.APIClient.CreateResource(&c.input)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"ID": c.input.ResourceID,
"Service ID": c.input.ServiceID,
"Service Version": c.input.ServiceVersion,
})
return err
}

if ok, err := c.WriteJSON(out, resource); ok {
return err
}

text.Success(out, "Created service resource link %s on service %s version %s", resource.ID, resource.ServiceID, resource.ServiceVersion)
return nil
}
120 changes: 120 additions & 0 deletions pkg/commands/serviceresource/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package serviceresource

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"
)

// DeleteCommand calls the Fastly API to delete service resource links.
type DeleteCommand struct {
cmd.Base
jsonOutput

autoClone cmd.OptionalAutoClone
input fastly.DeleteResourceInput
manifest manifest.Data
serviceVersion cmd.OptionalServiceVersion
}

// NewDeleteCommand returns a usable command registered under the parent.
func NewDeleteCommand(parent cmd.Registerer, globals *global.Data, data manifest.Data) *DeleteCommand {
c := DeleteCommand{
Base: cmd.Base{
Globals: globals,
},
manifest: data,
}
c.CmdClause = parent.Command("delete", "Delete a resource link for a Fastly service version").Alias("remove")

// Required.
c.RegisterFlag(cmd.StringFlagOpts{
Name: "id",
Description: "ID of resource link",
Dst: &c.input.ResourceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagServiceIDName,
Short: 's',
Description: cmd.FlagServiceIDDesc,
Dst: &c.manifest.Flag.ServiceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagVersionName,
Description: cmd.FlagVersionDesc,
Dst: &c.serviceVersion.Value,
Required: true,
})

// Optional.
c.RegisterAutoCloneFlag(cmd.AutoCloneFlagOpts{
Action: c.autoClone.Set,
Dst: &c.autoClone.Value,
})
c.RegisterFlagBool(c.jsonFlag()) // --json

return &c
}

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

serviceID, serviceVersion, err := cmd.ServiceDetails(cmd.ServiceDetailsOpts{
AutoCloneFlag: c.autoClone,
APIClient: c.Globals.APIClient,
Manifest: c.manifest,
Out: out,
ServiceNameFlag: cmd.OptionalServiceNameID{}, // ServiceID flag is required, no need to lookup service by name.
ServiceVersionFlag: c.serviceVersion,
VerboseMode: c.Globals.Flags.Verbose,
})
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"Service ID": c.manifest.Flag.ServiceID,
"Service Version": fsterr.ServiceVersion(serviceVersion),
})
return err
}

c.input.ServiceID = serviceID
c.input.ServiceVersion = serviceVersion.Number

err = c.Globals.APIClient.DeleteResource(&c.input)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"ID": c.input.ResourceID,
"Service ID": c.input.ServiceID,
"Service Version": c.input.ServiceVersion,
})
return err
}

if c.jsonOutput.enabled {
o := struct {
ResourceID string `json:"id"`
ServiceID string `json:"service_id"`
ServiceVersion int `json:"service_version"`
Deleted bool `json:"deleted"`
}{
c.input.ResourceID,
c.input.ServiceID,
c.input.ServiceVersion,
true,
}
_, err := c.WriteJSON(out, o)
return err
}

text.Success(out, "Deleted service resource link %s from service %s version %d", c.input.ResourceID, c.input.ServiceID, c.input.ServiceVersion)
return nil
}
104 changes: 104 additions & 0 deletions pkg/commands/serviceresource/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package serviceresource

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"
)

// DescribeCommand calls the Fastly API to describe a service resource link.
type DescribeCommand struct {
cmd.Base
jsonOutput

input fastly.GetResourceInput
manifest manifest.Data
serviceVersion cmd.OptionalServiceVersion
}

// NewDescribeCommand returns a usable command registered under the parent.
func NewDescribeCommand(parent cmd.Registerer, globals *global.Data, data manifest.Data) *DescribeCommand {
c := DescribeCommand{
Base: cmd.Base{
Globals: globals,
},
manifest: data,
}
c.CmdClause = parent.Command("describe", "Show detailed information about a Fastly service resource link").Alias("get")

// Required.
c.RegisterFlag(cmd.StringFlagOpts{
Name: "id",
Description: "ID of resource link",
Dst: &c.input.ResourceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagServiceIDName,
Short: 's',
Description: cmd.FlagServiceIDDesc,
Dst: &c.manifest.Flag.ServiceID,
Required: true,
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagVersionName,
Description: cmd.FlagVersionDesc,
Dst: &c.serviceVersion.Value,
Required: true,
})

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

return &c
}

// Exec invokes the application logic for the command.
func (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {
if c.Globals.Verbose() && c.jsonOutput.enabled {
return fsterr.ErrInvalidVerboseJSONCombo
}

serviceID, source, flag, err := cmd.ServiceID(cmd.OptionalServiceNameID{}, c.manifest, c.Globals.APIClient, c.Globals.ErrLog)
if err != nil {
return err
}
if c.Globals.Verbose() {
cmd.DisplayServiceID(serviceID, flag, source, out)
}

serviceVersion, err := c.serviceVersion.Parse(serviceID, c.Globals.APIClient)
if err != nil {
return err
}

c.input.ServiceID = serviceID
c.input.ServiceVersion = serviceVersion.Number

resource, err := c.Globals.APIClient.GetResource(&c.input)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"ID": c.input.ResourceID,
"Service ID": c.input.ServiceID,
"Service Version": c.input.ServiceVersion,
})
return err
}

if ok, err := c.WriteJSON(out, resource); ok {
return err
}

if !c.Globals.Verbose() {
text.Output(out, "Service ID: %s", resource.ServiceID)
}
text.Output(out, "Service Version: %s", resource.ServiceVersion)
text.PrintResource(out, "", resource)

return nil
}
Loading

0 comments on commit ac4b804

Please sign in to comment.