diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index f3425c1098..90429169f1 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -25,17 +25,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Run coverage - run: go test -race -coverprofile=coverage.out -covermode=atomic - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - name: Setup golang uses: ./.github/actions/golang - name: Run unit tests - run: make test-unit \ No newline at end of file + run: make test-unit + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 493e7381c2..2a68c38107 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ zarf-sbom/ test-*.txt __debug_bin .netlify +coverage.out diff --git a/Makefile b/Makefile index 5683962afc..1924780d4c 100644 --- a/Makefile +++ b/Makefile @@ -208,9 +208,7 @@ test-upgrade: ## Run the Zarf CLI E2E tests for an external registry and cluster .PHONY: test-unit test-unit: ## Run unit tests - cd src/pkg && go test ./... -failfast -v -timeout 30m - cd src/internal && go test ./... -failfast -v timeout 30m - cd src/extensions/bigbang && go test ./. -failfast -v timeout 30m + go test -failfast -v -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v '^github.com/defenseunicorns/zarf/src/test' | grep -v 'github.com/defenseunicorns/zarf/src/extensions/bigbang/test') # INTERNAL: used to test that a dev has ran `make docs-and-schema` in their PR test-docs-and-schema: diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 39efa83008..83af027217 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -13,6 +13,7 @@ import ( "github.com/fatih/color" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/pkg/helpers" @@ -115,7 +116,28 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // The default SA is required for pods to start properly. saCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() - if _, err := c.WaitForServiceAccount(saCtx, ZarfNamespaceName, "default"); err != nil { + err = func(ctx context.Context, ns, name string) error { + timer := time.NewTimer(0) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) + case <-timer.C: + _, err := c.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return err + } + if kerrors.IsNotFound(err) { + c.Log("Service account %s/%s not found, retrying...", ns, name) + timer.Reset(1 * time.Second) + continue + } + return nil + } + } + }(saCtx, ZarfNamespaceName, "default") + if err != nil { return fmt.Errorf("unable get default Zarf service account: %w", err) } diff --git a/src/pkg/k8s/sa.go b/src/pkg/k8s/sa.go deleted file mode 100644 index 38b7624130..0000000000 --- a/src/pkg/k8s/sa.go +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "fmt" - "time" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetAllServiceAccounts returns a list of services accounts for all namespaces. -func (k *K8s) GetAllServiceAccounts(ctx context.Context) (*corev1.ServiceAccountList, error) { - return k.GetServiceAccounts(ctx, corev1.NamespaceAll) -} - -// GetServiceAccounts returns a list of service accounts in a given namespace. -func (k *K8s) GetServiceAccounts(ctx context.Context, namespace string) (*corev1.ServiceAccountList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).List(ctx, metaOptions) -} - -// GetServiceAccount returns a single service account by namespace and name. -func (k *K8s) GetServiceAccount(ctx context.Context, namespace, name string) (*corev1.ServiceAccount, error) { - metaOptions := metav1.GetOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metaOptions) -} - -// UpdateServiceAccount updates the given service account in the cluster. -func (k *K8s) UpdateServiceAccount(ctx context.Context, svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { - metaOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(ctx, svcAccount, metaOptions) -} - -// WaitForServiceAccount waits for a service account to be created in the cluster. -func (k *K8s) WaitForServiceAccount(ctx context.Context, ns, name string) (*corev1.ServiceAccount, error) { - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) - case <-timer.C: - sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) - if err == nil { - return sa, nil - } - - if errors.IsNotFound(err) { - k.Log("Service account %s/%s not found, retrying...", ns, name) - } else { - return nil, fmt.Errorf("error getting service account %s/%s: %w", ns, name, err) - } - - timer.Reset(1 * time.Second) - } - } -} diff --git a/src/pkg/packager/creator/compose_test.go b/src/pkg/packager/creator/compose_test.go new file mode 100644 index 0000000000..7d1310bf3e --- /dev/null +++ b/src/pkg/packager/creator/compose_test.go @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package creator contains functions for creating Zarf packages. +package creator + +import ( + "testing" + + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestComposeComponents(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pkg types.ZarfPackage + flavor string + expectedPkg types.ZarfPackage + expectedErr string + }{ + { + name: "filter by architecture match", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + }, + }, + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + {Name: "component2"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by architecture mismatch", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "arm64", + }, + }, + }, + }, + }, + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by flavor match", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + {Name: "component2"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by flavor mismatch", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "special", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + }, + }, + expectedErr: "", + }, + { + name: "no architecture set error", + pkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{}, + expectedErr: "cannot build import chain: architecture must be provided", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkg, _, err := ComposeComponents(tt.pkg, tt.flavor) + + if tt.expectedErr == "" { + require.NoError(t, err) + require.Equal(t, tt.expectedPkg.Components, pkg.Components) + return + } + + require.EqualError(t, err, tt.expectedErr) + require.Empty(t, tt.expectedPkg) + }) + } +}