-
Notifications
You must be signed in to change notification settings - Fork 427
/
Copy pathstorage.go
196 lines (167 loc) · 6.28 KB
/
storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package manifest
import (
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/copilot-cli/internal/pkg/template"
"gopkg.in/yaml.v3"
)
var (
errUnmarshalEFSOpts = errors.New(`cannot unmarshal "efs" field into bool or map`)
)
// Storage represents the options for external and native storage.
type Storage struct {
Ephemeral *int `yaml:"ephemeral"`
ReadonlyRootFS *bool `yaml:"readonly_fs"`
Volumes map[string]*Volume `yaml:"volumes"` // NOTE: keep the pointers because `mergo` doesn't automatically deep merge map's value unless it's a pointer type.
}
// IsEmpty returns empty if the struct has all zero members.
func (s *Storage) IsEmpty() bool {
return s.Ephemeral == nil && s.Volumes == nil && s.ReadonlyRootFS == nil
}
func (s *Storage) requiredEnvFeatures() []string {
if s.hasManagedFS() {
return []string{template.EFSFeatureName}
}
return nil
}
func (s *Storage) hasManagedFS() bool {
for _, v := range s.Volumes {
if v.EmptyVolume() || !v.EFS.UseManagedFS() {
continue
}
return true
}
return false
}
// Volume is an abstraction which merges the MountPoint and Volumes concepts from the ECS Task Definition
type Volume struct {
EFS EFSConfigOrBool `yaml:"efs"`
MountPointOpts `yaml:",inline"`
}
// EmptyVolume returns true if the EFS configuration is nil or explicitly/implicitly disabled.
func (v *Volume) EmptyVolume() bool {
if v.EFS.IsEmpty() {
return true
}
// Respect Bool value first: return true if EFS is explicitly disabled.
if v.EFS.Disabled() {
return true
}
return false
}
// MountPointOpts is shared between Volumes for the main container and MountPoints for sidecars.
type MountPointOpts struct {
ContainerPath *string `yaml:"path"`
ReadOnly *bool `yaml:"read_only"`
}
// SidecarMountPoint is used to let sidecars mount volumes defined in `storage`
type SidecarMountPoint struct {
SourceVolume *string `yaml:"source_volume"`
MountPointOpts `yaml:",inline"`
}
// EFSVolumeConfiguration holds options which tell ECS how to reach out to the EFS filesystem.
type EFSVolumeConfiguration struct {
FileSystemID StringOrFromCFN `yaml:"id"` // Required. Can be specified as "copilot" or "managed" magic keys.
RootDirectory *string `yaml:"root_dir"` // Default "/". For BYO EFS.
AuthConfig AuthorizationConfig `yaml:"auth"` // Auth config for BYO EFS.
UID *uint32 `yaml:"uid"` // UID for managed EFS.
GID *uint32 `yaml:"gid"` // GID for managed EFS.
}
// IsEmpty returns empty if the struct has all zero members.
func (e *EFSVolumeConfiguration) IsEmpty() bool {
return e.FileSystemID.isEmpty() && e.RootDirectory == nil && e.AuthConfig.IsEmpty() && e.UID == nil && e.GID == nil
}
// EFSConfigOrBool contains custom unmarshaling logic for the `efs` field in the manifest.
type EFSConfigOrBool struct {
Advanced EFSVolumeConfiguration
Enabled *bool
}
// IsEmpty returns empty if the struct has all zero members.
func (e *EFSConfigOrBool) IsEmpty() bool {
return e.Advanced.IsEmpty() && e.Enabled == nil
}
// UnmarshalYAML implements the yaml(v3) interface. It allows EFS to be specified as a
// string or a struct alternately.
func (e *EFSConfigOrBool) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&e.Advanced); err != nil {
switch err.(type) {
case *yaml.TypeError:
break
default:
return err
}
}
if !e.Advanced.IsEmpty() {
if err := e.Advanced.isValid(); err != nil {
// NOTE: `e.Advanced` contains exclusive fields.
// Validating that exclusive fields cannot be set simultaneously is necessary during `UnmarshalYAML`
// because the `ApplyEnv` stage assumes that no exclusive fields are set together.
// Not validating it during `UnmarshalYAML` would potentially cause an invalid manifest being deemed valid.
return err
}
// Unmarshaled successfully to e.Config, unset e.ID, and return.
e.Enabled = nil
return nil
}
if err := value.Decode(&e.Enabled); err != nil {
return errUnmarshalEFSOpts
}
return nil
}
// UseManagedFS returns true if the user has specified EFS as a bool, or has only specified UID and GID.
func (e *EFSConfigOrBool) UseManagedFS() bool {
// Respect explicitly enabled or disabled value first.
if e.Enabled != nil {
return aws.BoolValue(e.Enabled)
}
// Check whether we're implicitly enabling managed EFS via UID/GID.
return !e.Advanced.EmptyUIDConfig()
}
// Disabled returns true if Enabled is explicitly set to false.
// This function is useful for checking that the EFS config has been intentionally turned off
// and whether we should ignore any values of the struct which have been populated erroneously.
func (e *EFSConfigOrBool) Disabled() bool {
if e.Enabled != nil && !aws.BoolValue(e.Enabled) {
return true
}
return false
}
// EmptyBYOConfig returns true if the `id`, `root_directory`, and `auth` fields are all empty.
// This would mean that no custom EFS information has been specified.
func (e *EFSVolumeConfiguration) EmptyBYOConfig() bool {
return e.FileSystemID.isEmpty() && e.AuthConfig.IsEmpty() && e.RootDirectory == nil
}
// EmptyUIDConfig returns true if the `uid` and `gid` fields are empty. These fields are mutually exclusive
// with BYO EFS. If they are nonempty, then we should use managed EFS instead.
func (e *EFSVolumeConfiguration) EmptyUIDConfig() bool {
return e.UID == nil && e.GID == nil
}
func (e *EFSVolumeConfiguration) unsetBYOConfig() {
e.FileSystemID = StringOrFromCFN{}
e.AuthConfig = AuthorizationConfig{}
e.RootDirectory = nil
}
func (e *EFSVolumeConfiguration) unsetUIDConfig() {
e.UID = nil
e.GID = nil
}
func (e *EFSVolumeConfiguration) isValid() error {
if !e.EmptyBYOConfig() && !e.EmptyUIDConfig() {
return &errFieldMutualExclusive{
firstField: "uid/gid",
secondField: "id/root_dir/auth",
}
}
return nil
}
// AuthorizationConfig holds options relating to access points and IAM authorization.
type AuthorizationConfig struct {
IAM *bool `yaml:"iam"` // Default true
AccessPointID *string `yaml:"access_point_id"` // Default ""
}
// IsEmpty returns empty if the struct has all zero members.
func (a *AuthorizationConfig) IsEmpty() bool {
return a.IAM == nil && a.AccessPointID == nil
}