diff --git a/src/cmd/tools.go b/src/cmd/tools.go index 6fad6867e2..1e97df1943 100644 --- a/src/cmd/tools.go +++ b/src/cmd/tools.go @@ -5,7 +5,6 @@ package cmd import ( - "net/url" "os" "github.com/anchore/syft/cmd/syft/cli" @@ -234,16 +233,13 @@ func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { return err } tunnelReg.Connect(cluster.ZarfRegistry, false) - registryURL, err := url.Parse(tunnelReg.HTTPEndpoint()) - if err != nil { - return err - } // Add the correct authentication to the crane command options authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PullUsername, zarfState.RegistryInfo.PullPassword) *cranePlatformOptions = append(*cranePlatformOptions, authOption) + registryEndpoint := tunnelReg.Endpoint() - return originalCatalogFn(cmd, []string{registryURL.Host}) + return originalCatalogFn(cmd, []string{registryEndpoint}) } return craneCatalog diff --git a/src/config/config.go b/src/config/config.go index 9c19c758e1..21378deeb1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -52,7 +52,6 @@ const ( ZarfSBOMDir = "zarf-sbom" ZarfPackagePrefix = "zarf-package-" - ZarfInClusterContainerRegistryURL = "http://zarf-registry-http.zarf.svc.cluster.local:5000" ZarfInClusterContainerRegistryNodePort = 31999 ZarfInClusterGitServiceURL = "http://zarf-gitea-http.zarf.svc.cluster.local:3000" @@ -165,16 +164,6 @@ func GetValidPackageExtensions() [3]string { return [...]string{".tar.zst", ".tar", ".zip"} } -// GetRegistry returns the registry URL based on the Zarf state. -func GetRegistry(state types.ZarfState) string { - // If a node port is populated, then we are using a registry internal to the cluster. Ignore the provided address and use localhost - if state.RegistryInfo.NodePort >= 30000 { - return fmt.Sprintf("%s:%d", IPV4Localhost, state.RegistryInfo.NodePort) - } - - return state.RegistryInfo.Address -} - // GetAbsCachePath gets the absolute cache path for images and git repos. func GetAbsCachePath() string { homePath, _ := os.UserHomeDir() diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 83521eee11..8c78e3cad8 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -239,6 +239,5 @@ const ( // Collection of reusable error messages. var ( - ErrInitNotFound = errors.New("this command requires a zarf-init package, but one was not found on the local system. Re-run the last command again without '--confirm' to download the package") - ErrNotAServiceURL = errors.New("the provided URL does not match service url format of http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}") + ErrInitNotFound = errors.New("this command requires a zarf-init package, but one was not found on the local system. Re-run the last command again without '--confirm' to download the package") ) diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index 3e628505e1..b194eae27c 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -64,7 +64,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - containerRegistryURL := config.GetRegistry(zarfState) + containerRegistryURL := zarfState.RegistryInfo.Address // update the image host for each init container for idx, container := range pod.Spec.InitContainers { diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go index e85cbb1537..b6cd1ea2dd 100644 --- a/src/internal/agent/start.go +++ b/src/internal/agent/start.go @@ -37,7 +37,7 @@ func StartWebhook() { } }() - message.Info(lang.AgentErrStart) + message.Infof(lang.AgentInfoPort, httpPort) // listen shutdown signal signalChan := make(chan os.Signal, 1) diff --git a/src/internal/cluster/injector.go b/src/internal/cluster/injector.go index b18f1cb6d0..fc120172bc 100644 --- a/src/internal/cluster/injector.go +++ b/src/internal/cluster/injector.go @@ -173,7 +173,7 @@ func (c *Cluster) injectorIsReady(spinner *message.Spinner) bool { spinner.Updatef("Testing the injector for seed image availability") - seedRegistry := fmt.Sprintf("http://%s/v2/library/%s/manifests/%s", tunnel.Endpoint(), config.ZarfSeedImage, config.ZarfSeedTag) + seedRegistry := fmt.Sprintf("%s/v2/library/%s/manifests/%s", tunnel.HTTPEndpoint(), config.ZarfSeedImage, config.ZarfSeedTag) if resp, err := http.Get(seedRegistry); err != nil || resp.StatusCode != 200 { // Just debug log the output because failures just result in trying the next image message.Debug(resp, err) diff --git a/src/internal/cluster/secrets.go b/src/internal/cluster/secrets.go index 0f562250a1..dd42ef76fe 100644 --- a/src/internal/cluster/secrets.go +++ b/src/internal/cluster/secrets.go @@ -11,7 +11,6 @@ import ( corev1 "k8s.io/api/core/v1" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -48,7 +47,7 @@ func (c *Cluster) GenerateRegistryPullCreds(namespace, name string) (*corev1.Sec fieldValue := zarfState.RegistryInfo.PullUsername + ":" + credential authEncodedValue := base64.StdEncoding.EncodeToString([]byte(fieldValue)) - registry := config.GetRegistry(zarfState) + registry := zarfState.RegistryInfo.Address // Create the expected structure for the dockerconfigjson dockerConfigJSON := DockerConfig{ Auths: DockerConfigEntry{ diff --git a/src/internal/cluster/seed.go b/src/internal/cluster/seed.go index 548da13e5f..5f5f4dab46 100644 --- a/src/internal/cluster/seed.go +++ b/src/internal/cluster/seed.go @@ -145,11 +145,15 @@ func (c *Cluster) PostSeedRegistry(tempPath types.TempPaths) error { } func (c *Cluster) fillInEmptyContainerRegistryValues(containerRegistry types.RegistryInfo) types.RegistryInfo { + // Set default NodePort if none was provided + if containerRegistry.NodePort == 0 { + containerRegistry.NodePort = config.ZarfInClusterContainerRegistryNodePort + } + // Set default url if an external registry was not provided if containerRegistry.Address == "" { containerRegistry.InternalRegistry = true - containerRegistry.NodePort = config.ZarfInClusterContainerRegistryNodePort - containerRegistry.Address = fmt.Sprintf("http://%s:%d", config.IPV4Localhost, containerRegistry.NodePort) + containerRegistry.Address = fmt.Sprintf("%s:%d", config.IPV4Localhost, containerRegistry.NodePort) } // Generate a push-user password if not provided by init flag diff --git a/src/internal/cluster/tunnel.go b/src/internal/cluster/tunnel.go index ae6a78e451..c6c5a6a530 100644 --- a/src/internal/cluster/tunnel.go +++ b/src/internal/cluster/tunnel.go @@ -21,7 +21,6 @@ import ( "syscall" "time" - "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/types" "github.com/defenseunicorns/zarf/src/config" @@ -67,6 +66,13 @@ type Tunnel struct { spinner *message.Spinner } +// ServiceInfo contains information necessary for connecting to a cluster service. +type ServiceInfo struct { + Namespace string + Name string + Port int +} + // PrintConnectTable will print a table of all Zarf connect matches found in the cluster. func (c *Cluster) PrintConnectTable() error { list, err := c.Kube.GetServicesByLabelExists(v1.NamespaceAll, config.ZarfConnectLabelName) @@ -91,33 +97,72 @@ func (c *Cluster) PrintConnectTable() error { return nil } -// IsServiceURL will check if the provided string is a valid serviceURL based on if it properly matches a validating regexp. -func IsServiceURL(serviceURL string) bool { - parsedURL, err := url.Parse(serviceURL) +// ServiceInfoFromNodePortURL takes a nodePortURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: +// Example nodePortURL: 127.0.0.1:{PORT}. +func ServiceInfoFromNodePortURL(nodePortURL string) *ServiceInfo { + // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) + parsedURL, err := url.Parse(nodePortURL) if err != nil { - return false + parsedURL, err = url.Parse("scheme://" + nodePortURL) + if err != nil { + return nil + } } - // Match hostname against local cluster service format - pattern := regexp.MustCompile(serviceURLPattern) - matches := pattern.FindStringSubmatch(parsedURL.Hostname()) + // Match hostname against localhost ip/hostnames + hostname := parsedURL.Hostname() + if hostname != config.IPV4Localhost && hostname != "localhost" { + return nil + } + + // Get the node port from the nodeportURL. + nodePort, err := strconv.Atoi(parsedURL.Port()) + if err != nil { + return nil + } + if nodePort < 30000 || nodePort > 32767 { + return nil + } + + kube, err := k8s.NewWithWait(message.Debugf, labels, defaultTimeout) + if err != nil { + return nil + } - // If incomplete match, return an error - return len(matches) == 3 + services, err := kube.GetServices("") + if err != nil { + return nil + } + + for _, svc := range services.Items { + if svc.Spec.Type == "NodePort" { + for _, port := range svc.Spec.Ports { + if int(port.NodePort) == nodePort { + return &ServiceInfo{ + Namespace: svc.Namespace, + Name: svc.Name, + Port: int(port.Port), + } + } + } + } + } + + return nil } -// NewTunnelFromServiceURL takes a serviceURL and parses it to create a tunnel to the cluster. The string is expected to follow the following format: +// ServiceInfoFromServiceURL takes a serviceURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: // Example serviceURL: http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}. -func NewTunnelFromServiceURL(serviceURL string) (*Tunnel, error) { +func ServiceInfoFromServiceURL(serviceURL string) *ServiceInfo { parsedURL, err := url.Parse(serviceURL) if err != nil { - return nil, err + return nil } // Get the remote port from the serviceURL. remotePort, err := strconv.Atoi(parsedURL.Port()) if err != nil { - return nil, err + return nil } // Match hostname against local cluster service format. @@ -126,14 +171,14 @@ func NewTunnelFromServiceURL(serviceURL string) (*Tunnel, error) { // If incomplete match, return an error. if len(matches) != 3 { - return nil, lang.ErrNotAServiceURL + return nil } - // Use the matched values to create a new tunnel. - name := matches[pattern.SubexpIndex("name")] - namespace := matches[pattern.SubexpIndex("namespace")] - - return NewTunnel(namespace, SvcResource, name, 0, remotePort) + return &ServiceInfo{ + Namespace: matches[pattern.SubexpIndex("namespace")], + Name: matches[pattern.SubexpIndex("name")], + Port: remotePort, + } } // NewTunnel will create a new Tunnel struct. @@ -261,7 +306,7 @@ func (tunnel *Tunnel) Connect(target string, blocking bool) error { return nil } -// Endpoint returns the tunnel endpoint. +// Endpoint returns the tunnel ip address and port (i.e. for docker registries) func (tunnel *Tunnel) Endpoint() string { message.Debug("tunnel.Endpoint()") return fmt.Sprintf("127.0.0.1:%d", tunnel.localPort) diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go index f385e65d2b..3815837f5b 100644 --- a/src/internal/packager/git/gitea.go +++ b/src/internal/packager/git/gitea.go @@ -29,10 +29,10 @@ func (g *Git) CreateReadOnlyUser() error { tunnel.Connect(cluster.ZarfGit, false) defer tunnel.Close() - tunnelURL := tunnel.Endpoint() + tunnelURL := tunnel.HTTPEndpoint() // Determine if the read only user already exists - getUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users", tunnelURL) + getUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users", tunnelURL) getUserRequest, _ := netHttp.NewRequest("GET", getUserEndpoint, nil) out, err := g.DoHTTPThings(getUserRequest, g.Server.PushUsername, g.Server.PushPassword) message.Debugf("GET %s:\n%s", getUserEndpoint, string(out)) @@ -60,7 +60,7 @@ func (g *Git) CreateReadOnlyUser() error { "password": g.Server.PullPassword, } updateUserData, _ := json.Marshal(updateUserBody) - updateUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername) + updateUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername) updateUserRequest, _ := netHttp.NewRequest("PATCH", updateUserEndpoint, bytes.NewBuffer(updateUserData)) out, err = g.DoHTTPThings(updateUserRequest, g.Server.PushUsername, g.Server.PushPassword) message.Debugf("PATCH %s:\n%s", updateUserEndpoint, string(out)) @@ -80,7 +80,7 @@ func (g *Git) CreateReadOnlyUser() error { } // Send API request to create the user - createUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users", tunnelURL) + createUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users", tunnelURL) createUserRequest, _ := netHttp.NewRequest("POST", createUserEndpoint, bytes.NewBuffer(createUserData)) out, err = g.DoHTTPThings(createUserRequest, g.Server.PushUsername, g.Server.PushPassword) message.Debugf("POST %s:\n%s", createUserEndpoint, string(out)) @@ -95,7 +95,7 @@ func (g *Git) CreateReadOnlyUser() error { "allow_create_organization": false, } updateUserData, _ := json.Marshal(updateUserBody) - updateUserEndpoint := fmt.Sprintf("http://%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername) + updateUserEndpoint := fmt.Sprintf("%s/api/v1/admin/users/%s", tunnelURL, g.Server.PullUsername) updateUserRequest, _ := netHttp.NewRequest("PATCH", updateUserEndpoint, bytes.NewBuffer(updateUserData)) out, err = g.DoHTTPThings(updateUserRequest, g.Server.PushUsername, g.Server.PushPassword) message.Debugf("PATCH %s:\n%s", updateUserEndpoint, string(out)) diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index 4823309009..a1a9ff5717 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -30,10 +30,13 @@ func (i *ImgConfig) PushToZarfRegistry() error { return err } target = cluster.ZarfRegistry - } else if cluster.IsServiceURL(i.RegInfo.Address) { - // If this is a serviceURL, create a port-forward tunnel to that resource - if tunnel, err = cluster.NewTunnelFromServiceURL(i.RegInfo.Address); err != nil { - return err + } else { + svcInfo := cluster.ServiceInfoFromNodePortURL(i.RegInfo.Address) + if svcInfo != nil { + // If this is a service, create a port-forward tunnel to that resource + if tunnel, err = cluster.NewTunnel(svcInfo.Namespace, cluster.SvcResource, svcInfo.Name, 0, svcInfo.Port); err != nil { + return err + } } } @@ -41,12 +44,14 @@ func (i *ImgConfig) PushToZarfRegistry() error { tunnel.Connect(target, false) defer tunnel.Close() registryURL = tunnel.Endpoint() + } else { + registryURL = i.RegInfo.Address } spinner := message.NewProgressSpinner("Storing images in the zarf registry") defer spinner.Stop() - pushOptions := config.GetCraneAuthOption(i.RegInfo.PushUsername, i.RegInfo.PushPassword) + pushOptions := []crane.Option{config.GetCraneAuthOption(i.RegInfo.PushUsername, i.RegInfo.PushPassword)} message.Debugf("crane pushOptions = %#v", pushOptions) for _, src := range i.ImgList { @@ -65,7 +70,7 @@ func (i *ImgConfig) PushToZarfRegistry() error { message.Debugf("crane.Push() %s:%s -> %s)", i.TarballPath, src, offlineNameCRC) - if err = crane.Push(img, offlineNameCRC, pushOptions); err != nil { + if err = crane.Push(img, offlineNameCRC, pushOptions...); err != nil { return err } } @@ -79,7 +84,7 @@ func (i *ImgConfig) PushToZarfRegistry() error { message.Debugf("crane.Push() %s:%s -> %s)", i.TarballPath, src, offlineName) - if err = crane.Push(img, offlineName, pushOptions); err != nil { + if err = crane.Push(img, offlineName, pushOptions...); err != nil { return err } } diff --git a/src/internal/packager/template/template.go b/src/internal/packager/template/template.go index dd38699598..063a4b1849 100644 --- a/src/internal/packager/template/template.go +++ b/src/internal/packager/template/template.go @@ -48,7 +48,7 @@ func Generate(cfg *types.PackagerConfig) (Values, error) { generated.htpasswd = fmt.Sprintf("%s\\n%s", pushUser, pullUser) - generated.registry = config.GetRegistry(cfg.State) + generated.registry = regInfo.Address return generated, nil } diff --git a/src/pkg/k8s/services.go b/src/pkg/k8s/services.go index 88dbe28a3c..bc880bc018 100644 --- a/src/pkg/k8s/services.go +++ b/src/pkg/k8s/services.go @@ -52,6 +52,11 @@ func (k *K8s) GetService(namespace, serviceName string) (*corev1.Service, error) return k.Clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) } +// GetServices returns a list of services in the provided namespace. To search all namespaces, pass "" in the namespace arg. +func (k *K8s) GetServices(namespace string) (*corev1.ServiceList, error) { + return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) +} + // GetServicesByLabel returns a list of matched services given a label and value. To search all namespaces, pass "" in the namespace arg. func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.ServiceList, error) { // Creat the selector and add the requirement diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 0a4ba6a893..52f6edcf69 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -401,9 +401,10 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error tryPush := func() error { gitClient := git.New(p.cfg.State.GitServer) - // If this is a serviceURL, create a port-forward tunnel to that resource - if cluster.IsServiceURL(gitClient.Server.Address) { - tunnel, err := cluster.NewTunnelFromServiceURL(gitClient.Server.Address) + svcInfo := cluster.ServiceInfoFromServiceURL(gitClient.Server.Address) + // If this is a service, create a port-forward tunnel to that resource + if svcInfo != nil { + tunnel, err := cluster.NewTunnel(svcInfo.Namespace, cluster.SvcResource, svcInfo.Name, 0, svcInfo.Port) if err != nil { return err @@ -411,7 +412,7 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error tunnel.Connect("", false) defer tunnel.Close() - gitClient.Server.Address = fmt.Sprintf("http://%s", tunnel.Endpoint()) + gitClient.Server.Address = tunnel.HTTPEndpoint() } // Convert the repo URL to a Zarf-formatted repo name diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go index a8e83dc5e1..84db6ee3f0 100644 --- a/src/test/e2e/20_zarf_init_test.go +++ b/src/test/e2e/20_zarf_init_test.go @@ -28,7 +28,7 @@ func TestZarfInit(t *testing.T) { defer cancel() // run `zarf init` - _, _, err := exec.CmdWithContext(ctx, exec.PrintCfg(), e2e.zarfBinPath, "init", "--components="+initComponents, "--confirm") + _, _, err := exec.CmdWithContext(ctx, exec.PrintCfg(), e2e.zarfBinPath, "init", "--components="+initComponents, "--confirm", "--nodeport", "31337") require.NoError(t, err) // Check that gitea is actually running and healthy @@ -47,6 +47,11 @@ func TestZarfInit(t *testing.T) { require.NoError(t, err) require.Contains(t, stdOut, "Running") + // Check that the registry is running on the correct NodePort + stdOut, _, err = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "service", "-n", "zarf", "zarf-docker-registry", "-o=jsonpath='{.spec.ports[*].nodePort}'") + require.NoError(t, err) + require.Contains(t, stdOut, "31337") + // Special sizing-hacking for reducing resources where Kind + CI eats a lot of free cycles (ignore errors) _, _, _ = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "scale", "deploy", "-n", "kube-system", "coredns", "--replicas=1") _, _, _ = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "scale", "deploy", "-n", "zarf", "agent-hook", "--replicas=1") diff --git a/src/test/external-test/common.go b/src/test/external-test/common.go new file mode 100644 index 0000000000..eee17d0ed5 --- /dev/null +++ b/src/test/external-test/common.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package external_test provides a test for the external init flow. +package external_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/defenseunicorns/zarf/src/pkg/utils/exec" +) + +func verifyKubectlWaitSuccess(t *testing.T, timeoutMinutes time.Duration, args []string, onTimeout string) bool { + return verifyWaitSuccess(t, timeoutMinutes, "kubectl", args, "condition met", onTimeout) +} + +func verifyWaitSuccess(t *testing.T, timeoutMinutes time.Duration, cmd string, args []string, condition string, onTimeout string) bool { + timeout := time.After(timeoutMinutes * time.Minute) + for { + // delay check 3 seconds + time.Sleep(3 * time.Second) + select { + // on timeout abort + case <-timeout: + t.Error(onTimeout) + + return false + + // after delay, try running + default: + // Check information from the given command + stdOut, _, err := exec.CmdWithContext(context.TODO(), exec.PrintCfg(), cmd, args...) + // Log error + if err != nil { + t.Log(string(stdOut), err) + } + if strings.Contains(string(stdOut), condition) { + return true + } + } + } +} diff --git a/src/test/external-test/configure-gitea.sh b/src/test/external-test/configure-gitea.sh new file mode 100755 index 0000000000..4a2f1759d8 --- /dev/null +++ b/src/test/external-test/configure-gitea.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Retry gitea migrate until the db is ready +timeout 30 bash -c 'until gitea migrate; do sleep 2; done' + +echo '==== BEGIN GITEA CONFIGURATION ====' + +function configure_admin_user() { + local ACCOUNT_ID=$(gitea admin user list --admin | grep -e "\s\+${GITEA_ADMIN_USERNAME}\s\+" | awk -F " " "{printf \$1}") + if [[ -z "${ACCOUNT_ID}" ]]; then + echo "No admin user '${GITEA_ADMIN_USERNAME}' found. Creating now..." + gitea admin user create --admin --username "${GITEA_ADMIN_USERNAME}" --password "${GITEA_ADMIN_PASSWORD}" --email "${GITEA_ADMIN_EMAIL}" --must-change-password=false + echo '...created.' + else + echo "Admin account '${GITEA_ADMIN_USERNAME}' already exist. Running update to sync password..." + gitea admin user change-password --username "${GITEA_ADMIN_USERNAME}" --password "${GITEA_ADMIN_PASSWORD}" + echo '...password sync done.' + fi +} + +configure_admin_user + +echo '==== END GITEA CONFIGURATION ====' diff --git a/src/test/external-test/docker-compose.yml b/src/test/external-test/docker-compose.yml new file mode 100644 index 0000000000..401476653b --- /dev/null +++ b/src/test/external-test/docker-compose.yml @@ -0,0 +1,43 @@ +version: "3" + +services: + server: + image: gitea/gitea:1.18.1 + container_name: gitea.localhost + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=sqlite3 + - GITEA__server__DISABLE_SSH=true + - GITEA__server__OFFLINE_MODE=true + - GITEA__security__INSTALL_LOCK=true + - GITEA__service__DISABLE_REGISTRATION=true + - GITEA__repository__ENABLE_PUSH_CREATE_USER=true + - GITEA__repository__FORCE_PRIVATE=true + restart: always + volumes: + - ./data/git:/data/git + - ./data/gitea:/data/gitea + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "3000:3000" + - "222:22" + init: + image: gitea/gitea:1.18.1 + container_name: gitea.init + environment: + - GITEA_APP_INI=/data/gitea/conf/app.ini + - GITEA_CUSTOM=/data/gitea + - GITEA_WORK_DIR=/data + - GITEA_TEMP=/tmp/gitea + - GITEA_ADMIN_USERNAME=git-user + - GITEA_ADMIN_PASSWORD=superSecurePassword + - GITEA_ADMIN_EMAIL=zarf@localhost + user: 1000:1000 + command: /usr/sbin/configure-gitea.sh + volumes: + - ./configure-gitea.sh:/usr/sbin/configure-gitea.sh + - ./data/gitea:/data/gitea + depends_on: + - server diff --git a/src/test/external-test/external_init_test.go b/src/test/external-test/ext_in_cluster_init_test.go similarity index 77% rename from src/test/external-test/external_init_test.go rename to src/test/external-test/ext_in_cluster_init_test.go index 0a518346e1..a873b7fabd 100644 --- a/src/test/external-test/external_init_test.go +++ b/src/test/external-test/ext_in_cluster_init_test.go @@ -7,9 +7,7 @@ package external_test import ( "context" "path" - "strings" "testing" - "time" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" test "github.com/defenseunicorns/zarf/src/test/e2e" @@ -17,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestExternalDeploy(t *testing.T) { +func TestExtInClusterDeploy(t *testing.T) { zarfBinPath := path.Join("../../../build", test.GetCLIName()) // Install a gitea chart to the k8s cluster to act as the 'remote' git server @@ -52,8 +50,7 @@ func TestExternalDeploy(t *testing.T) { "--git-url=http://gitea-http.git-server.svc.cluster.local:3000", "--registry-push-username=push-user", "--registry-push-password=superSecurePassword", - "--registry-url=http://external-registry-docker-registry.external-registry.svc.cluster.local:5000", - "--nodeport=31999", + "--registry-url=127.0.0.1:31999", "--confirm"} err = exec.CmdWithPrint(zarfBinPath, initArgs...) @@ -71,31 +68,7 @@ func TestExternalDeploy(t *testing.T) { errorStr := "unable to verify flux deployed the podinfo example" success = verifyKubectlWaitSuccess(t, 2, podinfoWaitCmd, errorStr) assert.True(t, success, errorStr) -} - -func verifyKubectlWaitSuccess(t *testing.T, timeoutMinutes time.Duration, waitCmd []string, errorStr string) bool { - timeout := time.After(timeoutMinutes * time.Minute) - for { - // delay check 3 seconds - time.Sleep(3 * time.Second) - select { - // on timeout abort - case <-timeout: - t.Error(errorStr) - - return false - // after delay, try running - default: - // Check that flux deployed the podinfo example - kubectlOut, kubectlErr, err := exec.CmdWithContext(context.TODO(), exec.Config{Print: true}, "kubectl", waitCmd...) - // Log error - if err != nil { - t.Logf("Error (%v) when running wait command with stdout of (%s) and stderr of (%s)", err, kubectlOut, kubectlErr) - } - if strings.Contains(string(kubectlOut), "condition met") { - return true - } - } - } + _, _, err = exec.CmdWithContext(context.TODO(), exec.PrintCfg(), zarfBinPath, "destroy", "--confirm") + require.NoError(t, err, "unable to teardown zarf") } diff --git a/src/test/external-test/ext_out_cluster_init_test.go b/src/test/external-test/ext_out_cluster_init_test.go new file mode 100644 index 0000000000..d17755e684 --- /dev/null +++ b/src/test/external-test/ext_out_cluster_init_test.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package external_test provides a test for the external init flow. +package external_test + +import ( + "path" + "testing" + + "github.com/defenseunicorns/zarf/src/pkg/utils/exec" + test "github.com/defenseunicorns/zarf/src/test/e2e" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExtOutClusterDeploy(t *testing.T) { + // Docker/k3d networking constants + network := "k3d-k3s-external-test" + subnet := "172.31.0.0/16" + gateway := "172.31.0.1" + giteaIP := "172.31.0.99" + giteaHost := "gitea.localhost" + registryHost := "registry.localhost" + + zarfBinPath := path.Join("../../../build", test.GetCLIName()) + + // Teardown any leftovers from previous tests + _ = exec.CmdWithPrint("k3d", "cluster", "delete") + _ = exec.CmdWithPrint("k3d", "registry", "delete", registryHost) + _ = exec.CmdWithPrint("docker", "network", "remove", network) + + // Setup a network for everything to live inside + err := exec.CmdWithPrint("docker", "network", "create", "--driver=bridge", "--subnet="+subnet, "--gateway="+gateway, network) + require.NoError(t, err, "unable to create the k3d registry") + + // Install a k3d-managed registry server to act as the 'remote' container registry + err = exec.CmdWithPrint("k3d", "registry", "create", registryHost, "--port", "5000") + require.NoError(t, err, "unable to create the k3d registry") + + // Create a k3d cluster with the proper networking and aliases + err = exec.CmdWithPrint("k3d", "cluster", "create", "--registry-use", "k3d-"+registryHost+":5000", "--host-alias", giteaIP+":"+giteaHost, "--network", network) + require.NoError(t, err, "unable to create the k3d cluster") + + // Install a gitea server via docker compose to act as the 'remote' git server + err = exec.CmdWithPrint("docker", "compose", "up", "-d") + require.NoError(t, err, "unable to install the gitea-server") + + // Wait for gitea to deploy properly + giteaArgs := []string{"inspect", "-f", "{{.State.Status}}", "gitea.init"} + giteaErrStr := "unable to verify the gitea container installed successfully" + success := verifyWaitSuccess(t, 2, "docker", giteaArgs, "exited", giteaErrStr) + require.True(t, success, giteaErrStr) + + // Connect gitea to the k3d network + err = exec.CmdWithPrint("docker", "network", "connect", "--ip", giteaIP, network, giteaHost) + require.NoError(t, err, "unable to connect the gitea-server top k3d") + + // Use Zarf to initialize the cluster + initArgs := []string{"init", + "--git-push-username=git-user", + "--git-push-password=superSecurePassword", + "--git-url=http://" + giteaHost + ":3000", + "--registry-push-username=git-user", + "--registry-push-password=superSecurePassword", + "--registry-url=k3d-" + registryHost + ":5000", + "--confirm"} + err = exec.CmdWithPrint(zarfBinPath, initArgs...) + + require.NoError(t, err, "unable to initialize the k8s server with zarf") + + // Deploy the flux example package + deployArgs := []string{"package", "deploy", "../../../build/zarf-package-flux-test-amd64.tar.zst", "--confirm"} + err = exec.CmdWithPrint(zarfBinPath, deployArgs...) + + require.NoError(t, err, "unable to deploy flux example package") + + // Verify flux was able to pull from the 'external' repository + podinfoArgs := []string{"wait", "deployment", "-n=podinfo", "podinfo", "--for", "condition=Available=True", "--timeout=3s"} + errorStr := "unable to verify flux deployed the podinfo example" + success = verifyKubectlWaitSuccess(t, 2, podinfoArgs, errorStr) + assert.True(t, success, errorStr) + + // Tear down all of that stuff we made for local runs + err = exec.CmdWithPrint("k3d", "cluster", "delete") + require.NoError(t, err, "unable to teardown zarf") + + err = exec.CmdWithPrint("docker", "compose", "down") + require.NoError(t, err, "unable to teardown the gitea-server") + + err = exec.CmdWithPrint("k3d", "registry", "delete", registryHost) + require.NoError(t, err, "unable to teardown the k3d registry") + + err = exec.CmdWithPrint("docker", "network", "remove", network) + require.NoError(t, err, "unable to teardown the docker test network") +} diff --git a/src/test/external-test/secret.yaml b/src/test/external-test/secret.yaml deleted file mode 100644 index e30a5fee35..0000000000 --- a/src/test/external-test/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: private-git-server - namespace: git-server -type: Opaque -data: - # Note: Super fake and not real username/password for testing purposes - username: Z2l0LXVzZXI= - password: c3VwZXJTZWN1cmVQYXNzd29yZA==