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