Skip to content

Commit b943893

Browse files
CR-12777 cluster add flow improvoments (#467)
* cluster name suffix cluster resources suffix wip * add job name suffix * job backoffLimit: 1 * start of unitest * test * unit tests * wip * bump * small fix + test * regex fix * - changes + comment * small fix * bump
1 parent 2784153 commit b943893

File tree

7 files changed

+258
-16
lines changed

7 files changed

+258
-16
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION=v0.0.416
1+
VERSION=v0.0.417
22

33
OUT_DIR=dist
44
YEAR?=$(shell date +"%Y")

cmd/commands/cluster.go

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

175182
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")
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

228295
func newClusterRemoveCommand() *cobra.Command {

cmd/commands/cluster_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright 2022 The Codefresh Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package commands
16+
17+
import (
18+
"testing"
19+
20+
"github.com/codefresh-io/go-sdk/pkg/codefresh/model"
21+
)
22+
23+
func Test_getSuffixToClusterName(t *testing.T) {
24+
cluster1 := getEmptyClusterEntity()
25+
cluster2 := getEmptyClusterEntity()
26+
cluster3 := getEmptyClusterEntity()
27+
28+
cluster1.Metadata.Name = "test-cluster"
29+
cluster2.Metadata.Name = "test-cluster-1"
30+
cluster3.Metadata.Name = "test-cluster-2"
31+
32+
clusters := []model.Cluster{
33+
cluster1,
34+
cluster2,
35+
cluster3,
36+
}
37+
38+
type args struct {
39+
clusters []model.Cluster
40+
name string
41+
tempName string
42+
counter int
43+
}
44+
tests := []struct {
45+
name string
46+
args args
47+
want int
48+
}{
49+
{
50+
name: "should return 3",
51+
args: args{
52+
clusters: clusters,
53+
name: "test-cluster",
54+
tempName: "test-cluster",
55+
counter: 0,
56+
},
57+
want: 3,
58+
},
59+
}
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
if got := getSuffixToClusterName(tt.args.clusters, tt.args.name, tt.args.tempName, tt.args.counter); got != tt.want {
63+
t.Errorf("getSuffixToClusterName() = %v, want %v", got, tt.want)
64+
}
65+
})
66+
}
67+
}
68+
69+
func Test_sanitizeClusterName(t *testing.T) {
70+
type args struct {
71+
name string
72+
}
73+
tests := []struct {
74+
name string
75+
args args
76+
want string
77+
}{
78+
{
79+
name: "should return sanitized string",
80+
args: args{
81+
name: "^-.test!@-:cluster&*`;')test.cluster(-12_3=+::±§.",
82+
},
83+
want: "test----cluster------test.cluster--12-3",
84+
},
85+
}
86+
for _, tt := range tests {
87+
t.Run(tt.name, func(t *testing.T) {
88+
if got := sanitizeClusterName(tt.args.name); got != tt.want {
89+
t.Errorf("sanitizeClusterName() = %v, want %v", got, tt.want)
90+
}
91+
})
92+
}
93+
}
94+
95+
func Test_validateClusterName(t *testing.T) {
96+
type args struct {
97+
name string
98+
}
99+
tests := []struct {
100+
name string
101+
args args
102+
wantErr bool
103+
}{
104+
{
105+
name: "name should be valid",
106+
args: args{
107+
name: "1test-cluster.test.cluster123z",
108+
},
109+
wantErr: false,
110+
},
111+
{
112+
name: "name should not be valid",
113+
args: args{
114+
name: ".test-cluster",
115+
},
116+
wantErr: true,
117+
},
118+
{
119+
name: "name should not be valid",
120+
args: args{
121+
name: "test-cluster.",
122+
},
123+
wantErr: true,
124+
},
125+
{
126+
name: "name should not be valid",
127+
args: args{
128+
name: "Test-cluster",
129+
},
130+
wantErr: true,
131+
},
132+
{
133+
name: "name should not be valid",
134+
args: args{
135+
name: "test-cluster:test/cluster",
136+
},
137+
wantErr: true,
138+
},
139+
}
140+
for _, tt := range tests {
141+
t.Run(tt.name, func(t *testing.T) {
142+
if err := validateClusterName(tt.args.name); (err != nil) != tt.wantErr {
143+
t.Errorf("validateClusterName() error = %v, wantErr %v", err, tt.wantErr)
144+
}
145+
})
146+
}
147+
}
148+
149+
func getEmptyClusterEntity() model.Cluster {
150+
empty := ""
151+
return model.Cluster{
152+
Metadata: &model.ObjectMeta{
153+
Group: "",
154+
Version: "",
155+
Kind: "",
156+
Name: "",
157+
Description: &empty,
158+
Namespace: &empty,
159+
Runtime: "",
160+
Cluster: &empty,
161+
Account: "",
162+
Labels: nil,
163+
Annotations: nil,
164+
LastUpdated: &empty,
165+
Created: &empty,
166+
UID: &empty,
167+
},
168+
Errors: []model.Error{},
169+
ReferencedBy: []model.BaseEntity{},
170+
References: []model.BaseEntity{},
171+
Server: "",
172+
Namespaces: []string{},
173+
}
174+
}

docs/releases/release_notes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ cf version
2323

2424
```bash
2525
# download and extract the binary
26-
curl -L --output - https://github.com/codefresh-io/cli-v2/releases/download/v0.0.416/cf-linux-amd64.tar.gz | tar zx
26+
curl -L --output - https://github.com/codefresh-io/cli-v2/releases/download/v0.0.417/cf-linux-amd64.tar.gz | tar zx
2727

2828
# move the binary to your $PATH
2929
mv ./cf-linux-amd64 /usr/local/bin/cf
@@ -36,7 +36,7 @@ cf version
3636

3737
```bash
3838
# download and extract the binary
39-
curl -L --output - https://github.com/codefresh-io/cli-v2/releases/download/v0.0.416/cf-darwin-amd64.tar.gz | tar zx
39+
curl -L --output - https://github.com/codefresh-io/cli-v2/releases/download/v0.0.417/cf-darwin-amd64.tar.gz | tar zx
4040

4141
# move the binary to your $PATH
4242
mv ./cf-darwin-amd64 /usr/local/bin/cf

manifests/add-cluster/kustomize/job.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ kind: Job
33
metadata:
44
name: csdp-add-cluster-job
55
spec:
6+
backoffLimit: 1
67
ttlSecondsAfterFinished: 600 # stick around for 10m
78
template:
89
metadata:

manifests/runtime.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
namespace: "{{ namespace }}"
66
spec:
77
defVersion: 1.0.1
8-
version: 0.0.416
8+
version: 0.0.417
99
bootstrapSpecifier: github.com/codefresh-io/cli-v2/manifests/argo-cd
1010
components:
1111
- name: events

pkg/store/store.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func Get() *Store {
155155
}
156156

157157
func init() {
158-
s.AddClusterJobName = "csdp-add-cluster-job"
158+
s.AddClusterJobName = "csdp-add-cluster-job-"
159159
s.ArgoCDServerName = "argocd-server"
160160
s.ArgoCDTokenKey = "token"
161161
s.ArgoCDTokenSecret = "argocd-token"

0 commit comments

Comments
 (0)