diff --git a/cmd/werf/bundle/copy/copy.go b/cmd/werf/bundle/copy/copy.go index a4fe6e32e6..8a9c80b000 100644 --- a/cmd/werf/bundle/copy/copy.go +++ b/cmd/werf/bundle/copy/copy.go @@ -153,8 +153,8 @@ func runCopy(ctx context.Context) error { return bundles.Copy(ctx, fromAddr, toAddr, bundles.CopyOptions{ BundlesRegistryClient: bundlesRegistryClient, - FromRegistry: fromRegistry, - ToRegistry: toRegistry, + FromRegistryClient: fromRegistry, + ToRegistryClient: toRegistry, }) }) } diff --git a/pkg/deploy/bundles/bundle_accessor.go b/pkg/deploy/bundles/bundle_accessor.go new file mode 100644 index 0000000000..a65140e48e --- /dev/null +++ b/pkg/deploy/bundles/bundle_accessor.go @@ -0,0 +1,35 @@ +package bundles + +import ( + "context" + "fmt" + + "helm.sh/helm/v3/pkg/chart" + + "github.com/werf/werf/pkg/docker_registry" +) + +type BundleAccessor interface { + ReadChart(ctx context.Context) (*chart.Chart, error) + WriteChart(ctx context.Context, ch *chart.Chart) error + + CopyTo(ctx context.Context, to BundleAccessor) error + CopyFromArchive(ctx context.Context, fromArchive *BundleArchive) error + CopyFromRemote(ctx context.Context, fromRemote *RemoteBundle) error +} + +type BundleAccessorOptions struct { + BundlesRegistryClient BundlesRegistryClient + RegistryClient docker_registry.Interface +} + +func NewBundleAccessor(addr *Addr, opts BundleAccessorOptions) BundleAccessor { + switch { + case addr.RegistryAddress != nil: + return NewRemoteBundle(addr.RegistryAddress, opts.BundlesRegistryClient, opts.RegistryClient) + case addr.ArchiveAddress != nil: + return NewBundleArchive(addr.ArchiveAddress.Path) + default: + panic(fmt.Sprintf("invalid address given %#v", addr)) + } +} diff --git a/pkg/deploy/bundles/bundle.go b/pkg/deploy/bundles/bundle_archive.go similarity index 69% rename from pkg/deploy/bundles/bundle.go rename to pkg/deploy/bundles/bundle_archive.go index 83d10c7b8b..ff7b99330e 100644 --- a/pkg/deploy/bundles/bundle.go +++ b/pkg/deploy/bundles/bundle_archive.go @@ -4,45 +4,37 @@ import ( "archive/tar" "bytes" "compress/gzip" + "context" "fmt" "io" "os" "time" "github.com/google/uuid" + "github.com/otiai10/copy" + "helm.sh/helm/v3/pkg/chart" - bundles_registry "github.com/werf/werf/pkg/deploy/bundles/registry" + "github.com/werf/logboek" + "github.com/werf/werf/pkg/image" ) const ( chartArchiveFileName = "chart.tar.gz" ) -type Bundle interface{} - -type BundleArchiveWriter interface { +type BundleWriter interface { Open() error WriteChartArchive(data []byte) error WriteImageArchive(imageTag string, data []byte) error Save() error } -type BundleArchiveReader interface { +type BundleReader interface { ReadChartArchive() ([]byte, error) GetImageArchiveOpener(imageTag string) *ImageArchiveOpener ReadImageArchive(imageTag string) (*ImageArchiveReadCloser, error) } -type RemoteBundle struct { - RegistryClient *bundles_registry.Client -} - -func NewRemoteBundle(registryClient *bundles_registry.Client) *RemoteBundle { - return &RemoteBundle{ - RegistryClient: registryClient, - } -} - type BundleArchive struct { Path string @@ -265,6 +257,97 @@ func (bundle *BundleArchive) ReadImageArchive(imageTag string) (*ImageArchiveRea } } +func (bundle *BundleArchive) ReadChart(ctx context.Context) (*chart.Chart, error) { + chartBytes, err := bundle.ReadChartArchive() + if err != nil { + return nil, fmt.Errorf("unable to read chart archive: %w", err) + } + + ch, err := BytesToChart(chartBytes) + if err != nil { + return nil, fmt.Errorf("unable to parse chart archive from bundle archive: %w", err) + } + + return ch, nil +} + +func (bundle *BundleArchive) WriteChart(ctx context.Context, ch *chart.Chart) error { + chartBytes, err := ChartToBytes(ch) + if err != nil { + return fmt.Errorf("unable to dump chart to archive: %w", err) + } + + if err := bundle.WriteChartArchive(chartBytes); err != nil { + return fmt.Errorf("unable to write chart archive into bundle archive: %w", err) + } + + return nil +} + +func (bundle *BundleArchive) CopyTo(ctx context.Context, to BundleAccessor) error { + return to.CopyFromArchive(ctx, bundle) +} + +func (bundle *BundleArchive) CopyFromArchive(ctx context.Context, fromArchive *BundleArchive) error { + if err := copy.Copy(fromArchive.Path, bundle.Path); err != nil { + return fmt.Errorf("unable to copy file %q to %q: %w", fromArchive.Path, bundle.Path, err) + } + return nil +} + +func (bundle *BundleArchive) CopyFromRemote(ctx context.Context, fromRemote *RemoteBundle) error { + ch, err := fromRemote.ReadChart(ctx) + if err != nil { + return fmt.Errorf("unable to read chart from remote bundle: %w", err) + } + + if err := bundle.Open(); err != nil { + return fmt.Errorf("unable to open target bundle archive: %w", err) + } + + if err := logboek.Context(ctx).LogProcess("Saving bundle %s into archive", fromRemote.RegistryAddress.FullName()).DoError(func() error { + return bundle.WriteChart(ctx, ch) + }); err != nil { + return err + } + + if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { + if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { + for imageName, v := range imageVals { + if imageRef, ok := v.(string); ok { + logboek.Context(ctx).Default().LogFDetails("Saving image %s\n", imageRef) + + _, tag := image.ParseRepositoryAndTag(imageRef) + + // TODO: maybe save into tmp file archive OR read resulting image size from the registry before pulling + imageBytes := bytes.NewBuffer(nil) + zipper := gzip.NewWriter(imageBytes) + + if err := fromRemote.RegistryClient.PullImageArchive(ctx, zipper, imageRef); err != nil { + return fmt.Errorf("error pulling image %q archive: %w", imageRef, err) + } + + if err := zipper.Close(); err != nil { + return fmt.Errorf("unable to close gzip writer: %w", err) + } + + if err := bundle.WriteImageArchive(tag, imageBytes.Bytes()); err != nil { + return fmt.Errorf("error writing image %q into bundle archive: %w", imageRef, err) + } + } else { + return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) + } + } + } + } + + if err := bundle.Save(); err != nil { + return fmt.Errorf("error saving destination bundle archive: %w", err) + } + + return nil +} + type ImageArchiveOpener struct { Archive *BundleArchive ImageTag string diff --git a/pkg/deploy/bundles/bundles_registry_client.go b/pkg/deploy/bundles/bundles_registry_client.go new file mode 100644 index 0000000000..18385d4fa6 --- /dev/null +++ b/pkg/deploy/bundles/bundles_registry_client.go @@ -0,0 +1,14 @@ +package bundles + +import ( + "helm.sh/helm/v3/pkg/chart" + + bundles_registry "github.com/werf/werf/pkg/deploy/bundles/registry" +) + +type BundlesRegistryClient interface { + PullChartToCache(ref *bundles_registry.Reference) error + LoadChart(ref *bundles_registry.Reference) (*chart.Chart, error) + SaveChart(ch *chart.Chart, ref *bundles_registry.Reference) error + PushChart(ref *bundles_registry.Reference) error +} diff --git a/pkg/deploy/bundles/copy.go b/pkg/deploy/bundles/copy.go index 3a8fb065b2..b4058b7e66 100644 --- a/pkg/deploy/bundles/copy.go +++ b/pkg/deploy/bundles/copy.go @@ -1,314 +1,25 @@ package bundles import ( - "bytes" - "compress/gzip" "context" - "fmt" - "strings" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/otiai10/copy" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "sigs.k8s.io/yaml" - - "github.com/werf/logboek" - bundles_registry "github.com/werf/werf/pkg/deploy/bundles/registry" "github.com/werf/werf/pkg/docker_registry" - "github.com/werf/werf/pkg/image" - "github.com/werf/werf/pkg/util" ) type CopyOptions struct { - BundlesRegistryClient *bundles_registry.Client - FromRegistry, ToRegistry docker_registry.Interface + BundlesRegistryClient BundlesRegistryClient + FromRegistryClient, ToRegistryClient docker_registry.Interface } func Copy(ctx context.Context, fromAddr, toAddr *Addr, opts CopyOptions) error { - switch { - case fromAddr.RegistryAddress != nil && toAddr.ArchiveAddress != nil: - return copyFromRegistryToArchive(ctx, fromAddr.RegistryAddress, toAddr.ArchiveAddress, opts.BundlesRegistryClient, opts.FromRegistry) - case fromAddr.ArchiveAddress != nil && toAddr.RegistryAddress != nil: - return copyFromArchiveToRegistry(ctx, fromAddr.ArchiveAddress, toAddr.RegistryAddress, opts.BundlesRegistryClient, opts.ToRegistry) - case fromAddr.RegistryAddress != nil && toAddr.RegistryAddress != nil: - return copyFromRegistryToRegistry(ctx, fromAddr.RegistryAddress, toAddr.RegistryAddress, opts.BundlesRegistryClient, opts.FromRegistry, opts.ToRegistry) - case fromAddr.ArchiveAddress != nil && toAddr.ArchiveAddress != nil: - return copyFromArchiveToArchive(ctx, fromAddr.ArchiveAddress, toAddr.ArchiveAddress) - default: - panic(fmt.Sprintf("unexpected from %v and to %v", fromAddr, toAddr)) - } -} - -func copyFromArchiveToArchive(ctx context.Context, from, to *ArchiveAddress) error { - logboek.Context(ctx).Debug().LogF("-- copyFromArchiveToArchive\n") - if err := copy.Copy(from.Path, to.Path); err != nil { - return err - } - return nil -} - -func copyFromArchiveToRegistry(ctx context.Context, from *ArchiveAddress, to *RegistryAddress, bundlesRegistryClient *bundles_registry.Client, toRegistry docker_registry.Interface) error { - logboek.Context(ctx).Debug().LogF("-- copyFromArchiveToRegistry\n") - - bundleArchive := NewBundleArchive(from.Path) - - chartBytes, err := bundleArchive.ReadChartArchive() - if err != nil { - return fmt.Errorf("unable to read chart from the bundle archive at %q: %w", bundleArchive.Path, err) - } - - ch, err := BytesToChart(chartBytes) - if err != nil { - return fmt.Errorf("unable to read chart from the bundle archive %q: %w", bundleArchive.Path, err) - } - - if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { - if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { - newImageVals := make(map[string]interface{}) - - for imageName, v := range imageVals { - if imageRef, ok := v.(string); ok { - ref, err := bundles_registry.ParseReference(imageRef) - if err != nil { - return fmt.Errorf("unable to parse bundle image %s: %w", imageRef, err) - } - ref.Repo = to.Repo - - if imageRef != ref.FullName() { - if err := logboek.Context(ctx).LogProcess("Copy image from bundle archive").DoError(func() error { - logboek.Context(ctx).Default().LogFDetails("Destination: %s\n", ref.FullName()) - - imageArchiveOpener := bundleArchive.GetImageArchiveOpener(ref.Tag) - - if err := toRegistry.PushImageArchive(ctx, imageArchiveOpener, ref.FullName()); err != nil { - return fmt.Errorf("error copying image from bundle archive %q into %q: %w", bundleArchive.Path, ref.FullName(), err) - } - - return nil - }); err != nil { - return err - } - } - - newImageVals[imageName] = ref.FullName() - } else { - return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) - } - } - - werfVals["image"] = newImageVals - } - - werfVals["repo"] = to.Repo - } - - ch.Metadata.Name = util.Reverse(strings.SplitN(util.Reverse(to.Repo), "/", 2)[0]) - - if err := logboek.Context(ctx).LogProcess("Saving bundle %s", to.FullName()).DoError(func() error { - if err := bundlesRegistryClient.SaveChart(ch, to.Reference); err != nil { - return fmt.Errorf("unable to save bundle %s to the local chart helm cache: %w", to.FullName(), err) - } - return nil - }); err != nil { - return err - } - - if err := logboek.Context(ctx).LogProcess("Pushing bundle %s", to.FullName()).DoError(func() error { - if err := bundlesRegistryClient.PushChart(to.Reference); err != nil { - return fmt.Errorf("unable to push bundle %s: %w", to.FullName(), err) - } - return nil - }); err != nil { - return err - } - - return nil -} - -func copyFromRegistryToArchive(ctx context.Context, from *RegistryAddress, to *ArchiveAddress, bundlesRegistryClient *bundles_registry.Client, fromRegistry docker_registry.Interface) error { - logboek.Context(ctx).Debug().LogF("-- copyFromRegistryToArchive\n") - - if err := logboek.Context(ctx).LogProcess("Pulling bundle %s", from.FullName()).DoError(func() error { - if err := bundlesRegistryClient.PullChartToCache(from.Reference); err != nil { - return fmt.Errorf("unable to pull bundle %s: %w", from.FullName(), err) - } - return nil - }); err != nil { - return err - } - - var ch *chart.Chart - if err := logboek.Context(ctx).LogProcess("Loading bundle %s", from.FullName()).DoError(func() error { - var err error - ch, err = bundlesRegistryClient.LoadChart(from.Reference) - if err != nil { - return fmt.Errorf("unable to load pulled bundle %s: %w", from.FullName(), err) - } - return nil - }); err != nil { - return err - } - - b := NewBundleArchive(to.Path) - - if err := b.Open(); err != nil { - return fmt.Errorf("unable to open target bundle archive: %w", err) - } - - if err := logboek.Context(ctx).LogProcess("Saving bundle %s into archive", from.FullName()).DoError(func() error { - chartBytes, err := ChartToBytes(ch) - if err != nil { - return fmt.Errorf("uanble to dump chart to bytes: %w", err) - } - - if err := b.WriteChartArchive(chartBytes); err != nil { - return fmt.Errorf("unable to write chart archive into bundle archive: %w", err) - } - - return nil - }); err != nil { - return err - } - - if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { - if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { - for imageName, v := range imageVals { - if imageRef, ok := v.(string); ok { - logboek.Context(ctx).Default().LogFDetails("Saving image %s\n", imageRef) - - _, tag := image.ParseRepositoryAndTag(imageRef) - - // TODO: maybe save into tmp file archive OR read resulting image size from the registry before pulling - imageBytes := bytes.NewBuffer(nil) - zipper := gzip.NewWriter(imageBytes) - - if err := fromRegistry.PullImageArchive(ctx, zipper, imageRef); err != nil { - return fmt.Errorf("error pulling image %q archive: %w", imageRef, err) - } - - if err := zipper.Close(); err != nil { - return fmt.Errorf("unable to close gzip writer: %w", err) - } - - if err := b.WriteImageArchive(tag, imageBytes.Bytes()); err != nil { - return fmt.Errorf("error writing image %q into bundle archive: %w", imageRef, err) - } - } else { - return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) - } - } - } - } - - if err := b.Save(); err != nil { - return fmt.Errorf("error saving destination bundle archive: %w", err) - } - - return nil -} - -func copyFromRegistryToRegistry(ctx context.Context, from, to *RegistryAddress, bundlesRegistryClient *bundles_registry.Client, fromRegistry, toRegistry docker_registry.Interface) error { - logboek.Context(ctx).Debug().LogF("-- copyFromRegistryToRegistry\n") - - if err := logboek.Context(ctx).LogProcess("Pulling bundle %s", from.FullName()).DoError(func() error { - if err := bundlesRegistryClient.PullChartToCache(from.Reference); err != nil { - return fmt.Errorf("unable to pull bundle %s: %w", from.FullName(), err) - } - return nil - }); err != nil { - return err - } - - var ch *chart.Chart - if err := logboek.Context(ctx).LogProcess("Loading bundle %s", from.FullName()).DoError(func() error { - var err error - ch, err = bundlesRegistryClient.LoadChart(from.Reference) - if err != nil { - return fmt.Errorf("unable to load pulled bundle %s: %w", from.FullName(), err) - } - return nil - }); err != nil { - return err - } - - { - d, err := yaml.Marshal(ch.Values) - logboek.Context(ctx).Debug().LogF("Values before change (%v):\n%s\n---\n", err, d) - } - - if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { - if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { - newImageVals := make(map[string]interface{}) - - for imageName, v := range imageVals { - if image, ok := v.(string); ok { - ref, err := bundles_registry.ParseReference(image) - if err != nil { - return fmt.Errorf("unable to parse bundle image %s: %w", image, err) - } - - ref.Repo = to.Repo - - // TODO: copy images in parallel - if image != ref.FullName() { - if err := logboek.Context(ctx).LogProcess("Copy image").DoError(func() error { - logboek.Context(ctx).Default().LogFDetails("Source: %s\n", image) - logboek.Context(ctx).Default().LogFDetails("Destination: %s\n", ref.FullName()) - - if err := fromRegistry.MutateAndPushImage(ctx, image, ref.FullName(), func(cfg v1.Config) (v1.Config, error) { return cfg, nil }); err != nil { - return fmt.Errorf("error copying image %s into %s: %w", image, ref.FullName(), err) - } - return nil - }); err != nil { - return err - } - } - - newImageVals[imageName] = ref.FullName() - } else { - return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) - } - } - - werfVals["image"] = newImageVals - } - - werfVals["repo"] = to.Repo - } - - valuesRaw, err := yaml.Marshal(ch.Values) - if err != nil { - return fmt.Errorf("unable to marshal changed bundle values: %w", err) - } - logboek.Context(ctx).Debug().LogF("Values after change (%v):\n%s\n---\n", err, valuesRaw) - - for _, f := range ch.Raw { - if f.Name == chartutil.ValuesfileName { - f.Data = valuesRaw - break - } - } - - ch.Metadata.Name = util.Reverse(strings.SplitN(util.Reverse(to.Repo), "/", 2)[0]) - - if err := logboek.Context(ctx).LogProcess("Saving bundle %s", to.FullName()).DoError(func() error { - if err := bundlesRegistryClient.SaveChart(ch, to.Reference); err != nil { - return fmt.Errorf("unable to save bundle %s to the local chart helm cache: %w", to.FullName(), err) - } - return nil - }); err != nil { - return err - } - - if err := logboek.Context(ctx).LogProcess("Pushing bundle %s", to.FullName()).DoError(func() error { - if err := bundlesRegistryClient.PushChart(to.Reference); err != nil { - return fmt.Errorf("unable to push bundle %s: %w", to.FullName(), err) - } - return nil - }); err != nil { - return err - } - - return nil + fromBundle := NewBundleAccessor(fromAddr, BundleAccessorOptions{ + BundlesRegistryClient: opts.BundlesRegistryClient, + RegistryClient: opts.FromRegistryClient, + }) + toBundle := NewBundleAccessor(toAddr, BundleAccessorOptions{ + BundlesRegistryClient: opts.BundlesRegistryClient, + RegistryClient: opts.ToRegistryClient, + }) + + return fromBundle.CopyTo(ctx, toBundle) } diff --git a/pkg/deploy/bundles/remote_bundle.go b/pkg/deploy/bundles/remote_bundle.go new file mode 100644 index 0000000000..465301b397 --- /dev/null +++ b/pkg/deploy/bundles/remote_bundle.go @@ -0,0 +1,210 @@ +package bundles + +import ( + "context" + "fmt" + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "sigs.k8s.io/yaml" + + "github.com/werf/logboek" + bundles_registry "github.com/werf/werf/pkg/deploy/bundles/registry" + "github.com/werf/werf/pkg/docker_registry" + "github.com/werf/werf/pkg/util" +) + +type RemoteBundle struct { + RegistryAddress *RegistryAddress + BundlesRegistryClient BundlesRegistryClient + RegistryClient docker_registry.Interface +} + +func NewRemoteBundle(registryAddress *RegistryAddress, bundlesRegistryClient BundlesRegistryClient, registryClient docker_registry.Interface) *RemoteBundle { + return &RemoteBundle{ + RegistryAddress: registryAddress, + BundlesRegistryClient: bundlesRegistryClient, + RegistryClient: registryClient, + } +} + +func (bundle *RemoteBundle) ReadChart(ctx context.Context) (*chart.Chart, error) { + if err := logboek.Context(ctx).LogProcess("Pulling bundle %s", bundle.RegistryAddress.FullName()).DoError(func() error { + if err := bundle.BundlesRegistryClient.PullChartToCache(bundle.RegistryAddress.Reference); err != nil { + return fmt.Errorf("unable to pull bundle %s: %w", bundle.RegistryAddress.FullName(), err) + } + return nil + }); err != nil { + return nil, err + } + + var ch *chart.Chart + if err := logboek.Context(ctx).LogProcess("Loading bundle %s", bundle.RegistryAddress.FullName()).DoError(func() error { + var err error + ch, err = bundle.BundlesRegistryClient.LoadChart(bundle.RegistryAddress.Reference) + if err != nil { + return fmt.Errorf("unable to load pulled bundle %s: %w", bundle.RegistryAddress.FullName(), err) + } + return nil + }); err != nil { + return nil, err + } + + return ch, nil +} + +func (bundle *RemoteBundle) WriteChart(ctx context.Context, ch *chart.Chart) error { + if err := logboek.Context(ctx).LogProcess("Saving bundle %s", bundle.RegistryAddress.FullName()).DoError(func() error { + if err := bundle.BundlesRegistryClient.SaveChart(ch, bundle.RegistryAddress.Reference); err != nil { + return fmt.Errorf("unable to save bundle %s to the local chart helm cache: %w", bundle.RegistryAddress.FullName(), err) + } + return nil + }); err != nil { + return err + } + + if err := logboek.Context(ctx).LogProcess("Pushing bundle %s", bundle.RegistryAddress.FullName()).DoError(func() error { + if err := bundle.BundlesRegistryClient.PushChart(bundle.RegistryAddress.Reference); err != nil { + return fmt.Errorf("unable to push bundle %s: %w", bundle.RegistryAddress.FullName(), err) + } + return nil + }); err != nil { + return err + } + + return nil +} + +func (bundle *RemoteBundle) CopyTo(ctx context.Context, to BundleAccessor) error { + return to.CopyFromRemote(ctx, bundle) +} + +func (bundle *RemoteBundle) CopyFromArchive(ctx context.Context, fromArchive *BundleArchive) error { + ch, err := fromArchive.ReadChart(ctx) + if err != nil { + return fmt.Errorf("unable to read chart from the bundle archive %q: %w", fromArchive.Path, err) + } + + if err := logboek.Context(ctx).LogProcess("Copy images from bundle archive").DoError(func() error { + if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { + if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { + newImageVals := make(map[string]interface{}) + + for imageName, v := range imageVals { + if imageRef, ok := v.(string); ok { + ref, err := bundles_registry.ParseReference(imageRef) + if err != nil { + return fmt.Errorf("unable to parse bundle image %s: %w", imageRef, err) + } + ref.Repo = bundle.RegistryAddress.Repo + + if imageRef != ref.FullName() { + logboek.Context(ctx).Default().LogFDetails("Image: %s\n", ref.FullName()) + + imageArchiveOpener := fromArchive.GetImageArchiveOpener(ref.Tag) + + if err := bundle.RegistryClient.PushImageArchive(ctx, imageArchiveOpener, ref.FullName()); err != nil { + return fmt.Errorf("error copying image from bundle archive %q into %q: %w", fromArchive.Path, ref.FullName(), err) + } + } + + newImageVals[imageName] = ref.FullName() + } else { + return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) + } + } + + werfVals["image"] = newImageVals + } + + werfVals["repo"] = bundle.RegistryAddress.Repo + } + + return nil + }); err != nil { + return err + } + + ch.Metadata.Name = util.Reverse(strings.SplitN(util.Reverse(bundle.RegistryAddress.Repo), "/", 2)[0]) + + if err := bundle.WriteChart(ctx, ch); err != nil { + return fmt.Errorf("unable to write chart to remote bundle: %w", err) + } + + return nil +} + +func (bundle *RemoteBundle) CopyFromRemote(ctx context.Context, fromRemote *RemoteBundle) error { + ch, err := fromRemote.ReadChart(ctx) + if err != nil { + return fmt.Errorf("unable to read chart from source remote bundle: %w", err) + } + + { + d, err := yaml.Marshal(ch.Values) + logboek.Context(ctx).Debug().LogF("Values before change (%v):\n%s\n---\n", err, d) + } + + if err := logboek.Context(ctx).LogProcess("Copy images from remote bundle").DoError(func() error { + if werfVals, ok := ch.Values["werf"].(map[string]interface{}); ok { + if imageVals, ok := werfVals["image"].(map[string]interface{}); ok { + newImageVals := make(map[string]interface{}) + + for imageName, v := range imageVals { + if image, ok := v.(string); ok { + ref, err := bundles_registry.ParseReference(image) + if err != nil { + return fmt.Errorf("unable to parse bundle image %s: %w", image, err) + } + + ref.Repo = bundle.RegistryAddress.Repo + + // TODO: copy images in parallel + if image != ref.FullName() { + logboek.Context(ctx).Default().LogFDetails("Source: %s\n", image) + logboek.Context(ctx).Default().LogFDetails("Destination: %s\n", ref.FullName()) + + if err := fromRemote.RegistryClient.MutateAndPushImage(ctx, image, ref.FullName(), func(cfg v1.Config) (v1.Config, error) { return cfg, nil }); err != nil { + return fmt.Errorf("error copying image %s into %s: %w", image, ref.FullName(), err) + } + } + + newImageVals[imageName] = ref.FullName() + } else { + return fmt.Errorf("unexpected value .Values.werf.image.%s=%v", imageName, v) + } + } + + werfVals["image"] = newImageVals + } + + werfVals["repo"] = bundle.RegistryAddress.Repo + } + return nil + }); err != nil { + return err + } + + valuesRaw, err := yaml.Marshal(ch.Values) + if err != nil { + return fmt.Errorf("unable to marshal changed bundle values: %w", err) + } + logboek.Context(ctx).Debug().LogF("Values after change (%v):\n%s\n---\n", err, valuesRaw) + + for _, f := range ch.Raw { + if f.Name == chartutil.ValuesfileName { + f.Data = valuesRaw + break + } + } + + ch.Metadata.Name = util.Reverse(strings.SplitN(util.Reverse(bundle.RegistryAddress.Repo), "/", 2)[0]) + + if err := bundle.WriteChart(ctx, ch); err != nil { + return fmt.Errorf("unable to write chart to destination remote bundle: %w", err) + } + + return nil +}