@@ -4,11 +4,12 @@ import (
44 "crypto/sha256"
55 "encoding/base64"
66 "encoding/hex"
7- devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context "
7+ "encoding/json "
88 "regexp"
9- "strings"
109 "time"
1110
11+ devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
12+
1213 kerrors "k8s.io/apimachinery/pkg/api/errors"
1314 "k8s.io/apimachinery/pkg/util/wait"
1415
@@ -32,15 +33,33 @@ type PullSecretOptions struct {
3233 Secret string
3334}
3435
36+ // DockerConfigJSON represents a local docker auth config file
37+ // for pulling images.
38+ type DockerConfigJSON struct {
39+ Auths DockerConfig `json:"auths"`
40+ }
41+
42+ // DockerConfig represents the config file used by the docker CLI.
43+ // This config that represents the credentials that should be used
44+ // when pulling images from specific image repositories.
45+ type DockerConfig map [string ]DockerConfigEntry
46+
47+ // DockerConfigEntry holds the user information that grant the access to docker registry
48+ type DockerConfigEntry struct {
49+ Auth string `json:"auth"`
50+ Email string `json:"email"`
51+ }
52+
3553// CreatePullSecret creates an image pull secret for a registry
3654func (r * client ) CreatePullSecret (ctx * devspacecontext.Context , options * PullSecretOptions ) error {
3755 pullSecretName := options .Secret
3856 if pullSecretName == "" {
3957 pullSecretName = GetRegistryAuthSecretName (options .RegistryURL )
4058 }
4159
42- if options .RegistryURL == "hub.docker.com" || options .RegistryURL == "" {
43- options .RegistryURL = "https://index.docker.io/v1/"
60+ registryURL := options .RegistryURL
61+ if registryURL == "hub.docker.com" || registryURL == "" {
62+ registryURL = "https://index.docker.io/v1/"
4463 }
4564
4665 authToken := options .PasswordOrToken
@@ -53,48 +72,64 @@ func (r *client) CreatePullSecret(ctx *devspacecontext.Context, options *PullSec
5372 email = "noreply@devspace.sh"
5473 }
5574
56- registryAuthEncoded := base64 .StdEncoding .EncodeToString ([]byte (authToken ))
57- pullSecretDataValue := []byte (`{
58- "auths": {
59- "` + options .RegistryURL + `": {
60- "auth": "` + registryAuthEncoded + `",
61- "email": "` + email + `"
75+ err := wait .PollImmediate (time .Second , time .Second * 30 , func () (bool , error ) {
76+ secret , err := ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Get (ctx .Context , pullSecretName , metav1.GetOptions {})
77+ if err != nil {
78+ if kerrors .IsNotFound (err ) {
79+ // Create the pull secret
80+ secret , err := newPullSecret (pullSecretName , registryURL , authToken , email )
81+ if err != nil {
82+ return false , err
6283 }
63- }
64- }` )
6584
66- pullSecretData := map [string ][]byte {}
67- pullSecretDataKey := k8sv1 .DockerConfigJsonKey
68- pullSecretData [pullSecretDataKey ] = pullSecretDataValue
85+ _ , err = ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Create (ctx .Context , secret , metav1.CreateOptions {})
86+ if err != nil {
87+ if kerrors .IsAlreadyExists (err ) {
88+ // Retry
89+ return false , nil
90+ }
6991
70- registryPullSecret := & k8sv1.Secret {
71- ObjectMeta : metav1.ObjectMeta {
72- Name : pullSecretName ,
73- },
74- Data : pullSecretData ,
75- Type : k8sv1 .SecretTypeDockerConfigJson ,
76- }
92+ return false , errors .Wrap (err , "create pull secret" )
93+ }
7794
78- err := wait .PollImmediate (time .Second , time .Second * 30 , func () (bool , error ) {
79- secret , err := ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Get (ctx .Context , pullSecretName , metav1.GetOptions {})
95+ ctx .Log .Donef ("Created image pull secret %s/%s" , options .Namespace , pullSecretName )
96+ return true , nil
97+ } else {
98+ // Retry
99+ return false , nil
100+ }
101+ }
102+
103+ dockerConfigJSON , err := fromPullSecretData (secret .Data )
80104 if err != nil {
81- _ , err = ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Create (ctx .Context , registryPullSecret , metav1.CreateOptions {})
105+ return false , err
106+ }
107+
108+ existingEntry := dockerConfigJSON .Auths [registryURL ]
109+ updatedEntry := newDockerConfigEntry (authToken , email )
110+ if hasChanges (existingEntry , updatedEntry ) {
111+ // Update secret entry
112+ dockerConfigJSON .Auths [registryURL ] = updatedEntry
113+
114+ // Update secret data
115+ secret .Data , err = toPullSecretData (dockerConfigJSON )
82116 if err != nil {
83- return false , errors . Errorf ( "Unable to create image pull secret: %s" , err . Error ())
117+ return false , err
84118 }
85119
86- ctx .Log .Donef ("Created image pull secret %s/%s" , options .Namespace , pullSecretName )
87- } else if secret .Data == nil || string (secret .Data [pullSecretDataKey ]) != string (pullSecretData [pullSecretDataKey ]) {
88- _ , err = ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Update (ctx .Context , registryPullSecret , metav1.UpdateOptions {})
120+ // Update secret
121+ _ , err = ctx .KubeClient .KubeClient ().CoreV1 ().Secrets (options .Namespace ).Update (ctx .Context , secret , metav1.UpdateOptions {})
89122 if err != nil {
90123 if kerrors .IsConflict (err ) {
124+ // Retry
91125 return false , nil
92126 }
93127
94- return false , errors .Errorf ( "Unable to update image pull secret: %s" , err . Error () )
128+ return false , errors .Wrap ( err , " update pull secret" )
95129 }
96- }
97130
131+ ctx .Log .Donef ("Updated image pull secret %s/%s" , options .Namespace , pullSecretName )
132+ }
98133 return true , nil
99134 })
100135 if err != nil {
@@ -106,11 +141,7 @@ func (r *client) CreatePullSecret(ctx *devspacecontext.Context, options *PullSec
106141
107142// GetRegistryAuthSecretName returns the name of the image pull secret for a registry
108143func GetRegistryAuthSecretName (registryURL string ) string {
109- if registryURL == "" {
110- return registryAuthSecretNamePrefix + "docker"
111- }
112-
113- return SafeName (registryAuthSecretNamePrefix + registryNameReplaceRegex .ReplaceAllString (strings .ToLower (registryURL ), "-" ))
144+ return "devspace-pull-secrets"
114145}
115146
116147func SafeName (name string ) string {
@@ -120,3 +151,60 @@ func SafeName(name string) string {
120151 }
121152 return name
122153}
154+
155+ func newPullSecret (name , registryURL , authToken , email string ) (* k8sv1.Secret , error ) {
156+ dockerConfig := & DockerConfigJSON {
157+ Auths : DockerConfig {
158+ registryURL : newDockerConfigEntry (authToken , email ),
159+ },
160+ }
161+
162+ pullSecretData , err := toPullSecretData (dockerConfig )
163+ if err != nil {
164+ return nil , errors .Wrap (err , "new pull secret" )
165+ }
166+
167+ return & k8sv1.Secret {
168+ ObjectMeta : metav1.ObjectMeta {
169+ Name : name ,
170+ },
171+ Data : pullSecretData ,
172+ Type : k8sv1 .SecretTypeDockerConfigJson ,
173+ }, nil
174+ }
175+
176+ func newDockerConfigEntry (authToken , email string ) DockerConfigEntry {
177+ return DockerConfigEntry {
178+ Auth : base64 .StdEncoding .EncodeToString ([]byte (authToken )),
179+ Email : email ,
180+ }
181+ }
182+
183+ func hasChanges (existing , updated DockerConfigEntry ) bool {
184+ return existing .Auth != updated .Auth || existing .Email != updated .Email
185+ }
186+
187+ func toPullSecretData (dockerConfig * DockerConfigJSON ) (map [string ][]byte , error ) {
188+ data , err := json .Marshal (dockerConfig )
189+ if err != nil {
190+ return nil , errors .Wrap (err , "marshal docker config" )
191+ }
192+
193+ return map [string ][]byte {
194+ k8sv1 .DockerConfigJsonKey : data ,
195+ }, nil
196+ }
197+
198+ func fromPullSecretData (data map [string ][]byte ) (* DockerConfigJSON , error ) {
199+ dockerConfig := & DockerConfigJSON {}
200+ if data == nil {
201+ return dockerConfig , nil
202+ }
203+
204+ err := json .Unmarshal (data [k8sv1 .DockerConfigJsonKey ], & dockerConfig )
205+ if err != nil {
206+ return nil , errors .Wrap (err , "unmarshal docker config" )
207+ }
208+
209+ return dockerConfig , nil
210+ }
0 commit comments