Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Initial Infra Manager #178

Merged
merged 2 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
79 changes: 79 additions & 0 deletions internal/infrastructure/kubernetes/infra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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"
)

// 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{},
Copy link
Contributor

@LukeShu LukeShu Aug 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this line is unnecessary

Client: cli,
Resources: &Resources{
ServiceAccount: new(corev1.ServiceAccount),
},
}
}

// addResource adds obj to the infra resources, using the object type
// to identify the object kind to add.
func (i *Infra) addResource(obj client.Object) error {
i.mu.Lock()
defer i.mu.Unlock()
if i.Resources == nil {
i.Resources = new(Resources)
}

switch o := obj.(type) {
case *corev1.ServiceAccount:
i.Resources.ServiceAccount = o
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
}

arkodg marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
114 changes: 114 additions & 0 deletions internal/infrastructure/kubernetes/infra_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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
obj client.Object
out *Resources
}{
{
name: "happy-path-sa",
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) {
danehans marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()
kube := &Infra{
mu: sync.Mutex{},
Client: fakeclient.NewClientBuilder().WithScheme(envoygateway.GetScheme()).Build(),
}
err := kube.addResource(tc.obj)
require.NoError(t, err)
require.Equal(t, tc.out, kube.Resources)
})
}
}
88 changes: 88 additions & 0 deletions internal/infrastructure/kubernetes/serviceaccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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(sa); err != nil {
return err
}
return nil
}
return err
}

if err := i.addResource(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{
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