internal/pkg/describe/workload.go (128 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package describe
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/copilot-cli/internal/pkg/aws/sessions"
cfnstack "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack"
"github.com/aws/copilot-cli/internal/pkg/describe/stack"
"github.com/aws/copilot-cli/internal/pkg/version"
"gopkg.in/yaml.v3"
)
// WorkloadStackDescriber provides base functionality for retrieving info about a workload stack.
type WorkloadStackDescriber struct {
app string
name string
env string
cfn stackDescriber
sess *session.Session
// Cache variables.
params map[string]string
outputs map[string]string
stackResources []*stack.Resource
}
// NewWorkloadConfig contains fields that initiates workload describer struct.
type NewWorkloadConfig struct {
App string
Env string
Name string
ConfigStore ConfigStoreSvc
}
// NewWorkloadStackDescriber instantiates the core elements of a new workload.
func NewWorkloadStackDescriber(opt NewWorkloadConfig) (*WorkloadStackDescriber, error) {
environment, err := opt.ConfigStore.GetEnvironment(opt.App, opt.Env)
if err != nil {
return nil, fmt.Errorf("get environment %s: %w", opt.Env, err)
}
sess, err := sessions.ImmutableProvider().FromRole(environment.ManagerRoleARN, environment.Region)
if err != nil {
return nil, err
}
return &WorkloadStackDescriber{
app: opt.App,
name: opt.Name,
env: opt.Env,
cfn: stack.NewStackDescriber(cfnstack.NameForWorkload(opt.App, opt.Env, opt.Name), sess),
sess: sess,
}, nil
}
// Version returns the CloudFormation template version associated with
// the workload by reading the Metadata.Version field from the template.
//
// If the Version field does not exist, then it's a legacy template and it returns an version.LegacyWorkloadTemplate and nil error.
func (d *WorkloadStackDescriber) Version() (string, error) {
return stackVersion(d.cfn, version.LegacyWorkloadTemplate)
}
func stackVersion(descr stackDescriber, legacyVersion string) (string, error) {
raw, err := descr.StackMetadata()
if err != nil {
return "", err
}
metadata := struct {
Version string `yaml:"Version"`
}{}
if err := yaml.Unmarshal([]byte(raw), &metadata); err != nil {
return "", fmt.Errorf("unmarshal Metadata property to read Version: %w", err)
}
if metadata.Version == "" {
return legacyVersion, nil
}
return metadata.Version, nil
}
// Params returns the parameters of the workload stack.
func (d *WorkloadStackDescriber) Params() (map[string]string, error) {
if d.params != nil {
return d.params, nil
}
descr, err := d.cfn.Describe()
if err != nil {
return nil, err
}
d.params = descr.Parameters
return descr.Parameters, nil
}
// Outputs returns the outputs of the service stack.
func (d *WorkloadStackDescriber) Outputs() (map[string]string, error) {
if d.outputs != nil {
return d.outputs, nil
}
descr, err := d.cfn.Describe()
if err != nil {
return nil, err
}
d.outputs = descr.Outputs
return descr.Outputs, nil
}
// StackResources returns the workload stack resources created by CloudFormation.
func (d *WorkloadStackDescriber) StackResources() ([]*stack.Resource, error) {
if len(d.stackResources) != 0 {
return d.stackResources, nil
}
svcResources, err := d.cfn.Resources()
if err != nil {
return nil, err
}
var resources []*stack.Resource
ignored := struct{}{}
ignoredResources := map[string]struct{}{
rulePriorityFunction: ignored,
waitCondition: ignored,
waitConditionHandle: ignored,
}
for _, svcResource := range svcResources {
if _, ok := ignoredResources[svcResource.Type]; !ok {
resources = append(resources, svcResource)
}
}
d.stackResources = resources
return resources, nil
}
// Manifest returns the contents of the manifest used to deploy a workload stack.
// If the Manifest metadata doesn't exist in the stack template, then returns ErrManifestNotFoundInTemplate.
func (d *WorkloadStackDescriber) Manifest() ([]byte, error) {
tpl, err := d.cfn.StackMetadata()
if err != nil {
return nil, fmt.Errorf("retrieve stack metadata for %s-%s-%s: %w", d.app, d.env, d.name, err)
}
metadata := struct {
Manifest string `yaml:"Manifest"`
}{}
if err := yaml.Unmarshal([]byte(tpl), &metadata); err != nil {
return nil, fmt.Errorf("unmarshal Metadata.Manifest in stack %s-%s-%s: %v", d.app, d.env, d.name, err)
}
if len(strings.TrimSpace(metadata.Manifest)) == 0 {
return nil, &ErrManifestNotFoundInTemplate{
app: d.app,
env: d.env,
name: d.name,
}
}
return []byte(metadata.Manifest), nil
}