@@ -18,14 +18,17 @@ import (
1818 "context"
1919 "fmt"
2020 "os"
21+ "regexp"
2122 "sort"
2223 "strings"
24+ "time"
2325
2426 "github.com/codefresh-io/cli-v2/pkg/log"
2527 "github.com/codefresh-io/cli-v2/pkg/store"
2628 "github.com/codefresh-io/cli-v2/pkg/util"
2729 kubeutil "github.com/codefresh-io/cli-v2/pkg/util/kube"
2830 kustutil "github.com/codefresh-io/cli-v2/pkg/util/kust"
31+ "github.com/codefresh-io/go-sdk/pkg/codefresh/model"
2932
3033 "github.com/Masterminds/semver/v3"
3134 "github.com/argoproj-labs/argocd-autopilot/pkg/kube"
@@ -102,8 +105,7 @@ func newClusterAddCommand() *cobra.Command {
102105 return err
103106 }
104107
105- setClusterName (& opts )
106- err = validateClusterName (opts .clusterName )
108+ err = setClusterName (cmd .Context (), & opts )
107109
108110 return err
109111 },
@@ -145,7 +147,7 @@ func runClusterAdd(ctx context.Context, opts *ClusterAddOptions) error {
145147 }
146148
147149 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 )
149151
150152 manifests , err := kustutil .BuildKustomization (k )
151153 if err != nil {
@@ -162,24 +164,83 @@ func runClusterAdd(ctx context.Context, opts *ClusterAddOptions) error {
162164 return fmt .Errorf ("failed applying manifests to cluster: %w" , err )
163165 }
164166
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 ) )
166168}
167169
168- func setClusterName (opts * ClusterAddOptions ) {
170+ func setClusterName (ctx context. Context , opts * ClusterAddOptions ) error {
169171 if opts .clusterName != "" {
170- return
172+ return validateClusterName ( opts . clusterName )
171173 }
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
173180}
174181
175182func 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" )
178195 }
196+
179197 return nil
180198}
181199
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 ()
183244 resourceUrl := store .AddClusterDefURL
184245 if strings .HasPrefix (resourceUrl , "http" ) {
185246 resourceUrl = fmt .Sprintf ("%s?ref=v%s" , resourceUrl , version )
@@ -219,10 +280,16 @@ func createAddClusterKustomization(ingressUrl, contextName, server, csdpToken, v
219280 Resources : []string {
220281 resourceUrl ,
221282 },
283+ NameSuffix : nameSuffix ,
222284 }
223285 k .FixKustomizationPostUnmarshalling ()
224286 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 ())
226293}
227294
228295func newClusterRemoveCommand () * cobra.Command {
0 commit comments