kindest

package
v0.0.0-...-8ce0486 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2023 License: Apache-2.0, MIT Imports: 75 Imported by: 0

Documentation

Index

Constants

View Source
const FILE_SYNC_HASH_LIMIT = 100 * 1000

Files over this size will be hashed asynchronously.

Variables

View Source
var DefaultTag = "latest"
View Source
var ErrMissingChartSource = fmt.Errorf("missing chart source")
View Source
var ErrMissingImageName = fmt.Errorf("missing image name")
View Source
var ErrMissingReleaseName = fmt.Errorf("missing release name")
View Source
var ErrModuleNotCached = fmt.Errorf("module is not cached")
View Source
var ErrMultipleChartSources = fmt.Errorf("multiple chart sources not allowed")
View Source
var ErrMultipleTestEnv = fmt.Errorf("multiple test environments defined")
View Source
var ErrNoEnvironment = fmt.Errorf("no environment in spec")
View Source
var ErrNoTestEnv = fmt.Errorf("no test environment")
View Source
var ErrNoTests = fmt.Errorf("no tests configured")

func (t *TestSpec) runKubernetes(

rootPath string,
options *TestOptions,
spec *KindestSpec,
restartImages []string,
log logger.Logger,
) error {
	isKind := options.Kind != "" || options.Transient
	var client *kubernetes.Clientset
	var kubeContext string
	image := sanitizeImageName(options.Repository, t.Build.Name, "latest")
	imagePullPolicy := corev1.PullAlways
	if isKind {
		cli, err := dockerclient.NewClientWithOpts(client.FromEnv)
		if err != nil {
			return err
		}
		name := options.Kind
		if name == "" {
			name = "test-" + uuid.New().String()[:8]
		}
		provider := cluster.NewProvider()
		exists := false
		if !options.Transient {
			clusters, err := provider.List()
			if err != nil {
				return err
			}
			for _, cluster := range clusters {
				if cluster == options.Kind {
					exists = true
					break
				}
			}
		}
		if exists {
			log.Info("Using existing kind cluster", zap.String("name", options.Kind))
		} else {
			log := log.With(
				zap.String("name", name),
				zap.Bool("transient", options.Transient),
			)
			log.Info("Creating cluster")
			ready := make(chan int, 1)
			go func() {
				start := time.Now()
				for {
					select {
					case <-time.After(5 * time.Second):
						log.Info("Still creating cluster", zap.String("elapsed", time.Since(start).String()))
					case <-ready:
						return
					}
				}
			}()
			kindConfig := generateKindConfig("kind-registry", 5000)
			err := provider.Create(name, cluster.CreateWithRawConfig([]byte(kindConfig)))
			ready <- 0
			if err != nil {
				return fmt.Errorf("create cluster: %v", err)
			}
			if options.Transient {
				defer func() {
					log.Info("Deleting transient cluster")
					if err := func() error {
						if err := provider.Delete(
							name,
							"",
						); err != nil {
							return err
						}
						return nil
					}(); err != nil {
						log.Error("Error cleaning up transient cluster", zap.String("message", err.Error()))
					} else {
						log.Info("Deleted transient cluster")
					}
				}()
			}
		}
		client, kubeContext, err = clientForKindCluster(name, provider)
		if err != nil {
			return err
		}
		if err := waitForCluster(client, log); err != nil {
			return err
		}
		if !options.SkipBuild {
			if options.NoRegistry {
				imagePullPolicy = corev1.PullNever
				images, err := getSpecImages(spec, rootPath)
				if err != nil {
					return err
				}
				images = append(images, image)
				if err := loadImagesOnCluster(
					images,
					name,
					provider,
					options.Concurrency,
					log,
				); err != nil {
					return err
				}
			} else {
				if err := registry.EnsureLocalRegistryRunning(cli, log); err != nil {
					return err
				}
				log := log.With(zap.String("image", image))
				log.Info("Pushing image to local registry")
				if err := cli.NetworkConnect(
					context.TODO(),
					"kind",
					"kind-registry",
					&networktypes.EndpointSettings{},
				); err != nil && !strings.Contains(err.Error(), "Error response from daemon: endpoint with name kind-registry already exists in network kind") {
					return err
				}
				resp, err := cli.ImagePush(
					context.TODO(),
					image,
					types.ImagePushOptions{
						RegistryAuth: "this_can_be_anything",
					},
				)
				if err != nil {
					return err
				}
				termFd, isTerm := term.GetFdInfo(os.Stderr)
				if err := jsonmessage.DisplayJSONMessagesStream(
					resp,
					os.Stderr,
					termFd,
					isTerm,
					nil,
				); err != nil {
					return fmt.Errorf("push: %v", err)
				}
				log.Info("Pushed image")
			}
		}
	} else if options.Context != "" {
		// Use existing kubernetes context from ~/.kube/config
		var err error
		kubeContext = options.Context
		client, _, err = util.ClientsetForContext(options.Context)
		if err != nil {
			return err
		}
		// TODO: push image to registry
	} else {
		// We didn't specify an existing cluster and we didn't
		// request a transient cluster. It's unclear where the
		// user is expecting these tests to run.
		return ErrUnknownCluster
	}

	start := time.Now()

	log.Debug("Checking RBAC...")
	if err := createTestRBAC(client, log); err != nil {
		return err
	}

	if err := applyManifests(
		kubeContext,
		rootPath,
		t.Env.Kubernetes.Resources,
	); err != nil {
		return err
	}
	if err := t.installCharts(
		rootPath,
		kubeContext,
		options,
		log,
	); err != nil {
		return err
	}

	//log.Info("Restarting deployments", zap.String("restartImages", fmt.Sprintf("%#v", restartImages)))
	//if err := restartDeployments(client, restartImages); err != nil {
	//	return err
	//}

	namespace := "default"
	pods := client.CoreV1().Pods(namespace)
	log = log.With(
		zap.String("t.Name", t.Name),
		zap.String("namespace", namespace),
		zap.String("image", image),
	)

	if err := deleteOldPods(pods, t.Name, log); err != nil {
		return err
	}

	// Wait for the rest of the the cluster to be Ready
	if err := waitForFullReady(client, log); err != nil {
		return err
	}

	log.Debug("Creating test pod")

	podName := t.Name + "-" + uuid.New().String()[:8]
	var env []corev1.EnvVar
	for _, v := range t.Variables {
		env = append(env, corev1.EnvVar{
			Name:  v.Name,
			Value: v.Value,
		})
	}
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      podName,
			Namespace: namespace,
			Labels: map[string]string{
				"kindest": t.Name,
			},
		},
		Spec: corev1.PodSpec{
			ServiceAccountName: "kindest-test",
			RestartPolicy:      corev1.RestartPolicyNever,
			Containers: []corev1.Container{{
				Name:            t.Name,
				Image:           image,
				ImagePullPolicy: imagePullPolicy,
				Command:         t.Build.Command,
				Env:             env,
			}},
		},
	}
	var err error
	if pod, err = pods.Create(
		context.TODO(),
		pod,
		metav1.CreateOptions{},
	); err != nil {
		return err
	}
	log.Debug("Created pod")
	if !options.Transient {
		defer func() {
			log.Warn("TODO: clean up pod")
		}()
	}
	timeout := 90 * time.Second
	delay := time.Second
	start = time.Now()
	scheduled := false
	for deadline := time.Now().Add(timeout); time.Now().Before(deadline); {
		pod, err = pods.Get(
			context.TODO(),
			podName,
			metav1.GetOptions{},
		)
		if err != nil {
			return err
		}
		switch pod.Status.Phase {
		case corev1.PodPending:
			if !scheduled {
				for _, condition := range pod.Status.Conditions {
					if condition.Status == "PodScheduled" {
						deadline = time.Now().Add(30 * time.Second)
						scheduled = true
					}
				}
			}
			for _, status := range pod.Status.ContainerStatuses {
				if status.State.Terminated != nil {
					if code := status.State.Terminated.ExitCode; code != 0 {
						return fmt.Errorf("pod failed with exit code '%d'", code)
					}
					return nil
				}
				if status.State.Waiting != nil {
					if strings.Contains(status.State.Waiting.Reason, "Err") {
						return fmt.Errorf("pod failed with '%s'", status.State.Waiting.Reason)
					}
				}
			}
			log.Info("Still waiting on pod",
				zap.String("phase", string(pod.Status.Phase)),
				zap.Bool("scheduled", scheduled),
				zap.String("elapsed", time.Since(start).String()),
				zap.String("timeout", timeout.String()))
			time.Sleep(delay)
			continue
		case corev1.PodRunning:
			fallthrough
		case corev1.PodSucceeded:
			fallthrough
		case corev1.PodFailed:
			for _, status := range pod.Status.ContainerStatuses {
				if status.State.Terminated != nil {
					if strings.Contains(status.State.Terminated.Reason, "Err") {
						return ErrTestFailed
					}
				}
			}
			req := pods.GetLogs(pod.Name, &corev1.PodLogOptions{
				Follow: true,
			})
			r, err := req.Stream(context.TODO())
			if err != nil {
				return err
			}
			rd := bufio.NewReader(r)
			for {
				message, err := rd.ReadString('\n')
				if err != nil {
					if err == io.EOF {
						break
					}
					return err
				}
				//log.Info("Test output", zap.String("message", message))
				//fmt.Println(message)
			}
		default:
			return fmt.Errorf("unexpected phase '%s'", pod.Status.Phase)
		}
		if pod, err = pods.Get(
			context.TODO(),
			pod.Name,
			metav1.GetOptions{},
		); err != nil {
			return err
		}
		if pod.Status.Phase == corev1.PodRunning {
			time.Sleep(delay)
			log.Warn("Log stream terminated prematurely. Retailing logs...")
			continue
		} else if pod.Status.Phase == corev1.PodSucceeded {
			return nil
		} else if pod.Status.Phase == corev1.PodFailed {
			// This should NOT happen. Container terminated status
			// should exist if the phase is Failed.
			return ErrTestFailed
		} else {
			return fmt.Errorf("unexpected pod phase '%s'", pod.Status.Phase)
		}
	}
	return ErrPodTimeout
}
View Source
var ErrPodTimeout = fmt.Errorf("pod timed out")
View Source
var ErrTestFailed = fmt.Errorf("test failed")
View Source
var ErrUnknownCluster = fmt.Errorf("unknown cluster")

Functions

func RegistryAuthFromEnv

func RegistryAuthFromEnv(imageName string) (*types.AuthConfig, error)

Types

type BuildArg

type BuildArg struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type BuildContext

type BuildContext map[string]Entity

func (BuildContext) Archive

func (c BuildContext) Archive() ([]byte, error)

func (BuildContext) Digest

func (c BuildContext) Digest(include *gogitignore.GitIgnore) (string, error)

type BuildOptions

type BuildOptions struct {
	NoCache    bool     `json:"nocache,omitempty" yaml:"nocache,omitempty"`
	Squash     bool     `json:"squash,omitempty" yaml:"squash,omitempty"`
	Tag        string   `json:"tag,omitempty" yaml:"tag,omitempty"`
	Builder    string   `json:"builder,omitempty" yaml:"builder,omitempty"`
	NoPush     bool     `json:"noPush,omitempty" yaml:"noPush,omitempty"`
	NoPushDeps bool     `json:"noPushDeps,omitempty" yaml:"noPushDeps,omitempty"`
	Repository string   `json:"repository,omitempty" yaml:"repository,omitempty"`
	Context    string   `json:"context,omitempty" yaml:"context,omitempty"`
	Namespace  string   `json:"namespace,omitempty" yaml:"namespace,omitempty"`
	Force      bool     `json:"force,omitempty"`     // If true, will always run docker build regardless of kindest digest
	SkipHooks  bool     `json:"skipHooks,omitempty"` // If true, skip before/after build hooks
	Verbose    bool     `json:"verbose,omitempty" yaml:"verbose,omitempty"`
	BuildArgs  []string `json:"buildArgs,omitempty" yaml:"buildArgs,omitempty"`
	Platform   string   `json:"platform,omitempty" yaml:"platform,omitempty"`
}

type BuildSpec

type BuildSpec struct {
	Name         string            `json:"name" yaml:"name"`
	Dockerfile   string            `json:"dockerfile,omitempty" yaml:"dockerfile,omitempty"`
	Context      string            `json:"context,omitempty" yaml:"context,omitempty"`
	BuildArgs    []*BuildArg       `json:"buildArgs,omitempty" yaml:"buildArgs,omitempty"`
	Target       string            `json:"target,omitempty" yaml:"target,omitempty"`
	DefaultTag   string            `json:"defaultTag,omitempty" yaml:"defaultTag,omitempty"`
	TagPrefix    string            `json:"tagPrefix,omitempty" yaml:"tagPrefix,omitempty"`
	TagSuffix    string            `json:"tagSuffix,omitempty" yaml:"tagSuffix,omitempty"`
	Command      []string          `json:"command,omitempty" yaml:"command,omitempty"`
	SkipPush     bool              `json:"skipPush,omitempty" yaml:"skipPush,omitempty"`
	NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"`
	Before       []Command         `json:"before,omitempty" yaml:"before,omitempty"`
	After        []Command         `json:"after,omitempty" yaml:"after,omitempty"`
}

func (*BuildSpec) DependsOnFiles

func (b *BuildSpec) DependsOnFiles(files []string, manifestPath string) (bool, error)

DependsOnFiles all inputs are absolute paths

func (*BuildSpec) GetBaseImage

func (b *BuildSpec) GetBaseImage(rootPath string) (string, error)

func (*BuildSpec) GetDockerfilePath

func (b *BuildSpec) GetDockerfilePath(rootPath string) string

func (*BuildSpec) ReadDockerfile

func (b *BuildSpec) ReadDockerfile(rootPath string) (string, error)

func (*BuildSpec) Verify

func (b *BuildSpec) Verify(manifestPath string, log logger.Logger) error

type BuildStatus

type BuildStatus int32
const (
	BuildStatusPending    BuildStatus = 0
	BuildStatusInProgress BuildStatus = 1
	BuildStatusFailed     BuildStatus = 2
	BuildStatusSucceeded  BuildStatus = 3
)

func (BuildStatus) String

func (b BuildStatus) String() string

type ChartSpec

type ChartSpec struct {
	ReleaseName    string                      `json:"releaseName" yaml:"releaseName"`
	Namespace      string                      `json:"namespace,omitempty" yaml:"namespace,omitempty"`
	Name           string                      `json:"name,omitempty" yaml:"name,omitempty"`
	RepoURL        string                      `json:"repoURL,omitempty" yaml:"repoURL,omitempty"`
	TargetRevision string                      `json:"targetRevision,omitempty" yaml:"targetRevision,omitempty"`
	Values         map[interface{}]interface{} `json:"values,omitempty" yaml:"values,omitempty"`
	ValuesFiles    []string                    `json:"valuesFiles,omitempty" yaml:"valuesFiles,omitempty"`
}

type Command

type Command struct {
	Name string   `json:"name" yaml:"name"`
	Args []string `json:"args" yaml:"args"`
}

type DeployOptions

type DeployOptions struct {
	Kind          string   `json:"kind"`
	KubeContext   string   `json:"kubeContext"`
	Repository    string   `json:"repository"`
	Tag           string   `json:"tag"`
	NoAutoRestart bool     `json:"noAutoRestart"`
	RestartImages []string `json:"restartImages"`
	Wait          bool     `json:"wait"`
	Verbose       bool     `json:"verbose,omitempty"`
	Force         bool     `json:"force,omitempty"`
}

type Directory

type Directory struct {
	Contents map[string]Entity
	// contains filtered or unexported fields
}

func (*Directory) Info

func (d *Directory) Info() os.FileInfo

type DockerEnvSpec

type DockerEnvSpec struct {
	Volumes []*DockerVolumeSpec `json:"volumes,omitempty"`
}

func (*DockerEnvSpec) Verify

func (d *DockerEnvSpec) Verify(manifestPath string) error

type DockerVolumeSpec

type DockerVolumeSpec struct {
	Type        string `json:"type,omitempty"`
	Source      string `json:"source,omitempty"`
	Target      string `json:"target,omitempty"`
	ReadOnly    bool   `json:"readOnly,omitempty"`
	Consistency string `json:"consistency,omitempty"`
}

type Entity

type Entity interface {
	Info() os.FileInfo
}

type EnvSpec

type EnvSpec struct {
	Kubernetes *KubernetesEnvSpec `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
	Docker     *DockerEnvSpec     `json:"docker,omitempty" yaml:"docker,omitempty"`
}

type File

type File struct {
	Content []byte
	// contains filtered or unexported fields
}

func (*File) Info

func (f *File) Info() os.FileInfo

type KindestSpec

type KindestSpec struct {
	Dependencies []string    `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
	Build        *BuildSpec  `json:"build" yaml:"build"`
	Env          EnvSpec     `json:"env,omitempty" yaml:"env,omitempty"`
	Test         []*TestSpec `json:"test,omitempty" yaml:"test,omitempty"`
}

func (*KindestSpec) RunTests

func (s *KindestSpec) RunTests(
	options *TestOptions,
	manifestPath string,
	p *Process,
	log logger.Logger,
) error

func (*KindestSpec) Verify

func (s *KindestSpec) Verify(manifestPath string, log logger.Logger) error

type KubernetesEnvSpec

type KubernetesEnvSpec struct {
	ImagePullSecret string                `json:"imagePullSecret,omitempty"`
	Resources       []string              `json:"resources,omitempty" yaml:"resources,omitempty"`
	Charts          map[string]*ChartSpec `json:"charts,omitempty" yaml:"charts,omitempty"`
}

func (*KubernetesEnvSpec) Verify

func (k *KubernetesEnvSpec) Verify(manifestPath string, log logger.Logger) error

type Module

type Module struct {
	Spec         *KindestSpec
	Path         string
	Dependencies []*Module //

	BuiltImages []string
	// contains filtered or unexported fields
}

func (*Module) Build

func (m *Module) Build(options *BuildOptions) (err error)

func (*Module) CachedDigest

func (m *Module) CachedDigest(resource string) (string, error)

func (*Module) DependsOnFiles

func (m *Module) DependsOnFiles(files []string) (bool, error)

func (*Module) Deploy

func (m *Module) Deploy(options *DeployOptions) (string, error)

func (*Module) Dir

func (m *Module) Dir() string

func (*Module) GetAffectedModules

func (m *Module) GetAffectedModules(files []string) ([]*Module, error)

func (*Module) HasEnvironment

func (m *Module) HasEnvironment() bool

func (*Module) ListImages

func (m *Module) ListImages() ([]string, error)

func (*Module) RestartContainers

func (m *Module) RestartContainers(restartImages []string, verbose bool, kubeContext string) error

func (*Module) RunTests

func (m *Module) RunTests(options *TestOptions, log logger.Logger) error

func (*Module) RunTests2

func (m *Module) RunTests2(options *TestOptions, log logger.Logger) error

func (*Module) Status

func (m *Module) Status() BuildStatus

func (*Module) UpdateResources

func (m *Module) UpdateResources(kubeContext string, verbose, force bool) error

func (*Module) WaitForCompletion

func (m *Module) WaitForCompletion() error

func (*Module) WaitForReady

func (m *Module) WaitForReady(kubeContext, repository, tag string) error

type Process

type Process struct {
	// contains filtered or unexported fields
}

func NewProcess

func NewProcess(concurrency int, log logger.Logger) *Process

func (*Process) Close

func (p *Process) Close()

func (*Process) GetModule

func (p *Process) GetModule(manifestPath string) (*Module, error)

func (*Process) GetModuleFromBuildSpec

func (p *Process) GetModuleFromBuildSpec(manifestPath string, b *BuildSpec) *Module

func (*Process) GetModuleFromTestSpec

func (p *Process) GetModuleFromTestSpec(manifestPath string, t *TestSpec) *Module

type TestOptions

type TestOptions struct {
	BuildOptions

	KubeContext   string `json:"kubeContext,omitempty"`
	Kind          string `json:"kind,omitempty"`
	Transient     bool   `json:"transient,omitempty"`
	Namespace     string `json:"namespace,omitempty"`
	SkipBuild     bool   `json:"skipBuild,omitempty"`
	SkipTestBuild bool   `json:"skipTestBuild,omitempty"`
	SkipDeploy    bool   `json:"skipDeploy,omitempty"`
	Timeout       string `json:"timeout,omitempty"`
}

type TestSpec

type TestSpec struct {
	Name           string      `json:"name"`
	Build          *BuildSpec  `json:"build"`
	Variables      []*Variable `json:"variables,omitempty" yaml:"variables,omitempty"`
	Env            EnvSpec     `json:"env,omitempty" yaml:"env,omitempty"`
	DefaultTimeout string      `json:"defaultTimeout,omitempty" yaml:"defaultTimeout,omitempty"`
}

func (*TestSpec) Run

func (t *TestSpec) Run(
	options *TestOptions,
	manifestPath string,
	p *Process,
	log logger.Logger,
) error

func (*TestSpec) Verify

func (t *TestSpec) Verify(manifestPath string, log logger.Logger) error

type Variable

type Variable struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type WaitingMessage

type WaitingMessage struct {
	// contains filtered or unexported fields
}

func NewWaitingMessage

func NewWaitingMessage(name string, delay time.Duration, log logger.Logger) *WaitingMessage

func (*WaitingMessage) Stop

func (w *WaitingMessage) Stop()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL