Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pass embedded cluster artifacts in upstream upgrade #4549

Merged
merged 9 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/kots/cli/admin-console-push-images.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func AdminPushImagesCmd() *cobra.Command {
}

if _, err := os.Stat(imageSource); err == nil {
_, err = image.TagAndPushImagesFromBundle(imageSource, *options)
err = image.TagAndPushImagesFromBundle(imageSource, *options)
if err != nil {
return errors.Wrap(err, "failed to push images")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/replicatedhq/embedded-cluster-kinds v1.1.2
github.com/replicatedhq/kotskinds v0.0.0-20240326213823-6a0ed11e7397
github.com/replicatedhq/kotskinds v0.0.0-20240416132840-4e646b87f7a1
github.com/replicatedhq/kurlkinds v1.5.0
github.com/replicatedhq/troubleshoot v0.87.0
github.com/replicatedhq/yaml/v3 v3.0.0-beta5-replicatedhq
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,12 @@ github.com/replicatedhq/embedded-cluster-kinds v1.1.2 h1:2ITzcUzh5uh0fsnfZsVHvkw
github.com/replicatedhq/embedded-cluster-kinds v1.1.2/go.mod h1:LheSDOgMngMRAbwAj0sVZUVv2ciKIVR2bYTMeOBGwlg=
github.com/replicatedhq/kotskinds v0.0.0-20240326213823-6a0ed11e7397 h1:JNuBcFH9D3Osyi+1QUwdvaAklEd6HXznqZDfpWlr73M=
github.com/replicatedhq/kotskinds v0.0.0-20240326213823-6a0ed11e7397/go.mod h1:QjhIUu3+OmHZ09u09j3FCoTt8F3BYtQglS+OLmftu9I=
github.com/replicatedhq/kotskinds v0.0.0-20240402213802-a6d75e97be70 h1:55fMr60YysSf+ac5zeW+xTIIJ8edUpAjorQyFn0iP2c=
github.com/replicatedhq/kotskinds v0.0.0-20240402213802-a6d75e97be70/go.mod h1:QjhIUu3+OmHZ09u09j3FCoTt8F3BYtQglS+OLmftu9I=
github.com/replicatedhq/kotskinds v0.0.0-20240415152931-2837245679f5 h1:SI1Ar5c6QWTm1IpPOO/CVv8dktdqd8KaQUEOeHEbhgM=
github.com/replicatedhq/kotskinds v0.0.0-20240415152931-2837245679f5/go.mod h1:QjhIUu3+OmHZ09u09j3FCoTt8F3BYtQglS+OLmftu9I=
github.com/replicatedhq/kotskinds v0.0.0-20240416132840-4e646b87f7a1 h1:+RvMZ646tQTRzWFZTy6mnmgWJZOLFu6B9PXv8tcIcFY=
github.com/replicatedhq/kotskinds v0.0.0-20240416132840-4e646b87f7a1/go.mod h1:QjhIUu3+OmHZ09u09j3FCoTt8F3BYtQglS+OLmftu9I=
github.com/replicatedhq/kurlkinds v1.5.0 h1:zZ0PKNeh4kXvSzVGkn62DKTo314GxhXg1TSB3azURMc=
github.com/replicatedhq/kurlkinds v1.5.0/go.mod h1:rUpBMdC81IhmJNCWMU/uRsMETv9P0xFoMvdSP/TAr5A=
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851 h1:eRlNDHxGfVkPCRXbA4BfQJvt5DHjFiTtWy3R/t4djyY=
Expand Down
32 changes: 6 additions & 26 deletions pkg/embeddedcluster/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"sort"
"time"

embeddedclusterv1beta1 "github.com/replicatedhq/embedded-cluster-kinds/apis/v1beta1"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -26,13 +24,6 @@ const configMapNamespace = "embedded-cluster"
// ErrNoInstallations is returned when no installation object is found in the cluster.
var ErrNoInstallations = fmt.Errorf("no installations found")

var (
chartsArtifactRegex = regexp.MustCompile(`\/embedded-cluster\/(charts\.tar\.gz):`)
imagesArtifactRegex = regexp.MustCompile(`\/embedded-cluster\/(images-.+\.tar):`)
binaryArtifactRegex = regexp.MustCompile(`\/embedded-cluster\/(embedded-cluster-.+):`)
metadataArtifactRegex = regexp.MustCompile(`\/embedded-cluster\/(version-metadata\.json):`)
)

// ReadConfigMap will read the Kurl config from a configmap
func ReadConfigMap(client kubernetes.Interface) (*corev1.ConfigMap, error) {
return client.CoreV1().ConfigMaps(configMapNamespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
Expand Down Expand Up @@ -113,27 +104,16 @@ func ClusterConfig(ctx context.Context) (*embeddedclusterv1beta1.ConfigSpec, err
}

func getArtifactsFromInstallation(installation kotsv1beta1.Installation, appSlug string) *embeddedclusterv1beta1.ArtifactsLocation {
if len(installation.Spec.EmbeddedClusterArtifacts) == 0 {
if installation.Spec.EmbeddedClusterArtifacts == nil {
return nil
}

artifacts := &embeddedclusterv1beta1.ArtifactsLocation{}
for _, artifact := range installation.Spec.EmbeddedClusterArtifacts {
switch {
case chartsArtifactRegex.MatchString(artifact):
artifacts.HelmCharts = artifact
case imagesArtifactRegex.MatchString(artifact):
artifacts.Images = artifact
case binaryArtifactRegex.MatchString(artifact):
artifacts.EmbeddedClusterBinary = artifact
case metadataArtifactRegex.MatchString(artifact):
artifacts.EmbeddedClusterMetadata = artifact
default:
logger.Warnf("unknown artifact in installation: %s", artifact)
}
return &embeddedclusterv1beta1.ArtifactsLocation{
EmbeddedClusterBinary: installation.Spec.EmbeddedClusterArtifacts.BinaryAmd64,
Images: installation.Spec.EmbeddedClusterArtifacts.ImagesAmd64,
HelmCharts: installation.Spec.EmbeddedClusterArtifacts.Charts,
EmbeddedClusterMetadata: installation.Spec.EmbeddedClusterArtifacts.Metadata,
}

return artifacts
}

// startClusterUpgrade will create a new installation with the provided config.
Expand Down
10 changes: 5 additions & 5 deletions pkg/embeddedcluster/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ func Test_getArtifactsFromInstallation(t *testing.T) {
args: args{
installation: kotsv1beta1.Installation{
Spec: kotsv1beta1.InstallationSpec{
EmbeddedClusterArtifacts: []string{
"onprem.registry.com/my-app/embedded-cluster/charts.tar.gz:v1",
"onprem.registry.com/my-app/embedded-cluster/images-amd64.tar:v1",
"onprem.registry.com/my-app/embedded-cluster/embedded-cluster-amd64:v1",
"onprem.registry.com/my-app/embedded-cluster/version-metadata.json:v1",
EmbeddedClusterArtifacts: &kotsv1beta1.EmbeddedClusterArtifacts{
Charts: "onprem.registry.com/my-app/embedded-cluster/charts.tar.gz:v1",
ImagesAmd64: "onprem.registry.com/my-app/embedded-cluster/images-amd64.tar:v1",
BinaryAmd64: "onprem.registry.com/my-app/embedded-cluster/embedded-cluster-amd64:v1",
Metadata: "onprem.registry.com/my-app/embedded-cluster/version-metadata.json:v1",
},
},
},
Expand Down
99 changes: 56 additions & 43 deletions pkg/image/airgap.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/replicatedhq/kots/pkg/imageutil"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
oras "oras.land/oras-go/v2"
orasfile "oras.land/oras-go/v2/content/file"
orasremote "oras.land/oras-go/v2/registry/remote"
Expand Down Expand Up @@ -108,9 +109,9 @@ func WriteProgressLine(progressWriter io.Writer, line string) {
}

// CopyAirgapImages pushes images found in the airgap bundle/airgap root to the configured registry.
func CopyAirgapImages(opts imagetypes.ProcessImageOptions, log *logger.CLILogger) (*imagetypes.CopyAirgapImagesResult, error) {
func CopyAirgapImages(opts imagetypes.ProcessImageOptions, log *logger.CLILogger) error {
if opts.AirgapBundle == "" {
return &imagetypes.CopyAirgapImagesResult{}, nil
return nil
}

pushOpts := imagetypes.PushImagesOptions{
Expand All @@ -125,27 +126,25 @@ func CopyAirgapImages(opts imagetypes.ProcessImageOptions, log *logger.CLILogger
LogForUI: true,
}

copyResult, err := TagAndPushImagesFromBundle(opts.AirgapBundle, pushOpts)
err := TagAndPushImagesFromBundle(opts.AirgapBundle, pushOpts)
if err != nil {
return nil, errors.Wrap(err, "failed to push images from bundle")
return errors.Wrap(err, "failed to push images from bundle")
}

return &imagetypes.CopyAirgapImagesResult{
EmbeddedClusterArtifacts: copyResult.EmbeddedClusterArtifacts,
}, nil
return nil
}

func TagAndPushImagesFromBundle(airgapBundle string, options imagetypes.PushImagesOptions) (*imagetypes.CopyAirgapImagesResult, error) {
func TagAndPushImagesFromBundle(airgapBundle string, options imagetypes.PushImagesOptions) error {
airgap, err := kotsutil.FindAirgapMetaInBundle(airgapBundle)
if err != nil {
return nil, errors.Wrap(err, "failed to find airgap meta")
return errors.Wrap(err, "failed to find airgap meta")
}

switch airgap.Spec.Format {
case dockertypes.FormatDockerRegistry:
extractedBundle, err := os.MkdirTemp("", "extracted-airgap-kots")
if err != nil {
return nil, errors.Wrap(err, "failed to create temp dir for unarchived airgap bundle")
return errors.Wrap(err, "failed to create temp dir for unarchived airgap bundle")
}
defer os.RemoveAll(extractedBundle)

Expand All @@ -155,34 +154,32 @@ func TagAndPushImagesFromBundle(airgapBundle string, options imagetypes.PushImag
},
}
if err := tarGz.Unarchive(airgapBundle, extractedBundle); err != nil {
return nil, errors.Wrap(err, "falied to unarchive airgap bundle")
return errors.Wrap(err, "falied to unarchive airgap bundle")
}
if err := PushImagesFromTempRegistry(extractedBundle, airgap.Spec.SavedImages, options); err != nil {
return nil, errors.Wrap(err, "failed to push images from docker registry bundle")
return errors.Wrap(err, "failed to push images from docker registry bundle")
}
case dockertypes.FormatDockerArchive, "":
if err := PushImagesFromDockerArchiveBundle(airgapBundle, options); err != nil {
return nil, errors.Wrap(err, "failed to push images from docker archive bundle")
return errors.Wrap(err, "failed to push images from docker archive bundle")
}
default:
return nil, errors.Errorf("Airgap bundle format '%s' is not supported", airgap.Spec.Format)
return errors.Errorf("Airgap bundle format '%s' is not supported", airgap.Spec.Format)
}

pushEmbeddedArtifactsOpts := imagetypes.PushEmbeddedClusterArtifactsOptions{
Registry: options.Registry,
Tag: imageutil.SanitizeTag(fmt.Sprintf("%s-%s-%s", airgap.Spec.ChannelID, airgap.Spec.UpdateCursor, airgap.Spec.VersionLabel)),
HTTPClient: orasretry.DefaultClient,
Registry: options.Registry,
ChannelID: airgap.Spec.ChannelID,
UpdateCursor: airgap.Spec.UpdateCursor,
VersionLabel: airgap.Spec.VersionLabel,
HTTPClient: orasretry.DefaultClient,
}
pushedArtifacts, err := PushEmbeddedClusterArtifacts(airgapBundle, pushEmbeddedArtifactsOpts)
err = PushEmbeddedClusterArtifacts(airgapBundle, airgap.Spec.EmbeddedClusterArtifacts, pushEmbeddedArtifactsOpts)
if err != nil {
return nil, errors.Wrap(err, "failed to push embedded cluster artifacts")
}

result := &imagetypes.CopyAirgapImagesResult{
EmbeddedClusterArtifacts: pushedArtifacts,
return errors.Wrap(err, "failed to push embedded cluster artifacts")
}

return result, nil
return nil
}

func PushImagesFromTempRegistry(airgapRootDir string, imageList []string, options imagetypes.PushImagesOptions) error {
Expand Down Expand Up @@ -684,70 +681,75 @@ func reportWriterWithProgress(imageInfos map[string]*imagetypes.ImageInfo, repor
return pipeWriter
}

func PushEmbeddedClusterArtifacts(airgapBundle string, opts imagetypes.PushEmbeddedClusterArtifactsOptions) ([]string, error) {
func PushEmbeddedClusterArtifacts(airgapBundle string, artifactsToPush *kotsv1beta1.EmbeddedClusterArtifacts, opts imagetypes.PushEmbeddedClusterArtifactsOptions) error {
tmpDir, err := os.MkdirTemp("", "embedded-cluster-artifacts")
if err != nil {
return nil, errors.Wrap(err, "failed to create temp directory")
return errors.Wrap(err, "failed to create temp directory")
}
defer os.RemoveAll(tmpDir)

fileReader, err := os.Open(airgapBundle)
if err != nil {
return nil, errors.Wrap(err, "failed to open file")
return errors.Wrap(err, "failed to open file")
}
defer fileReader.Close()

gzipReader, err := gzip.NewReader(fileReader)
if err != nil {
return nil, errors.Wrap(err, "failed to get new gzip reader")
return errors.Wrap(err, "failed to get new gzip reader")
}
defer gzipReader.Close()

var artifacts []string

tarReader := tar.NewReader(gzipReader)
pushedArtifacts := make([]string, 0)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrap(err, "failed to get read archive")
return errors.Wrap(err, "failed to get read archive")
}

if header.Typeflag != tar.TypeReg {
continue
}

if filepath.Dir(header.Name) != "embedded-cluster" {
if !shouldPushArtifact(header.Name, artifactsToPush) {
continue
}

dstFilePath := filepath.Join(tmpDir, header.Name)
if err := os.MkdirAll(filepath.Dir(dstFilePath), 0755); err != nil {
return nil, errors.Wrap(err, "failed to create path")
return errors.Wrap(err, "failed to create path")
}

dstFile, err := os.Create(dstFilePath)
if err != nil {
return nil, errors.Wrap(err, "failed to create file")
return errors.Wrap(err, "failed to create file")
}

if _, err := io.Copy(dstFile, tarReader); err != nil {
dstFile.Close()
return nil, errors.Wrap(err, "failed to copy file data")
return errors.Wrap(err, "failed to copy file data")
}

dstFile.Close()
artifacts = append(artifacts, dstFilePath)
}

for i, dstFilePath := range artifacts {
name := filepath.Base(dstFilePath)
repository := filepath.Join("embedded-cluster", imageutil.SanitizeRepo(name))
ociArtifactPath := imageutil.NewEmbeddedClusterOCIArtifactPath(dstFilePath, imageutil.EmbeddedClusterArtifactOCIPathOptions{
RegistryHost: opts.Registry.Endpoint,
RegistryNamespace: opts.Registry.Namespace,
ChannelID: opts.ChannelID,
UpdateCursor: opts.UpdateCursor,
VersionLabel: opts.VersionLabel,
})

artifactFile := imagetypes.OCIArtifactFile{
Name: name,
Name: ociArtifactPath.Name,
Path: dstFilePath,
MediaType: EmbeddedClusterMediaType,
}
Expand All @@ -756,20 +758,31 @@ func PushEmbeddedClusterArtifacts(airgapBundle string, opts imagetypes.PushEmbed
Files: []imagetypes.OCIArtifactFile{artifactFile},
ArtifactType: EmbeddedClusterArtifactType,
Registry: opts.Registry,
Repository: repository,
Tag: opts.Tag,
Repository: ociArtifactPath.Repository,
Tag: ociArtifactPath.Tag,
HTTPClient: opts.HTTPClient,
}

fmt.Printf("Pushing embedded cluster artifacts (%d/%d)\n", i+1, len(artifacts))
artifact := fmt.Sprintf("%s:%s", filepath.Join(opts.Registry.Endpoint, opts.Registry.Namespace, repository), opts.Tag)
if err := pushOCIArtifact(pushOCIArtifactOpts); err != nil {
return nil, errors.Wrapf(err, "failed to push oci artifact %s", name)
return errors.Wrapf(err, "failed to push oci artifact %s", ociArtifactPath.Name)
}
pushedArtifacts = append(pushedArtifacts, artifact)
}

return pushedArtifacts, nil
return nil
}

func shouldPushArtifact(artifactPath string, artifactsToPush *kotsv1beta1.EmbeddedClusterArtifacts) bool {
if artifactsToPush == nil {
return false
}

switch artifactPath {
case artifactsToPush.BinaryAmd64, artifactsToPush.Charts, artifactsToPush.ImagesAmd64, artifactsToPush.Metadata:
return true
default:
return false
}
}

func pushOCIArtifact(opts imagetypes.PushOCIArtifactOptions) error {
Expand Down
Loading
Loading