66 "os"
77 "path/filepath"
88 "regexp"
9- "strings"
109
1110 "github.com/aws/aws-sdk-go-v2/service/ecs"
1211)
@@ -44,6 +43,10 @@ const TaskHealthCheckTimeout = "CAGE_TASK_HEALTH_CHECK_TIMEOUT"
4443const TaskStoppedTimeout = "CAGE_TASK_STOPPED_TIMEOUT"
4544const ServiceStableTimeout = "CAGE_SERVICE_STABLE_TIMEOUT"
4645
46+ var (
47+ envarLiteralRegexp = regexp .MustCompile (`\$\{([^}\r\n]+)\}` )
48+ )
49+
4750func EnsureEnvars (
4851 dest * Envars ,
4952) error {
@@ -70,7 +73,7 @@ func LoadServiceDefinition(dir string) (*ecs.CreateServiceInput, error) {
7073 if noSvc != nil {
7174 return nil , fmt .Errorf ("no 'service.json' found in %s" , dir )
7275 }
73- if err := ReadAndUnmarshalJson (svcPath , & service ); err != nil {
76+ if err := readAndUnmarshalJson (svcPath , & service ); err != nil {
7477 return nil , fmt .Errorf ("failed to read and unmarshal 'service.json': %s" , err )
7578 }
7679 return & service , nil
@@ -83,7 +86,7 @@ func LoadTaskDefinition(dir string) (*ecs.RegisterTaskDefinitionInput, error) {
8386 if noTd != nil {
8487 return nil , fmt .Errorf ("no 'task-definition.json' found in %s" , dir )
8588 }
86- if err := ReadAndUnmarshalJson (tdPath , & td ); err != nil {
89+ if err := readAndUnmarshalJson (tdPath , & td ); err != nil {
8790 return nil , fmt .Errorf ("failed to read and unmarshal 'task-definition.json': %s" , err )
8891 }
8992 return & td , nil
@@ -113,29 +116,82 @@ func MergeEnvars(dest *Envars, src *Envars) {
113116 }
114117}
115118
116- func ReadAndUnmarshalJson (path string , dest interface {}) error {
117- if d , err := ReadFileAndApplyEnvars (path ); err != nil {
119+ func readAndUnmarshalJson (path string , dest any ) error {
120+ if b , err := os .ReadFile (path ); err != nil {
121+ return err
122+ } else if d , err := applyEnvarsToJSON (b , path ); err != nil {
118123 return err
119124 } else if err := json .Unmarshal (d , dest ); err != nil {
120125 return err
121126 }
122127 return nil
123128}
124129
125- func ReadFileAndApplyEnvars (path string ) ([]byte , error ) {
126- d , err := os .ReadFile (path )
130+ func applyEnvarsToJSON (d []byte , path string ) ([]byte , error ) {
131+ var js any
132+ if err := json .Unmarshal (d , & js ); err != nil {
133+ return nil , err
134+ }
135+ applied , err := applyEnvarsToJSONValue (js , path )
127136 if err != nil {
128137 return nil , err
129138 }
130- str := string (d )
131- reg := regexp .MustCompile (`\${(.+?)}` )
132- submatches := reg .FindAllStringSubmatch (str , - 1 )
133- for _ , m := range submatches {
134- if envar , ok := os .LookupEnv (m [1 ]); ok {
135- str = strings .Replace (str , m [0 ], envar , - 1 )
136- } else {
137- return nil , fmt .Errorf ("envar literal '%s' found in %s but was not defined" , m [0 ], path )
139+ return json .Marshal (applied )
140+ }
141+
142+ func applyEnvarsToJSONValue (value any , path string ) (any , error ) {
143+ switch v := value .(type ) {
144+ case map [string ]any :
145+ for key , child := range v {
146+ if envarLiteralRegexp .MatchString (key ) {
147+ return nil , fmt .Errorf ("envar literal found in JSON object key '%s' in %s; envars can only be used in JSON string values" , key , path )
148+ }
149+ applied , err := applyEnvarsToJSONValue (child , path )
150+ if err != nil {
151+ return nil , err
152+ }
153+ v [key ] = applied
154+ }
155+ return v , nil
156+ case []any :
157+ for i , child := range v {
158+ applied , err := applyEnvarsToJSONValue (child , path )
159+ if err != nil {
160+ return nil , err
161+ }
162+ v [i ] = applied
163+ }
164+ return v , nil
165+ case string :
166+ return applyEnvarsToString (v , path )
167+ default :
168+ return v , nil
169+ }
170+ }
171+
172+ func applyEnvarsToString (str string , path string ) (string , error ) {
173+ var replaceErr error
174+ replaced := envarLiteralRegexp .ReplaceAllStringFunc (str , func (literal string ) string {
175+ if replaceErr != nil {
176+ return literal
138177 }
178+ envar , err := lookupEnvar (literal , path )
179+ if err != nil {
180+ replaceErr = err
181+ return literal
182+ }
183+ return envar
184+ })
185+ return replaced , replaceErr
186+ }
187+
188+ func lookupEnvar (literal string , path string ) (string , error ) {
189+ match := envarLiteralRegexp .FindStringSubmatch (literal )
190+ if len (match ) != 2 {
191+ return "" , fmt .Errorf ("invalid envar literal '%s' found in %s" , literal , path )
192+ }
193+ if envar , ok := os .LookupEnv (match [1 ]); ok {
194+ return envar , nil
139195 }
140- return [] byte ( str ), nil
196+ return "" , fmt . Errorf ( "envar literal '%s' found in %s but was not defined" , literal , path )
141197}
0 commit comments