mirror of https://github.com/fluxcd/flux2.git
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			421 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			421 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
/*
 | 
						|
Copyright 2023 The Flux authors
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package integration
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	extgogit "github.com/go-git/go-git/v5"
 | 
						|
	"github.com/go-git/go-git/v5/plumbing"
 | 
						|
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
						|
	"github.com/google/go-containerregistry/pkg/crane"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	kerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
						|
	"sigs.k8s.io/controller-runtime/pkg/client"
 | 
						|
 | 
						|
	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
 | 
						|
	"github.com/fluxcd/pkg/apis/meta"
 | 
						|
	"github.com/fluxcd/pkg/git"
 | 
						|
	"github.com/fluxcd/pkg/git/gogit"
 | 
						|
	"github.com/fluxcd/pkg/git/repository"
 | 
						|
	"github.com/fluxcd/pkg/runtime/conditions"
 | 
						|
	sourcev1 "github.com/fluxcd/source-controller/api/v1"
 | 
						|
	"github.com/fluxcd/test-infra/tftestenv"
 | 
						|
)
 | 
						|
 | 
						|
// installFlux adds the core Flux components to the cluster specified in the kubeconfig file.
 | 
						|
func installFlux(ctx context.Context, tmpDir string, kubeconfigPath string) error {
 | 
						|
	// Create flux-system namespace
 | 
						|
	namespace := corev1.Namespace{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name: "flux-system",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	err := testEnv.Create(ctx, &namespace)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	repoURL := getTransportURL(cfg.fleetInfraRepository)
 | 
						|
	if cfg.kustomizationYaml != "" {
 | 
						|
		files := make(map[string]io.Reader)
 | 
						|
		files["clusters/e2e/flux-system/kustomization.yaml"] = strings.NewReader(cfg.kustomizationYaml)
 | 
						|
		files["clusters/e2e/flux-system/gotk-components.yaml"] = strings.NewReader("")
 | 
						|
		files["clusters/e2e/flux-system/gotk-sync.yaml"] = strings.NewReader("")
 | 
						|
		c, err := getRepository(ctx, tmpDir, repoURL, defaultBranch, cfg.defaultAuthOpts)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		err = commitAndPushAll(ctx, c, files, defaultBranch)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var bootstrapArgs string
 | 
						|
	if cfg.defaultGitTransport == git.SSH {
 | 
						|
		f, err := os.CreateTemp("", "flux-e2e-ssh-key-*")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(f.Name(), []byte(cfg.gitPrivateKey), 0o600)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		bootstrapArgs = fmt.Sprintf("--private-key-file=%s -s", f.Name())
 | 
						|
	} else {
 | 
						|
		bootstrapArgs = fmt.Sprintf("--token-auth --password=%s", cfg.gitPat)
 | 
						|
	}
 | 
						|
 | 
						|
	bootstrapCmd := fmt.Sprintf("%s bootstrap git  --url=%s %s --kubeconfig=%s --path=clusters/e2e "+
 | 
						|
		" --components-extra image-reflector-controller,image-automation-controller",
 | 
						|
		fluxBin, repoURL, bootstrapArgs, kubeconfigPath)
 | 
						|
 | 
						|
	return tftestenv.RunCommand(ctx, "./", bootstrapCmd, tftestenv.RunCommandOptions{
 | 
						|
		Timeout: 15 * time.Minute,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func runFluxCheck(ctx context.Context) error {
 | 
						|
	checkCmd := fmt.Sprintf("%s check --kubeconfig %s", fluxBin, kubeconfigPath)
 | 
						|
	return tftestenv.RunCommand(ctx, "./", checkCmd, tftestenv.RunCommandOptions{
 | 
						|
		AttachConsole: true,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func uninstallFlux(ctx context.Context) error {
 | 
						|
	uninstallCmd := fmt.Sprintf("%s uninstall --kubeconfig %s -s", fluxBin, kubeconfigPath)
 | 
						|
	if err := tftestenv.RunCommand(ctx, "./", uninstallCmd, tftestenv.RunCommandOptions{
 | 
						|
		Timeout: 15 * time.Minute,
 | 
						|
	}); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly.
 | 
						|
func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error {
 | 
						|
	nn := types.NamespacedName{
 | 
						|
		Name:      name,
 | 
						|
		Namespace: namespace,
 | 
						|
	}
 | 
						|
	source := &sourcev1.GitRepository{}
 | 
						|
	if err := kubeClient.Get(ctx, nn, source); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if err := checkReadyCondition(source); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	kustomization := &kustomizev1.Kustomization{}
 | 
						|
	if err := kubeClient.Get(ctx, nn, kustomization); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if err := checkReadyCondition(kustomization); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type nsConfig struct {
 | 
						|
	repoURL      string
 | 
						|
	ref          *sourcev1.GitRepositoryRef
 | 
						|
	protocol     git.TransportType
 | 
						|
	objectName   string
 | 
						|
	path         string
 | 
						|
	modifyKsSpec func(spec *kustomizev1.KustomizationSpec)
 | 
						|
}
 | 
						|
 | 
						|
// setUpFluxConfigs creates the namespace, then creates the git secret,
 | 
						|
// git repository and kustomization in that namespace
 | 
						|
func setUpFluxConfig(ctx context.Context, name string, opts nsConfig) error {
 | 
						|
	transport := cfg.defaultGitTransport
 | 
						|
	if opts.protocol != "" {
 | 
						|
		transport = opts.protocol
 | 
						|
	}
 | 
						|
 | 
						|
	namespace := corev1.Namespace{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name: name,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	if err := testEnv.Create(ctx, &namespace); err != nil && !apierrors.IsAlreadyExists(err) {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	secret := corev1.Secret{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      "git-credentials",
 | 
						|
			Namespace: name,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	secret.StringData = map[string]string{
 | 
						|
		"username": cfg.gitUsername,
 | 
						|
		"password": cfg.gitPat,
 | 
						|
	}
 | 
						|
 | 
						|
	if transport == git.SSH {
 | 
						|
		secret.StringData = map[string]string{
 | 
						|
			"identity":     cfg.gitPrivateKey,
 | 
						|
			"identity.pub": cfg.gitPublicKey,
 | 
						|
			"known_hosts":  cfg.knownHosts,
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := testEnv.Create(ctx, &secret); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ref := &sourcev1.GitRepositoryRef{
 | 
						|
		Branch: name,
 | 
						|
	}
 | 
						|
 | 
						|
	if opts.ref != nil {
 | 
						|
		ref = opts.ref
 | 
						|
	}
 | 
						|
 | 
						|
	gitSpec := &sourcev1.GitRepositorySpec{
 | 
						|
		Interval: metav1.Duration{
 | 
						|
			Duration: 1 * time.Minute,
 | 
						|
		},
 | 
						|
		Reference: ref,
 | 
						|
		SecretRef: &meta.LocalObjectReference{
 | 
						|
			Name: secret.Name,
 | 
						|
		},
 | 
						|
		URL: opts.repoURL,
 | 
						|
	}
 | 
						|
 | 
						|
	source := &sourcev1.GitRepository{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name},
 | 
						|
		Spec:       *gitSpec,
 | 
						|
	}
 | 
						|
	if err := testEnv.Create(ctx, source); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ksSpec := &kustomizev1.KustomizationSpec{
 | 
						|
		Path:            opts.path,
 | 
						|
		TargetNamespace: name,
 | 
						|
		SourceRef: kustomizev1.CrossNamespaceSourceReference{
 | 
						|
			Kind:      sourcev1.GitRepositoryKind,
 | 
						|
			Name:      source.Name,
 | 
						|
			Namespace: source.Namespace,
 | 
						|
		},
 | 
						|
		Interval: metav1.Duration{
 | 
						|
			Duration: 1 * time.Minute,
 | 
						|
		},
 | 
						|
		Prune: true,
 | 
						|
	}
 | 
						|
	if opts.modifyKsSpec != nil {
 | 
						|
		opts.modifyKsSpec(ksSpec)
 | 
						|
	}
 | 
						|
	kustomization := &kustomizev1.Kustomization{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name},
 | 
						|
		Spec:       *ksSpec,
 | 
						|
	}
 | 
						|
 | 
						|
	return testEnv.Create(ctx, kustomization)
 | 
						|
}
 | 
						|
 | 
						|
func tearDownFluxConfig(ctx context.Context, name string) error {
 | 
						|
	var allErr []error
 | 
						|
 | 
						|
	source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
 | 
						|
	if err := testEnv.Delete(ctx, source); err != nil {
 | 
						|
		allErr = append(allErr, err)
 | 
						|
	}
 | 
						|
 | 
						|
	kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}}
 | 
						|
	if err := testEnv.Delete(ctx, kustomization); err != nil {
 | 
						|
		allErr = append(allErr, err)
 | 
						|
	}
 | 
						|
 | 
						|
	namespace := corev1.Namespace{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name: name,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	if err := testEnv.Delete(ctx, &namespace); err != nil {
 | 
						|
		allErr = append(allErr, err)
 | 
						|
	}
 | 
						|
 | 
						|
	return kerrors.NewAggregate(allErr)
 | 
						|
}
 | 
						|
 | 
						|
// getRepository and clones the git repository to the directory.
 | 
						|
func getRepository(ctx context.Context, dir, repoURL, branchName string, authOpts *git.AuthOptions) (*gogit.Client, error) {
 | 
						|
	c, err := gogit.NewClient(dir, authOpts, gogit.WithSingleBranch(false), gogit.WithDiskStorage())
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = c.Clone(ctx, repoURL, repository.CloneConfig{
 | 
						|
		CheckoutStrategy: repository.CheckoutStrategy{
 | 
						|
			Branch: branchName,
 | 
						|
		},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return c, nil
 | 
						|
}
 | 
						|
 | 
						|
// commitAndPushAll checks out to the specified branch, creates the files, commits and then pushes them to
 | 
						|
// the remote git repository.
 | 
						|
func commitAndPushAll(ctx context.Context, client *gogit.Client, files map[string]io.Reader, branchName string) error {
 | 
						|
	err := client.SwitchBranch(ctx, branchName)
 | 
						|
	if err != nil && !errors.Is(err, plumbing.ErrReferenceNotFound) {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.Commit(git.Commit{
 | 
						|
		Author: git.Signature{
 | 
						|
			Name:  git.DefaultPublicKeyAuthUser,
 | 
						|
			Email: "test@example.com",
 | 
						|
			When:  time.Now(),
 | 
						|
		},
 | 
						|
	}, repository.WithFiles(files))
 | 
						|
	if err != nil {
 | 
						|
		if errors.Is(err, git.ErrNoStagedFiles) {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = client.Push(ctx, repository.PushConfig{})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to push: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func createTagAndPush(ctx context.Context, client *gogit.Client, branchName, newTag string) error {
 | 
						|
	repo, err := extgogit.PlainOpen(client.Path())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ref, err := repo.Reference(plumbing.NewBranchReferenceName(branchName), false)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	tags, err := repo.TagObjects()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = tags.ForEach(func(tag *object.Tag) error {
 | 
						|
		if tag.Name == newTag {
 | 
						|
			err = repo.DeleteTag(tag.Name)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error deleting local tag: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete remote tag
 | 
						|
	if err := client.Push(ctx, repository.PushConfig{
 | 
						|
		Refspecs: []string{fmt.Sprintf(":refs/tags/%s", newTag)},
 | 
						|
		Force:    true,
 | 
						|
	}); err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {
 | 
						|
		return fmt.Errorf("unable to delete existing tag: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	sig := &object.Signature{
 | 
						|
		Name:  git.DefaultPublicKeyAuthUser,
 | 
						|
		Email: "test@example.com",
 | 
						|
		When:  time.Now(),
 | 
						|
	}
 | 
						|
	if _, err = repo.CreateTag(newTag, ref.Hash(), &extgogit.CreateTagOptions{
 | 
						|
		Tagger:  sig,
 | 
						|
		Message: "create tag",
 | 
						|
	}); err != nil {
 | 
						|
		return fmt.Errorf("unable to create tag: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return client.Push(ctx, repository.PushConfig{
 | 
						|
		Refspecs: []string{"refs/tags/*:refs/tags/*"},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func pushImagesFromURL(repoURL, imgURL string, tags []string) error {
 | 
						|
	img, err := crane.Pull(imgURL)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tag := range tags {
 | 
						|
		if err := crane.Push(img, fmt.Sprintf("%s:%s", repoURL, tag)); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func getTransportURL(urls gitUrl) string {
 | 
						|
	if cfg.defaultGitTransport == git.SSH {
 | 
						|
		return urls.ssh
 | 
						|
	}
 | 
						|
 | 
						|
	return urls.http
 | 
						|
}
 | 
						|
 | 
						|
func authOpts(repoURL string, authData map[string][]byte) (*git.AuthOptions, error) {
 | 
						|
	u, err := url.Parse(repoURL)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return git.NewAuthOptions(*u, authData)
 | 
						|
}
 | 
						|
 | 
						|
// checkReadyCondition checks for a Ready condition, it returns nil if the condition is true
 | 
						|
// or an error (with the message if the Ready condition is present).
 | 
						|
func checkReadyCondition(from conditions.Getter) error {
 | 
						|
	if conditions.IsReady(from) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	errMsg := fmt.Sprintf("object not ready")
 | 
						|
	readyMsg := conditions.GetMessage(from, meta.ReadyCondition)
 | 
						|
	if readyMsg != "" {
 | 
						|
		errMsg += ": " + readyMsg
 | 
						|
	}
 | 
						|
	return errors.New(errMsg)
 | 
						|
}
 |