internal/pkg/manifest/rd_web_svc.go (161 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package manifest
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo"
"github.com/aws/copilot-cli/internal/pkg/template"
"github.com/imdario/mergo"
)
const (
requestDrivenWebSvcManifestPath string = "workloads/services/rd-web/manifest.yml"
)
// RequestDrivenWebService holds the configuration to create a Request-Driven Web Service.
type RequestDrivenWebService struct {
Workload `yaml:",inline"`
RequestDrivenWebServiceConfig `yaml:",inline"`
Environments map[string]*RequestDrivenWebServiceConfig `yaml:",flow"` // Fields to override per environment.
parser template.Parser
}
func (s *RequestDrivenWebService) subnets() *SubnetListOrArgs {
return &s.Network.VPC.Placement.Subnets
}
// RequestDrivenWebServiceConfig holds the configuration that can be overridden per environments.
type RequestDrivenWebServiceConfig struct {
RequestDrivenWebServiceHttpConfig `yaml:"http,flow"`
InstanceConfig AppRunnerInstanceConfig `yaml:",inline"`
ImageConfig ImageWithPort `yaml:"image"`
Variables map[string]Variable `yaml:"variables"`
Secrets map[string]Secret `yaml:"secrets"`
StartCommand *string `yaml:"command"`
Tags map[string]string `yaml:"tags"`
PublishConfig PublishConfig `yaml:"publish"`
Network RequestDrivenWebServiceNetworkConfig `yaml:"network"`
Observability Observability `yaml:"observability"`
Count *string `yaml:"count"`
}
// Observability holds configuration for observability to the service.
type Observability struct {
Tracing *string `yaml:"tracing"`
}
func (o *Observability) isEmpty() bool {
return o.Tracing == nil
}
// ImageWithPort represents a container image with an exposed port.
type ImageWithPort struct {
Image Image `yaml:",inline"`
Port *uint16 `yaml:"port"`
}
// RequestDrivenWebServiceNetworkConfig represents options for network connection to AWS resources for a Request-Driven Web Service.
type RequestDrivenWebServiceNetworkConfig struct {
VPC rdwsVpcConfig `yaml:"vpc"`
}
// IsEmpty returns empty if the struct has all zero members.
func (c *RequestDrivenWebServiceNetworkConfig) IsEmpty() bool {
return c.VPC.isEmpty()
}
func (c *RequestDrivenWebServiceNetworkConfig) requiredEnvFeatures() []string {
if aws.StringValue((*string)(c.VPC.Placement.PlacementString)) == string(PrivateSubnetPlacement) {
return []string{template.NATFeatureName}
}
return nil
}
type rdwsVpcConfig struct {
Placement PlacementArgOrString `yaml:"placement"`
}
func (c *rdwsVpcConfig) isEmpty() bool {
return c.Placement.IsEmpty()
}
// RequestDrivenWebServiceHttpConfig represents options for configuring http.
type RequestDrivenWebServiceHttpConfig struct {
HealthCheckConfiguration HealthCheckArgsOrString `yaml:"healthcheck"`
Alias *string `yaml:"alias"`
Private Union[*bool, VPCEndpoint] `yaml:"private"`
}
// VPCEndpoint is used to configure a pre-existing VPC endpoint.
type VPCEndpoint struct {
Endpoint *string `yaml:"endpoint"`
}
// AppRunnerInstanceConfig contains the instance configuration properties for an App Runner service.
type AppRunnerInstanceConfig struct {
CPU *int `yaml:"cpu"`
Memory *int `yaml:"memory"`
Platform PlatformArgsOrString `yaml:"platform,omitempty"`
}
// RequestDrivenWebServiceProps contains properties for creating a new request-driven web service manifest.
type RequestDrivenWebServiceProps struct {
*WorkloadProps
Port uint16
Platform PlatformArgsOrString
Private bool
}
// NewRequestDrivenWebService creates a new Request-Driven Web Service manifest with default values.
func NewRequestDrivenWebService(props *RequestDrivenWebServiceProps) *RequestDrivenWebService {
svc := newDefaultRequestDrivenWebService()
svc.Name = aws.String(props.Name)
svc.RequestDrivenWebServiceConfig.ImageConfig.Image.Location = stringP(props.Image)
svc.RequestDrivenWebServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile)
svc.RequestDrivenWebServiceConfig.ImageConfig.Port = aws.Uint16(props.Port)
svc.RequestDrivenWebServiceConfig.InstanceConfig.Platform = props.Platform
if props.Private {
svc.Private = BasicToUnion[*bool, VPCEndpoint](aws.Bool(true))
svc.Network.VPC.Placement.PlacementString = (*PlacementString)(aws.String("private"))
}
svc.parser = template.New()
return svc
}
// MarshalBinary serializes the manifest object into a binary YAML document.
// Implements the encoding.BinaryMarshaler interface.
func (s *RequestDrivenWebService) MarshalBinary() ([]byte, error) {
content, err := s.parser.Parse(requestDrivenWebSvcManifestPath, *s)
if err != nil {
return nil, err
}
return content.Bytes(), nil
}
// Dockerfile returns the relative path of the Dockerfile in the manifest.
func (s *RequestDrivenWebService) Dockerfile() string {
return s.ImageConfig.Image.dockerfilePath()
}
// Port returns the exposed the exposed port in the manifest.
// A RequestDrivenWebService always has a port exposed therefore the boolean is always true.
func (s *RequestDrivenWebService) Port() (port uint16, ok bool) {
return aws.Uint16Value(s.ImageConfig.Port), true
}
// Publish returns the list of topics where notifications can be published.
func (s *RequestDrivenWebService) Publish() []Topic {
return s.RequestDrivenWebServiceConfig.PublishConfig.publishedTopics()
}
// ContainerPlatform returns the platform for the service.
func (s *RequestDrivenWebService) ContainerPlatform() string {
if s.InstanceConfig.Platform.IsEmpty() {
return platformString(OSLinux, ArchAMD64)
}
return platformString(s.InstanceConfig.Platform.OS(), s.InstanceConfig.Platform.Arch())
}
// BuildArgs returns a docker.BuildArguments object given a context directory.
func (s *RequestDrivenWebService) BuildArgs(contextDir string) (map[string]*DockerBuildArgs, error) {
required, err := requiresBuild(s.ImageConfig.Image)
if err != nil {
return nil, err
}
// Creating an map to store buildArgs of all sidecar images and main container image.
buildArgsPerContainer := make(map[string]*DockerBuildArgs, 1)
if required {
buildArgsPerContainer[aws.StringValue(s.Name)] = s.ImageConfig.Image.BuildConfig(contextDir)
}
return buildArgsPerContainer, nil
}
func (s RequestDrivenWebService) applyEnv(envName string) (workloadManifest, error) {
overrideConfig, ok := s.Environments[envName]
if !ok {
return &s, nil
}
// Apply overrides to the original service configuration.
for _, t := range defaultTransformers {
err := mergo.Merge(&s, RequestDrivenWebService{
RequestDrivenWebServiceConfig: *overrideConfig,
}, mergo.WithOverride, mergo.WithTransformers(t))
if err != nil {
return nil, err
}
}
s.Environments = nil
return &s, nil
}
func (s *RequestDrivenWebService) requiredEnvironmentFeatures() []string {
var features []string
features = append(features, s.Network.requiredEnvFeatures()...)
return features
}
// newDefaultRequestDrivenWebService returns an empty RequestDrivenWebService with only the default values set.
func newDefaultRequestDrivenWebService() *RequestDrivenWebService {
return &RequestDrivenWebService{
Workload: Workload{
Type: aws.String(manifestinfo.RequestDrivenWebServiceType),
},
RequestDrivenWebServiceConfig: RequestDrivenWebServiceConfig{
ImageConfig: ImageWithPort{},
InstanceConfig: AppRunnerInstanceConfig{
CPU: aws.Int(1024),
Memory: aws.Int(2048),
},
},
}
}