internal/pkg/manifest/workload.go (623 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package manifest
import (
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/copilot-cli/internal/pkg/aws/ec2"
"github.com/aws/copilot-cli/internal/pkg/docker/dockerengine"
"github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo"
"github.com/aws/copilot-cli/internal/pkg/template"
"github.com/google/shlex"
"github.com/aws/aws-sdk-go/aws"
"gopkg.in/yaml.v3"
)
const (
defaultDockerfileName = "Dockerfile"
)
// SQS Queue field options.
const (
sqsFIFOThroughputLimitPerMessageGroupID = "perMessageGroupId"
sqsFIFOThroughputLimitPerQueue = "perQueue"
sqsDeduplicationScopeMessageGroup = "messageGroup"
sqsDeduplicationScopeQueue = "queue"
)
// AWS VPC subnet placement options.
const (
PublicSubnetPlacement = PlacementString("public")
PrivateSubnetPlacement = PlacementString("private")
)
// All placement options.
var (
subnetPlacements = []string{string(PublicSubnetPlacement), string(PrivateSubnetPlacement)}
)
// Error definitions.
var (
ErrAppRunnerInvalidPlatformWindows = errors.New("Windows is not supported for App Runner services")
errUnmarshalBuildOpts = errors.New("unable to unmarshal build field into string or compose-style map")
errUnmarshalPlatformOpts = errors.New("unable to unmarshal platform field into string or compose-style map")
errUnmarshalSecurityGroupOpts = errors.New(`unable to unmarshal "security_groups" field into slice of strings or compose-style map`)
errUnmarshalPlacementOpts = errors.New("unable to unmarshal placement field into string or compose-style map")
errUnmarshalServiceConnectOpts = errors.New(`unable to unmarshal "connect" field into boolean or compose-style map`)
errUnmarshalSubnetsOpts = errors.New("unable to unmarshal subnets field into string slice or compose-style map")
errUnmarshalCountOpts = errors.New(`unable to unmarshal "count" field to an integer or autoscaling configuration`)
errUnmarshalRangeOpts = errors.New(`unable to unmarshal "range" field`)
errUnmarshalExec = errors.New(`unable to unmarshal "exec" field into boolean or exec configuration`)
errUnmarshalEntryPoint = errors.New(`unable to unmarshal "entrypoint" into string or slice of strings`)
errUnmarshalAlias = errors.New(`unable to unmarshal "alias" into advanced alias map, string, or slice of strings`)
errUnmarshalCommand = errors.New(`unable to unmarshal "command" into string or slice of strings`)
)
// DynamicWorkload represents a dynamically populated workload.
type DynamicWorkload interface {
ApplyEnv(envName string) (DynamicWorkload, error)
Validate() error
RequiredEnvironmentFeatures() []string
Load(sess *session.Session) error
Manifest() any
}
type workloadManifest interface {
validate() error
applyEnv(envName string) (workloadManifest, error)
requiredEnvironmentFeatures() []string
subnets() *SubnetListOrArgs
}
// UnmarshalWorkload deserializes the YAML input stream into a workload manifest object.
// If an error occurs during deserialization, then returns the error.
// If the workload type in the manifest is invalid, then returns an ErrInvalidmanifestinfo.
func UnmarshalWorkload(in []byte) (DynamicWorkload, error) {
am := Workload{}
if err := yaml.Unmarshal(in, &am); err != nil {
return nil, fmt.Errorf("unmarshal to workload manifest: %w", err)
}
typeVal := aws.StringValue(am.Type)
var m workloadManifest
switch typeVal {
case manifestinfo.LoadBalancedWebServiceType:
m = newDefaultLoadBalancedWebService()
case manifestinfo.RequestDrivenWebServiceType:
m = newDefaultRequestDrivenWebService()
case manifestinfo.BackendServiceType:
m = newDefaultBackendService()
case manifestinfo.WorkerServiceType:
m = newDefaultWorkerService()
case manifestinfo.StaticSiteType:
m = newDefaultStaticSite()
case manifestinfo.ScheduledJobType:
m = newDefaultScheduledJob()
default:
return nil, &ErrInvalidWorkloadType{Type: typeVal}
}
if err := yaml.Unmarshal(in, m); err != nil {
return nil, fmt.Errorf("unmarshal manifest for %s: %w", typeVal, err)
}
return newDynamicWorkloadManifest(m), nil
}
// WorkloadProps contains properties for creating a new workload manifest.
type WorkloadProps struct {
Name string
Dockerfile string
Image string
PrivateOnlyEnvironments []string
}
// Workload holds the basic data that every workload manifest file needs to have.
type Workload struct {
Name *string `yaml:"name"`
Type *string `yaml:"type"` // must be one of the supported manifest types.
}
// Image represents the workload's container image.
type Image struct {
ImageLocationOrBuild `yaml:",inline"`
Credentials *string `yaml:"credentials"` // ARN of the secret containing the private repository credentials.
DockerLabels map[string]string `yaml:"labels,flow"` // Apply Docker labels to the container at runtime.
DependsOn DependsOn `yaml:"depends_on,flow"` // Add any sidecar dependencies.
}
// ImageLocationOrBuild represents the docker build arguments and location of the existing image.
type ImageLocationOrBuild struct {
Build BuildArgsOrString `yaml:"build"` // Build an image from a Dockerfile.
Location *string `yaml:"location"` // Use an existing image instead.
}
// DependsOn represents container dependency for a container.
type DependsOn map[string]string
// UnmarshalYAML overrides the default YAML unmarshaling logic for the Image
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (i *Image) UnmarshalYAML(value *yaml.Node) error {
type image Image
if err := value.Decode((*image)(i)); err != nil {
return err
}
if !i.Build.isEmpty() && i.Location != nil {
return &errFieldMutualExclusive{
firstField: "build",
secondField: "location",
mustExist: true,
}
}
return nil
}
// GetLocation returns the location of the image.
func (i Image) GetLocation() string {
return aws.StringValue(i.Location)
}
// BuildConfig populates a docker.BuildArguments struct from the fields available in the manifest.
// Prefer the following hierarchy:
// 1. Specific dockerfile, specific context
// 2. Specific dockerfile, context = dockerfile dir
// 3. "Dockerfile" located in context dir
// 4. "Dockerfile" located in ws root.
func (i *ImageLocationOrBuild) BuildConfig(rootDirectory string) *DockerBuildArgs {
return &DockerBuildArgs{
Dockerfile: aws.String(filepath.Join(rootDirectory, i.dockerfilePath())),
Context: aws.String(filepath.Join(rootDirectory, i.contextPath())),
Args: i.args(),
Target: i.target(),
CacheFrom: i.cacheFrom(),
}
}
// dockerfilePath returns the relative path of a Dockerfile.
// Prefer a specific Dockerfile, then a Dockerfile in the context directory.
func (i *ImageLocationOrBuild) dockerfilePath() string {
if df := i.dockerfile(); df != "" {
return df
}
return filepath.Join(i.context(), defaultDockerfileName)
}
// dockerfileContext returns the relative context of an image.
// Prefer a specific context, then the dockerfile directory.
func (i *ImageLocationOrBuild) contextPath() string {
if ctx := i.context(); ctx != "" {
return ctx
}
return filepath.Dir(i.dockerfile())
}
// dockerfile returns the path to the workload's Dockerfile. If no dockerfile is specified,
// returns "".
func (i *ImageLocationOrBuild) dockerfile() string {
// Prefer to use the "Dockerfile" string in BuildArgs. Otherwise,
// "BuildString". If no dockerfile specified, return "".
if i.Build.BuildArgs.Dockerfile != nil {
return aws.StringValue(i.Build.BuildArgs.Dockerfile)
}
var dfPath string
if i.Build.BuildString != nil {
dfPath = aws.StringValue(i.Build.BuildString)
}
return dfPath
}
// context returns the build context directory if it exists, otherwise an empty string.
func (i *ImageLocationOrBuild) context() string {
return aws.StringValue(i.Build.BuildArgs.Context)
}
// args returns the args section, if it exists, to override args in the dockerfile.
// Otherwise it returns an empty map.
func (i *ImageLocationOrBuild) args() map[string]string {
return i.Build.BuildArgs.Args
}
// target returns the build target stage if it exists, otherwise nil.
func (i *ImageLocationOrBuild) target() *string {
return i.Build.BuildArgs.Target
}
// cacheFrom returns the cache from build section, if it exists.
// Otherwise it returns nil.
func (i *ImageLocationOrBuild) cacheFrom() []string {
return i.Build.BuildArgs.CacheFrom
}
// ImageOverride holds fields that override Dockerfile image defaults.
type ImageOverride struct {
EntryPoint EntryPointOverride `yaml:"entrypoint"`
Command CommandOverride `yaml:"command"`
}
// StringSliceOrShellString is either a slice of string or a string using shell-style rules.
type stringSliceOrShellString StringSliceOrString
// EntryPointOverride is a custom type which supports unmarshalling "entrypoint" yaml which
// can either be of type string or type slice of string.
type EntryPointOverride stringSliceOrShellString
// CommandOverride is a custom type which supports unmarshalling "command" yaml which
// can either be of type string or type slice of string.
type CommandOverride stringSliceOrShellString
// UnmarshalYAML overrides the default YAML unmarshalling logic for the EntryPointOverride
// struct, allowing it to be unmarshalled into a string slice or a string.
// This method implements the yaml.Unmarshaler (v3) interface.
func (e *EntryPointOverride) UnmarshalYAML(value *yaml.Node) error {
if err := (*StringSliceOrString)(e).UnmarshalYAML(value); err != nil {
return errUnmarshalEntryPoint
}
return nil
}
// ToStringSlice converts an EntryPointOverride to a slice of string using shell-style rules.
func (e *EntryPointOverride) ToStringSlice() ([]string, error) {
out, err := (*stringSliceOrShellString)(e).toStringSlice()
if err != nil {
return nil, err
}
return out, nil
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the CommandOverride
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (c *CommandOverride) UnmarshalYAML(value *yaml.Node) error {
if err := (*StringSliceOrString)(c).UnmarshalYAML(value); err != nil {
return errUnmarshalCommand
}
return nil
}
// ToStringSlice converts an CommandOverride to a slice of string using shell-style rules.
func (c *CommandOverride) ToStringSlice() ([]string, error) {
out, err := (*stringSliceOrShellString)(c).toStringSlice()
if err != nil {
return nil, err
}
return out, nil
}
// StringSliceOrString is a custom type that can either be of type string or type slice of string.
type StringSliceOrString struct {
String *string
StringSlice []string
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the StringSliceOrString
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (s *StringSliceOrString) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&s.StringSlice); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if s.StringSlice != nil {
// Unmarshaled successfully to s.StringSlice, unset s.String, and return.
s.String = nil
return nil
}
return value.Decode(&s.String)
}
func (s *StringSliceOrString) isEmpty() bool {
return s.String == nil && len(s.StringSlice) == 0
}
// ToStringSlice converts an StringSliceOrString to a slice of string.
func (s *StringSliceOrString) ToStringSlice() []string {
if s.StringSlice != nil {
return s.StringSlice
}
if s.String == nil {
return nil
}
return []string{*s.String}
}
func (s *stringSliceOrShellString) toStringSlice() ([]string, error) {
if s.StringSlice != nil {
return s.StringSlice, nil
}
if s.String == nil {
return nil, nil
}
out, err := shlex.Split(*s.String)
if err != nil {
return nil, fmt.Errorf("convert string into tokens using shell-style rules: %w", err)
}
return out, nil
}
// BuildArgsOrString is a custom type which supports unmarshaling yaml which
// can either be of type string or type DockerBuildArgs.
type BuildArgsOrString struct {
BuildString *string
BuildArgs DockerBuildArgs
}
func (b *BuildArgsOrString) isEmpty() bool {
if aws.StringValue(b.BuildString) == "" && b.BuildArgs.isEmpty() {
return true
}
return false
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the BuildArgsOrString
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (b *BuildArgsOrString) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&b.BuildArgs); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !b.BuildArgs.isEmpty() {
// Unmarshaled successfully to b.BuildArgs, unset b.BuildString, and return.
b.BuildString = nil
return nil
}
if err := value.Decode(&b.BuildString); err != nil {
return errUnmarshalBuildOpts
}
return nil
}
// DockerBuildArgs represents the options specifiable under the "build" field
// of Docker Compose services. For more information, see:
// https://docs.docker.com/compose/compose-file/build
type DockerBuildArgs struct {
Context *string `yaml:"context,omitempty"`
Dockerfile *string `yaml:"dockerfile,omitempty"`
Args map[string]string `yaml:"args,omitempty"`
Target *string `yaml:"target,omitempty"`
CacheFrom []string `yaml:"cache_from,omitempty"`
}
func (b *DockerBuildArgs) isEmpty() bool {
if b.Context == nil && b.Dockerfile == nil && b.Args == nil && b.Target == nil && b.CacheFrom == nil {
return true
}
return false
}
// PublishConfig represents the configurable options for setting up publishers.
type PublishConfig struct {
Topics []Topic `yaml:"topics"`
}
// Topic represents the configurable options for setting up a SNS Topic.
type Topic struct {
Name *string `yaml:"name"`
FIFO FIFOTopicAdvanceConfigOrBool `yaml:"fifo"`
}
// FIFOTopicAdvanceConfigOrBool represents the configurable options for fifo topics.
type FIFOTopicAdvanceConfigOrBool struct {
Enable *bool
Advanced FIFOTopicAdvanceConfig
}
// IsEmpty returns true if the FifoAdvanceConfigOrBool struct has all nil values.
func (f *FIFOTopicAdvanceConfigOrBool) IsEmpty() bool {
return f.Enable == nil && f.Advanced.IsEmpty()
}
// IsEnabled returns true if the FIFO is enabled on the SQS queue.
func (f *FIFOTopicAdvanceConfigOrBool) IsEnabled() bool {
return aws.BoolValue(f.Enable) || !f.Advanced.IsEmpty()
}
// FIFOTopicAdvanceConfig represents the advanced fifo topic config.
type FIFOTopicAdvanceConfig struct {
ContentBasedDeduplication *bool `yaml:"content_based_deduplication"`
}
// IsEmpty returns true if the FifoAdvanceConfig struct has all nil values.
func (a *FIFOTopicAdvanceConfig) IsEmpty() bool {
return a.ContentBasedDeduplication == nil
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the FIFOTopicAdvanceConfigOrBool
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (t *FIFOTopicAdvanceConfigOrBool) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&t.Advanced); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !t.Advanced.IsEmpty() {
return nil
}
if err := value.Decode(&t.Enable); err != nil {
return errUnmarshalFifoConfig
}
return nil
}
// NetworkConfig represents options for network connection to AWS resources within a VPC.
type NetworkConfig struct {
VPC vpcConfig `yaml:"vpc"`
Connect ServiceConnectBoolOrArgs `yaml:"connect"`
}
// IsEmpty returns empty if the struct has all zero members.
func (c *NetworkConfig) IsEmpty() bool {
return c.VPC.isEmpty()
}
func (c *NetworkConfig) requiredEnvFeatures() []string {
if aws.StringValue((*string)(c.VPC.Placement.PlacementString)) == string(PrivateSubnetPlacement) {
return []string{template.NATFeatureName}
}
return nil
}
// ServiceConnectBoolOrArgs represents ECS Service Connect configuration.
type ServiceConnectBoolOrArgs struct {
EnableServiceConnect *bool
ServiceConnectArgs
}
// Enabled returns if ServiceConnect is enabled or not.
func (s *ServiceConnectBoolOrArgs) Enabled() bool {
return aws.BoolValue(s.EnableServiceConnect) || !s.ServiceConnectArgs.isEmpty()
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the ServiceConnect
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (s *ServiceConnectBoolOrArgs) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&s.ServiceConnectArgs); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !s.ServiceConnectArgs.isEmpty() {
s.EnableServiceConnect = nil
return nil
}
if err := value.Decode(&s.EnableServiceConnect); err != nil {
return errUnmarshalServiceConnectOpts
}
return nil
}
// ServiceConnectArgs includes the advanced configuration for ECS Service Connect.
type ServiceConnectArgs struct {
Alias *string
}
func (s *ServiceConnectArgs) isEmpty() bool {
return s.Alias == nil
}
// PlacementArgOrString represents where to place tasks.
type PlacementArgOrString struct {
*PlacementString
PlacementArgs
}
// PlacementString represents what types of subnets (public or private subnets) to place tasks.
type PlacementString string
// IsEmpty returns empty if the struct has all zero members.
func (p *PlacementArgOrString) IsEmpty() bool {
return p.PlacementString == nil && p.PlacementArgs.isEmpty()
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the PlacementArgOrString
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (p *PlacementArgOrString) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&p.PlacementArgs); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !p.PlacementArgs.isEmpty() {
// Unmarshaled successfully to p.PlacementArgs, unset p.PlacementString, and return.
p.PlacementString = nil
return nil
}
if err := value.Decode(&p.PlacementString); err != nil {
return errUnmarshalPlacementOpts
}
return nil
}
// PlacementArgs represents where to place tasks.
type PlacementArgs struct {
Subnets SubnetListOrArgs `yaml:"subnets"`
}
func (p *PlacementArgs) isEmpty() bool {
return p.Subnets.isEmpty()
}
// SubnetListOrArgs represents what subnets to place tasks. It supports unmarshalling
// yaml which can either be of type SubnetArgs or a list of strings.
type SubnetListOrArgs struct {
IDs []string
SubnetArgs
}
func (s *SubnetListOrArgs) isEmpty() bool {
return len(s.IDs) == 0 && s.SubnetArgs.isEmpty()
}
type dynamicSubnets struct {
cfg *SubnetListOrArgs
client subnetIDsGetter
}
// Load populates the subnet's IDs field if the client is using tags.
func (dyn *dynamicSubnets) load() error {
if dyn.cfg == nil || dyn.cfg.isEmpty() {
return nil
}
if len(dyn.cfg.IDs) > 0 {
return nil
}
var filters []ec2.Filter
for k, v := range dyn.cfg.FromTags {
values := v.StringSlice
if v.String != nil {
values = v.ToStringSlice()
}
filters = append(filters, ec2.FilterForTags(k, values...))
}
ids, err := dyn.client.SubnetIDs(filters...)
if err != nil {
return fmt.Errorf("get subnet IDs: %w", err)
}
dyn.cfg.IDs = ids
return nil
}
// Tags represents the aws tags which take string as key and slice of string as values.
type Tags map[string]StringSliceOrString
// SubnetArgs represents what subnets to place tasks.
type SubnetArgs struct {
FromTags Tags `yaml:"from_tags"`
}
func (s *SubnetArgs) isEmpty() bool {
return len(s.FromTags) == 0
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the SubnetListOrArgs
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (s *SubnetListOrArgs) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&s.SubnetArgs); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !s.SubnetArgs.isEmpty() {
// Unmarshaled successfully to s.SubnetArgs, unset s.Subnets, and return.
s.IDs = nil
return nil
}
if err := value.Decode(&s.IDs); err != nil {
return errUnmarshalSubnetsOpts
}
return nil
}
// SecurityGroupsIDsOrConfig represents security groups attached to task. It supports unmarshalling
// yaml which can either be of type SecurityGroupsConfig or a list of strings.
type SecurityGroupsIDsOrConfig struct {
IDs []StringOrFromCFN
AdvancedConfig SecurityGroupsConfig
}
func (s *SecurityGroupsIDsOrConfig) isEmpty() bool {
return len(s.IDs) == 0 && s.AdvancedConfig.isEmpty()
}
// SecurityGroupsConfig represents which security groups are attached to a task
// and if default security group is applied.
type SecurityGroupsConfig struct {
SecurityGroups []StringOrFromCFN `yaml:"groups"`
DenyDefault *bool `yaml:"deny_default"`
}
func (s *SecurityGroupsConfig) isEmpty() bool {
return len(s.SecurityGroups) == 0 && s.DenyDefault == nil
}
// UnmarshalYAML overrides the default YAML unmarshalling logic for the SecurityGroupsIDsOrConfig
// struct, allowing it to be unmarshalled into a string slice or a string.
// This method implements the yaml.Unmarshaler (v3) interface.
func (s *SecurityGroupsIDsOrConfig) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&s.AdvancedConfig); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !s.AdvancedConfig.isEmpty() {
// Unmarshalled successfully to s.AdvancedConfig, unset s.IDs, and return.
s.IDs = nil
return nil
}
if err := value.Decode(&s.IDs); err != nil {
return errUnmarshalSecurityGroupOpts
}
return nil
}
// GetIDs returns security groups from SecurityGroupsIDsOrConfig that are attached to task.
// nil is returned if no security groups are specified.
func (s *SecurityGroupsIDsOrConfig) GetIDs() []StringOrFromCFN {
if !s.AdvancedConfig.isEmpty() {
return s.AdvancedConfig.SecurityGroups
}
return s.IDs
}
// IsDefaultSecurityGroupDenied returns true if DenyDefault is set to true
// in SecurityGroupsIDsOrConfig.AdvancedConfig. Otherwise, false is returned.
func (s *SecurityGroupsIDsOrConfig) IsDefaultSecurityGroupDenied() bool {
if !s.AdvancedConfig.isEmpty() {
return aws.BoolValue(s.AdvancedConfig.DenyDefault)
}
return false
}
// vpcConfig represents the security groups and subnets attached to a task.
type vpcConfig struct {
Placement PlacementArgOrString `yaml:"placement"`
SecurityGroups SecurityGroupsIDsOrConfig `yaml:"security_groups"`
}
func (v *vpcConfig) isEmpty() bool {
return v.Placement.IsEmpty() && v.SecurityGroups.isEmpty()
}
// PlatformArgsOrString is a custom type which supports unmarshaling yaml which
// can either be of type string or type PlatformArgs.
type PlatformArgsOrString struct {
*PlatformString
PlatformArgs PlatformArgs
}
// UnmarshalYAML overrides the default YAML unmarshaling logic for the PlatformArgsOrString
// struct, allowing it to perform more complex unmarshaling behavior.
// This method implements the yaml.Unmarshaler (v3) interface.
func (p *PlatformArgsOrString) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&p.PlatformArgs); err != nil {
var yamlTypeErr *yaml.TypeError
if !errors.As(err, &yamlTypeErr) {
return err
}
}
if !p.PlatformArgs.isEmpty() {
// Unmarshaled successfully to p.PlatformArgs, unset p.PlatformString, and return.
p.PlatformString = nil
return nil
}
if err := value.Decode(&p.PlatformString); err != nil {
return errUnmarshalPlatformOpts
}
return nil
}
// OS returns the operating system family.
func (p *PlatformArgsOrString) OS() string {
if p := aws.StringValue((*string)(p.PlatformString)); p != "" {
args := strings.Split(p, "/")
return strings.ToLower(args[0])
}
return strings.ToLower(aws.StringValue(p.PlatformArgs.OSFamily))
}
// Arch returns the architecture of PlatformArgsOrString.
func (p *PlatformArgsOrString) Arch() string {
if p := aws.StringValue((*string)(p.PlatformString)); p != "" {
args := strings.Split(p, "/")
return strings.ToLower(args[1])
}
return strings.ToLower(aws.StringValue(p.PlatformArgs.Arch))
}
// PlatformArgs represents the specifics of a target OS.
type PlatformArgs struct {
OSFamily *string `yaml:"osfamily,omitempty"`
Arch *string `yaml:"architecture,omitempty"`
}
// PlatformString represents the string format of Platform.
type PlatformString string
// String implements the fmt.Stringer interface.
func (p *PlatformArgs) String() string {
return fmt.Sprintf("('%s', '%s')", aws.StringValue(p.OSFamily), aws.StringValue(p.Arch))
}
// IsEmpty returns if the platform field is empty.
func (p *PlatformArgsOrString) IsEmpty() bool {
return p.PlatformString == nil && p.PlatformArgs.isEmpty()
}
func (p *PlatformArgs) isEmpty() bool {
return p.OSFamily == nil && p.Arch == nil
}
func (p *PlatformArgs) bothSpecified() bool {
return (p.OSFamily != nil) && (p.Arch != nil)
}
// platformString returns a specified of the format <os>/<arch>.
func platformString(os, arch string) string {
return fmt.Sprintf("%s/%s", os, arch)
}
// RedirectPlatform returns a platform that's supported for the given manifest type.
func RedirectPlatform(os, arch, wlType string) (platform string, err error) {
// Return nil if passed the default platform.
if platformString(os, arch) == defaultPlatform {
return "", nil
}
// Return an error if a platform cannot be redirected.
if wlType == manifestinfo.RequestDrivenWebServiceType && os == OSWindows {
return "", ErrAppRunnerInvalidPlatformWindows
}
// All architectures default to 'x86_64' (though 'arm64' is now also supported); leave OS as is.
// If a string is returned, the platform is not the default platform but is supported (except for more obscure platforms).
return platformString(os, dockerengine.ArchX86), nil
}
func isWindowsPlatform(platform PlatformArgsOrString) bool {
for _, win := range windowsOSFamilies {
if platform.OS() == win {
return true
}
}
return false
}
// IsArmArch returns whether or not the arch is ARM.
func IsArmArch(arch string) bool {
return strings.ToLower(arch) == ArchARM || strings.ToLower(arch) == ArchARM64
}
func requiresBuild(image Image) (bool, error) {
noBuild, noURL := image.Build.isEmpty(), image.Location == nil
// Error if both of them are specified or neither is specified.
if noBuild == noURL {
return false, fmt.Errorf(`either "image.build" or "image.location" needs to be specified in the manifest`)
}
if image.Location == nil {
return true, nil
}
return false, nil
}
func stringP(s string) *string {
if s == "" {
return nil
}
return &s
}
func uint16P(n uint16) *uint16 {
if n == 0 {
return nil
}
return &n
}
func placementStringP(p PlacementString) *PlacementString {
if p == "" {
return nil
}
placement := p
return &placement
}
func (cfg PublishConfig) publishedTopics() []Topic {
if len(cfg.Topics) == 0 {
return nil
}
pubs := make([]Topic, len(cfg.Topics))
for i, topic := range cfg.Topics {
if topic.FIFO.IsEnabled() {
topic.Name = aws.String(aws.StringValue(topic.Name) + ".fifo")
}
pubs[i] = topic
}
return pubs
}
// ContainerDependencies returns a map of ContainerDependency objects from workload manifest.
func ContainerDependencies(unmarshaledManifest interface{}) map[string]ContainerDependency {
type containerDependency interface {
ContainerDependencies() map[string]ContainerDependency
}
mf, ok := unmarshaledManifest.(containerDependency)
if ok {
return mf.ContainerDependencies()
}
return nil
}