422 lines
9.8 KiB
Go
422 lines
9.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
. "github.com/agiledragon/gomonkey/v2"
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v2"
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
|
"github.com/oam-dev/terraform-controller/api/v1beta1"
|
|
"github.com/oam-dev/terraform-controller/controllers/provider"
|
|
)
|
|
|
|
func TestReconcile(t *testing.T) {
|
|
r1 := &ProviderReconciler{}
|
|
ctx := context.Background()
|
|
s := runtime.NewScheme()
|
|
v1beta1.AddToScheme(s)
|
|
v1.AddToScheme(s)
|
|
r1.Client = fake.NewClientBuilder().WithScheme(s).Build()
|
|
|
|
r2 := &ProviderReconciler{}
|
|
provider2 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{
|
|
Source: "Secret",
|
|
SecretRef: &crossplanetypes.SecretKeySelector{
|
|
SecretReference: crossplanetypes.SecretReference{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Key: "credentials",
|
|
},
|
|
},
|
|
Provider: "aws",
|
|
},
|
|
}
|
|
|
|
creds, _ := yaml.Marshal(&provider.AWSCredentials{
|
|
AWSAccessKeyID: "a",
|
|
AWSSecretAccessKey: "b",
|
|
AWSSessionToken: "c",
|
|
})
|
|
secret2 := &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"credentials": creds,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
}
|
|
|
|
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).Build()
|
|
|
|
r3 := &ProviderReconciler{}
|
|
provider3 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{
|
|
Source: "Secret",
|
|
SecretRef: &crossplanetypes.SecretKeySelector{
|
|
SecretReference: crossplanetypes.SecretReference{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Key: "credentials",
|
|
},
|
|
},
|
|
Provider: "aws",
|
|
},
|
|
}
|
|
|
|
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
|
|
|
|
r4 := &ProviderReconciler{}
|
|
provider4 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{Source: "InjectedIdentity"},
|
|
Provider: "aws",
|
|
},
|
|
}
|
|
r4.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider4).Build()
|
|
|
|
r5 := &ProviderReconciler{}
|
|
provider5 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{Source: "Invalid"},
|
|
Provider: "aws",
|
|
},
|
|
}
|
|
r5.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider5).Build()
|
|
|
|
type args struct {
|
|
req reconcile.Request
|
|
r *ProviderReconciler
|
|
}
|
|
|
|
type want struct {
|
|
errMsg string
|
|
}
|
|
|
|
req := ctrl.Request{}
|
|
req.NamespacedName = types.NamespacedName{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
name: "Provider is not found",
|
|
args: args{
|
|
req: req,
|
|
r: r1,
|
|
},
|
|
},
|
|
{
|
|
name: "Provider is found",
|
|
args: args{
|
|
req: req,
|
|
r: r2,
|
|
},
|
|
want: want{},
|
|
},
|
|
{
|
|
name: "Provider is found but the secret is not available",
|
|
args: args{
|
|
req: req,
|
|
r: r3,
|
|
},
|
|
want: want{
|
|
errMsg: `failed to get the Secret from Provider: secrets "abc" not found`,
|
|
},
|
|
},
|
|
{
|
|
name: "Provider is using source injected identity",
|
|
args: args{
|
|
req: req,
|
|
r: r4,
|
|
},
|
|
want: want{},
|
|
},
|
|
{
|
|
name: "Provider source is invalid",
|
|
args: args{
|
|
req: req,
|
|
r: r5,
|
|
},
|
|
want: want{
|
|
errMsg: `unsupported credentials source: Invalid`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := tc.args.r.Reconcile(ctx, tc.args.req)
|
|
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
|
|
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReconcileProviderIsReadyButFailedToUpdateStatus(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := runtime.NewScheme()
|
|
v1beta1.AddToScheme(s)
|
|
v1.AddToScheme(s)
|
|
|
|
r2 := &ProviderReconciler{}
|
|
provider2 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{
|
|
Source: "Secret",
|
|
SecretRef: &crossplanetypes.SecretKeySelector{
|
|
SecretReference: crossplanetypes.SecretReference{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Key: "credentials",
|
|
},
|
|
},
|
|
Provider: "aws",
|
|
},
|
|
}
|
|
|
|
creds, _ := yaml.Marshal(&provider.AWSCredentials{
|
|
AWSAccessKeyID: "a",
|
|
AWSSecretAccessKey: "b",
|
|
AWSSessionToken: "c",
|
|
})
|
|
secret2 := &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"credentials": creds,
|
|
},
|
|
Type: v1.SecretTypeOpaque,
|
|
}
|
|
|
|
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).Build()
|
|
|
|
patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
|
|
switch obj.(type) {
|
|
case *v1beta1.Provider:
|
|
p := obj.(*v1beta1.Provider)
|
|
if p.Status.State != "" {
|
|
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
|
|
}
|
|
}
|
|
return apiutilGVKForObject(obj, scheme)
|
|
})
|
|
defer patches.Reset()
|
|
|
|
type args struct {
|
|
req reconcile.Request
|
|
r *ProviderReconciler
|
|
}
|
|
|
|
type want struct {
|
|
errMsg string
|
|
}
|
|
|
|
req := ctrl.Request{}
|
|
req.NamespacedName = types.NamespacedName{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
name: "Provider is found",
|
|
args: args{
|
|
req: req,
|
|
r: r2,
|
|
},
|
|
want: want{
|
|
errMsg: "failed to set status",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if _, err := tc.args.r.Reconcile(ctx, tc.args.req); (tc.want.errMsg != "") &&
|
|
!strings.Contains(err.Error(), tc.want.errMsg) {
|
|
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReconcile3(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := runtime.NewScheme()
|
|
v1beta1.AddToScheme(s)
|
|
v1.AddToScheme(s)
|
|
|
|
r3 := &ProviderReconciler{}
|
|
provider3 := &v1beta1.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1beta1.ProviderSpec{
|
|
Credentials: v1beta1.ProviderCredentials{
|
|
Source: "Secret",
|
|
SecretRef: &crossplanetypes.SecretKeySelector{
|
|
SecretReference: crossplanetypes.SecretReference{
|
|
Name: "abc",
|
|
Namespace: "default",
|
|
},
|
|
Key: "credentials",
|
|
},
|
|
},
|
|
Provider: errSettingStatus,
|
|
},
|
|
}
|
|
|
|
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
|
|
|
|
patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
|
|
switch obj.(type) {
|
|
case *v1beta1.Provider:
|
|
p := obj.(*v1beta1.Provider)
|
|
if p.Status.State != "" {
|
|
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
|
|
}
|
|
}
|
|
return apiutilGVKForObject(obj, scheme)
|
|
})
|
|
defer patches.Reset()
|
|
|
|
type args struct {
|
|
req reconcile.Request
|
|
r *ProviderReconciler
|
|
}
|
|
|
|
type want struct {
|
|
errMsg string
|
|
}
|
|
|
|
req := ctrl.Request{}
|
|
req.NamespacedName = types.NamespacedName{
|
|
Name: "aws",
|
|
Namespace: "default",
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
name: "Provider is found, but the secret is not available",
|
|
args: args{
|
|
req: req,
|
|
r: r3,
|
|
},
|
|
want: want{
|
|
errMsg: errSettingStatus,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if _, err := tc.args.r.Reconcile(ctx, tc.args.req); (tc.want.errMsg != "") &&
|
|
!strings.Contains(err.Error(), tc.want.errMsg) {
|
|
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func apiutilGVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
|
|
switch obj.(type) {
|
|
case *v1beta1.Provider:
|
|
p := obj.(*v1beta1.Provider)
|
|
if p.Status.State != "" {
|
|
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
|
|
}
|
|
}
|
|
// a copy implementation of `apiutil.GVKForObject`
|
|
_, isPartial := obj.(*metav1.PartialObjectMetadata) //nolint:ifshort
|
|
_, isPartialList := obj.(*metav1.PartialObjectMetadataList)
|
|
if isPartial || isPartialList {
|
|
// we require that the GVK be populated in order to recognize the object
|
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
|
if len(gvk.Kind) == 0 {
|
|
return schema.GroupVersionKind{}, runtime.NewMissingKindErr("unstructured object has no kind")
|
|
}
|
|
if len(gvk.Version) == 0 {
|
|
return schema.GroupVersionKind{}, runtime.NewMissingVersionErr("unstructured object has no version")
|
|
}
|
|
return gvk, nil
|
|
}
|
|
|
|
gvks, isUnversioned, err := scheme.ObjectKinds(obj)
|
|
if err != nil {
|
|
return schema.GroupVersionKind{}, err
|
|
}
|
|
if isUnversioned {
|
|
return schema.GroupVersionKind{}, fmt.Errorf("cannot create group-version-kind for unversioned type %T", obj)
|
|
}
|
|
|
|
if len(gvks) < 1 {
|
|
return schema.GroupVersionKind{}, fmt.Errorf("no group-version-kinds associated with type %T", obj)
|
|
}
|
|
if len(gvks) > 1 {
|
|
// this should only trigger for things like metav1.XYZ --
|
|
// normal versioned types should be fine
|
|
return schema.GroupVersionKind{}, fmt.Errorf(
|
|
"multiple group-version-kinds associated with type %T, refusing to guess at one", obj)
|
|
}
|
|
return gvks[0], nil
|
|
}
|