Skip to content

Commit

Permalink
pass embedded cluster artifacts in upstream upgrade (#4549)
Browse files Browse the repository at this point in the history
  • Loading branch information
Craig O'Donnell authored Apr 16, 2024
1 parent bebc3e9 commit 3c24246
Show file tree
Hide file tree
Showing 23 changed files with 309 additions and 126 deletions.
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

0 comments on commit 3c24246

Please sign in to comment.