From 3b6f710c39dc2f8748111127ee8ee2df0ecb00b8 Mon Sep 17 00:00:00 2001 From: Patrick Hamann Date: Fri, 20 Mar 2020 16:00:58 +0000 Subject: [PATCH] Add CRUD commands for S3 logging endpoints (#9) --- pkg/api/interface.go | 6 + pkg/app/run.go | 15 + pkg/app/run_test.go | 109 +++++++ pkg/logging/bigquery/bigquery_test.go | 2 +- pkg/logging/s3/create.go | 84 +++++ pkg/logging/s3/delete.go | 47 +++ pkg/logging/s3/describe.go | 66 ++++ pkg/logging/s3/doc.go | 3 + pkg/logging/s3/list.go | 82 +++++ pkg/logging/s3/root.go | 28 ++ pkg/logging/s3/s3_test.go | 453 ++++++++++++++++++++++++++ pkg/logging/s3/update.go | 198 +++++++++++ pkg/mock/api.go | 31 ++ 13 files changed, 1123 insertions(+), 1 deletion(-) create mode 100644 pkg/logging/s3/create.go create mode 100644 pkg/logging/s3/delete.go create mode 100644 pkg/logging/s3/describe.go create mode 100644 pkg/logging/s3/doc.go create mode 100644 pkg/logging/s3/list.go create mode 100644 pkg/logging/s3/root.go create mode 100644 pkg/logging/s3/s3_test.go create mode 100644 pkg/logging/s3/update.go diff --git a/pkg/api/interface.go b/pkg/api/interface.go index 66211ac71..04cc4f777 100644 --- a/pkg/api/interface.go +++ b/pkg/api/interface.go @@ -58,6 +58,12 @@ type Interface interface { UpdateBigQuery(*fastly.UpdateBigQueryInput) (*fastly.BigQuery, error) DeleteBigQuery(*fastly.DeleteBigQueryInput) error + CreateS3(*fastly.CreateS3Input) (*fastly.S3, error) + ListS3s(*fastly.ListS3sInput) ([]*fastly.S3, error) + GetS3(*fastly.GetS3Input) (*fastly.S3, error) + UpdateS3(*fastly.UpdateS3Input) (*fastly.S3, error) + DeleteS3(*fastly.DeleteS3Input) error + GetUser(*fastly.GetUserInput) (*fastly.User, error) } diff --git a/pkg/app/run.go b/pkg/app/run.go index 8766294e5..a30b5d5df 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -20,6 +20,7 @@ import ( "github.com/fastly/cli/pkg/healthcheck" "github.com/fastly/cli/pkg/logging" "github.com/fastly/cli/pkg/logging/bigquery" + "github.com/fastly/cli/pkg/logging/s3" "github.com/fastly/cli/pkg/service" "github.com/fastly/cli/pkg/serviceversion" "github.com/fastly/cli/pkg/text" @@ -126,6 +127,13 @@ func Run(args []string, env config.Environment, file config.File, configFilePath bigQueryUpdate := bigquery.NewUpdateCommand(bigQueryRoot.CmdClause, &globals) bigQueryDelete := bigquery.NewDeleteCommand(bigQueryRoot.CmdClause, &globals) + s3Root := s3.NewRootCommand(loggingRoot.CmdClause, &globals) + s3Create := s3.NewCreateCommand(s3Root.CmdClause, &globals) + s3List := s3.NewListCommand(s3Root.CmdClause, &globals) + s3Describe := s3.NewDescribeCommand(s3Root.CmdClause, &globals) + s3Update := s3.NewUpdateCommand(s3Root.CmdClause, &globals) + s3Delete := s3.NewDeleteCommand(s3Root.CmdClause, &globals) + commands := []common.Command{ configureRoot, whoamiRoot, @@ -183,6 +191,13 @@ func Run(args []string, env config.Environment, file config.File, configFilePath bigQueryDescribe, bigQueryUpdate, bigQueryDelete, + + s3Root, + s3Create, + s3List, + s3Describe, + s3Update, + s3Delete, } // Handle parse errors and display contextal usage if possible. Due to bugs diff --git a/pkg/app/run_test.go b/pkg/app/run_test.go index ddd80305f..492324b86 100644 --- a/pkg/app/run_test.go +++ b/pkg/app/run_test.go @@ -618,6 +618,115 @@ COMMANDS --version=VERSION Number of service version -n, --name=NAME The name of the BigQuery logging object + logging s3 create --name=NAME --version=VERSION --bucket=BUCKET --access-key=ACCESS-KEY --secret-key=SECRET-KEY [] + Create an Amazon S3 logging endpoint on a Fastly service version + + -n, --name=NAME The name of the S3 logging object. Used as a + primary key for API access + -s, --service-id=SERVICE-ID Service ID + --version=VERSION Number of service version + --bucket=BUCKET Your S3 bucket name + --access-key=ACCESS-KEY Your S3 account access key + --secret-key=SECRET-KEY Your S3 account secret key + --domain=DOMAIN The domain of the S3 endpoint + --path=PATH The path to upload logs to + --period=PERIOD How frequently log files are finalized so they + can be available for reading (in seconds, + default 3600) + --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when + dumping logs (default 0, no compression) + --format=FORMAT Apache style log formatting + --format-version=FORMAT-VERSION + The version of the custom logging format used + for the configured endpoint. Can be either 2 + (default) or 1 + --message-type=MESSAGE-TYPE + How the message should be formatted. One of: + classic (default), loggly, logplex or blank + --response-condition=RESPONSE-CONDITION + The name of an existing condition in the + configured endpoint, or leave blank to always + execute + --timestamp-format=TIMESTAMP-FORMAT + strftime specified timestamp formatting + (default "%Y-%m-%dT%H:%M:%S.000") + --redundancy=REDUNDANCY The S3 redundancy level. Can be either standard + or reduced_redundancy + --placement=PLACEMENT Where in the generated VCL the logging call + should be placed, overriding any format_version + default. Can be none or waf_debug + --server-side-encryption=SERVER-SIDE-ENCRYPTION + Set to enable S3 Server Side Encryption. Can be + either AES256 or aws:kms + --server-side-encryption-kms-key-id=SERVER-SIDE-ENCRYPTION-KMS-KEY-ID + Server-side KMS Key ID. Must be set if + server-side-encryption is set to aws:kms + + logging s3 list --version=VERSION [] + List S3 endpoints on a Fastly service version + + -s, --service-id=SERVICE-ID Service ID + --version=VERSION Number of service version + + logging s3 describe --version=VERSION --name=NAME [] + Show detailed information about a S3 logging endpoint on a Fastly service + version + + -s, --service-id=SERVICE-ID Service ID + --version=VERSION Number of service version + -d, --name=NAME The name of the S3 logging object + + logging s3 update --version=VERSION --name=NAME [] + Update a S3 logging endpoint on a Fastly service version + + -s, --service-id=SERVICE-ID Service ID + --version=VERSION Number of service version + -n, --name=NAME The name of the S3 logging object + --new-name=NEW-NAME New name of the S3 logging object + --bucket=BUCKET Your S3 bucket name + --access-key=ACCESS-KEY Your S3 account access key + --secret-key=SECRET-KEY Your S3 account secret key + --domain=DOMAIN The domain of the S3 endpoint + --path=PATH The path to upload logs to + --period=PERIOD How frequently log files are finalized so they + can be available for reading (in seconds, + default 3600) + --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when + dumping logs (default 0, no compression) + --format=FORMAT Apache style log formatting + --format-version=FORMAT-VERSION + The version of the custom logging format used + for the configured endpoint. Can be either 2 + (default) or 1 + --message-type=MESSAGE-TYPE + How the message should be formatted. One of: + classic (default), loggly, logplex or blank + --response-condition=RESPONSE-CONDITION + The name of an existing condition in the + configured endpoint, or leave blank to always + execute + --timestamp-format=TIMESTAMP-FORMAT + strftime specified timestamp formatting + (default "%Y-%m-%dT%H:%M:%S.000") + --redundancy=REDUNDANCY The S3 redundancy level. Can be either standard + or reduced_redundancy + --placement=PLACEMENT Where in the generated VCL the logging call + should be placed, overriding any format_version + default. Can be none or waf_debug + --server-side-encryption=SERVER-SIDE-ENCRYPTION + Set to enable S3 Server Side Encryption. Can be + either AES256 or aws:kms + --server-side-encryption-kms-key-id=SERVER-SIDE-ENCRYPTION-KMS-KEY-ID + Server-side KMS Key ID. Must be set if + server-side-encryption is set to aws:kms + + logging s3 delete --version=VERSION --name=NAME [] + Delete a S3 logging endpoint on a Fastly service version + + -s, --service-id=SERVICE-ID Service ID + --version=VERSION Number of service version + -n, --name=NAME The name of the S3 logging object + For help on a specific command, try e.g. fastly help configure diff --git a/pkg/logging/bigquery/bigquery_test.go b/pkg/logging/bigquery/bigquery_test.go index 735f9d0bc..c9a18aa94 100644 --- a/pkg/logging/bigquery/bigquery_test.go +++ b/pkg/logging/bigquery/bigquery_test.go @@ -115,7 +115,7 @@ func TestBigQueryList(t *testing.T) { } } -func TestBiogQueryDescribe(t *testing.T) { +func TestBigQueryDescribe(t *testing.T) { for _, testcase := range []struct { args []string api mock.API diff --git a/pkg/logging/s3/create.go b/pkg/logging/s3/create.go new file mode 100644 index 000000000..1f58d86af --- /dev/null +++ b/pkg/logging/s3/create.go @@ -0,0 +1,84 @@ +package s3 + +import ( + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/compute/manifest" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/errors" + "github.com/fastly/cli/pkg/text" + "github.com/fastly/go-fastly/fastly" +) + +// CreateCommand calls the Fastly API to create Amazon S3 logging endpoints. +type CreateCommand struct { + common.Base + manifest manifest.Data + Input fastly.CreateS3Input + + redundancy string + serverSideEncryption string +} + +// NewCreateCommand returns a usable command registered under the parent. +func NewCreateCommand(parent common.Registerer, globals *config.Data) *CreateCommand { + var c CreateCommand + c.Globals = globals + c.manifest.File.Read(manifest.Filename) + c.CmdClause = parent.Command("create", "Create an Amazon S3 logging endpoint on a Fastly service version").Alias("add") + + c.CmdClause.Flag("name", "The name of the S3 logging object. Used as a primary key for API access").Short('n').Required().StringVar(&c.Input.Name) + c.CmdClause.Flag("service-id", "Service ID").Short('s').StringVar(&c.manifest.Flag.ServiceID) + c.CmdClause.Flag("version", "Number of service version").Required().IntVar(&c.Input.Version) + + c.CmdClause.Flag("bucket", "Your S3 bucket name").Required().StringVar(&c.Input.BucketName) + c.CmdClause.Flag("access-key", "Your S3 account access key").Required().StringVar(&c.Input.AccessKey) + c.CmdClause.Flag("secret-key", "Your S3 account secret key").Required().StringVar(&c.Input.SecretKey) + + c.CmdClause.Flag("domain", "The domain of the S3 endpoint").StringVar(&c.Input.Domain) + c.CmdClause.Flag("path", "The path to upload logs to").StringVar(&c.Input.Path) + c.CmdClause.Flag("period", "How frequently log files are finalized so they can be available for reading (in seconds, default 3600)").UintVar(&c.Input.Period) + c.CmdClause.Flag("gzip-level", "What level of GZIP encoding to have when dumping logs (default 0, no compression)").UintVar(&c.Input.GzipLevel) + c.CmdClause.Flag("format", "Apache style log formatting").StringVar(&c.Input.Format) + c.CmdClause.Flag("format-version", "The version of the custom logging format used for the configured endpoint. Can be either 2 (default) or 1").UintVar(&c.Input.FormatVersion) + c.CmdClause.Flag("message-type", "How the message should be formatted. One of: classic (default), loggly, logplex or blank").StringVar(&c.Input.MessageType) + c.CmdClause.Flag("response-condition", "The name of an existing condition in the configured endpoint, or leave blank to always execute").StringVar(&c.Input.ResponseCondition) + c.CmdClause.Flag("timestamp-format", `strftime specified timestamp formatting (default "%Y-%m-%dT%H:%M:%S.000")`).StringVar(&c.Input.TimestampFormat) + c.CmdClause.Flag("redundancy", "The S3 redundancy level. Can be either standard or reduced_redundancy").EnumVar(&c.redundancy, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyReduced)) + c.CmdClause.Flag("placement", "Where in the generated VCL the logging call should be placed, overriding any format_version default. Can be none or waf_debug").StringVar(&c.Input.Placement) + c.CmdClause.Flag("server-side-encryption", "Set to enable S3 Server Side Encryption. Can be either AES256 or aws:kms").EnumVar(&c.serverSideEncryption, string(fastly.S3ServerSideEncryptionAES), string(fastly.S3ServerSideEncryptionKMS)) + c.CmdClause.Flag("server-side-encryption-kms-key-id", "Server-side KMS Key ID. Must be set if server-side-encryption is set to aws:kms").StringVar(&c.Input.ServerSideEncryptionKMSKeyID) + return &c +} + +// Exec invokes the application logic for the command. +func (c *CreateCommand) Exec(in io.Reader, out io.Writer) error { + serviceID, source := c.manifest.ServiceID() + if source == manifest.SourceUndefined { + return errors.ErrNoServiceID + } + c.Input.Service = serviceID + + switch c.redundancy { + case string(fastly.S3RedundancyStandard): + c.Input.Redundancy = fastly.S3RedundancyStandard + case string(fastly.S3RedundancyReduced): + c.Input.Redundancy = fastly.S3RedundancyReduced + } + + switch c.serverSideEncryption { + case string(fastly.S3ServerSideEncryptionAES): + c.Input.ServerSideEncryption = fastly.S3ServerSideEncryptionAES + case string(fastly.S3ServerSideEncryptionKMS): + c.Input.ServerSideEncryption = fastly.S3ServerSideEncryptionKMS + } + + d, err := c.Globals.Client.CreateS3(&c.Input) + if err != nil { + return err + } + + text.Success(out, "Created S3 logging endpoint %s (service %s version %d)", d.Name, d.ServiceID, d.Version) + return nil +} diff --git a/pkg/logging/s3/delete.go b/pkg/logging/s3/delete.go new file mode 100644 index 000000000..e2efca77c --- /dev/null +++ b/pkg/logging/s3/delete.go @@ -0,0 +1,47 @@ +package s3 + +import ( + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/compute/manifest" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/errors" + "github.com/fastly/cli/pkg/text" + "github.com/fastly/go-fastly/fastly" +) + +// DeleteCommand calls the Fastly API to delete Amazon S3 logging endpoints. +type DeleteCommand struct { + common.Base + manifest manifest.Data + Input fastly.DeleteS3Input +} + +// NewDeleteCommand returns a usable command registered under the parent. +func NewDeleteCommand(parent common.Registerer, globals *config.Data) *DeleteCommand { + var c DeleteCommand + c.Globals = globals + c.manifest.File.Read(manifest.Filename) + c.CmdClause = parent.Command("delete", "Delete a S3 logging endpoint on a Fastly service version").Alias("remove") + c.CmdClause.Flag("service-id", "Service ID").Short('s').StringVar(&c.manifest.Flag.ServiceID) + c.CmdClause.Flag("version", "Number of service version").Required().IntVar(&c.Input.Version) + c.CmdClause.Flag("name", "The name of the S3 logging object").Short('n').Required().StringVar(&c.Input.Name) + return &c +} + +// Exec invokes the application logic for the command. +func (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error { + serviceID, source := c.manifest.ServiceID() + if source == manifest.SourceUndefined { + return errors.ErrNoServiceID + } + c.Input.Service = serviceID + + if err := c.Globals.Client.DeleteS3(&c.Input); err != nil { + return err + } + + text.Success(out, "Deleted S3 logging endpoint %s (service %s version %d)", c.Input.Name, c.Input.Service, c.Input.Version) + return nil +} diff --git a/pkg/logging/s3/describe.go b/pkg/logging/s3/describe.go new file mode 100644 index 000000000..f0d757955 --- /dev/null +++ b/pkg/logging/s3/describe.go @@ -0,0 +1,66 @@ +package s3 + +import ( + "fmt" + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/compute/manifest" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/errors" + "github.com/fastly/go-fastly/fastly" +) + +// DescribeCommand calls the Fastly API to describe an Amazon S3 logging endpoint. +type DescribeCommand struct { + common.Base + manifest manifest.Data + Input fastly.GetS3Input +} + +// NewDescribeCommand returns a usable command registered under the parent. +func NewDescribeCommand(parent common.Registerer, globals *config.Data) *DescribeCommand { + var c DescribeCommand + c.Globals = globals + c.manifest.File.Read(manifest.Filename) + c.CmdClause = parent.Command("describe", "Show detailed information about a S3 logging endpoint on a Fastly service version").Alias("get") + c.CmdClause.Flag("service-id", "Service ID").Short('s').StringVar(&c.manifest.Flag.ServiceID) + c.CmdClause.Flag("version", "Number of service version").Required().IntVar(&c.Input.Version) + c.CmdClause.Flag("name", "The name of the S3 logging object").Short('d').Required().StringVar(&c.Input.Name) + return &c +} + +// Exec invokes the application logic for the command. +func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error { + serviceID, source := c.manifest.ServiceID() + if source == manifest.SourceUndefined { + return errors.ErrNoServiceID + } + c.Input.Service = serviceID + + s3, err := c.Globals.Client.GetS3(&c.Input) + if err != nil { + return err + } + + fmt.Fprintf(out, "Service ID: %s\n", s3.ServiceID) + fmt.Fprintf(out, "Version: %d\n", s3.Version) + fmt.Fprintf(out, "Name: %s\n", s3.Name) + fmt.Fprintf(out, "Bucket: %s\n", s3.BucketName) + fmt.Fprintf(out, "Access key: %s\n", s3.AccessKey) + fmt.Fprintf(out, "Secret key: %s\n", s3.SecretKey) + fmt.Fprintf(out, "Path: %s\n", s3.Path) + fmt.Fprintf(out, "Period: %d\n", s3.Period) + fmt.Fprintf(out, "GZip level: %d\n", s3.GzipLevel) + fmt.Fprintf(out, "Format: %s\n", s3.Format) + fmt.Fprintf(out, "Format version: %d\n", s3.FormatVersion) + fmt.Fprintf(out, "Response condition: %s\n", s3.ResponseCondition) + fmt.Fprintf(out, "Message type: %s\n", s3.MessageType) + fmt.Fprintf(out, "Timestamp format: %s\n", s3.TimestampFormat) + fmt.Fprintf(out, "Placement: %s\n", s3.Placement) + fmt.Fprintf(out, "Redundancy: %s\n", s3.Redundancy) + fmt.Fprintf(out, "Server-side encryption: %s\n", s3.ServerSideEncryption) + fmt.Fprintf(out, "Server-side encryption KMS key ID: %s\n", s3.ServerSideEncryption) + + return nil +} diff --git a/pkg/logging/s3/doc.go b/pkg/logging/s3/doc.go new file mode 100644 index 000000000..780f378c8 --- /dev/null +++ b/pkg/logging/s3/doc.go @@ -0,0 +1,3 @@ +// Package s3 contains commands to inspect and manipulate Fastly service S3 +// logging endpoints. +package s3 diff --git a/pkg/logging/s3/list.go b/pkg/logging/s3/list.go new file mode 100644 index 000000000..1ab1217ef --- /dev/null +++ b/pkg/logging/s3/list.go @@ -0,0 +1,82 @@ +package s3 + +import ( + "fmt" + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/compute/manifest" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/errors" + "github.com/fastly/cli/pkg/text" + "github.com/fastly/go-fastly/fastly" +) + +// ListCommand calls the Fastly API to list Amazon S3 logging endpoints. +type ListCommand struct { + common.Base + manifest manifest.Data + Input fastly.ListS3sInput +} + +// NewListCommand returns a usable command registered under the parent. +func NewListCommand(parent common.Registerer, globals *config.Data) *ListCommand { + var c ListCommand + c.Globals = globals + c.manifest.File.Read(manifest.Filename) + c.CmdClause = parent.Command("list", "List S3 endpoints on a Fastly service version") + c.CmdClause.Flag("service-id", "Service ID").Short('s').StringVar(&c.manifest.Flag.ServiceID) + c.CmdClause.Flag("version", "Number of service version").Required().IntVar(&c.Input.Version) + return &c +} + +// Exec invokes the application logic for the command. +func (c *ListCommand) Exec(in io.Reader, out io.Writer) error { + serviceID, source := c.manifest.ServiceID() + if source == manifest.SourceUndefined { + return errors.ErrNoServiceID + } + c.Input.Service = serviceID + + s3s, err := c.Globals.Client.ListS3s(&c.Input) + if err != nil { + return err + } + + if !c.Globals.Verbose() { + tw := text.NewTable(out) + tw.AddHeader("SERVICE", "VERSION", "NAME") + for _, s3 := range s3s { + tw.AddLine(s3.ServiceID, s3.Version, s3.Name) + } + tw.Print() + return nil + } + + fmt.Fprintf(out, "Service ID: %s\n", c.Input.Service) + fmt.Fprintf(out, "Version: %d\n", c.Input.Version) + for i, s3 := range s3s { + fmt.Fprintf(out, "\tS3 %d/%d\n", i+1, len(s3s)) + fmt.Fprintf(out, "\t\tService ID: %s\n", s3.ServiceID) + fmt.Fprintf(out, "\t\tVersion: %d\n", s3.Version) + fmt.Fprintf(out, "\t\tName: %s\n", s3.Name) + fmt.Fprintf(out, "\t\tBucket: %s\n", s3.BucketName) + fmt.Fprintf(out, "\t\tAccess key: %s\n", s3.AccessKey) + fmt.Fprintf(out, "\t\tSecret key: %s\n", s3.SecretKey) + fmt.Fprintf(out, "\t\tPath: %s\n", s3.Path) + fmt.Fprintf(out, "\t\tPeriod: %d\n", s3.Period) + fmt.Fprintf(out, "\t\tGZip level: %d\n", s3.GzipLevel) + fmt.Fprintf(out, "\t\tFormat: %s\n", s3.Format) + fmt.Fprintf(out, "\t\tFormat version: %d\n", s3.FormatVersion) + fmt.Fprintf(out, "\t\tResponse condition: %s\n", s3.ResponseCondition) + fmt.Fprintf(out, "\t\tMessage type: %s\n", s3.MessageType) + fmt.Fprintf(out, "\t\tTimestamp format: %s\n", s3.TimestampFormat) + fmt.Fprintf(out, "\t\tPlacement: %s\n", s3.Placement) + fmt.Fprintf(out, "\t\tRedundancy: %s\n", s3.Redundancy) + fmt.Fprintf(out, "\t\tServer-side encryption: %s\n", s3.ServerSideEncryption) + fmt.Fprintf(out, "\t\tServer-side encryption KMS key ID: %s\n", s3.ServerSideEncryption) + } + fmt.Fprintln(out) + + return nil +} diff --git a/pkg/logging/s3/root.go b/pkg/logging/s3/root.go new file mode 100644 index 000000000..cfcd78ff5 --- /dev/null +++ b/pkg/logging/s3/root.go @@ -0,0 +1,28 @@ +package s3 + +import ( + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/config" +) + +// RootCommand is the parent command for all subcommands in this package. +// It should be installed under the primary root command. +type RootCommand struct { + common.Base + // no flags +} + +// NewRootCommand returns a new command registered in the parent. +func NewRootCommand(parent common.Registerer, globals *config.Data) *RootCommand { + var c RootCommand + c.Globals = globals + c.CmdClause = parent.Command("s3", "Manipulate Fastly service version S3 logging endpoints.") + return &c +} + +// Exec implements the command interface. +func (c *RootCommand) Exec(in io.Reader, out io.Writer) error { + panic("unreachable") +} diff --git a/pkg/logging/s3/s3_test.go b/pkg/logging/s3/s3_test.go new file mode 100644 index 000000000..151c7f153 --- /dev/null +++ b/pkg/logging/s3/s3_test.go @@ -0,0 +1,453 @@ +package s3_test + +import ( + "bytes" + "errors" + "io" + "net/http" + "strings" + "testing" + + "github.com/fastly/cli/pkg/app" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/mock" + "github.com/fastly/cli/pkg/testutil" + "github.com/fastly/cli/pkg/update" + "github.com/fastly/go-fastly/fastly" +) + +func TestS3Create(t *testing.T) { + for _, testcase := range []struct { + args []string + api mock.API + wantError string + wantOutput string + }{ + { + args: []string{"logging", "s3", "create", "--service-id", "123", "--version", "1", "--name", "log", "--bucket", "log", "--access-key", "foo"}, + wantError: "error parsing arguments: required flag --secret-key not provided", + }, + { + args: []string{"logging", "s3", "create", "--service-id", "123", "--version", "1", "--name", "log", "--bucket", "log", "--access-key", "foo", "--secret-key", "bar"}, + api: mock.API{CreateS3Fn: createS3OK}, + wantOutput: "Created S3 logging endpoint log (service 123 version 1)", + }, + { + args: []string{"logging", "s3", "create", "--service-id", "123", "--version", "1", "--name", "log", "--bucket", "log", "--access-key", "foo", "--secret-key", "bar"}, + api: mock.API{CreateS3Fn: createS3Error}, + wantError: errTest.Error(), + }, + } { + t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { + var ( + args = testcase.args + env = config.Environment{} + file = config.File{} + appConfigFile = "/dev/null" + clientFactory = mock.APIClient(testcase.api) + httpClient = http.DefaultClient + versioner update.Versioner = nil + in io.Reader = nil + out bytes.Buffer + ) + err := app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, &out) + testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertStringContains(t, out.String(), testcase.wantOutput) + }) + } +} + +func TestS3List(t *testing.T) { + for _, testcase := range []struct { + args []string + api mock.API + wantError string + wantOutput string + }{ + { + args: []string{"logging", "s3", "list", "--service-id", "123", "--version", "1"}, + api: mock.API{ListS3sFn: listS3sOK}, + wantOutput: listS3sShortOutput, + }, + { + args: []string{"logging", "s3", "list", "--service-id", "123", "--version", "1", "--verbose"}, + api: mock.API{ListS3sFn: listS3sOK}, + wantOutput: listS3sVerboseOutput, + }, + { + args: []string{"logging", "s3", "list", "--service-id", "123", "--version", "1", "-v"}, + api: mock.API{ListS3sFn: listS3sOK}, + wantOutput: listS3sVerboseOutput, + }, + { + args: []string{"logging", "s3", "--verbose", "list", "--service-id", "123", "--version", "1"}, + api: mock.API{ListS3sFn: listS3sOK}, + wantOutput: listS3sVerboseOutput, + }, + { + args: []string{"logging", "-v", "s3", "list", "--service-id", "123", "--version", "1"}, + api: mock.API{ListS3sFn: listS3sOK}, + wantOutput: listS3sVerboseOutput, + }, + { + args: []string{"logging", "s3", "list", "--service-id", "123", "--version", "1"}, + api: mock.API{ListS3sFn: listS3sError}, + wantError: errTest.Error(), + }, + } { + t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { + var ( + args = testcase.args + env = config.Environment{} + file = config.File{} + appConfigFile = "/dev/null" + clientFactory = mock.APIClient(testcase.api) + httpClient = http.DefaultClient + versioner update.Versioner = nil + in io.Reader = nil + out bytes.Buffer + ) + err := app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, &out) + testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertString(t, testcase.wantOutput, out.String()) + }) + } +} + +func TestS3Describe(t *testing.T) { + for _, testcase := range []struct { + args []string + api mock.API + wantError string + wantOutput string + }{ + { + args: []string{"logging", "s3", "describe", "--service-id", "123", "--version", "1"}, + wantError: "error parsing arguments: required flag --name not provided", + }, + { + args: []string{"logging", "s3", "describe", "--service-id", "123", "--version", "1", "--name", "logs"}, + api: mock.API{GetS3Fn: getS3Error}, + wantError: errTest.Error(), + }, + { + args: []string{"logging", "s3", "describe", "--service-id", "123", "--version", "1", "--name", "logs"}, + api: mock.API{GetS3Fn: getS3OK}, + wantOutput: describeS3Output, + }, + } { + t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { + var ( + args = testcase.args + env = config.Environment{} + file = config.File{} + appConfigFile = "/dev/null" + clientFactory = mock.APIClient(testcase.api) + httpClient = http.DefaultClient + versioner update.Versioner = nil + in io.Reader = nil + out bytes.Buffer + ) + err := app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, &out) + testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertString(t, testcase.wantOutput, out.String()) + }) + } +} + +func TestS3Update(t *testing.T) { + for _, testcase := range []struct { + args []string + api mock.API + wantError string + wantOutput string + }{ + { + args: []string{"logging", "s3", "update", "--service-id", "123", "--version", "1", "--new-name", "log"}, + wantError: "error parsing arguments: required flag --name not provided", + }, + { + args: []string{"logging", "s3", "update", "--service-id", "123", "--version", "1", "--name", "logs", "--new-name", "log"}, + api: mock.API{ + GetS3Fn: getS3Error, + UpdateS3Fn: updateS3OK, + }, + wantError: errTest.Error(), + }, + { + args: []string{"logging", "s3", "update", "--service-id", "123", "--version", "1", "--name", "logs", "--new-name", "log"}, + api: mock.API{ + GetS3Fn: getS3OK, + UpdateS3Fn: updateS3Error, + }, + wantError: errTest.Error(), + }, + { + args: []string{"logging", "s3", "update", "--service-id", "123", "--version", "1", "--name", "logs", "--new-name", "log"}, + api: mock.API{ + GetS3Fn: getS3OK, + UpdateS3Fn: updateS3OK, + }, + wantOutput: "Updated S3 logging endpoint log (service 123 version 1)", + }, + } { + t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { + var ( + args = testcase.args + env = config.Environment{} + file = config.File{} + appConfigFile = "/dev/null" + clientFactory = mock.APIClient(testcase.api) + httpClient = http.DefaultClient + versioner update.Versioner = nil + in io.Reader = nil + out bytes.Buffer + ) + err := app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, &out) + testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertStringContains(t, out.String(), testcase.wantOutput) + }) + } +} + +func TestS3Delete(t *testing.T) { + for _, testcase := range []struct { + args []string + api mock.API + wantError string + wantOutput string + }{ + { + args: []string{"logging", "s3", "delete", "--service-id", "123", "--version", "1"}, + wantError: "error parsing arguments: required flag --name not provided", + }, + { + args: []string{"logging", "s3", "delete", "--service-id", "123", "--version", "1", "--name", "logs"}, + api: mock.API{DeleteS3Fn: deleteS3Error}, + wantError: errTest.Error(), + }, + { + args: []string{"logging", "s3", "delete", "--service-id", "123", "--version", "1", "--name", "logs"}, + api: mock.API{DeleteS3Fn: deleteS3OK}, + wantOutput: "Deleted S3 logging endpoint logs (service 123 version 1)", + }, + } { + t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { + var ( + args = testcase.args + env = config.Environment{} + file = config.File{} + appConfigFile = "/dev/null" + clientFactory = mock.APIClient(testcase.api) + httpClient = http.DefaultClient + versioner update.Versioner = nil + in io.Reader = nil + out bytes.Buffer + ) + err := app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, &out) + testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertStringContains(t, out.String(), testcase.wantOutput) + }) + } +} + +var errTest = errors.New("fixture error") + +func createS3OK(i *fastly.CreateS3Input) (*fastly.S3, error) { + return &fastly.S3{ + ServiceID: i.Service, + Version: i.Version, + Name: i.Name, + }, nil +} + +func createS3Error(i *fastly.CreateS3Input) (*fastly.S3, error) { + return nil, errTest +} + +func listS3sOK(i *fastly.ListS3sInput) ([]*fastly.S3, error) { + return []*fastly.S3{ + &fastly.S3{ + ServiceID: i.Service, + Version: i.Version, + Name: "logs", + BucketName: "my-logs", + AccessKey: "1234", + SecretKey: "-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA", + Domain: "https://s3.us-east-1.amazonaws.com", + Path: "logs/", + Period: 3600, + GzipLevel: 9, + Format: `%h %l %u %t "%r" %>s %b`, + FormatVersion: 2, + MessageType: "classic", + ResponseCondition: "Prevent default logging", + TimestampFormat: "%Y-%m-%dT%H:%M:%S.000", + Redundancy: "standard", + Placement: "none", + ServerSideEncryption: "aws:kms", + ServerSideEncryptionKMSKeyID: "1234", + }, + &fastly.S3{ + ServiceID: i.Service, + Version: i.Version, + Name: "analytics", + BucketName: "analytics", + AccessKey: "1234", + SecretKey: "-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA", + Domain: "https://s3.us-east-2.amazonaws.com", + Path: "logs/", + Period: 86400, + GzipLevel: 9, + Format: `%h %l %u %t "%r" %>s %b`, + FormatVersion: 2, + MessageType: "classic", + ResponseCondition: "Prevent default logging", + TimestampFormat: "%Y-%m-%dT%H:%M:%S.000", + Redundancy: "standard", + Placement: "none", + ServerSideEncryption: "aws:kms", + ServerSideEncryptionKMSKeyID: "1234", + }, + }, nil +} + +func listS3sError(i *fastly.ListS3sInput) ([]*fastly.S3, error) { + return nil, errTest +} + +var listS3sShortOutput = strings.TrimSpace(` +SERVICE VERSION NAME +123 1 logs +123 1 analytics +`) + "\n" + +var listS3sVerboseOutput = strings.TrimSpace(` +Fastly API token not provided +Fastly API endpoint: https://api.fastly.com +Service ID: 123 +Version: 1 + S3 1/2 + Service ID: 123 + Version: 1 + Name: logs + Bucket: my-logs + Access key: 1234 + Secret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA + Path: logs/ + Period: 3600 + GZip level: 9 + Format: %h %l %u %t "%r" %>s %b + Format version: 2 + Response condition: Prevent default logging + Message type: classic + Timestamp format: %Y-%m-%dT%H:%M:%S.000 + Placement: none + Redundancy: standard + Server-side encryption: aws:kms + Server-side encryption KMS key ID: aws:kms + S3 2/2 + Service ID: 123 + Version: 1 + Name: analytics + Bucket: analytics + Access key: 1234 + Secret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA + Path: logs/ + Period: 86400 + GZip level: 9 + Format: %h %l %u %t "%r" %>s %b + Format version: 2 + Response condition: Prevent default logging + Message type: classic + Timestamp format: %Y-%m-%dT%H:%M:%S.000 + Placement: none + Redundancy: standard + Server-side encryption: aws:kms + Server-side encryption KMS key ID: aws:kms +`) + "\n\n" + +func getS3OK(i *fastly.GetS3Input) (*fastly.S3, error) { + return &fastly.S3{ + ServiceID: i.Service, + Version: i.Version, + Name: "logs", + BucketName: "my-logs", + AccessKey: "1234", + SecretKey: "-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA", + Domain: "https://s3.us-east-1.amazonaws.com", + Path: "logs/", + Period: 3600, + GzipLevel: 9, + Format: `%h %l %u %t "%r" %>s %b`, + FormatVersion: 2, + MessageType: "classic", + ResponseCondition: "Prevent default logging", + TimestampFormat: "%Y-%m-%dT%H:%M:%S.000", + Redundancy: "standard", + Placement: "none", + ServerSideEncryption: "aws:kms", + ServerSideEncryptionKMSKeyID: "1234", + }, nil +} + +func getS3Error(i *fastly.GetS3Input) (*fastly.S3, error) { + return nil, errTest +} + +var describeS3Output = strings.TrimSpace(` +Service ID: 123 +Version: 1 +Name: logs +Bucket: my-logs +Access key: 1234 +Secret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA +Path: logs/ +Period: 3600 +GZip level: 9 +Format: %h %l %u %t "%r" %>s %b +Format version: 2 +Response condition: Prevent default logging +Message type: classic +Timestamp format: %Y-%m-%dT%H:%M:%S.000 +Placement: none +Redundancy: standard +Server-side encryption: aws:kms +Server-side encryption KMS key ID: aws:kms +`) + "\n" + +func updateS3OK(i *fastly.UpdateS3Input) (*fastly.S3, error) { + return &fastly.S3{ + ServiceID: i.Service, + Version: i.Version, + Name: "log", + BucketName: "my-logs", + AccessKey: "1234", + SecretKey: "-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA", + Domain: "https://s3.us-east-1.amazonaws.com", + Path: "logs/", + Period: 3600, + GzipLevel: 9, + Format: `%h %l %u %t "%r" %>s %b`, + FormatVersion: 2, + MessageType: "classic", + ResponseCondition: "Prevent default logging", + TimestampFormat: "%Y-%m-%dT%H:%M:%S.000", + Redundancy: "standard", + Placement: "none", + ServerSideEncryption: "aws:kms", + ServerSideEncryptionKMSKeyID: "1234", + }, nil +} + +func updateS3Error(i *fastly.UpdateS3Input) (*fastly.S3, error) { + return nil, errTest +} + +func deleteS3OK(i *fastly.DeleteS3Input) error { + return nil +} + +func deleteS3Error(i *fastly.DeleteS3Input) error { + return errTest +} diff --git a/pkg/logging/s3/update.go b/pkg/logging/s3/update.go new file mode 100644 index 000000000..2b7f76a2f --- /dev/null +++ b/pkg/logging/s3/update.go @@ -0,0 +1,198 @@ +package s3 + +import ( + "io" + + "github.com/fastly/cli/pkg/common" + "github.com/fastly/cli/pkg/compute/manifest" + "github.com/fastly/cli/pkg/config" + "github.com/fastly/cli/pkg/errors" + "github.com/fastly/cli/pkg/text" + "github.com/fastly/go-fastly/fastly" +) + +// UpdateCommand calls the Fastly API to update Amazon S3 logging endpoints. +type UpdateCommand struct { + common.Base + manifest manifest.Data + + Input fastly.GetS3Input + + NewName common.OptionalString + + BucketName common.OptionalString + AccessKey common.OptionalString + SecretKey common.OptionalString + + Domain common.OptionalString + Path common.OptionalString + Period common.OptionalUint + GzipLevel common.OptionalUint + Format common.OptionalString + FormatVersion common.OptionalUint + MessageType common.OptionalString + ResponseCondition common.OptionalString + TimestampFormat common.OptionalString + Redundancy common.OptionalString + Placement common.OptionalString + ServerSideEncryption common.OptionalString + ServerSideEncryptionKMSKeyID common.OptionalString +} + +// NewUpdateCommand returns a usable command registered under the parent. +func NewUpdateCommand(parent common.Registerer, globals *config.Data) *UpdateCommand { + var c UpdateCommand + c.Globals = globals + c.manifest.File.Read(manifest.Filename) + + c.CmdClause = parent.Command("update", "Update a S3 logging endpoint on a Fastly service version") + + c.CmdClause.Flag("service-id", "Service ID").Short('s').StringVar(&c.manifest.Flag.ServiceID) + c.CmdClause.Flag("version", "Number of service version").Required().IntVar(&c.Input.Version) + c.CmdClause.Flag("name", "The name of the S3 logging object").Short('n').Required().StringVar(&c.Input.Name) + + c.CmdClause.Flag("new-name", "New name of the S3 logging object").Action(c.NewName.Set).StringVar(&c.NewName.Value) + c.CmdClause.Flag("bucket", "Your S3 bucket name").Action(c.BucketName.Set).StringVar(&c.BucketName.Value) + c.CmdClause.Flag("access-key", "Your S3 account access key").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value) + c.CmdClause.Flag("secret-key", "Your S3 account secret key").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value) + + c.CmdClause.Flag("domain", "The domain of the S3 endpoint").Action(c.Domain.Set).StringVar(&c.Domain.Value) + c.CmdClause.Flag("path", "The path to upload logs to").Action(c.Path.Set).StringVar(&c.Path.Value) + c.CmdClause.Flag("period", "How frequently log files are finalized so they can be available for reading (in seconds, default 3600)").Action(c.Period.Set).UintVar(&c.Period.Value) + c.CmdClause.Flag("gzip-level", "What level of GZIP encoding to have when dumping logs (default 0, no compression)").Action(c.GzipLevel.Set).UintVar(&c.GzipLevel.Value) + c.CmdClause.Flag("format", "Apache style log formatting").Action(c.Format.Set).StringVar(&c.Format.Value) + c.CmdClause.Flag("format-version", "The version of the custom logging format used for the configured endpoint. Can be either 2 (default) or 1").Action(c.FormatVersion.Set).UintVar(&c.FormatVersion.Value) + c.CmdClause.Flag("message-type", "How the message should be formatted. One of: classic (default), loggly, logplex or blank").Action(c.MessageType.Set).StringVar(&c.MessageType.Value) + c.CmdClause.Flag("response-condition", "The name of an existing condition in the configured endpoint, or leave blank to always execute").Action(c.ResponseCondition.Set).StringVar(&c.ResponseCondition.Value) + c.CmdClause.Flag("timestamp-format", `strftime specified timestamp formatting (default "%Y-%m-%dT%H:%M:%S.000")`).Action(c.TimestampFormat.Set).StringVar(&c.TimestampFormat.Value) + c.CmdClause.Flag("redundancy", "The S3 redundancy level. Can be either standard or reduced_redundancy").Action(c.Redundancy.Set).EnumVar(&c.Redundancy.Value, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyReduced)) + c.CmdClause.Flag("placement", "Where in the generated VCL the logging call should be placed, overriding any format_version default. Can be none or waf_debug").Action(c.Placement.Set).StringVar(&c.Placement.Value) + c.CmdClause.Flag("server-side-encryption", "Set to enable S3 Server Side Encryption. Can be either AES256 or aws:kms").Action(c.ServerSideEncryption.Set).EnumVar(&c.ServerSideEncryption.Value, string(fastly.S3ServerSideEncryptionAES), string(fastly.S3ServerSideEncryptionKMS)) + c.CmdClause.Flag("server-side-encryption-kms-key-id", "Server-side KMS Key ID. Must be set if server-side-encryption is set to aws:kms").Action(c.ServerSideEncryptionKMSKeyID.Set).StringVar(&c.ServerSideEncryptionKMSKeyID.Value) + + return &c +} + +// Exec invokes the application logic for the command. +func (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error { + serviceID, source := c.manifest.ServiceID() + if source == manifest.SourceUndefined { + return errors.ErrNoServiceID + } + c.Input.Service = serviceID + + s3, err := c.Globals.Client.GetS3(&c.Input) + if err != nil { + return err + } + + input := &fastly.UpdateS3Input{ + Service: s3.ServiceID, + Version: s3.Version, + Name: s3.Name, + NewName: s3.Name, + BucketName: s3.BucketName, + Domain: s3.Domain, + AccessKey: s3.AccessKey, + SecretKey: s3.SecretKey, + Path: s3.Path, + Period: s3.Period, + GzipLevel: s3.GzipLevel, + Format: s3.Format, + FormatVersion: s3.FormatVersion, + ResponseCondition: s3.ResponseCondition, + MessageType: s3.MessageType, + TimestampFormat: s3.TimestampFormat, + Redundancy: s3.Redundancy, + Placement: s3.Placement, + ServerSideEncryption: s3.ServerSideEncryption, + ServerSideEncryptionKMSKeyID: s3.ServerSideEncryptionKMSKeyID, + } + + // Set new values if set by user. + if c.NewName.Valid { + input.NewName = c.NewName.Value + } + + if c.BucketName.Valid { + input.BucketName = c.BucketName.Value + } + + if c.Domain.Valid { + input.Domain = c.Domain.Value + } + + if c.AccessKey.Valid { + input.AccessKey = c.AccessKey.Value + } + + if c.SecretKey.Valid { + input.SecretKey = c.SecretKey.Value + } + + if c.Path.Valid { + input.Path = c.Path.Value + } + + if c.Period.Valid { + input.Period = c.Period.Value + } + + if c.GzipLevel.Valid { + input.GzipLevel = c.GzipLevel.Value + } + + if c.Format.Valid { + input.Format = c.Format.Value + } + + if c.FormatVersion.Valid { + input.FormatVersion = c.FormatVersion.Value + } + + if c.ResponseCondition.Valid { + input.ResponseCondition = c.ResponseCondition.Value + } + + if c.MessageType.Valid { + input.MessageType = c.MessageType.Value + } + + if c.TimestampFormat.Valid { + input.TimestampFormat = c.TimestampFormat.Value + } + + if c.Redundancy.Valid { + switch c.Redundancy.Value { + case string(fastly.S3RedundancyStandard): + input.Redundancy = fastly.S3RedundancyStandard + case string(fastly.S3RedundancyReduced): + input.Redundancy = fastly.S3RedundancyReduced + } + } + + if c.Placement.Valid { + input.Placement = c.Placement.Value + } + + if c.ServerSideEncryption.Valid { + switch c.ServerSideEncryption.Value { + case string(fastly.S3ServerSideEncryptionAES): + input.ServerSideEncryption = fastly.S3ServerSideEncryptionAES + case string(fastly.S3ServerSideEncryptionKMS): + input.ServerSideEncryption = fastly.S3ServerSideEncryptionKMS + } + } + + if c.ServerSideEncryptionKMSKeyID.Valid { + input.ServerSideEncryptionKMSKeyID = c.ServerSideEncryptionKMSKeyID.Value + } + + s3, err = c.Globals.Client.UpdateS3(input) + if err != nil { + return err + } + + text.Success(out, "Updated S3 logging endpoint %s (service %s version %d)", s3.Name, s3.ServiceID, s3.Version) + return nil +} diff --git a/pkg/mock/api.go b/pkg/mock/api.go index cad74459e..bbf49df94 100644 --- a/pkg/mock/api.go +++ b/pkg/mock/api.go @@ -49,6 +49,12 @@ type API struct { UpdateBigQueryFn func(*fastly.UpdateBigQueryInput) (*fastly.BigQuery, error) DeleteBigQueryFn func(*fastly.DeleteBigQueryInput) error + CreateS3Fn func(*fastly.CreateS3Input) (*fastly.S3, error) + ListS3sFn func(*fastly.ListS3sInput) ([]*fastly.S3, error) + GetS3Fn func(*fastly.GetS3Input) (*fastly.S3, error) + UpdateS3Fn func(*fastly.UpdateS3Input) (*fastly.S3, error) + DeleteS3Fn func(*fastly.DeleteS3Input) error + GetUserFn func(*fastly.GetUserInput) (*fastly.User, error) } @@ -222,6 +228,31 @@ func (m API) DeleteBigQuery(i *fastly.DeleteBigQueryInput) error { return m.DeleteBigQueryFn(i) } +// CreateS3 implements Interface. +func (m API) CreateS3(i *fastly.CreateS3Input) (*fastly.S3, error) { + return m.CreateS3Fn(i) +} + +// ListS3s implements Interface. +func (m API) ListS3s(i *fastly.ListS3sInput) ([]*fastly.S3, error) { + return m.ListS3sFn(i) +} + +// GetS3 implements Interface. +func (m API) GetS3(i *fastly.GetS3Input) (*fastly.S3, error) { + return m.GetS3Fn(i) +} + +// UpdateS3 implements Interface. +func (m API) UpdateS3(i *fastly.UpdateS3Input) (*fastly.S3, error) { + return m.UpdateS3Fn(i) +} + +// DeleteS3 implements Interface. +func (m API) DeleteS3(i *fastly.DeleteS3Input) error { + return m.DeleteS3Fn(i) +} + // GetUser implements Interface. func (m API) GetUser(i *fastly.GetUserInput) (*fastly.User, error) { return m.GetUserFn(i)