github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/logging/s3/create.go (about)

     1  package s3
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/fastly/go-fastly/v9/fastly"
     8  
     9  	"github.com/fastly/cli/pkg/argparser"
    10  	"github.com/fastly/cli/pkg/commands/logging/common"
    11  	"github.com/fastly/cli/pkg/errors"
    12  	"github.com/fastly/cli/pkg/global"
    13  	"github.com/fastly/cli/pkg/manifest"
    14  	"github.com/fastly/cli/pkg/text"
    15  )
    16  
    17  // CreateCommand calls the Fastly API to create an Amazon S3 logging endpoint.
    18  type CreateCommand struct {
    19  	argparser.Base
    20  	Manifest manifest.Data
    21  
    22  	// Required.
    23  	ServiceName    argparser.OptionalServiceNameID
    24  	ServiceVersion argparser.OptionalServiceVersion
    25  
    26  	// mutual exclusions
    27  	// AccessKey + SecretKey or IAMRole must be provided
    28  	AccessKey argparser.OptionalString
    29  	SecretKey argparser.OptionalString
    30  	IAMRole   argparser.OptionalString
    31  
    32  	// Optional.
    33  	AutoClone                    argparser.OptionalAutoClone
    34  	BucketName                   argparser.OptionalString
    35  	CompressionCodec             argparser.OptionalString
    36  	Domain                       argparser.OptionalString
    37  	EndpointName                 argparser.OptionalString // Can't shadow argparser.Base method Name().
    38  	FileMaxBytes                 argparser.OptionalInt
    39  	Format                       argparser.OptionalString
    40  	FormatVersion                argparser.OptionalInt
    41  	GzipLevel                    argparser.OptionalInt
    42  	MessageType                  argparser.OptionalString
    43  	Path                         argparser.OptionalString
    44  	Period                       argparser.OptionalInt
    45  	Placement                    argparser.OptionalString
    46  	PublicKey                    argparser.OptionalString
    47  	Redundancy                   argparser.OptionalString
    48  	ResponseCondition            argparser.OptionalString
    49  	ServerSideEncryption         argparser.OptionalString
    50  	ServerSideEncryptionKMSKeyID argparser.OptionalString
    51  	TimestampFormat              argparser.OptionalString
    52  }
    53  
    54  // NewCreateCommand returns a usable command registered under the parent.
    55  func NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {
    56  	c := CreateCommand{
    57  		Base: argparser.Base{
    58  			Globals: g,
    59  		},
    60  	}
    61  	c.CmdClause = parent.Command("create", "Create an Amazon S3 logging endpoint on a Fastly service version").Alias("add")
    62  
    63  	// Required.
    64  	c.CmdClause.Flag("name", "The name of the S3 logging object. Used as a primary key for API access").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)
    65  	c.RegisterFlag(argparser.StringFlagOpts{
    66  		Name:        argparser.FlagVersionName,
    67  		Description: argparser.FlagVersionDesc,
    68  		Dst:         &c.ServiceVersion.Value,
    69  		Required:    true,
    70  	})
    71  
    72  	// Optional.
    73  	c.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{
    74  		Action: c.AutoClone.Set,
    75  		Dst:    &c.AutoClone.Value,
    76  	})
    77  	c.CmdClause.Flag("access-key", "Your S3 account access key").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)
    78  	c.CmdClause.Flag("bucket", "Your S3 bucket name").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)
    79  	common.CompressionCodec(c.CmdClause, &c.CompressionCodec)
    80  	c.CmdClause.Flag("domain", "The domain of the S3 endpoint").Action(c.Domain.Set).StringVar(&c.Domain.Value)
    81  	c.CmdClause.Flag("file-max-bytes", "The maximum size of a log file in bytes").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value)
    82  	common.Format(c.CmdClause, &c.Format)
    83  	common.FormatVersion(c.CmdClause, &c.FormatVersion)
    84  	common.GzipLevel(c.CmdClause, &c.GzipLevel)
    85  	c.CmdClause.Flag("iam-role", "The IAM role ARN for logging").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value)
    86  	common.MessageType(c.CmdClause, &c.MessageType)
    87  	common.Path(c.CmdClause, &c.Path)
    88  	common.Period(c.CmdClause, &c.Period)
    89  	common.Placement(c.CmdClause, &c.Placement)
    90  	common.PublicKey(c.CmdClause, &c.PublicKey)
    91  	c.CmdClause.Flag("redundancy", "The S3 storage class. One of: standard, intelligent_tiering, standard_ia, onezone_ia, glacier, glacier_ir, deep_archive, or reduced_redundancy").Action(c.Redundancy.Set).EnumVar(&c.Redundancy.Value, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyIntelligentTiering), string(fastly.S3RedundancyStandardIA), string(fastly.S3RedundancyOneZoneIA), string(fastly.S3RedundancyGlacierFlexibleRetrieval), string(fastly.S3RedundancyGlacierInstantRetrieval), string(fastly.S3RedundancyGlacierDeepArchive), string(fastly.S3RedundancyReduced))
    92  	common.ResponseCondition(c.CmdClause, &c.ResponseCondition)
    93  	c.CmdClause.Flag("secret-key", "Your S3 account secret key").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)
    94  	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))
    95  	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)
    96  	c.RegisterFlag(argparser.StringFlagOpts{
    97  		Name:        argparser.FlagServiceIDName,
    98  		Description: argparser.FlagServiceIDDesc,
    99  		Dst:         &g.Manifest.Flag.ServiceID,
   100  		Short:       's',
   101  	})
   102  	c.RegisterFlag(argparser.StringFlagOpts{
   103  		Action:      c.ServiceName.Set,
   104  		Name:        argparser.FlagServiceName,
   105  		Description: argparser.FlagServiceDesc,
   106  		Dst:         &c.ServiceName.Value,
   107  	})
   108  	common.TimestampFormat(c.CmdClause, &c.TimestampFormat)
   109  	return &c
   110  }
   111  
   112  // ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.
   113  func (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateS3Input, error) {
   114  	var input fastly.CreateS3Input
   115  
   116  	input.ServiceID = serviceID
   117  	input.ServiceVersion = serviceVersion
   118  	if c.EndpointName.WasSet {
   119  		input.Name = &c.EndpointName.Value
   120  	}
   121  	if c.BucketName.WasSet {
   122  		input.BucketName = &c.BucketName.Value
   123  	}
   124  
   125  	// The following block checks for invalid permutations of the ways in
   126  	// which the AccessKey + SecretKey and IAMRole flags can be
   127  	// provided. This is necessary because either the AccessKey and
   128  	// SecretKey or the IAMRole is required, but they are mutually
   129  	// exclusive. The kingpin library lacks a way to express this constraint
   130  	// via the flag specification API so we enforce it manually here.
   131  	switch {
   132  	case !c.AccessKey.WasSet && !c.SecretKey.WasSet && !c.IAMRole.WasSet:
   133  		return nil, fmt.Errorf("error parsing arguments: the --access-key and --secret-key flags or the --iam-role flag must be provided")
   134  	case (c.AccessKey.WasSet || c.SecretKey.WasSet) && c.IAMRole.WasSet:
   135  		// Enforce mutual exclusion
   136  		return nil, fmt.Errorf("error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag")
   137  	case c.AccessKey.WasSet && !c.SecretKey.WasSet:
   138  		return nil, fmt.Errorf("error parsing arguments: required flag --secret-key not provided")
   139  	case !c.AccessKey.WasSet && c.SecretKey.WasSet:
   140  		return nil, fmt.Errorf("error parsing arguments: required flag --access-key not provided")
   141  	}
   142  
   143  	// The following blocks enforces the mutual exclusivity of the
   144  	// CompressionCodec and GzipLevel flags.
   145  	if c.CompressionCodec.WasSet && c.GzipLevel.WasSet {
   146  		return nil, fmt.Errorf("error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag")
   147  	}
   148  
   149  	if c.AccessKey.WasSet {
   150  		input.AccessKey = &c.AccessKey.Value
   151  	}
   152  
   153  	if c.SecretKey.WasSet {
   154  		input.SecretKey = &c.SecretKey.Value
   155  	}
   156  
   157  	if c.IAMRole.WasSet {
   158  		input.IAMRole = &c.IAMRole.Value
   159  	}
   160  
   161  	if c.Domain.WasSet {
   162  		input.Domain = &c.Domain.Value
   163  	}
   164  
   165  	if c.FileMaxBytes.WasSet {
   166  		input.FileMaxBytes = &c.FileMaxBytes.Value
   167  	}
   168  
   169  	if c.Path.WasSet {
   170  		input.Path = &c.Path.Value
   171  	}
   172  
   173  	if c.Period.WasSet {
   174  		input.Period = &c.Period.Value
   175  	}
   176  
   177  	if c.GzipLevel.WasSet {
   178  		input.GzipLevel = &c.GzipLevel.Value
   179  	}
   180  
   181  	if c.Format.WasSet {
   182  		input.Format = &c.Format.Value
   183  	}
   184  
   185  	if c.FormatVersion.WasSet {
   186  		input.FormatVersion = &c.FormatVersion.Value
   187  	}
   188  
   189  	if c.MessageType.WasSet {
   190  		input.MessageType = &c.MessageType.Value
   191  	}
   192  
   193  	if c.ResponseCondition.WasSet {
   194  		input.ResponseCondition = &c.ResponseCondition.Value
   195  	}
   196  
   197  	if c.TimestampFormat.WasSet {
   198  		input.TimestampFormat = &c.TimestampFormat.Value
   199  	}
   200  
   201  	if c.Placement.WasSet {
   202  		input.Placement = &c.Placement.Value
   203  	}
   204  
   205  	if c.PublicKey.WasSet {
   206  		input.PublicKey = &c.PublicKey.Value
   207  	}
   208  
   209  	if c.ServerSideEncryptionKMSKeyID.WasSet {
   210  		input.ServerSideEncryptionKMSKeyID = &c.ServerSideEncryptionKMSKeyID.Value
   211  	}
   212  
   213  	if c.CompressionCodec.WasSet {
   214  		input.CompressionCodec = &c.CompressionCodec.Value
   215  	}
   216  
   217  	if c.Redundancy.WasSet {
   218  		redundancy, err := ValidateRedundancy(c.Redundancy.Value)
   219  		if err == nil {
   220  			input.Redundancy = &redundancy
   221  		}
   222  	}
   223  
   224  	if c.ServerSideEncryption.WasSet {
   225  		switch c.ServerSideEncryption.Value {
   226  		case string(fastly.S3ServerSideEncryptionAES):
   227  			sse := fastly.S3ServerSideEncryptionAES
   228  			input.ServerSideEncryption = &sse
   229  		case string(fastly.S3ServerSideEncryptionKMS):
   230  			sse := fastly.S3ServerSideEncryptionKMS
   231  			input.ServerSideEncryption = &sse
   232  		}
   233  	}
   234  
   235  	return &input, nil
   236  }
   237  
   238  // ValidateRedundancy identifies the given redundancy type.
   239  func ValidateRedundancy(val string) (redundancy fastly.S3Redundancy, err error) {
   240  	switch val {
   241  	case string(fastly.S3RedundancyStandard):
   242  		redundancy = fastly.S3RedundancyStandard
   243  	case string(fastly.S3RedundancyIntelligentTiering):
   244  		redundancy = fastly.S3RedundancyIntelligentTiering
   245  	case string(fastly.S3RedundancyStandardIA):
   246  		redundancy = fastly.S3RedundancyStandardIA
   247  	case string(fastly.S3RedundancyOneZoneIA):
   248  		redundancy = fastly.S3RedundancyOneZoneIA
   249  	case string(fastly.S3RedundancyGlacierInstantRetrieval):
   250  		redundancy = fastly.S3RedundancyGlacierInstantRetrieval
   251  	case string(fastly.S3RedundancyGlacierFlexibleRetrieval):
   252  		redundancy = fastly.S3RedundancyGlacierFlexibleRetrieval
   253  	case string(fastly.S3RedundancyGlacierDeepArchive):
   254  		redundancy = fastly.S3RedundancyGlacierDeepArchive
   255  	case string(fastly.S3RedundancyReduced):
   256  		redundancy = fastly.S3RedundancyReduced
   257  	default:
   258  		err = fmt.Errorf("unknown redundancy: " + val)
   259  	}
   260  	return redundancy, err
   261  }
   262  
   263  // Exec invokes the application logic for the command.
   264  func (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {
   265  	serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{
   266  		AutoCloneFlag:      c.AutoClone,
   267  		APIClient:          c.Globals.APIClient,
   268  		Manifest:           *c.Globals.Manifest,
   269  		Out:                out,
   270  		ServiceNameFlag:    c.ServiceName,
   271  		ServiceVersionFlag: c.ServiceVersion,
   272  		VerboseMode:        c.Globals.Flags.Verbose,
   273  	})
   274  	if err != nil {
   275  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
   276  			"Service ID":      serviceID,
   277  			"Service Version": errors.ServiceVersion(serviceVersion),
   278  		})
   279  		return err
   280  	}
   281  
   282  	input, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))
   283  	if err != nil {
   284  		c.Globals.ErrLog.Add(err)
   285  		return err
   286  	}
   287  
   288  	d, err := c.Globals.APIClient.CreateS3(input)
   289  	if err != nil {
   290  		c.Globals.ErrLog.Add(err)
   291  		return err
   292  	}
   293  
   294  	text.Success(out,
   295  		"Created S3 logging endpoint %s (service %s version %d)",
   296  		fastly.ToValue(d.Name),
   297  		fastly.ToValue(d.ServiceID),
   298  		fastly.ToValue(d.ServiceVersion),
   299  	)
   300  	return nil
   301  }