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

e2e: apply k8s resources from Go #234

Merged
merged 2 commits into from
Mar 14, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/e2e_openssl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Build, deploy, contrast generate, contrast set, contrast verify
run: |
just coordinator initializer openssl
just deploy openssl contrast.cli
just generate openssl contrast.cli
- name: Setup Summary
run: |
cat ./workspace/just.namespace | tee -a "${GITHUB_STEP_SUMMARY}"
Expand Down
39 changes: 39 additions & 0 deletions e2e/internal/kubeclient/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package kubeclient
import (
"context"
"fmt"
"sort"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
)

// WaitForPod watches the given pod and blocks until it meets the condition Ready=True or the
Expand Down Expand Up @@ -73,3 +76,39 @@ func (c *Kubeclient) WaitForDeployment(ctx context.Context, namespace, name stri
}
}
}

func (c *Kubeclient) resourceInterfaceFor(obj *unstructured.Unstructured) (dynamic.ResourceInterface, error) {
dyn := dynamic.New(c.client.RESTClient())
gvk := obj.GroupVersionKind()

mapping, err := c.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, fmt.Errorf("getting resource for %#v: %w", gvk, err)
}
c.log.Info("found mapping", "resource", mapping.Resource)
ri := dyn.Resource(mapping.Resource)
if namespace := obj.GetNamespace(); namespace != "" {
return ri.Namespace(namespace), nil
}
return ri, nil
}

// Apply a set of namespaced manifests to a namespace.
func (c *Kubeclient) Apply(ctx context.Context, objects ...*unstructured.Unstructured) error {
// Move namespaces to the head of the list so that they are applied first and ready for the other objects.
sort.Slice(objects, func(i, j int) bool {
return objects[i].GetKind() == "Namespace" && objects[j].GetKind() != "Namespace"
})
for _, obj := range objects {
ri, err := c.resourceInterfaceFor(obj)
if err != nil {
return err
}
applied, err := ri.Apply(ctx, obj.GetName(), obj, metav1.ApplyOptions{Force: true, FieldManager: "e2e-test"})
if err != nil {
return fmt.Errorf("could not apply %s %s in namespace %s: %w", obj.GetKind(), obj.GetName(), obj.GetNamespace(), err)
}
c.log.Info("object applied", "namespace", applied.GetNamespace(), "kind", applied.GetKind(), "name", applied.GetName())
}
return nil
}
16 changes: 13 additions & 3 deletions e2e/internal/kubeclient/kubeclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (
"testing"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
)
Expand All @@ -28,6 +30,8 @@ type Kubeclient struct {

// client is the underlying Kubernetes client.
client *kubernetes.Clientset
// restMapper allows to look up schema information for dynamic resources
restMapper meta.RESTMapper
// config is the "Kubeconfig" for the client
config *rest.Config
}
Expand All @@ -39,10 +43,16 @@ func New(config *rest.Config, log *slog.Logger) (*Kubeclient, error) {
return nil, fmt.Errorf("creating kubernetes client: %w", err)
}

resources, err := restmapper.GetAPIGroupResources(client.Discovery())
if err != nil {
return nil, fmt.Errorf("getting resource groups: %w", err)
}

return &Kubeclient{
log: log,
client: client,
config: config,
log: log,
client: client,
config: config,
restMapper: restmapper.NewDiscoveryRESTMapper(resources),
}, nil
}

Expand Down
37 changes: 32 additions & 5 deletions e2e/openssl/openssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (

"github.com/edgelesssys/contrast/cli/cmd"
"github.com/edgelesssys/contrast/e2e/internal/kubeclient"
"github.com/edgelesssys/contrast/internal/kubeapi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// namespace the tests are executed in.
Expand All @@ -36,6 +38,34 @@ func TestOpenSSL(t *testing.T) {
namespace := os.Getenv(namespaceEnv)
require.NotEmpty(t, namespace, "environment variable %q must be set", namespaceEnv)

resources, err := filepath.Glob("./workspace/deployment/*.yml")
require.NoError(t, err)

// TODO(burgerdev): policy hash should come from contrast generate output.
coordinatorPolicyHashBytes, err := os.ReadFile("workspace/coordinator-policy.sha256")
require.NoError(t, err)
coordinatorPolicyHash := string(coordinatorPolicyHashBytes)
require.NotEmpty(t, coordinatorPolicyHash, "expected apply to fill coordinator policy hash")

require.True(t, t.Run("apply", func(t *testing.T) {
require := require.New(t)

var objects []*unstructured.Unstructured
for _, file := range resources {
yaml, err := os.ReadFile(file)
require.NoError(err)
fileObjects, err := kubeapi.UnmarshalUnstructuredK8SResource(yaml)
require.NoError(err)
objects = append(objects, fileObjects...)
}

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

c := kubeclient.NewForTest(t)
require.NoError(c.Apply(ctx, objects...))
}), "Kubernetes resources need to be applied for subsequent tests")

require.True(t, t.Run("set", func(t *testing.T) {
require := require.New(t)

Expand All @@ -48,11 +78,8 @@ func TestOpenSSL(t *testing.T) {
require.NoError(err)
defer cancelPortForward()

resources, err := filepath.Glob("./workspace/deployment/*.yml")
require.NoError(err)

args := []string{
"--coordinator-policy-hash=", // TODO(burgerdev): enable policy checking
"--coordinator-policy-hash", coordinatorPolicyHash,
"--coordinator", coordinator,
"--workspace-dir", "./workspace",
}
Expand Down Expand Up @@ -88,7 +115,7 @@ func TestOpenSSL(t *testing.T) {
verify := cmd.NewVerifyCmd()
verify.SetArgs([]string{
"--workspace-dir", workspaceDir,
"--coordinator-policy-hash=", // TODO(burgerdev): enable policy checking
"--coordinator-policy-hash", coordinatorPolicyHash,
"--coordinator", coordinator,
})
verify.SetOut(io.Discard)
Expand Down
5 changes: 3 additions & 2 deletions internal/kubeapi/kubeapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type (
// UnmarshalK8SResources unmarshals a Kubernetes resource into a list of objects that can be
// type casted to a Kubernetes resource.
func UnmarshalK8SResources(data []byte) ([]any, error) {
objs, err := unmarshalUnstructuredK8SResource(data)
objs, err := UnmarshalUnstructuredK8SResource(data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -77,7 +77,8 @@ func UnmarshalK8SResources(data []byte) ([]any, error) {
return result, nil
}

func unmarshalUnstructuredK8SResource(data []byte) ([]*unstructured.Unstructured, error) {
// UnmarshalUnstructuredK8SResource parses the input YAML into unstructured Kubernetes resources.
func UnmarshalUnstructuredK8SResource(data []byte) ([]*unstructured.Unstructured, error) {
documentsData, err := splitYAML(data)
if err != nil {
return nil, fmt.Errorf("splitting YAML into multiple documents: %w", err)
Expand Down