Skip to content

Commit

Permalink
Adds Initial Infra Manager
Browse files Browse the repository at this point in the history
Signed-off-by: danehans <[email protected]>
  • Loading branch information
danehans committed Aug 2, 2022
1 parent c83b7b0 commit af1c29c
Show file tree
Hide file tree
Showing 10 changed files with 857 additions and 4 deletions.
4 changes: 4 additions & 0 deletions api/config/v1alpha1/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ func DefaultProvider() *Provider {
Type: ProviderTypeKubernetes,
}
}

func ProviderTypePtr(p ProviderType) *ProviderType {
return &p
}
4 changes: 2 additions & 2 deletions internal/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package cmd
import (
"os"

"github.com/envoyproxy/gateway/api/config/v1alpha1"
"github.com/envoyproxy/gateway/internal/envoygateway/config"
"github.com/spf13/cobra"

"github.com/envoyproxy/gateway/api/config/v1alpha1"
"github.com/envoyproxy/gateway/internal/envoygateway/config"
"github.com/envoyproxy/gateway/internal/provider"
)

Expand Down
89 changes: 89 additions & 0 deletions internal/infrastructure/kubernetes/infra.go
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
}
116 changes: 116 additions & 0 deletions internal/infrastructure/kubernetes/infra_test.go
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)
})
}
}
92 changes: 92 additions & 0 deletions internal/infrastructure/kubernetes/serviceaccount.go
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
}
Loading

0 comments on commit af1c29c

Please sign in to comment.