sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/ssm/secret_test.go (about) 1 /* 2 Copyright 2022 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 ssm 18 19 import ( 20 "math/rand" 21 "sort" 22 "strings" 23 "testing" 24 25 "github.com/aws/aws-sdk-go/aws" 26 "github.com/aws/aws-sdk-go/service/ssm" 27 "github.com/golang/mock/gomock" 28 "github.com/google/go-cmp/cmp" 29 . "github.com/onsi/gomega" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/client/fake" 34 35 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 36 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 37 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 38 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/ssm/mock_ssmiface" 39 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 40 ) 41 42 func TestService_Create(t *testing.T) { 43 mockCtrl := gomock.NewController(t) 44 defer mockCtrl.Finish() 45 46 generateBytes := func(count int64) []byte { 47 token := make([]byte, count) 48 _, err := rand.Read(token) 49 if err != nil { 50 t.Fatalf("error while creating data: %v", err) 51 } 52 return token 53 } 54 55 check := func(actualPrefix string, expectedPrefix string, err error, IsErrorExpected bool) { 56 if !strings.HasPrefix(actualPrefix, expectedPrefix) { 57 t.Fatalf("Prefix is not as expected: %v", actualPrefix) 58 } 59 if (err != nil) != IsErrorExpected { 60 t.Fatalf("Unexpected error value, error = %v, expectedError %v", err, IsErrorExpected) 61 } 62 } 63 64 sortTagsByKey := func(tags []*ssm.Tag) { 65 sort.Slice(tags, func(i, j int) bool { 66 return *(tags[i].Key) < *(tags[j].Key) 67 }) 68 } 69 70 expectedTags := []*ssm.Tag{ 71 { 72 Key: aws.String("Name"), 73 Value: aws.String("infra-cluster"), 74 }, 75 { 76 Key: aws.String("kubernetes.io/cluster/test"), 77 Value: aws.String("owned"), 78 }, 79 { 80 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test"), 81 Value: aws.String("owned"), 82 }, 83 { 84 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 85 Value: aws.String("node"), 86 }, 87 } 88 89 tests := []struct { 90 name string 91 bytesCount int64 92 secretPrefix string 93 expectedPrefix string 94 wantErr bool 95 expect func(m *mock_ssmiface.MockSSMAPIMockRecorder) 96 }{ 97 { 98 name: "Should not store data in SSM if data is having zero bytes", 99 bytesCount: 0, 100 secretPrefix: "/awsprefix", 101 expectedPrefix: "/prefix", 102 }, 103 { 104 name: "Should store data in SSM if data is having non-zero bytes", 105 bytesCount: 10000, 106 secretPrefix: "prefix", 107 expectedPrefix: "/prefix", 108 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) { 109 m.PutParameter(gomock.AssignableToTypeOf(&ssm.PutParameterInput{})).MinTimes(1).Return(&ssm.PutParameterOutput{}, nil).Do( 110 func(putParameterInput *ssm.PutParameterInput) { 111 if !strings.HasPrefix(*(putParameterInput.Name), "/prefix/") { 112 t.Fatalf("Prefix is not as expected: %v", putParameterInput.Name) 113 } 114 sortTagsByKey(putParameterInput.Tags) 115 if !cmp.Equal(putParameterInput.Tags, expectedTags) { 116 t.Fatalf("Tags are not as expected, actual: %v, expected: %v", putParameterInput.Tags, expectedTags) 117 } 118 }, 119 ) 120 }, 121 }, 122 { 123 name: "Should not retry if non-retryable error occurred while storing data in SSM", 124 bytesCount: 10, 125 secretPrefix: "/prefix", 126 expectedPrefix: "/prefix", 127 wantErr: true, 128 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) { 129 m.PutParameter(gomock.AssignableToTypeOf(&ssm.PutParameterInput{})).Return(nil, &ssm.ParameterAlreadyExists{}).Do( 130 func(putParameterInput *ssm.PutParameterInput) { 131 if !strings.HasPrefix(*(putParameterInput.Name), "/prefix/") { 132 t.Fatalf("Prefix is not as expected: %v", putParameterInput.Name) 133 } 134 sortTagsByKey(putParameterInput.Tags) 135 if !cmp.Equal(putParameterInput.Tags, expectedTags) { 136 t.Fatalf("Tags are not as expected, actual: %v, expected: %v", putParameterInput.Tags, expectedTags) 137 } 138 }, 139 ) 140 }, 141 }, 142 { 143 name: "Should retry if retryable error occurred while storing data in SSM", 144 bytesCount: 10, 145 secretPrefix: "", 146 expectedPrefix: "/cluster.x-k8s.io", 147 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) { 148 m.PutParameter(gomock.AssignableToTypeOf(&ssm.PutParameterInput{})).Return(nil, &ssm.ParameterLimitExceeded{}) 149 m.PutParameter(gomock.AssignableToTypeOf(&ssm.PutParameterInput{})).Return(&ssm.PutParameterOutput{}, nil) 150 }, 151 }, 152 } 153 for _, tt := range tests { 154 t.Run(tt.name, func(t *testing.T) { 155 g := NewWithT(t) 156 scheme := runtime.NewScheme() 157 _ = infrav1.AddToScheme(scheme) 158 client := fake.NewClientBuilder().WithScheme(scheme).Build() 159 160 clusterScope, err := getClusterScope(client) 161 g.Expect(err).NotTo(HaveOccurred()) 162 ssmClientMock := mock_ssmiface.NewMockSSMAPI(mockCtrl) 163 if tt.expect != nil { 164 tt.expect(ssmClientMock.EXPECT()) 165 } 166 s := NewService(clusterScope) 167 s.SSMClient = ssmClientMock 168 169 ms, err := getMachineScope(client, clusterScope) 170 g.Expect(err).NotTo(HaveOccurred()) 171 ms.SetSecretPrefix(tt.secretPrefix) 172 data := generateBytes(tt.bytesCount) 173 174 prefix, _, err := s.Create(ms, data) 175 check(prefix, tt.expectedPrefix, err, tt.wantErr) 176 }) 177 } 178 } 179 180 func TestService_Delete(t *testing.T) { 181 mockCtrl := gomock.NewController(t) 182 defer mockCtrl.Finish() 183 184 tests := []struct { 185 name string 186 secretCount int32 187 expect func(m *mock_ssmiface.MockSSMAPIMockRecorder) 188 wantErr bool 189 check func(error) 190 }{ 191 { 192 name: "Should not call AWS when secret count has zero value", 193 secretCount: 0, 194 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) {}, 195 }, 196 { 197 name: "Should not return error when delete is successful", 198 secretCount: 1, 199 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) { 200 m.DeleteParameter(gomock.Eq(&ssm.DeleteParameterInput{ 201 Name: aws.String("prefix/0"), 202 })).Return(&ssm.DeleteParameterOutput{}, nil) 203 }, 204 }, 205 { 206 name: "Should return all errors except not found errors", 207 secretCount: 3, 208 expect: func(m *mock_ssmiface.MockSSMAPIMockRecorder) { 209 m.DeleteParameter(gomock.Eq(&ssm.DeleteParameterInput{ 210 Name: aws.String("prefix/0"), 211 })).Return(nil, awserrors.NewFailedDependency("failed dependency")) 212 m.DeleteParameter(gomock.Eq(&ssm.DeleteParameterInput{ 213 Name: aws.String("prefix/1"), 214 })).Return(nil, awserrors.NewNotFound("not found")) 215 m.DeleteParameter(gomock.Eq(&ssm.DeleteParameterInput{ 216 Name: aws.String("prefix/2"), 217 })).Return(nil, awserrors.NewConflict("new conflict")) 218 }, 219 wantErr: true, 220 check: func(err error) { 221 if err.Error() != "[failed dependency, new conflict]" { 222 t.Fatalf("Unexpected error: %v", err) 223 } 224 }, 225 }, 226 } 227 for _, tt := range tests { 228 t.Run(tt.name, func(t *testing.T) { 229 g := NewWithT(t) 230 scheme := runtime.NewScheme() 231 _ = infrav1.AddToScheme(scheme) 232 client := fake.NewClientBuilder().WithScheme(scheme).Build() 233 234 clusterScope, err := getClusterScope(client) 235 g.Expect(err).NotTo(HaveOccurred()) 236 237 ssmClientMock := mock_ssmiface.NewMockSSMAPI(mockCtrl) 238 tt.expect(ssmClientMock.EXPECT()) 239 s := NewService(clusterScope) 240 s.SSMClient = ssmClientMock 241 ms, err := getMachineScope(client, clusterScope) 242 g.Expect(err).NotTo(HaveOccurred()) 243 244 ms.SetSecretPrefix("prefix") 245 ms.SetSecretCount(tt.secretCount) 246 247 err = s.Delete(ms) 248 if tt.wantErr { 249 g.Expect(err).To(HaveOccurred()) 250 if tt.check != nil { 251 tt.check(err) 252 } 253 return 254 } 255 g.Expect(err).NotTo(HaveOccurred()) 256 }) 257 } 258 } 259 260 func getClusterScope(client client.Client) (*scope.ClusterScope, error) { 261 cluster := &clusterv1.Cluster{ 262 ObjectMeta: metav1.ObjectMeta{ 263 Name: "test", 264 }, 265 } 266 return scope.NewClusterScope(scope.ClusterScopeParams{ 267 Client: client, 268 Cluster: cluster, 269 AWSCluster: &infrav1.AWSCluster{}, 270 }) 271 } 272 273 func getMachineScope(client client.Client, clusterScope *scope.ClusterScope) (*scope.MachineScope, error) { 274 return scope.NewMachineScope(scope.MachineScopeParams{ 275 Client: client, 276 Cluster: clusterScope.Cluster, 277 Machine: &clusterv1.Machine{}, 278 InfraCluster: clusterScope, 279 AWSMachine: &infrav1.AWSMachine{ 280 ObjectMeta: metav1.ObjectMeta{ 281 Name: "infra-cluster", 282 }, 283 }, 284 }) 285 }