@@ -15,6 +15,7 @@ import (
1515 "io"
1616 "io/fs"
1717 "log"
18+ "maps"
1819 "os"
1920 "os/exec"
2021 "path/filepath"
@@ -42,6 +43,14 @@ func withPrefix(f logf, prefix string) logf {
4243 }
4344}
4445
46+ func withPlatformPrefix (f logf , p v1.Platform ) logf {
47+ var variantSlash string
48+ if v := p .Variant ; v != "" {
49+ variantSlash = "/" + v
50+ }
51+ return withPrefix (f , fmt .Sprintf ("%v/%v%s: " , p .OS , p .Architecture , variantSlash ))
52+ }
53+
4554// parseFiles parses a comma-separated list of colon-separated pairs
4655// into a map of filePathOnDisk -> filePathInContainer.
4756func parseFiles (s string ) (map [string ]string , error ) {
@@ -87,6 +96,7 @@ type buildParams struct {
8796 verbose bool
8897 annotations map [string ]string // OCI image annotations
8998 volumes map [string ]struct {}
99+ envVars []string // Environment variables to add to image config
90100}
91101
92102func main () {
@@ -107,6 +117,7 @@ func main() {
107117 Annotations must be comma separated key=value pairs, i.e key1=val1,key2=val2. For a single image manifest annotations will get added to the image manifest.
108118 For an image index (a multi-platform manifest list) annotations will get added to each image manifest as well as the image index.
109119 Annotations with empty values are not supported.` )
120+ envArg = flag .String ("env" , "" , "comma-separated list of environment variables in KEY=value form to add to the image config" )
110121 )
111122 flag .Parse ()
112123 if * tagArg == "" {
@@ -139,11 +150,13 @@ func main() {
139150 log .Fatal ("at least one of --files or --gopaths must be set" )
140151 }
141152 var vols map [string ]struct {}
142- for vol := range strings .SplitSeq (* volumes , "," ) {
143- if vols == nil {
144- vols = make (map [string ]struct {})
153+ if * volumes != "" {
154+ for vol := range strings .SplitSeq (* volumes , "," ) {
155+ if vols == nil {
156+ vols = make (map [string ]struct {})
157+ }
158+ vols [strings .TrimSpace (vol )] = struct {}{}
145159 }
146- vols [strings .TrimSpace (vol )] = struct {}{}
147160 }
148161
149162 bp := & buildParams {
@@ -159,6 +172,7 @@ func main() {
159172 goarch : strings .Split (* goarch , "," ),
160173 annotations : parseAnnotations (* annotations ),
161174 volumes : vols ,
175+ envVars : parseEnv (* envArg ),
162176 }
163177
164178 if err := fetchAndBuild (bp ); err != nil {
@@ -250,11 +264,15 @@ func fetchAndBuild(bp *buildParams) error {
250264 if err := bp .verifyPlatform (p ); err != nil {
251265 return err
252266 }
253- logf := withPrefix (logf , fmt . Sprintf ( "%v/%v: " , p . OS , p . Architecture ) )
267+ logf := withPlatformPrefix (logf , p )
254268 img , err := createImageForBase (bp , logf , baseImage , p )
255269 if err != nil {
256270 return err
257271 }
272+ img , err = applyEnvVars (img , bp .envVars )
273+ if err != nil {
274+ return err
275+ }
258276 if ! bp .publish {
259277 logf ("not pushing" )
260278 return nil
@@ -292,10 +310,13 @@ func fetchAndBuild(bp *buildParams) error {
292310 var adds []mutate.IndexAddendum
293311 // Try to build images for all supported platforms.
294312 for _ , id := range im .Manifests {
295- logf := withPrefix (logf , fmt .Sprintf ("%v/%v: " , id .Platform .OS , id .Platform .Architecture ))
296313 if id .Platform == nil {
297314 return fmt .Errorf ("unknown platform for image: %v" , bp .baseImage )
298315 }
316+ if id .Platform .OS == "unknown" {
317+ continue
318+ }
319+ logf := withPlatformPrefix (logf , * id .Platform )
299320 if err := bp .verifyPlatform (* id .Platform ); err != nil {
300321 logf ("skipping: %v" , err )
301322 continue
@@ -314,15 +335,31 @@ func fetchAndBuild(bp *buildParams) error {
314335 // Ensure that any provided OCI annotations are added to each OCI image manifest.
315336 img = mutate .Annotations (img , bp .annotations ).(v1.Image )
316337
338+ img , err = applyEnvVars (img , bp .envVars )
339+ if err != nil {
340+ return err
341+ }
342+
343+ if bp .volumes != nil {
344+ img , err = mutateConfig (img , func (c * v1.Config ) error {
345+ c .Volumes = bp .volumes
346+ return nil
347+ })
348+ if err != nil {
349+ return err
350+ }
351+ }
352+
317353 if args := flag .Args (); len (args ) > 0 {
318- img , err = mutate . Config (img , v1.Config {
319- Cmd : args ,
320- Volumes : bp . volumes ,
354+ img , err = mutateConfig (img , func ( c * v1.Config ) error {
355+ c . Cmd = args
356+ return nil
321357 })
322358 if err != nil {
323359 return err
324360 }
325361 }
362+
326363 d , err := img .Digest ()
327364 if err != nil {
328365 return err
@@ -634,3 +671,74 @@ func parseAnnotations(s string) map[string]string {
634671 }
635672 return annotations
636673}
674+
675+ // parseEnv accepts a string with comma separated KEY=value pairs of environment variables
676+ // and returns them as a slice of "KEY=value" strings.
677+ func parseEnv (s string ) []string {
678+ if len (s ) == 0 {
679+ return nil
680+ }
681+ var envVars []string
682+ for env := range strings .SplitSeq (s , "," ) {
683+ env = strings .TrimSpace (env )
684+ if len (env ) == 0 {
685+ continue
686+ }
687+ if ! strings .Contains (env , "=" ) {
688+ continue
689+ }
690+ envVars = append (envVars , env )
691+ }
692+ return envVars
693+ }
694+
695+ // applyEnvVars applies environment variables to an image config, merging with existing env vars.
696+ // New env vars override existing ones with the same key.
697+ func applyEnvVars (img v1.Image , newEnvVars []string ) (v1.Image , error ) {
698+ if len (newEnvVars ) == 0 {
699+ return img , nil
700+ }
701+ config , err := img .ConfigFile ()
702+ if err != nil {
703+ return nil , fmt .Errorf ("error getting config: %w" , err )
704+ }
705+
706+ envMap := make (map [string ]string )
707+ for _ , kv := range config .Config .Env {
708+ if k , v , ok := strings .Cut (kv , "=" ); ok {
709+ envMap [k ] = v
710+ }
711+ }
712+
713+ for _ , env := range newEnvVars {
714+ if k , v , ok := strings .Cut (env , "=" ); ok {
715+ envMap [k ] = v
716+ }
717+ }
718+
719+ // Apply the merged env vars
720+ return mutateConfig (img , func (c * v1.Config ) error {
721+ c .Env = nil
722+ for _ , k := range slices .Sorted (maps .Keys (envMap )) {
723+ c .Env = append (c .Env , k + "=" + envMap [k ])
724+ }
725+ return nil
726+ })
727+ }
728+
729+ // mutateConfig returns img with its config mutated by f.
730+ //
731+ // The pointer given to f is a deep copy of the existing config,
732+ // so any fields untouched by f will be preserved.
733+ func mutateConfig (img v1.Image , f func (* v1.Config ) error ) (v1.Image , error ) {
734+ config , err := img .ConfigFile ()
735+ if err != nil {
736+ return nil , fmt .Errorf ("error getting config: %w" , err )
737+ }
738+
739+ confCopy := config .DeepCopy ()
740+ if err := f (& confCopy .Config ); err != nil {
741+ return nil , err
742+ }
743+ return mutate .Config (img , confCopy .Config )
744+ }
0 commit comments