-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: danehans <[email protected]>
- Loading branch information
Showing
10 changed files
with
857 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"sync" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/envoyproxy/gateway/internal/ir" | ||
) | ||
|
||
const ( | ||
KindServiceAccount Kind = "ServiceAccount" | ||
) | ||
|
||
type Kind string | ||
|
||
// Infra holds all the translated Infra IR resources and provides | ||
// the scaffolding for the managing Kubernetes infrastructure. | ||
type Infra struct { | ||
mu sync.Mutex | ||
Client client.Client | ||
Resources *Resources | ||
} | ||
|
||
// Resources are managed Kubernetes resources. | ||
type Resources struct { | ||
ServiceAccount *corev1.ServiceAccount | ||
} | ||
|
||
// NewInfra returns a new Infra. | ||
func NewInfra(cli client.Client) *Infra { | ||
return &Infra{ | ||
mu: sync.Mutex{}, | ||
Client: cli, | ||
Resources: &Resources{ | ||
ServiceAccount: new(corev1.ServiceAccount), | ||
}, | ||
} | ||
} | ||
|
||
// addResource adds the resource to the infra resources, using kind to | ||
// identify the object kind to add. | ||
func (i *Infra) addResource(kind Kind, obj client.Object) error { | ||
i.mu.Lock() | ||
defer i.mu.Unlock() | ||
if i.Resources == nil { | ||
i.Resources = new(Resources) | ||
} | ||
|
||
switch kind { | ||
case KindServiceAccount: | ||
sa, ok := obj.(*corev1.ServiceAccount) | ||
if !ok { | ||
return fmt.Errorf("unexpected object kind %s", obj.GetObjectKind()) | ||
} | ||
i.Resources.ServiceAccount = sa | ||
default: | ||
return fmt.Errorf("unexpected object kind %s", obj.GetObjectKind()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// CreateInfra creates the managed kube infra if it doesn't exist. | ||
func (i *Infra) CreateInfra(ctx context.Context, infra *ir.Infra) error { | ||
if infra == nil { | ||
return errors.New("infra ir is nil") | ||
} | ||
|
||
if infra.Proxy == nil { | ||
return errors.New("infra proxy ir is nil") | ||
} | ||
|
||
if i.Resources == nil { | ||
i.Resources = &Resources{ | ||
ServiceAccount: new(corev1.ServiceAccount), | ||
} | ||
} | ||
|
||
if err := i.createServiceAccountIfNeeded(ctx, infra); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
"github.com/envoyproxy/gateway/internal/envoygateway" | ||
"github.com/envoyproxy/gateway/internal/ir" | ||
) | ||
|
||
func TestCreateIfNeeded(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
in *ir.Infra | ||
out *Resources | ||
expect bool | ||
}{ | ||
{ | ||
name: "default infra", | ||
in: ir.NewInfra(), | ||
out: &Resources{ | ||
ServiceAccount: &corev1.ServiceAccount{ | ||
TypeMeta: metav1.TypeMeta{ | ||
Kind: "ServiceAccount", | ||
APIVersion: "v1", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: "default", | ||
Name: "envoy-default", | ||
ResourceVersion: "1", | ||
}, | ||
}, | ||
}, | ||
expect: true, | ||
}, | ||
{ | ||
name: "nil-infra", | ||
in: nil, | ||
out: &Resources{}, | ||
expect: false, | ||
}, | ||
{ | ||
name: "nil-infra-proxy", | ||
in: &ir.Infra{ | ||
Proxy: nil, | ||
}, | ||
out: &Resources{}, | ||
expect: false, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
kube := &Infra{ | ||
mu: sync.Mutex{}, | ||
Client: fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build(), | ||
} | ||
err := kube.CreateInfra(context.Background(), tc.in) | ||
if !tc.expect { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
require.Equal(t, *tc.out.ServiceAccount, *kube.Resources.ServiceAccount) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAddResource(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
kind Kind | ||
obj client.Object | ||
out *Resources | ||
}{ | ||
{ | ||
name: "happy-path-sa", | ||
kind: KindServiceAccount, | ||
obj: &corev1.ServiceAccount{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: "test", | ||
Name: "test", | ||
}, | ||
}, | ||
out: &Resources{ | ||
ServiceAccount: &corev1.ServiceAccount{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: "test", | ||
Name: "test", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
kube := &Infra{ | ||
mu: sync.Mutex{}, | ||
Client: fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build(), | ||
} | ||
err := kube.addResource(tc.kind, tc.obj) | ||
require.NoError(t, err) | ||
require.Equal(t, tc.out, kube.Resources) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
kerrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
|
||
"github.com/envoyproxy/gateway/internal/ir" | ||
) | ||
|
||
// createServiceAccountIfNeeded creates a serviceaccount based on the provided infra, if | ||
// it doesn't exist in the kube api server. | ||
func (i *Infra) createServiceAccountIfNeeded(ctx context.Context, infra *ir.Infra) error { | ||
if infra == nil { | ||
return errors.New("infra ir is nil") | ||
} | ||
|
||
if infra.Proxy == nil { | ||
return errors.New("proxy infra ir is nil") | ||
} | ||
|
||
current, err := i.getServiceAccount(ctx, infra) | ||
if err != nil { | ||
if kerrors.IsNotFound(err) { | ||
sa, err := i.createServiceAccount(ctx, infra) | ||
if err != nil { | ||
return err | ||
} | ||
if err := i.addResource(KindServiceAccount, sa); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
if err := i.addResource(KindServiceAccount, current); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getServiceAccount gets the ServiceAccount from the kube api for the provided infra. | ||
func (i *Infra) getServiceAccount(ctx context.Context, infra *ir.Infra) (*corev1.ServiceAccount, error) { | ||
ns := infra.Proxy.Namespace | ||
name := infra.Proxy.Name | ||
key := types.NamespacedName{ | ||
Namespace: ns, | ||
Name: name, | ||
} | ||
sa := new(corev1.ServiceAccount) | ||
if err := i.Client.Get(ctx, key, sa); err != nil { | ||
return nil, fmt.Errorf("failed to get serviceaccount %s/%s: %w", ns, name, err) | ||
} | ||
|
||
return sa, nil | ||
} | ||
|
||
// expectedServiceAccount returns the expected proxy serviceAccount based on the provided infra. | ||
func (i *Infra) expectedServiceAccount(infra *ir.Infra) *corev1.ServiceAccount { | ||
return &corev1.ServiceAccount{ | ||
TypeMeta: metav1.TypeMeta{ | ||
Kind: "ServiceAccount", | ||
APIVersion: "v1", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: infra.Proxy.Namespace, | ||
Name: infra.Proxy.ObjectName(), | ||
}, | ||
} | ||
} | ||
|
||
// createServiceAccount creates sa in the kube api server if it doesn't exist. | ||
func (i *Infra) createServiceAccount(ctx context.Context, infra *ir.Infra) (*corev1.ServiceAccount, error) { | ||
expected := i.expectedServiceAccount(infra) | ||
err := i.Client.Create(ctx, expected) | ||
if err != nil { | ||
if kerrors.IsAlreadyExists(err) { | ||
return expected, nil | ||
} | ||
return nil, fmt.Errorf("failed to create serviceaccount %s/%s: %w", | ||
expected.Namespace, expected.Name, err) | ||
} | ||
|
||
return expected, nil | ||
} |
Oops, something went wrong.