@@ -2,12 +2,17 @@ package kube_run
22
33import (
44 "context"
5+ "encoding/base64"
6+ "encoding/json"
57 "fmt"
68 "math/rand"
79 "os"
810 "os/exec"
911 "strings"
1012
13+ "github.com/containers/image/v5/docker/reference"
14+ config2 "github.com/containers/image/v5/pkg/docker/config"
15+ imgtypes "github.com/containers/image/v5/types"
1116 "github.com/spf13/cobra"
1217 corev1 "k8s.io/api/core/v1"
1318 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -40,6 +45,7 @@ type cmdDataType struct {
4045 AllocateTty bool
4146 Rm bool
4247 RmWithNamespace bool
48+ AutoPullSecret bool
4349
4450 Pod string
4551 Command []string
5359 commonCmdData common.CmdData
5460)
5561
62+ type dockerConfigJson struct {
63+ Auths map [string ]dockerAuthJson `json:"auths"`
64+ }
65+
66+ type dockerAuthJson struct {
67+ Auth string `json:"auth,omitempty"`
68+ IdentityToken string `json:"identitytoken,omitempty"`
69+ }
70+
5671func NewCmd () * cobra.Command {
5772 cmd := & cobra.Command {
5873 Use : "kube-run [options] [IMAGE_NAME] [-- COMMAND ARG...]" ,
@@ -153,10 +168,11 @@ func NewCmd() *cobra.Command {
153168 cmd .Flags ().StringVarP (& cmdData .Pod , "pod" , "" , os .Getenv ("WERF_POD" ), "Set created pod name (default $WERF_POD or autogenerated if not specified)" )
154169 cmd .Flags ().StringVarP (& cmdData .Overrides , "overrides" , "" , os .Getenv ("WERF_OVERRIDES" ), "Inline JSON to override/extend any fields in created Pod, e.g. to add imagePullSecrets field (default $WERF_OVERRIDES)" )
155170 cmd .Flags ().StringVarP (& cmdData .ExtraOptions , "extra-options" , "" , os .Getenv ("WERF_EXTRA_OPTIONS" ), "Pass extra options to \" kubectl run\" command (default $WERF_EXTRA_OPTIONS)" )
156- cmd .Flags ().BoolVarP (& cmdData .Rm , "rm" , "" , common .GetBoolEnvironmentDefaultTrue ("WERF_RM" ), "Remove pod after command completion (default $WERF_RM or true if not specified)" )
171+ cmd .Flags ().BoolVarP (& cmdData .Rm , "rm" , "" , common .GetBoolEnvironmentDefaultTrue ("WERF_RM" ), "Remove pod and other created resources after command completion (default $WERF_RM or true if not specified)" )
157172 cmd .Flags ().BoolVarP (& cmdData .RmWithNamespace , "rm-with-namespace" , "" , common .GetBoolEnvironmentDefaultFalse ("WERF_RM_WITH_NAMESPACE" ), "Remove also a namespace after command completion (default $WERF_RM_WITH_NAMESPACE or false if not specified)" )
158173 cmd .Flags ().BoolVarP (& cmdData .Interactive , "interactive" , "i" , common .GetBoolEnvironmentDefaultFalse ("WERF_INTERACTIVE" ), "Enable interactive mode (default $WERF_INTERACTIVE or false if not specified)" )
159174 cmd .Flags ().BoolVarP (& cmdData .AllocateTty , "tty" , "t" , common .GetBoolEnvironmentDefaultFalse ("WERF_TTY" ), "Allocate a TTY (default $WERF_TTY or false if not specified)" )
175+ cmd .Flags ().BoolVarP (& cmdData .AutoPullSecret , "auto-pull-secret" , "" , common .GetBoolEnvironmentDefaultTrue ("WERF_AUTO_PULL_SECRET" ), "Automatically create docker config secret in the namespace and plug it via pod's imagePullSecrets for private registry access (default $WERF_AUTO_PULL_SECRET or true if not specified)" )
160176
161177 return cmd
162178}
@@ -252,6 +268,7 @@ func runMain(ctx context.Context) error {
252268 } else {
253269 pod = cmdData .Pod
254270 }
271+ secret := pod
255272
256273 _ , werfConfig , err := common .GetRequiredWerfConfig (ctx , & commonCmdData , giterminismManager , common .GetWerfConfigOptions (& commonCmdData , false ))
257274 if err != nil {
@@ -271,31 +288,31 @@ func runMain(ctx context.Context) error {
271288 }
272289
273290 defer func () {
274- cleanupResources (ctx , pod , namespace )
291+ cleanupResources (ctx , pod , secret , namespace )
275292 }()
276293
277294 if * commonCmdData .Follow {
278295 return common .FollowGitHead (ctx , & commonCmdData , func (ctx context.Context , headCommitGiterminismManager giterminism_manager.Interface ) error {
279- cleanupResources (ctx , pod , namespace )
296+ cleanupResources (ctx , pod , secret , namespace )
280297
281- if err := run (ctx , pod , namespace , werfConfig , containerRuntime , giterminismManager ); err != nil {
298+ if err := run (ctx , pod , secret , namespace , werfConfig , containerRuntime , giterminismManager ); err != nil {
282299 return err
283300 }
284301
285- cleanupResources (ctx , pod , namespace )
302+ cleanupResources (ctx , pod , secret , namespace )
286303
287304 return nil
288305 })
289306 } else {
290- if err := run (ctx , pod , namespace , werfConfig , containerRuntime , giterminismManager ); err != nil {
307+ if err := run (ctx , pod , secret , namespace , werfConfig , containerRuntime , giterminismManager ); err != nil {
291308 return err
292309 }
293310 }
294311
295312 return nil
296313}
297314
298- func run (ctx context.Context , pod string , namespace string , werfConfig * config.WerfConfig , containerRuntime container_runtime.ContainerRuntime , giterminismManager giterminism_manager.Interface ) error {
315+ func run (ctx context.Context , pod , secret , namespace string , werfConfig * config.WerfConfig , containerRuntime container_runtime.ContainerRuntime , giterminismManager giterminism_manager.Interface ) error {
299316 projectName := werfConfig .Meta .Project
300317
301318 projectTmpDir , err := tmp_manager .CreateProjectDir (ctx )
@@ -371,6 +388,19 @@ func run(ctx context.Context, pod string, namespace string, werfConfig *config.W
371388 return err
372389 }
373390
391+ if cmdData .AutoPullSecret {
392+ namedRef , dockerAuthConf , err := getDockerConfigCredentials (image )
393+ if err != nil {
394+ return fmt .Errorf ("unable to get docker config credentials: %w" , err )
395+ }
396+
397+ if dockerAuthConf != (imgtypes.DockerAuthConfig {}) {
398+ if err := createDockerRegistrySecret (ctx , secret , namespace , * namedRef , dockerAuthConf ); err != nil {
399+ return fmt .Errorf ("unable to create docker registry secret: %w" , err )
400+ }
401+ }
402+ }
403+
374404 args := []string {
375405 "kubectl" ,
376406 "run" ,
@@ -391,8 +421,26 @@ func run(ctx context.Context, pod string, namespace string, werfConfig *config.W
391421 args = append (args , "-t" )
392422 }
393423
424+ var overrides map [string ]interface {}
394425 if cmdData .Overrides != "" {
395- args = append (args , "--overrides" , cmdData .Overrides )
426+ if err := json .Unmarshal ([]byte (cmdData .Overrides ), & overrides ); err != nil {
427+ return fmt .Errorf ("unable to unmarshal --overrides: %w" , err )
428+ }
429+ }
430+
431+ if cmdData .AutoPullSecret {
432+ overrides , err = addImagePullSecret (secret , overrides )
433+ if err != nil {
434+ return fmt .Errorf ("unable to add imagePullSecret to --overrides: %w" , err )
435+ }
436+ }
437+
438+ if len (overrides ) > 0 {
439+ overridesB , err := json .Marshal (overrides )
440+ if err != nil {
441+ return fmt .Errorf ("unable to marshal generated --overrides: %w" , err )
442+ }
443+ args = append (args , "--overrides" , string (overridesB ))
396444 }
397445
398446 if cmdData .ExtraOptions != "" {
@@ -431,21 +479,34 @@ func run(ctx context.Context, pod string, namespace string, werfConfig *config.W
431479 })
432480}
433481
434- func cleanupResources (ctx context.Context , pod string , namespace string ) {
482+ func cleanupResources (ctx context.Context , pod , secret , namespace string ) {
483+ if ! cmdData .Rm {
484+ return
485+ }
486+
435487 if isNsExist , err := isNamespaceExist (ctx , namespace ); err != nil {
436488 logboek .Context (ctx ).Warn ().LogF ("WARNING: unable to check for namespace existence: %s\n " , err )
437489 return
438490 } else if ! isNsExist {
439491 return
440492 }
441493
442- if cmdData .Rm {
443- if isPodExist , err := isPodExist (ctx , pod , namespace ); err != nil {
444- logboek .Context (ctx ).Warn ().LogF ("WARNING: unable to check for pod existence: %s\n " , err )
445- } else if isPodExist {
446- logboek .Context (ctx ).LogF ("Cleaning up pod %q ...\n " , pod )
447- if err := kube .Client .CoreV1 ().Pods (namespace ).Delete (ctx , pod , v1.DeleteOptions {}); err != nil {
448- logboek .Context (ctx ).Warn ().LogF ("WARNING: pod cleaning up failed: %s\n " , err )
494+ if isPodExist , err := isPodExist (ctx , pod , namespace ); err != nil {
495+ logboek .Context (ctx ).Warn ().LogF ("WARNING: unable to check for pod existence: %s\n " , err )
496+ } else if isPodExist {
497+ logboek .Context (ctx ).LogF ("Cleaning up pod %q ...\n " , pod )
498+ if err := kube .Client .CoreV1 ().Pods (namespace ).Delete (ctx , pod , v1.DeleteOptions {}); err != nil {
499+ logboek .Context (ctx ).Warn ().LogF ("WARNING: pod cleaning up failed: %s\n " , err )
500+ }
501+ }
502+
503+ if cmdData .AutoPullSecret {
504+ if isSecretExist , err := isSecretExist (ctx , secret , namespace ); err != nil {
505+ logboek .Context (ctx ).Warn ().LogF ("WARNING: unable to check for secret existence: %s\n " , err )
506+ } else if isSecretExist {
507+ logboek .Context (ctx ).LogF ("Cleaning up secret %q ...\n " , secret )
508+ if err := kube .Client .CoreV1 ().Secrets (namespace ).Delete (ctx , secret , v1.DeleteOptions {}); err != nil {
509+ logboek .Context (ctx ).Warn ().LogF ("WARNING: secret cleaning up failed: %s\n " , err )
449510 }
450511 }
451512 }
@@ -480,6 +541,45 @@ func createNamespace(ctx context.Context, namespace string) error {
480541 return nil
481542}
482543
544+ func createDockerRegistrySecret (ctx context.Context , name , namespace string , ref reference.Named , dockerAuthConf imgtypes.DockerAuthConfig ) error {
545+ secret := & corev1.Secret {
546+ ObjectMeta : v1.ObjectMeta {
547+ Name : name ,
548+ Namespace : namespace ,
549+ },
550+ Data : map [string ][]byte {},
551+ Type : corev1 .SecretTypeDockerConfigJson ,
552+ }
553+
554+ var authJson dockerAuthJson
555+ switch {
556+ case dockerAuthConf .IdentityToken != "" :
557+ authJson .IdentityToken = dockerAuthConf .IdentityToken
558+ case dockerAuthConf .Username != "" && dockerAuthConf .Password != "" :
559+ authJson .Auth = base64 .StdEncoding .EncodeToString ([]byte (dockerAuthConf .Username + ":" + dockerAuthConf .Password ))
560+ default :
561+ panic ("unexpected dockerAuthConf" )
562+ }
563+
564+ dockerConfJson := & dockerConfigJson {
565+ Auths : map [string ]dockerAuthJson {
566+ ref .Name (): authJson ,
567+ },
568+ }
569+
570+ dockerConf , err := json .Marshal (dockerConfJson )
571+ if err != nil {
572+ return fmt .Errorf ("unable to marshal docker config json: %w" , err )
573+ }
574+
575+ secret .Data [corev1 .DockerConfigJsonKey ] = dockerConf
576+
577+ logboek .Context (ctx ).LogF ("Creating secret %q in namespace %q ...\n " , name , namespace )
578+ kube .Client .CoreV1 ().Secrets (namespace ).Create (ctx , secret , v1.CreateOptions {})
579+
580+ return nil
581+ }
582+
483583func isNamespaceExist (ctx context.Context , namespace string ) (bool , error ) {
484584 if matchedNamespaces , err := kube .Client .CoreV1 ().Namespaces ().List (ctx , v1.ListOptions {
485585 FieldSelector : fields .OneTermEqualSelector ("metadata.name" , namespace ).String (),
@@ -503,3 +603,74 @@ func isPodExist(ctx context.Context, pod string, namespace string) (bool, error)
503603
504604 return false , nil
505605}
606+
607+ func isSecretExist (ctx context.Context , secret string , namespace string ) (bool , error ) {
608+ if matchedSecrets , err := kube .Client .CoreV1 ().Secrets (namespace ).List (ctx , v1.ListOptions {
609+ FieldSelector : fields .OneTermEqualSelector ("metadata.name" , secret ).String (),
610+ }); err != nil {
611+ return false , fmt .Errorf ("unable to list secrets: %w" , err )
612+ } else if len (matchedSecrets .Items ) > 0 {
613+ return true , nil
614+ }
615+
616+ return false , nil
617+ }
618+
619+ // Might return empty DockerAuthConfig.
620+ func getDockerConfigCredentials (ref string ) (* reference.Named , imgtypes.DockerAuthConfig , error ) {
621+ namedRef , err := reference .ParseNormalizedNamed (ref )
622+ if err != nil {
623+ return nil , imgtypes.DockerAuthConfig {}, fmt .Errorf ("unable to parse docker config registry reference %q: %w" , ref , err )
624+ }
625+
626+ dockerAuthConf , err := config2 .GetCredentialsForRef (& imgtypes.SystemContext {AuthFilePath : * commonCmdData .DockerConfig }, namedRef )
627+ if err != nil {
628+ return nil , imgtypes.DockerAuthConfig {}, fmt .Errorf ("unable to get docker registry creds for ref %q: %w" , ref , err )
629+ }
630+
631+ return & namedRef , dockerAuthConf , nil
632+ }
633+
634+ func addImagePullSecret (secret string , overrides map [string ]interface {}) (map [string ]interface {}, error ) {
635+ if secret == "" {
636+ panic ("secret name can't be empty" )
637+ }
638+
639+ newImagePullSecret := map [string ]interface {}{"name" : secret }
640+ newImagePullSecrets := []interface {}{newImagePullSecret }
641+ newSpec := map [string ]interface {}{"imagePullSecrets" : newImagePullSecrets }
642+ newOverrides := map [string ]interface {}{"spec" : newSpec }
643+
644+ if len (overrides ) == 0 {
645+ return newOverrides , nil
646+ }
647+
648+ if _ , ok := overrides ["spec" ]; ! ok {
649+ overrides ["spec" ] = newSpec
650+ return overrides , nil
651+ }
652+
653+ overridesSpec , ok := overrides ["spec" ].(map [string ]interface {})
654+ if ! ok {
655+ return nil , fmt .Errorf ("unexpected pod spec overrides format: %+v" , overrides )
656+ }
657+
658+ if len (overridesSpec ) == 0 {
659+ overrides ["spec" ] = newSpec
660+ return overrides , nil
661+ }
662+
663+ _ , ok = overridesSpec ["imagePullSecrets" ]
664+ if ! ok {
665+ overrides ["spec" ].(map [string ]interface {})["imagePullSecrets" ] = newImagePullSecrets
666+ return overrides , nil
667+ }
668+
669+ _ , ok = overridesSpec ["imagePullSecrets" ].([]interface {})
670+ if ! ok {
671+ return nil , fmt .Errorf ("unexpected imagePullSecrets overrides format: %+v" , overrides )
672+ }
673+
674+ overrides ["spec" ].(map [string ]interface {})["imagePullSecrets" ] = append (overrides ["spec" ].(map [string ]interface {})["imagePullSecrets" ].([]interface {}), newImagePullSecret )
675+ return overrides , nil
676+ }
0 commit comments