sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/clusterclass_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package client 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 . "github.com/onsi/gomega" 25 "github.com/onsi/gomega/types" 26 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" 34 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" 35 yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor" 36 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" 37 utilyaml "sigs.k8s.io/cluster-api/util/yaml" 38 ) 39 40 func TestClusterClassExists(t *testing.T) { 41 tests := []struct { 42 name string 43 objs []client.Object 44 clusterClass string 45 want bool 46 }{ 47 { 48 name: "should return true when checking for an installed cluster class", 49 objs: []client.Object{ 50 &clusterv1.ClusterClass{ 51 ObjectMeta: metav1.ObjectMeta{ 52 Name: "clusterclass-installed", 53 Namespace: metav1.NamespaceDefault, 54 }, 55 }, 56 }, 57 clusterClass: "clusterclass-installed", 58 want: true, 59 }, 60 { 61 name: "should return false when checking for a not-installed cluster class", 62 objs: []client.Object{}, 63 clusterClass: "clusterclass-not-installed", 64 want: false, 65 }, 66 } 67 for _, tt := range tests { 68 t.Run(tt.name, func(t *testing.T) { 69 g := NewWithT(t) 70 71 ctx := context.Background() 72 73 config := newFakeConfig(ctx) 74 client := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config).WithObjs(tt.objs...) 75 c, _ := client.Proxy().NewClient(ctx) 76 77 actual, err := clusterClassExists(ctx, c, tt.clusterClass, metav1.NamespaceDefault) 78 g.Expect(err).ToNot(HaveOccurred()) 79 g.Expect(actual).To(Equal(tt.want)) 80 }) 81 } 82 } 83 84 func TestAddClusterClassIfMissing(t *testing.T) { 85 infraClusterTemplateNS4 := unstructured.Unstructured{} 86 infraClusterTemplateNS4.SetNamespace("ns4") 87 infraClusterTemplateNS4.SetGroupVersionKind(schema.GroupVersionKind{ 88 Version: "v1", 89 Kind: "InfrastructureClusterTemplate", 90 }) 91 infraClusterTemplateNS4.SetName("testInfraClusterTemplate") 92 infraClusterTemplateNS4Bytes, err := utilyaml.FromUnstructured([]unstructured.Unstructured{infraClusterTemplateNS4}) 93 if err != nil { 94 panic("failed to convert template to bytes") 95 } 96 97 tests := []struct { 98 name string 99 clusterInitialized bool 100 objs []client.Object 101 clusterClassTemplateContent []byte 102 targetNamespace string 103 listVariablesOnly bool 104 wantClusterClassInTemplate bool 105 wantError bool 106 }{ 107 { 108 name: "should add the cluster class to the template if cluster is not initialized", 109 clusterInitialized: false, 110 objs: []client.Object{}, 111 targetNamespace: "ns1", 112 clusterClassTemplateContent: clusterClassYAML("ns1", "dev"), 113 listVariablesOnly: false, 114 wantClusterClassInTemplate: true, 115 wantError: false, 116 }, 117 { 118 name: "should add the cluster class to the template if cluster is initialized and cluster class is not installed", 119 clusterInitialized: true, 120 objs: []client.Object{}, 121 targetNamespace: "ns2", 122 clusterClassTemplateContent: clusterClassYAML("ns2", "dev"), 123 listVariablesOnly: false, 124 wantClusterClassInTemplate: true, 125 wantError: false, 126 }, 127 { 128 name: "should NOT add the cluster class to the template if cluster is initialized and cluster class is installed", 129 clusterInitialized: true, 130 objs: []client.Object{&clusterv1.ClusterClass{ 131 ObjectMeta: metav1.ObjectMeta{ 132 Name: "dev", 133 Namespace: "ns3", 134 }, 135 }}, 136 targetNamespace: "ns3", 137 clusterClassTemplateContent: clusterClassYAML("ns3", "dev"), 138 listVariablesOnly: false, 139 wantClusterClassInTemplate: false, 140 wantError: false, 141 }, 142 { 143 name: "should throw error if the cluster is initialized and templates from the cluster class template already exist in the cluster", 144 clusterInitialized: true, 145 objs: []client.Object{ 146 &infraClusterTemplateNS4, 147 }, 148 clusterClassTemplateContent: utilyaml.JoinYaml(clusterClassYAML("ns4", "dev"), infraClusterTemplateNS4Bytes), 149 targetNamespace: "ns4", 150 listVariablesOnly: false, 151 wantClusterClassInTemplate: false, 152 wantError: true, 153 }, 154 } 155 156 for _, tt := range tests { 157 t.Run(tt.name, func(t *testing.T) { 158 ctx := context.Background() 159 160 config1 := newFakeConfig(ctx).WithProvider(infraProviderConfig) 161 repository1 := newFakeRepository(ctx, infraProviderConfig, config1). 162 WithPaths("root", ""). 163 WithDefaultVersion("v1.0.0"). 164 WithFile("v1.0.0", "clusterclass-dev.yaml", tt.clusterClassTemplateContent) 165 cluster := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgt-cluster"}, config1).WithObjs(tt.objs...) 166 167 if tt.clusterInitialized { 168 cluster.WithObjs(&apiextensionsv1.CustomResourceDefinition{ 169 TypeMeta: metav1.TypeMeta{ 170 APIVersion: apiextensionsv1.SchemeGroupVersion.String(), 171 Kind: "CustomResourceDefinition", 172 }, 173 ObjectMeta: metav1.ObjectMeta{ 174 Name: fmt.Sprintf("clusters.%s", clusterv1.GroupVersion.Group), 175 Labels: map[string]string{ 176 clusterv1.GroupVersion.String(): "v1beta1", 177 }, 178 }, 179 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 180 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 181 { 182 Storage: true, 183 Name: clusterv1.GroupVersion.Version, 184 }, 185 }, 186 }, 187 }) 188 } 189 190 clusterClassClient := repository1.ClusterClasses("v1.0.0") 191 192 clusterWithTopology := []byte(fmt.Sprintf("apiVersion: %s\n", clusterv1.GroupVersion.String()) + 193 "kind: Cluster\n" + 194 "metadata:\n" + 195 " name: cluster-dev\n" + 196 fmt.Sprintf(" namespace: %s\n", tt.targetNamespace) + 197 "spec:\n" + 198 " topology:\n" + 199 " class: dev") 200 201 baseTemplate, err := repository.NewTemplate(repository.TemplateInput{ 202 RawArtifact: clusterWithTopology, 203 ConfigVariablesClient: test.NewFakeVariableClient(), 204 Processor: yaml.NewSimpleProcessor(), 205 TargetNamespace: tt.targetNamespace, 206 SkipTemplateProcess: false, 207 }) 208 if err != nil { 209 t.Fatalf("failed to create template %v", err) 210 } 211 212 g := NewWithT(t) 213 template, err := addClusterClassIfMissing(ctx, baseTemplate, clusterClassClient, cluster, tt.targetNamespace, tt.listVariablesOnly) 214 if tt.wantError { 215 g.Expect(err).To(HaveOccurred()) 216 } else { 217 if tt.wantClusterClassInTemplate { 218 g.Expect(template.Objs()).To(ContainElement(MatchClusterClass("dev", tt.targetNamespace))) 219 } else { 220 g.Expect(template.Objs()).NotTo(ContainElement(MatchClusterClass("dev", tt.targetNamespace))) 221 } 222 } 223 }) 224 } 225 } 226 227 func MatchClusterClass(name, namespace string) types.GomegaMatcher { 228 return &clusterClassMatcher{name, namespace} 229 } 230 231 type clusterClassMatcher struct { 232 name string 233 namespace string 234 } 235 236 func (cm *clusterClassMatcher) Match(actual interface{}) (bool, error) { 237 uClass := actual.(unstructured.Unstructured) 238 239 // check name 240 name, ok, err := unstructured.NestedString(uClass.UnstructuredContent(), "metadata", "name") 241 if err != nil { 242 return false, err 243 } 244 if !ok { 245 return false, nil 246 } 247 if name != cm.name { 248 return false, nil 249 } 250 251 // check namespace 252 namespace, ok, err := unstructured.NestedString(uClass.UnstructuredContent(), "metadata", "namespace") 253 if err != nil { 254 return false, err 255 } 256 if !ok { 257 return false, nil 258 } 259 if namespace != cm.namespace { 260 return false, nil 261 } 262 263 return true, nil 264 } 265 266 func (cm *clusterClassMatcher) FailureMessage(_ interface{}) string { 267 return fmt.Sprintf("Expected ClusterClass of name %v in namespace %v to be present", cm.name, cm.namespace) 268 } 269 270 func (cm *clusterClassMatcher) NegatedFailureMessage(_ interface{}) string { 271 return fmt.Sprintf("Expected ClusterClass of name %v in namespace %v not to be present", cm.name, cm.namespace) 272 }