@@ -18,14 +18,17 @@ import (
18
18
"context"
19
19
"fmt"
20
20
"os"
21
+ "regexp"
21
22
"sort"
22
23
"strings"
24
+ "time"
23
25
24
26
"github.com/codefresh-io/cli-v2/pkg/log"
25
27
"github.com/codefresh-io/cli-v2/pkg/store"
26
28
"github.com/codefresh-io/cli-v2/pkg/util"
27
29
kubeutil "github.com/codefresh-io/cli-v2/pkg/util/kube"
28
30
kustutil "github.com/codefresh-io/cli-v2/pkg/util/kust"
31
+ "github.com/codefresh-io/go-sdk/pkg/codefresh/model"
29
32
30
33
"github.com/Masterminds/semver/v3"
31
34
"github.com/argoproj-labs/argocd-autopilot/pkg/kube"
@@ -102,8 +105,7 @@ func newClusterAddCommand() *cobra.Command {
102
105
return err
103
106
}
104
107
105
- setClusterName (& opts )
106
- err = validateClusterName (opts .clusterName )
108
+ err = setClusterName (cmd .Context (), & opts )
107
109
108
110
return err
109
111
},
@@ -145,7 +147,7 @@ func runClusterAdd(ctx context.Context, opts *ClusterAddOptions) error {
145
147
}
146
148
147
149
csdpToken := cfConfig .GetCurrentContext ().Token
148
- k := createAddClusterKustomization (ingressUrl , opts .clusterName , server , csdpToken , * runtime .RuntimeVersion )
150
+ k , nameSuffix := createAddClusterKustomization (ingressUrl , opts .clusterName , server , csdpToken , * runtime .RuntimeVersion )
149
151
150
152
manifests , err := kustutil .BuildKustomization (k )
151
153
if err != nil {
@@ -162,24 +164,83 @@ func runClusterAdd(ctx context.Context, opts *ClusterAddOptions) error {
162
164
return fmt .Errorf ("failed applying manifests to cluster: %w" , err )
163
165
}
164
166
165
- return kubeutil .WaitForJob (ctx , opts .kubeFactory , "kube-system" , store .Get ().AddClusterJobName )
167
+ return kubeutil .WaitForJob (ctx , opts .kubeFactory , "kube-system" , fmt . Sprintf ( "%s%s" , store .Get ().AddClusterJobName , nameSuffix ) )
166
168
}
167
169
168
- func setClusterName (opts * ClusterAddOptions ) {
170
+ func setClusterName (ctx context. Context , opts * ClusterAddOptions ) error {
169
171
if opts .clusterName != "" {
170
- return
172
+ return validateClusterName ( opts . clusterName )
171
173
}
172
- opts .clusterName = opts .kubeContext
174
+
175
+ var err error
176
+ sanitizedName := sanitizeClusterName (opts .kubeContext )
177
+ opts .clusterName , err = ensureNoClusterNameDuplicates (ctx , sanitizedName , opts .runtimeName )
178
+
179
+ return err
173
180
}
174
181
175
182
func validateClusterName (name string ) error {
176
- if strings .ContainsAny (name , "%`" ) {
177
- return fmt .Errorf ("cluster name '%s' is invalid. '%%' and '`' are not allowed" , name )
183
+ maxDNSNameLength := 253
184
+ if len (name ) > maxDNSNameLength {
185
+ return fmt .Errorf ("cluster name can contain no more than 253 characters" )
186
+ }
187
+
188
+ match , err := regexp .MatchString ("^[a-z\\ d]([-a-z\\ d\\ .]{0,251}[a-z\\ d])?$" , name )
189
+ if err != nil {
190
+ return err
191
+ }
192
+
193
+ if ! match {
194
+ return fmt .Errorf ("cluster name must be according to k8s resource naming rules" )
178
195
}
196
+
179
197
return nil
180
198
}
181
199
182
- func createAddClusterKustomization (ingressUrl , contextName , server , csdpToken , version string ) * kusttypes.Kustomization {
200
+ // copied from https://github.com/argoproj/argo-cd/blob/master/applicationset/generators/cluster.go#L214
201
+ func sanitizeClusterName (name string ) string {
202
+ invalidDNSNameChars := regexp .MustCompile ("[^-a-z0-9.]" )
203
+ maxDNSNameLength := 253
204
+
205
+ name = strings .ToLower (name )
206
+ name = invalidDNSNameChars .ReplaceAllString (name , "-" )
207
+ // saving space for 2 chars in case a cluster with the sanitized name already exists
208
+ if len (name ) > (maxDNSNameLength - 2 ) {
209
+ name = name [:(maxDNSNameLength - 2 )]
210
+ }
211
+
212
+ return strings .Trim (name , "-." )
213
+ }
214
+
215
+ func ensureNoClusterNameDuplicates (ctx context.Context , name string , runtimeName string ) (string , error ) {
216
+ clusters , err := cfConfig .NewClient ().V2 ().Cluster ().List (ctx , runtimeName )
217
+ if err != nil {
218
+ return "" , fmt .Errorf ("failed to get clusters list: %w" , err )
219
+ }
220
+
221
+ suffix := getSuffixToClusterName (clusters , name , name , 0 )
222
+ if suffix != 0 {
223
+ return fmt .Sprintf ("%s-%d" , name , suffix ), nil
224
+ }
225
+
226
+ return name , nil
227
+ }
228
+
229
+ func getSuffixToClusterName (clusters []model.Cluster , name string , tempName string , counter int ) int {
230
+ for _ , cluster := range clusters {
231
+ if cluster .Metadata .Name == tempName {
232
+ counter ++
233
+ tempName = fmt .Sprintf ("%s-%d" , name , counter )
234
+ counter = getSuffixToClusterName (clusters , name , tempName , counter )
235
+ break
236
+ }
237
+ }
238
+
239
+ return counter
240
+ }
241
+
242
+ func createAddClusterKustomization (ingressUrl , contextName , server , csdpToken , version string ) (* kusttypes.Kustomization , string ) {
243
+ nameSuffix := getClusterResourcesNameSuffix ()
183
244
resourceUrl := store .AddClusterDefURL
184
245
if strings .HasPrefix (resourceUrl , "http" ) {
185
246
resourceUrl = fmt .Sprintf ("%s?ref=v%s" , resourceUrl , version )
@@ -219,10 +280,16 @@ func createAddClusterKustomization(ingressUrl, contextName, server, csdpToken, v
219
280
Resources : []string {
220
281
resourceUrl ,
221
282
},
283
+ NameSuffix : nameSuffix ,
222
284
}
223
285
k .FixKustomizationPostUnmarshalling ()
224
286
util .Die (k .FixKustomizationPreMarshalling ())
225
- return k
287
+ return k , nameSuffix
288
+ }
289
+
290
+ func getClusterResourcesNameSuffix () string {
291
+ now := time .Now ()
292
+ return fmt .Sprintf ("-%d" , now .UnixMilli ())
226
293
}
227
294
228
295
func newClusterRemoveCommand () * cobra.Command {
0 commit comments