internal/pkg/config/env.go (140 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config
import (
"encoding/json"
"fmt"
"sort"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ssm"
)
// Environment represents a deployment environment in an application.
type Environment struct {
App string `json:"app"` // Name of the app this environment belongs to.
Name string `json:"name"` // Name of the environment, must be unique within a App.
Region string `json:"region"` // Name of the region this environment is stored in.
AccountID string `json:"accountID"` // Account ID of the account this environment is stored in.
RegistryURL string `json:"registryURL"` // URL For ECR Registry for this environment.
ExecutionRoleARN string `json:"executionRoleARN"` // ARN used by CloudFormation to make modification to the environment stack.
ManagerRoleARN string `json:"managerRoleARN"` // ARN for the manager role assumed to manipulate the environment and its services.
// Fields that store user configuration is no longer updated, but kept for retrofitting purpose.
CustomConfig *CustomizeEnv `json:"customConfig,omitempty"` // Deprecated. Custom environment configuration by users. This configuration is now available in the env manifest.
Telemetry *Telemetry `json:"telemetry,omitempty"` // Deprecated. Optional environment telemetry features. This configuration is now available in the env manifest.
}
// CustomizeEnv represents the custom environment config.
type CustomizeEnv struct {
ImportVPC *ImportVPC `json:"importVPC,omitempty"`
VPCConfig *AdjustVPC `json:"adjustVPC,omitempty"`
ImportCertARNs []string `json:"importCertARNs,omitempty"`
InternalALBSubnets []string `json:"internalALBSubnets,omitempty"`
EnableInternalALBVPCIngress bool `json:"enableInternalALBVPCIngress,omitempty"`
}
// IsEmpty returns true if CustomizeEnv is an empty struct.
func (c *CustomizeEnv) IsEmpty() bool {
if c == nil {
return true
}
return c.ImportVPC == nil && c.VPCConfig == nil && len(c.ImportCertARNs) == 0 && len(c.InternalALBSubnets) == 0 && !c.EnableInternalALBVPCIngress
}
// ImportVPC holds the fields to import VPC resources.
type ImportVPC struct {
ID string `json:"id"` // ID for the VPC.
PublicSubnetIDs []string `json:"publicSubnetIDs"`
PrivateSubnetIDs []string `json:"privateSubnetIDs"`
}
// AdjustVPC holds the fields to adjust default VPC resources.
type AdjustVPC struct {
CIDR string `json:"cidr"` // CIDR range for the VPC.
AZs []string `json:"availabilityZoneNames"`
PublicSubnetCIDRs []string `json:"publicSubnetCIDRs"`
PrivateSubnetCIDRs []string `json:"privateSubnetCIDRs"`
}
// Telemetry represents optional observability and monitoring configuration.
type Telemetry struct {
EnableContainerInsights bool `json:"containerInsights"`
}
// CreateEnvironment instantiates a new environment within an existing App. Skip if
// the environment already exists in the App.
func (s *Store) CreateEnvironment(environment *Environment) error {
if _, err := s.GetApplication(environment.App); err != nil {
return err
}
environmentPath := fmt.Sprintf(fmtEnvParamPath, environment.App, environment.Name)
data, err := marshal(environment)
if err != nil {
return fmt.Errorf("serializing environment %s: %w", environment.Name, err)
}
_, err = s.ssm.PutParameter(&ssm.PutParameterInput{
Name: aws.String(environmentPath),
Description: aws.String(fmt.Sprintf("The %s deployment stage", environment.Name)),
Type: aws.String(ssm.ParameterTypeString),
Value: aws.String(data),
Tags: []*ssm.Tag{
{
Key: aws.String("copilot-application"),
Value: aws.String(environment.App),
},
{
Key: aws.String("copilot-environment"),
Value: aws.String(environment.Name),
},
},
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ssm.ErrCodeParameterAlreadyExists:
return nil
}
}
return fmt.Errorf("create environment %s in application %s: %w", environment.Name, environment.App, err)
}
return nil
}
// GetEnvironment gets an environment belonging to a particular application by name. If no environment is found
// it returns ErrNoSuchEnvironment.
func (s *Store) GetEnvironment(appName string, environmentName string) (*Environment, error) {
environmentPath := fmt.Sprintf(fmtEnvParamPath, appName, environmentName)
environmentParam, err := s.ssm.GetParameter(&ssm.GetParameterInput{
Name: aws.String(environmentPath),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ssm.ErrCodeParameterNotFound:
return nil, &ErrNoSuchEnvironment{
ApplicationName: appName,
EnvironmentName: environmentName,
}
}
}
return nil, fmt.Errorf("get environment %s in application %s: %w", environmentName, appName, err)
}
var env Environment
err = json.Unmarshal([]byte(*environmentParam.Parameter.Value), &env)
if err != nil {
return nil, fmt.Errorf("read configuration for environment %s in application %s: %w", environmentName, appName, err)
}
return &env, nil
}
// ListEnvironments returns all environments belonging to a particular application.
func (s *Store) ListEnvironments(appName string) ([]*Environment, error) {
var environments []*Environment
environmentsPath := fmt.Sprintf(rootEnvParamPath, appName)
serializedEnvs, err := s.listParams(environmentsPath)
if err != nil {
return nil, fmt.Errorf("list environments for application %s: %w", appName, err)
}
for _, serializedEnv := range serializedEnvs {
var env Environment
if err := json.Unmarshal([]byte(*serializedEnv), &env); err != nil {
return nil, fmt.Errorf("read environment configuration for application %s: %w", appName, err)
}
environments = append(environments, &env)
}
sort.SliceStable(environments, func(i, j int) bool { return environments[i].Name < environments[j].Name })
return environments, nil
}
// DeleteEnvironment removes an environment from SSM.
// If the environment does not exist in the store or is successfully deleted then returns nil. Otherwise, returns an error.
func (s *Store) DeleteEnvironment(appName, environmentName string) error {
paramName := fmt.Sprintf(fmtEnvParamPath, appName, environmentName)
_, err := s.ssm.DeleteParameter(&ssm.DeleteParameterInput{
Name: aws.String(paramName),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ssm.ErrCodeParameterNotFound:
return nil
}
}
return fmt.Errorf("delete environment %s from application %s: %w", environmentName, appName, err)
}
return nil
}