/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package cmd import ( "bytes" "context" "fmt" "testing" "github.com/edgelesssys/constellation/v2/cli/internal/kubecmd" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const successOutput = targetVersions + versionsOutput + nodesUpToDateOutput + attestationConfigOutput const inProgressOutput = targetVersions + versionsOutput + nodesInProgressOutput + attestationConfigOutput const targetVersions = `Target versions: Image: v1.1.0 Kubernetes: v1.2.3 ` const nodesUpToDateOutput = `Cluster status: Node version of every node is up to date ` const nodesInProgressOutput = `Cluster status: Some node versions are out of date Image: 1/2 Kubernetes: 1/2 ` const versionsOutput = `Service versions: Cilium: v1.0.0 cert-manager: v1.0.0 constellation-operators: v1.1.0 constellation-services: v1.1.0 ` const attestationConfigOutput = `Attestation config: measurements: 15: expected: "0000000000000000000000000000000000000000000000000000000000000000" warnOnly: false ` // TestStatus checks that the status function produces the correct strings. func TestStatus(t *testing.T) { mustParseNodeVersion := func(nV updatev1alpha1.NodeVersion) kubecmd.NodeVersion { nodeVersion, err := kubecmd.NewNodeVersion(nV) require.NoError(t, err) return nodeVersion } testCases := map[string]struct { kubeClient stubKubeClient expectedOutput string wantErr bool }{ "success": { kubeClient: stubKubeClient{ status: map[string]kubecmd.NodeStatus{ "outdated": kubecmd.NewNodeStatus(corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Annotations: map[string]string{ "constellation.edgeless.systems/node-image": "v1.1.0", }, }, Status: corev1.NodeStatus{ NodeInfo: corev1.NodeSystemInfo{ KubeletVersion: "v1.2.3", }, }, }), }, version: mustParseNodeVersion(updatev1alpha1.NodeVersion{ Spec: updatev1alpha1.NodeVersionSpec{ ImageVersion: "v1.1.0", ImageReference: "v1.1.0", KubernetesClusterVersion: "v1.2.3", }, Status: updatev1alpha1.NodeVersionStatus{ Conditions: []metav1.Condition{ { Message: "Node version of every node is up to date", }, }, }, }), attestation: &config.QEMUVTPM{ Measurements: measurements.M{ 15: measurements.WithAllBytes(0, measurements.Enforce, measurements.PCRMeasurementLength), }, }, }, expectedOutput: successOutput, }, "one of two nodes not upgraded": { kubeClient: stubKubeClient{ status: map[string]kubecmd.NodeStatus{ "outdated": kubecmd.NewNodeStatus(corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "outdated", Annotations: map[string]string{ "constellation.edgeless.systems/node-image": "v1.0.0", }, }, Status: corev1.NodeStatus{ NodeInfo: corev1.NodeSystemInfo{ KubeletVersion: "v1.2.2", }, }, }), "uptodate": kubecmd.NewNodeStatus(corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "uptodate", Annotations: map[string]string{ "constellation.edgeless.systems/node-image": "v1.1.0", }, }, Status: corev1.NodeStatus{ NodeInfo: corev1.NodeSystemInfo{ KubeletVersion: "v1.2.3", }, }, }), }, version: mustParseNodeVersion(updatev1alpha1.NodeVersion{ Spec: updatev1alpha1.NodeVersionSpec{ ImageVersion: "v1.1.0", ImageReference: "v1.1.0", KubernetesClusterVersion: "v1.2.3", }, Status: updatev1alpha1.NodeVersionStatus{ Conditions: []metav1.Condition{ { Message: "Some node versions are out of date", }, }, }, }), attestation: &config.QEMUVTPM{ Measurements: measurements.M{ 15: measurements.WithAllBytes(0, measurements.Enforce, measurements.PCRMeasurementLength), }, }, }, expectedOutput: inProgressOutput, }, "error getting node status": { kubeClient: stubKubeClient{ statusErr: assert.AnError, version: mustParseNodeVersion(updatev1alpha1.NodeVersion{ Spec: updatev1alpha1.NodeVersionSpec{ ImageVersion: "v1.1.0", ImageReference: "v1.1.0", KubernetesClusterVersion: "v1.2.3", }, Status: updatev1alpha1.NodeVersionStatus{ Conditions: []metav1.Condition{ { Message: "Node version of every node is up to date", }, }, }, }), attestation: &config.QEMUVTPM{ Measurements: measurements.M{ 15: measurements.WithAllBytes(0, measurements.Enforce, measurements.PCRMeasurementLength), }, }, }, expectedOutput: successOutput, wantErr: true, }, "error getting node version": { kubeClient: stubKubeClient{ status: map[string]kubecmd.NodeStatus{ "outdated": kubecmd.NewNodeStatus(corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Annotations: map[string]string{ "constellation.edgeless.systems/node-image": "v1.1.0", }, }, Status: corev1.NodeStatus{ NodeInfo: corev1.NodeSystemInfo{ KubeletVersion: "v1.2.3", }, }, }), }, versionErr: assert.AnError, attestation: &config.QEMUVTPM{ Measurements: measurements.M{ 15: measurements.WithAllBytes(0, measurements.Enforce, measurements.PCRMeasurementLength), }, }, }, expectedOutput: successOutput, wantErr: true, }, "error getting attestation config": { kubeClient: stubKubeClient{ status: map[string]kubecmd.NodeStatus{ "outdated": kubecmd.NewNodeStatus(corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Annotations: map[string]string{ "constellation.edgeless.systems/node-image": "v1.1.0", }, }, Status: corev1.NodeStatus{ NodeInfo: corev1.NodeSystemInfo{ KubeletVersion: "v1.2.3", }, }, }), }, version: mustParseNodeVersion(updatev1alpha1.NodeVersion{ Spec: updatev1alpha1.NodeVersionSpec{ ImageVersion: "v1.1.0", ImageReference: "v1.1.0", KubernetesClusterVersion: "v1.2.3", }, Status: updatev1alpha1.NodeVersionStatus{ Conditions: []metav1.Condition{ { Message: "Node version of every node is up to date", }, }, }, }), attestationErr: assert.AnError, }, expectedOutput: successOutput, wantErr: true, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { require := require.New(t) assert := assert.New(t) cmd := NewStatusCmd() var out bytes.Buffer cmd.SetOut(&out) var errOut bytes.Buffer cmd.SetErr(&errOut) fileHandler := file.NewHandler(afero.NewMemMapFs()) cfg, err := createConfigWithAttestationVariant(cloudprovider.QEMU, "", variant.QEMUVTPM{}) require.NoError(err) require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg)) s := statusCmd{fileHandler: fileHandler} err = s.status( cmd, stubGetVersions(versionsOutput), tc.kubeClient, stubAttestationFetcher{}, ) if tc.wantErr { assert.Error(err) return } require.NoError(err) assert.Equal(tc.expectedOutput, out.String()) }) } } type stubKubeClient struct { status map[string]kubecmd.NodeStatus statusErr error version kubecmd.NodeVersion versionErr error attestation config.AttestationCfg attestationErr error } func (s stubKubeClient) ClusterStatus(_ context.Context) (map[string]kubecmd.NodeStatus, error) { return s.status, s.statusErr } func (s stubKubeClient) GetConstellationVersion(_ context.Context) (kubecmd.NodeVersion, error) { return s.version, s.versionErr } func (s stubKubeClient) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, error) { return s.attestation, s.attestationErr } func stubGetVersions(output string) func() (fmt.Stringer, error) { return func() (fmt.Stringer, error) { return stubServiceVersions{output}, nil } } type stubServiceVersions struct { output string } func (s stubServiceVersions) String() string { return s.output }