diff --git a/.gitignore b/.gitignore
index cad84d1f0..e94a54334 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ v2/cmd/mirror/logs/
v2/cmd/mirror/working-dir/
v2/launch.json
v2/logs/
+v2/tests/results
pkg/image/testdata/v2/single_manifest/manifests/oc-mirror
olm_artifacts/
diff --git a/pkg/api/v1alpha2/types_include_config.go b/pkg/api/v1alpha2/types_include_config.go
index 5169305dd..26b88e7e4 100644
--- a/pkg/api/v1alpha2/types_include_config.go
+++ b/pkg/api/v1alpha2/types_include_config.go
@@ -27,6 +27,13 @@ type IncludePackage struct {
// All channels containing these bundles are parsed for an upgrade graph.
IncludeBundle `json:",inline"`
+
+ // New field added due to the following filed cases for oc-mirror
+ // - CASE03657982
+ // - CASE03655018
+ // - CASE03676821
+ // Ability to override default channel.
+ DefaultChannel string `json:"defaultChannel,omitempty"`
}
// IncludeChannel contains a name (required) and versions (optional)
@@ -66,7 +73,7 @@ func (ic *IncludeConfig) ConvertToDiffIncludeConfig() (dic diff.DiffIncludeConfi
return dic, fmt.Errorf("package %s: %v", pkg.Name, err)
}
- dpkg := diff.DiffIncludePackage{Name: pkg.Name}
+ dpkg := diff.DiffIncludePackage{Name: pkg.Name, DefaultChannel: pkg.DefaultChannel}
switch {
case pkg.MinVersion != "" && pkg.MaxVersion != "":
dpkg.Range = fmt.Sprintf(">=%s <=%s", pkg.MinVersion, pkg.MaxVersion)
diff --git a/pkg/cli/mirror/mirror.go b/pkg/cli/mirror/mirror.go
index 72ab3a869..d37f68915 100644
--- a/pkg/cli/mirror/mirror.go
+++ b/pkg/cli/mirror/mirror.go
@@ -263,11 +263,33 @@ func (o *MirrorOptions) Validate() error {
if err != nil {
return fmt.Errorf("unable to read the configuration file provided with --config: %v", err)
}
+
for _, op := range cfg.Mirror.Operators {
if op.IsFBCOCI() {
break
}
}
+
+ // check for defaultChannel
+ for _, op := range cfg.Mirror.Operators {
+ for _, pkg := range op.Packages {
+ if len(pkg.DefaultChannel) > 0 {
+ valid := false
+ for _, ch := range pkg.Channels {
+ // check that it's set in the channel stanza
+ if pkg.DefaultChannel == ch.Name {
+ valid = true
+ break
+ }
+ }
+ if !valid {
+ // if we get here it means that the channel has not been set with the same value as defaultChannel
+ return fmt.Errorf("defaultChannel has been set with '%s', please ensure that '%s' is declared in the channels section for the package '%s' in the config ", pkg.DefaultChannel, pkg.DefaultChannel, pkg.Name)
+ }
+ }
+ }
+ }
+
}
if o.SkipPruning {
diff --git a/pkg/operator/diff/diff.go b/pkg/operator/diff/diff.go
index 79a9beef3..fe7be1f69 100644
--- a/pkg/operator/diff/diff.go
+++ b/pkg/operator/diff/diff.go
@@ -128,6 +128,13 @@ type DiffIncludePackage struct {
// and cannot combined with `Range` in `DiffIncludeChannel`.
// Range setting is mutually exclusive with channel versions/bundles/range settings.
Range string `json:"range,omitempty" yaml:"range,omitempty"`
+ // New field added due to the following filed cases for oc-mirror
+ // - CASE03657982
+ // - CASE03655018
+ // - CASE03676821
+ // - OCPBUGS-385
+ // Ability to override default channel.
+ DefaultChannel string `json:"defaultChannel,omitempty" yaml:"defaultChannel,omitempty"`
}
// DiffIncludeChannel contains a name (required) and versions (optional)
@@ -201,6 +208,7 @@ func convertIncludeConfigToIncluder(c DiffIncludeConfig) (includer diffInternal.
for pkgI, cpkg := range c.Packages {
pkg := &includer.Packages[pkgI]
pkg.Name = cpkg.Name
+ pkg.DefaultChannel = cpkg.DefaultChannel
pkg.AllChannels.Versions = cpkg.Versions
pkg.AllChannels.Bundles = cpkg.Bundles
if cpkg.Range != "" {
diff --git a/pkg/operator/diff/internal/diff.go b/pkg/operator/diff/internal/diff.go
index a23659a18..942dd4578 100644
--- a/pkg/operator/diff/internal/diff.go
+++ b/pkg/operator/diff/internal/diff.go
@@ -174,7 +174,6 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error)
}
}
-
}
if !g.SkipDependencies {
@@ -185,22 +184,40 @@ func (g *DiffGenerator) Run(oldModel, newModel model.Model) (model.Model, error)
}
// Default channel may not have been copied, so set it to the new default channel here.
- for _, outputPkg := range outputModel {
+ for idx, outputPkg := range outputModel {
+ overrideSet := false
newPkg, found := newModel[outputPkg.Name]
if !found {
return nil, fmt.Errorf("package %s not present in the diff new model", outputPkg.Name)
}
var outputHasDefault bool
outputPkg.DefaultChannel, outputHasDefault = outputPkg.Channels[newPkg.DefaultChannel.Name]
- if !outputHasDefault {
- // Set the defaultChannel using the priority of a channel when the default got filtered out
- // If no channels with the Priority property, raise an error
- if err := setDefaultChannel(outputPkg, newPkg.DefaultChannel.Name); err != nil {
- return nil, err
+ if !outputHasDefault && !overrideSet {
+ // OCPBUGS-385
+ // check if selectedDefaultChannel is the same as the channel being looked at
+ // if yes the then set it, this ensures we select the correct override for the selected
+ // package
+ for _, pkg := range g.Includer.Packages {
+ // check if the selectedDefaultChannel is valid
+ if len(pkg.DefaultChannel) > 0 {
+ overrideDefaultChannel, isValid := newPkg.Channels[pkg.DefaultChannel]
+ if pkg.Name == newPkg.Name && isValid {
+ outputModel[idx].DefaultChannel = overrideDefaultChannel
+ overrideSet = true
+ // no use continuing
+ break
+ }
+ }
+ }
+ if !overrideSet {
+ // Set the defaultChannel using the priority of a channel when the default got filtered out
+ // If no channels with the Priority property, raise an error
+ if err := setDefaultChannel(outputPkg, newPkg.DefaultChannel.Name); err != nil {
+ return nil, err
+ }
}
}
}
-
return outputModel, nil
}
@@ -262,7 +279,7 @@ This can be resolved by one of the following changes:
3) by changing the ImageSetConfiguration to filter channels or packages in such a way that it will include a package version that exists in the current default channel`)
// include a short message that does not mention any of the above to keep things simple
- return fmt.Errorf("the current default channel %q for package %q could not be determined... ensure that your ImageSetConfiguration filtering criteria results in a package version that exists in the current default channel", newPackageDefaultChannelName, outputPkg.Name)
+ return fmt.Errorf("the current default channel %q for package %q could not be determined... ensure that your ImageSetConfiguration filtering criteria results in a package version that exists in the current default channel or use the 'defaultChannel' field ", newPackageDefaultChannelName, outputPkg.Name)
}
// pruneOldFromNewPackage prune any bundles and channels from newPkg that
diff --git a/pkg/operator/diff/internal/diff_include.go b/pkg/operator/diff/internal/diff_include.go
index 66acbc22e..60a07f451 100644
--- a/pkg/operator/diff/internal/diff_include.go
+++ b/pkg/operator/diff/internal/diff_include.go
@@ -51,6 +51,12 @@ type DiffIncludePackage struct {
// HeadsOnly is the mode that selects the head of the channels only.
// This setting will be overridden by any versions or bundles in the channels.
HeadsOnly bool
+ // New field added due to the following filed cases for oc-mirror
+ // - CASE03657982
+ // - CASE03655018
+ // - CASE03676821
+ // ability to override default channel
+ DefaultChannel string `json:"defaultChannel,omitempty"`
}
// DiffIncludeChannel specifies a channel, and optionally bundles and bundle versions
diff --git a/pkg/operator/diff/internal/diff_test.go b/pkg/operator/diff/internal/diff_test.go
index a50b997d9..0fab0dad1 100644
--- a/pkg/operator/diff/internal/diff_test.go
+++ b/pkg/operator/diff/internal/diff_test.go
@@ -3805,7 +3805,108 @@ func TestSetDefaultChannelRange(t *testing.T) {
property.MustBuildPackage("ibm-mq", "1.7.0"),
},
},
+ },
+ },
+ },
+
+ {
+ name: "ibm-mq-test/Valid/OverrideDefaultChannel",
+ oldCfg: declcfg.DeclarativeConfig{},
+ newCfg: declcfg.DeclarativeConfig{
+ Packages: []declcfg.Package{
+ {Schema: declcfg.SchemaPackage, Name: "ibm-mq", DefaultChannel: "v1.8"},
+ },
+ Channels: []declcfg.Channel{
+ {Schema: declcfg.SchemaChannel, Name: "v1.8", Package: "ibm-mq", Entries: []declcfg.ChannelEntry{
+ {Name: "ibm-mq.v1.8.1"}},
+ Properties: []property.Property{
+ property.MustBuildChannelPriority("v1.8", 3),
+ },
+ },
+ {Schema: declcfg.SchemaChannel, Name: "v1.7", Package: "ibm-mq", Entries: []declcfg.ChannelEntry{
+ {Name: "ibm-mq.v1.7.0"}},
+ Properties: []property.Property{
+ property.MustBuildChannelPriority("v1.7", 1),
+ },
+ },
+ {Schema: declcfg.SchemaChannel, Name: "v1.6", Package: "ibm-mq", Entries: []declcfg.ChannelEntry{
+ {Name: "ibm-mq.v1.6.0"}},
+ Properties: []property.Property{
+ property.MustBuildChannelPriority("v1.6", 2),
+ },
+ },
+ },
+ Bundles: []declcfg.Bundle{
+ {
+ Schema: declcfg.SchemaBundle,
+ Name: "ibm-mq.v1.6.0",
+ Package: "ibm-mq",
+ Image: "reg/ibm-mq:latest",
+ Properties: []property.Property{
+ property.MustBuildPackage("ibm-mq", "1.6.0"),
+ },
+ },
+ {
+ Schema: declcfg.SchemaBundle,
+ Name: "ibm-mq.v1.7.0",
+ Package: "ibm-mq",
+ Image: "reg/ibm-mq:latest",
+ Properties: []property.Property{
+ property.MustBuildPackage("ibm-mq", "1.7.0"),
+ },
+ },
+ {
+ Schema: declcfg.SchemaBundle,
+ Name: "ibm-mq.v1.8.1",
+ Package: "ibm-mq",
+ Image: "reg/ibm-mq:latest",
+ Properties: []property.Property{
+ property.MustBuildPackage("ibm-mq", "1.8.1"),
+ },
+ },
}},
+ g: &DiffGenerator{
+ IncludeAdditively: false,
+ HeadsOnly: false,
+ SkipDependencies: true,
+ Includer: DiffIncluder{
+ Packages: []DiffIncludePackage{
+ {
+ Name: "ibm-mq",
+ Channels: []DiffIncludeChannel{
+ {
+ Name: "v1.7",
+ },
+ },
+ DefaultChannel: "v1.7",
+ },
+ },
+ },
+ },
+ expCfg: declcfg.DeclarativeConfig{
+ Packages: []declcfg.Package{
+ {Schema: declcfg.SchemaPackage, Name: "ibm-mq", DefaultChannel: "v1.7"},
+ },
+ Channels: []declcfg.Channel{
+ {Schema: declcfg.SchemaChannel, Name: "v1.7", Package: "ibm-mq", Entries: []declcfg.ChannelEntry{
+ {Name: "ibm-mq.v1.7.0"}},
+ Properties: []property.Property{
+ property.MustBuildChannelPriority("v1.7", 1),
+ },
+ },
+ },
+ Bundles: []declcfg.Bundle{
+ {
+ Schema: declcfg.SchemaBundle,
+ Name: "ibm-mq.v1.7.0",
+ Package: "ibm-mq",
+ Image: "reg/ibm-mq:latest",
+ Properties: []property.Property{
+ property.MustBuildPackage("ibm-mq", "1.7.0"),
+ },
+ },
+ },
+ },
},
}
@@ -3815,9 +3916,6 @@ func TestSetDefaultChannelRange(t *testing.T) {
s.assertion = require.NoError
}
- //oldModel, err := declcfg.ConvertToModel(s.oldCfg)
- //require.NoError(t, err)
-
newModel, err := declcfg.ConvertToModel(s.newCfg)
require.NoError(t, err)
@@ -3826,7 +3924,6 @@ func TestSetDefaultChannelRange(t *testing.T) {
if err := outputModel.Validate(); err != nil {
fmt.Println(err)
- //return nil, err
}
outputCfg := declcfg.ConvertFromModel(outputModel)
diff --git a/v2/pkg/mirror/unshare.go b/v2/pkg/mirror/unshare.go
index 8daf678d6..1f219a34a 100644
--- a/v2/pkg/mirror/unshare.go
+++ b/v2/pkg/mirror/unshare.go
@@ -1,4 +1,4 @@
-//go: build !windows
+// go: build !windows
// build !windows
package mirror
diff --git a/v2/tests/results/cover.html b/v2/tests/results/cover.html
deleted file mode 100644
index 4313d844c..000000000
--- a/v2/tests/results/cover.html
+++ /dev/null
@@ -1,3727 +0,0 @@
-
-
-
-
-
-
-
package additional
-
-import (
- "context"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha3"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/manifest"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
-)
-
-const (
- indexJson string = "index.json"
- operatorImageExtractDir string = "hold-operator"
- workingDir string = "working-dir/"
- dockerProtocol string = "docker://"
- ociProtocol string = "oci://"
- ociProtocolTrimmed string = "oci:"
- additionalImagesDir string = "additional-images"
- blobsDir string = "/blobs/sha256/"
- diskToMirror string = "diskToMirror"
- mirrorToDisk string = "mirrorToDisk"
- errMsg string = "[AdditionalImagesCollector] %v "
- logsFile string = "logs/additional-images.log"
-)
-
-type CollectorInterface interface {
- AdditionalImagesCollector(ctx context.Context) ([]string, error)
-}
-
-func New(log clog.PluggableLoggerInterface,
- config v1alpha2.ImageSetConfiguration,
- opts mirror.CopyOptions,
- mirror mirror.MirrorInterface,
- manifest manifest.ManifestInterface,
-) CollectorInterface {
- return &Collector{Log: log, Config: config, Opts: opts, Mirror: mirror, Manifest: manifest}
-}
-
-type Collector struct {
- Log clog.PluggableLoggerInterface
- Mirror mirror.MirrorInterface
- Manifest manifest.ManifestInterface
- Config v1alpha2.ImageSetConfiguration
- Opts mirror.CopyOptions
-}
-
-// AdditionalImagesCollector - this looks into the additional images field
-// taking into account the mode we are in (mirrorToDisk, diskToMirror)
-// the image is downloaded in oci format
-func (o *Collector) AdditionalImagesCollector(ctx context.Context) ([]string, error) {
-
- var allImages []string
- if !strings.Contains(o.Opts.Destination, ociProtocol) && !strings.Contains(o.Opts.Destination, dockerProtocol) {
- return []string{}, fmt.Errorf(errMsg, "destination must use oci:// or docker:// prefix")
- }
-
- if o.Opts.Mode == mirrorToDisk {
- for _, img := range o.Config.ImageSetConfigurationSpec.Mirror.AdditionalImages {
- irs, err := customImageParser(img.Name)
- o.Log.Debug("%v", irs)
- if err != nil {
- return []string{}, nil
- }
- src := dockerProtocol + img.Name
- dest := ociProtocolTrimmed + o.Opts.Global.Dir + "/" + additionalImagesDir + "/" + irs.Component
- o.Log.Trace("source %s", src)
- o.Log.Trace("destination %s", dest)
- allImages = append(allImages, src+"*"+dest)
- }
- }
-
- if o.Opts.Mode == diskToMirror {
- regex, e := regexp.Compile(indexJson)
- if e != nil {
- o.Log.Error("%v", e)
- }
- e = filepath.Walk(workingDir+"/"+o.Opts.Global.From+"/"+additionalImagesDir, func(path string, info os.FileInfo, err error) error {
- if err == nil && regex.MatchString(info.Name()) {
- ns := strings.Split(filepath.Dir(path), additionalImagesDir)
- if len(ns) == 0 {
- return fmt.Errorf(errMsg+"%s", "no directory found for additional-images ", path)
- } else {
- name := strings.Split(ns[1], "/")
- if len(name) != 2 {
- return fmt.Errorf(errMsg+" %s ", "additional images name and related compents are incorrect", name)
- }
- src := strings.Trim(ociProtocol, "/") + ns[0] + additionalImagesDir + "/" + name[1]
- dest := o.Opts.Destination + "/" + name[1]
- allImages = append(allImages, src+"*"+dest)
- }
- }
- return nil
- })
- if e != nil {
- return []string{}, e
- }
- }
- return allImages, nil
-}
-
-// customImageParser - simple image string parser
-func customImageParser(image string) (*v1alpha3.ImageRefSchema, error) {
- var irs *v1alpha3.ImageRefSchema
- var component string
- parts := strings.Split(image, "/")
- if len(parts) < 3 {
- return irs, fmt.Errorf("[customImageParser] image url seems to be wrong %s ", image)
- }
- component = parts[2]
- if strings.Contains(parts[2], "@") {
- component = strings.Split(parts[2], "@")[0]
- }
- if strings.Contains(parts[2], ":") {
- component = strings.Split(parts[2], ":")[0]
- }
- irs = &v1alpha3.ImageRefSchema{Repository: parts[0], Namespace: parts[1], Component: component}
- return irs, nil
-}
-
-
-
package batch
-
-import (
- "bufio"
- "context"
- "fmt"
- "io"
- "os"
- "strconv"
- "strings"
- "sync"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha3"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/manifest"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
-)
-
-const (
- BATCH_SIZE int = 8
- logFile string = "logs/worker-{batch}.log"
-)
-
-type BatchInterface interface {
- Worker(ctx context.Context, images []string, opts mirror.CopyOptions) error
-}
-
-func New(log clog.PluggableLoggerInterface,
- mirror mirror.MirrorInterface,
- manifest manifest.ManifestInterface,
-) BatchInterface {
- return &Batch{Log: log, Mirror: mirror, Manifest: manifest}
-}
-
-type Batch struct {
- Log clog.PluggableLoggerInterface
- Mirror mirror.MirrorInterface
- Manifest manifest.ManifestInterface
-}
-
-type BatchSchema struct {
- Writer io.Writer
- CopyImages []v1alpha3.RelatedImage
- Items int
- Count int
- BatchSize int
- BatchIndex int
- Remainder int
-}
-
-// Worker - the main batch processor
-func (o *Batch) Worker(ctx context.Context, images []string, opts mirror.CopyOptions) error {
-
- var errArray []error
- var wg sync.WaitGroup
- var err error
-
- var b *BatchSchema
- imgs := len(images)
- if imgs < BATCH_SIZE {
- b = &BatchSchema{Items: imgs, Count: 1, BatchSize: imgs, BatchIndex: 0, Remainder: 0}
- } else {
- b = &BatchSchema{Items: imgs, Count: (imgs / BATCH_SIZE), BatchSize: BATCH_SIZE, Remainder: (imgs % BATCH_SIZE)}
- }
-
- o.Log.Info("images to mirror %d ", b.Items)
- o.Log.Info("batch count %d ", b.Count)
- o.Log.Info("batch index %d ", b.BatchIndex)
- o.Log.Info("batch size %d ", b.BatchSize)
- o.Log.Info("remainder size %d ", b.Remainder)
-
- f := make([]*os.File, b.Count)
- //f, err := make([]os.File)
- // prepare batching
- wg.Add(b.BatchSize)
- for i := 0; i < b.Count; i++ {
- // create a log file for each batch
- f[i], err = os.Create(strings.Replace(logFile, "{batch}", strconv.Itoa(i), -1))
- if err != nil {
- o.Log.Error("[Worker] %v", err)
- }
- writer := bufio.NewWriter(f[i])
- o.Log.Info(fmt.Sprintf("starting batch %d ", i))
- for x := 0; x < b.BatchSize; x++ {
- index := (i * b.BatchSize) + x
- hld := strings.Split(images[index], "*")
- if len(hld) == 0 {
- return fmt.Errorf("the source and destination selector is missing")
- }
- o.Log.Debug("destination %s ", hld[1])
- go func(ctx context.Context, src, dest string, opts *mirror.CopyOptions, writer bufio.Writer) {
- defer wg.Done()
- err := o.Mirror.Run(ctx, src, dest, "copy", opts, writer)
- if err != nil {
- errArray = append(errArray, err)
- }
- }(ctx, hld[0], hld[1], &opts, *writer)
- }
- wg.Wait()
- // rather than use defer Close we intentianally close the log files
- for _, f := range f {
- f.Close()
- }
- o.Log.Info("completed batch %d", i)
- if b.Count > 1 {
- wg.Add(BATCH_SIZE)
- }
- if len(errArray) > 0 {
- for _, err := range errArray {
- o.Log.Error("[Worker] errArray %v", err)
- }
- return fmt.Errorf("[Worker] error in batch - refer to console logs")
- }
- }
- if b.Remainder > 0 {
- // one level of simple recursion
- i := b.Count * BATCH_SIZE
- o.Log.Info("executing remainder [batch size of 1]")
- err := o.Worker(ctx, images[i:], opts)
- if err != nil {
- return err
- }
- // output the logs to console
- if !opts.Global.Quiet {
- consoleLogFromFile(o.Log)
- }
- o.Log.Info("[Worker] successfully completed all batches")
- }
- return nil
-}
-
-// consoleLogFromFile
-func consoleLogFromFile(log clog.PluggableLoggerInterface) {
- dir, _ := os.ReadDir("logs")
- for _, f := range dir {
- if strings.Contains(f.Name(), "worker") {
- log.Debug("[batch] %s ", f.Name())
- data, _ := os.ReadFile("logs/" + f.Name())
- lines := strings.Split(string(data), "\n")
- for _, s := range lines {
- if len(s) > 0 {
- // clean the line
- log.Debug("%s ", strings.ToLower(s))
- }
- }
- }
- }
-}
-
-
-
package services
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "k8s.io/kubectl/pkg/util/templates"
-
- "github.com/google/uuid"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/additional"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/batch"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/config"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/diff"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/manifest"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/operator"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/release"
- "github.com/spf13/cobra"
-)
-
-const (
- dockerProtocol string = "docker://"
- ociProtocol string = "oci://"
- diskToMirror string = "diskToMirror"
- mirrorToDisk string = "mirrorToDisk"
- releaseImageDir string = "release-images"
-)
-
-var (
- mirrorlongDesc = templates.LongDesc(
- `
- Create and publish user-configured mirrors with a declarative configuration input.
- used for authenticating to the registries.
-
- The podman location for credentials is also supported as a secondary location.
-
- 1. Destination prefix is docker:// - The current working directory will be used.
- 2. Destination prefix is oci:// - The destination directory specified will be used.
-
- `,
- )
- mirrorExamples = templates.Examples(
- `
- # Mirror to a directory
- oc-mirror oci:mirror --config mirror-config.yaml
- `,
- )
-)
-
-const (
- logsDir string = "logs/"
- workingDir string = "working-dir/"
- additionalImages string = "additional-images"
- releaseImageExtractDir string = "hold-release"
- operatorImageExtractDir string = "hold-operator"
-)
-
-type ExecutorSchema struct {
- Log clog.PluggableLoggerInterface
- Config v1alpha2.ImageSetConfiguration
- MetaData diff.SequenceSchema
- Opts mirror.CopyOptions
- Operator operator.CollectorInterface
- Release release.CollectorInterface
- AdditionalImages additional.CollectorInterface
- Mirror mirror.MirrorInterface
- Manifest manifest.ManifestInterface
- Batch batch.BatchInterface
- Diff diff.DiffInterface
-}
-
-// NewMirrorCmd - cobra entry point
-func NewMirrorCmd(log clog.PluggableLoggerInterface) *cobra.Command {
-
- global := &mirror.GlobalOptions{
- TlsVerify: false,
- InsecurePolicy: true,
- }
-
- flagSharedOpts, sharedOpts := mirror.SharedImageFlags()
- flagDepTLS, deprecatedTLSVerifyOpt := mirror.DeprecatedTLSVerifyFlags()
- flagSrcOpts, srcOpts := mirror.ImageFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "src-", "screds")
- flagDestOpts, destOpts := mirror.ImageDestFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds")
- flagRetryOpts, retryOpts := mirror.RetryFlags()
-
- opts := mirror.CopyOptions{
- Global: global,
- DeprecatedTLSVerify: deprecatedTLSVerifyOpt,
- SrcImage: srcOpts,
- DestImage: destOpts,
- RetryOpts: retryOpts,
- Dev: false,
- }
-
- ex := &ExecutorSchema{
- Log: log,
- Opts: opts,
- }
-
- cmd := &cobra.Command{
- Use: fmt.Sprintf("%v <destination type>:<destination location>", filepath.Base(os.Args[0])),
- Version: "v0.0.1",
- Short: "Manage mirrors per user configuration",
- Long: mirrorlongDesc,
- Example: mirrorExamples,
- Args: cobra.MinimumNArgs(1),
- SilenceErrors: false,
- SilenceUsage: false,
- Run: func(cmd *cobra.Command, args []string) {
- err := ex.Validate(args)
- if err != nil {
- log.Error("%v ", err)
- os.Exit(1)
- }
-
- ex.Complete(args)
-
- err = ex.Run(cmd, args)
- if err != nil {
- log.Error("%v ", err)
- os.Exit(1)
- }
- },
- }
-
- cmd.PersistentFlags().StringVar(&opts.Global.ConfigPath, "config", "", "Path to imageset configuration file")
- cmd.Flags().StringVar(&opts.Global.LogLevel, "loglevel", "info", "Log level one of (info, debug, trace, error)")
- cmd.Flags().StringVar(&opts.Global.Dir, "dir", "working-dir", "Assets directory")
- cmd.Flags().StringVar(&opts.Global.From, "from", "", "directory used when doing the oci: (mirrorToDisk) mode")
- cmd.Flags().BoolVarP(&opts.Global.Quiet, "quiet", "q", false, "enable detailed logging when copying images")
- cmd.Flags().BoolVarP(&opts.Global.Force, "force", "f", false, "force the copy and mirror functionality")
- cmd.Flags().AddFlagSet(&flagSharedOpts)
- cmd.Flags().AddFlagSet(&flagRetryOpts)
- cmd.Flags().AddFlagSet(&flagDepTLS)
- cmd.Flags().AddFlagSet(&flagSrcOpts)
- cmd.Flags().AddFlagSet(&flagDestOpts)
- return cmd
-}
-
-// Run - start the mirror functionality
-func (o *ExecutorSchema) Run(cmd *cobra.Command, args []string) error {
-
- // clean up logs directory
- os.RemoveAll(logsDir)
- // ensure working dir exists
- err := os.MkdirAll(workingDir, 0755)
- if err != nil {
- o.Log.Error(" %v ", err)
- return err
- }
-
- // create logs directory
- err = os.MkdirAll(logsDir, 0755)
- if err != nil {
- o.Log.Error(" %v ", err)
- return err
- }
-
- // create release cache dir
- o.Log.Trace("creating release cache directory %s ", o.Opts.Global.Dir+"/"+releaseImageExtractDir)
- err = os.MkdirAll(o.Opts.Global.Dir+"/"+releaseImageExtractDir, 0755)
- if err != nil {
- o.Log.Error(" %v ", err)
- return err
- }
-
- // create operator cache dir
- o.Log.Trace("creating operator cache directory %s ", o.Opts.Global.Dir+"/"+operatorImageExtractDir)
- err = os.MkdirAll(o.Opts.Global.Dir+"/"+operatorImageExtractDir, 0755)
- if err != nil {
- o.Log.Error(" %v ", err)
- return err
- }
-
- var allRelatedImages []string
-
- // check if we need to copy or mirror
- // check if there is a change if so then continue as normal else
- // report that there is nothing to do (all up to date)
- _, prevCfg, err := o.Diff.GetAllMetadata(o.Opts.Global.Dir)
- if err != nil {
- o.Log.Error("%v", err)
- }
- res, err := o.Diff.CheckDiff(prevCfg)
- if err != nil {
- o.Log.Error("%v", err)
- }
-
- // check if there has been a change in the imagesetconfig
- // between a copy and mirror - it should not be changed
- if o.Opts.Mode == diskToMirror && res && !o.Opts.Global.Force {
- return fmt.Errorf("imagesetconfig should not be changed (from copy)")
- }
-
- if !res && !o.Opts.Global.Force {
- o.Log.Info("no change detected for copy and mirror")
- return nil
- }
-
- // do releases
- imgs, err := o.Release.ReleaseImageCollector(cmd.Context())
- if err != nil {
- return err
- }
- o.Log.Info("total release images to copy %d ", len(imgs))
- o.Opts.ImageType = "release"
- allRelatedImages = mergeImages(allRelatedImages, imgs)
-
- // do operators
- imgs, err = o.Operator.OperatorImageCollector(cmd.Context())
- if err != nil {
- return err
- }
- o.Log.Info("total operator images to copy %d ", len(imgs))
- o.Opts.ImageType = "operator"
- allRelatedImages = mergeImages(allRelatedImages, imgs)
-
- // do additionalImages
- imgs, err = o.AdditionalImages.AdditionalImagesCollector(cmd.Context())
- if err != nil {
- return err
- }
- o.Log.Info("total additional images to copy %d ", len(imgs))
- allRelatedImages = mergeImages(allRelatedImages, imgs)
-
- //call the batch worker
- err = o.Batch.Worker(cmd.Context(), allRelatedImages, o.Opts)
- if err != nil {
- return err
- }
-
- // only execute if mode is diskToMirror
- err = o.Diff.DeleteImages(cmd.Context())
- if err != nil {
- o.Log.Error("%v", err)
- }
- return nil
-}
-
-// Complete - do the final setup of modules
-func (o *ExecutorSchema) Complete(args []string) {
- // override log level
- o.Log.Level(o.Opts.Global.LogLevel)
- o.Log.Debug("imagesetconfig file %s ", o.Opts.Global.ConfigPath)
- // read the ImageSetConfiguration
- cfg, err := config.ReadConfig(o.Opts.Global.ConfigPath)
- if err != nil {
- o.Log.Error("imagesetconfig %v ", err)
- }
- o.Log.Trace("imagesetconfig : %v ", cfg)
-
- // update all dependant modules
- mc := mirror.NewMirrorCopy()
- md := mirror.NewMirrorDelete()
- o.Manifest = manifest.New(o.Log)
- o.Mirror = mirror.New(mc, md)
- o.Config = cfg
- o.Batch = batch.New(o.Log, o.Mirror, o.Manifest)
-
- // logic to check mode
- var dest string
- if strings.Contains(args[0], ociProtocol) {
- o.Opts.Mode = mirrorToDisk
- dest = workingDir + strings.Split(args[0], ociProtocol)[1]
- o.Log.Debug("destination %s ", dest)
- } else if strings.Contains(args[0], dockerProtocol) {
- o.Opts.Mode = diskToMirror
- dest = o.Opts.Global.From
- o.Log.Debug("destination %s ", dest)
- }
- o.Opts.Destination = args[0]
- o.Opts.Global.Dir = dest
- o.Log.Info("mode %s ", o.Opts.Mode)
-
- client, _ := release.NewOCPClient(uuid.New())
- cn := release.NewCincinnati(o.Log, &o.Config, &o.Opts, client, false)
- o.Release = release.New(o.Log, o.Config, o.Opts, o.Mirror, o.Manifest, cn)
- o.Operator = operator.New(o.Log, o.Config, o.Opts, o.Mirror, o.Manifest)
- o.AdditionalImages = additional.New(o.Log, o.Config, o.Opts, o.Mirror, o.Manifest)
- o.Diff = diff.New(o.Log, o.Config, o.Opts, o.Mirror)
-
- metadata, _, err := o.Diff.GetAllMetadata(dest)
- if err != nil {
- // if no previous imagesetconfig was found create new one
- item := []diff.Item{
- {
- Value: 0,
- Current: true,
- Timestamp: time.Now().Unix(),
- Destination: dest,
- },
- }
- seq := diff.Sequence{Item: item}
- metadata = diff.SequenceSchema{Title: "golang-oci-mirror", Owner: "CFE-EAMA", Sequence: seq}
- o.Log.Info("added new metadata %v ", metadata)
- err := o.Diff.WriteMetadata(o.Opts.Global.ConfigPath, dest, metadata, o.Config)
- if err != nil {
- o.Log.Error("%v", err)
- }
- }
- o.MetaData = metadata
- o.Log.Info("metadata %v ", metadata)
-}
-
-// Validate - cobra validation
-func (o *ExecutorSchema) Validate(dest []string) error {
- if len(o.Opts.Global.ConfigPath) == 0 && strings.Contains(dest[0], ociProtocol) {
- return fmt.Errorf("use the --config flag when using oci: protocol")
- }
- if len(o.Opts.Global.From) == 0 && strings.Contains(dest[0], dockerProtocol) {
- return fmt.Errorf("use the --from flag when using docker: protocol")
- }
- if strings.Contains(dest[0], ociProtocol) || strings.Contains(dest[0], dockerProtocol) {
- return nil
- } else {
- return fmt.Errorf("destination must have either oci:// or docker:// protocol prefixes")
- }
-}
-
-// mergeImages - simple function to append releated images
-// nolint
-func mergeImages(base, in []string) []string {
- for _, img := range in {
- base = append(base, img)
- }
- return base
-}
-
-
-
package config
-
-import (
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
-)
-
-// Complete set default values in the ImageSetConfiguration
-// when applicable
-func Complete(cfg *v1alpha2.ImageSetConfiguration) {
- completeReleaseArchitectures(cfg)
-}
-
-func completeReleaseArchitectures(cfg *v1alpha2.ImageSetConfiguration) {
- if len(cfg.Mirror.Platform.Channels) != 0 && len(cfg.Mirror.Platform.Architectures) == 0 {
- cfg.Mirror.Platform.Architectures = []string{v1alpha2.DefaultPlatformArchitecture}
- }
-}
-
-
-
package config
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
-
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "sigs.k8s.io/yaml"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
-)
-
-// TODO(estroz): create interface scheme such that configuration and metadata
-// versions do not matter to the caller.
-// See https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/config/config.go
-
-// ReadConfig opens an imageset configuration file at the given path
-// and loads it into a v1alpha2.ImageSetConfiguration instance for processing and validation.
-func ReadConfig(configPath string) (c v1alpha2.ImageSetConfiguration, err error) {
-
- data, err := os.ReadFile(filepath.Clean(configPath))
- if err != nil {
- return c, err
- }
- typeMeta, err := getTypeMeta(data)
-
- if err != nil {
- return c, err
- }
-
- switch typeMeta.GroupVersionKind() {
- case v1alpha2.GroupVersion.WithKind(v1alpha2.ImageSetConfigurationKind):
- c, err = LoadConfig(data)
- if err != nil {
- return c, err
- }
- default:
- return c, fmt.Errorf("config GVK not recognized: %s", typeMeta.GroupVersionKind())
- }
-
- Complete(&c)
-
- return c, Validate(&c)
-}
-
-// LoadConfig loads data into a v1alpha2.ImageSetConfiguration instance
-func LoadConfig(data []byte) (c v1alpha2.ImageSetConfiguration, err error) {
-
- gvk := v1alpha2.GroupVersion.WithKind(v1alpha2.ImageSetConfigurationKind)
-
- if data, err = yaml.YAMLToJSON(data); err != nil {
- return c, fmt.Errorf("yaml to json %s: %v", gvk, err)
- }
-
- dec := json.NewDecoder(bytes.NewBuffer(data))
- dec.DisallowUnknownFields()
- if err := dec.Decode(&c); err != nil {
- return c, fmt.Errorf("decode %s: %v", gvk, err)
- }
-
- c.SetGroupVersionKind(gvk)
-
- return c, nil
-}
-
-// LoadMetadata loads data into a v1alpha2.Metadata instance
-func LoadMetadata(data []byte) (m v1alpha2.Metadata, err error) {
-
- gvk := v1alpha2.GroupVersion.WithKind(v1alpha2.MetadataKind)
-
- dec := json.NewDecoder(bytes.NewBuffer(data))
- dec.DisallowUnknownFields()
- if err := dec.Decode(&m); err != nil {
- return m, fmt.Errorf("decode %s: %v", gvk, err)
- }
-
- m.SetGroupVersionKind(gvk)
-
- return m, nil
-}
-
-func getTypeMeta(data []byte) (typeMeta metav1.TypeMeta, err error) {
- if err := yaml.Unmarshal(data, &typeMeta); err != nil {
- return typeMeta, fmt.Errorf("get type meta: %v", err)
- }
- return typeMeta, nil
-}
-
-
-
package config
-
-import (
- "fmt"
-
- utilerrors "k8s.io/apimachinery/pkg/util/errors"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
-)
-
-type validationFunc func(cfg *v1alpha2.ImageSetConfiguration) error
-
-var validationChecks = []validationFunc{validateOperatorOptions, validateReleaseChannels}
-
-// Validate will check an ImagesetConfiguration for input errors.
-func Validate(cfg *v1alpha2.ImageSetConfiguration) error {
- var errs []error
- for _, check := range validationChecks {
- if err := check(cfg); err != nil {
- errs = append(errs, fmt.Errorf("invalid configuration: %v", err))
- }
- }
- return utilerrors.NewAggregate(errs)
-}
-
-func validateOperatorOptions(cfg *v1alpha2.ImageSetConfiguration) error {
- seen := map[string]bool{}
- for _, ctlg := range cfg.Mirror.Operators {
- ctlgName, err := ctlg.GetUniqueName()
- if err != nil {
- return err
- }
- if seen[ctlgName] {
- return fmt.Errorf(
- "catalog %q: duplicate found in configuration", ctlgName,
- )
- }
- seen[ctlgName] = true
- }
- return nil
-}
-
-func validateReleaseChannels(cfg *v1alpha2.ImageSetConfiguration) error {
- seen := map[string]bool{}
- for _, channel := range cfg.Mirror.Platform.Channels {
- if seen[channel.Name] {
- return fmt.Errorf(
- "release channel %q: duplicate found in configuration", channel.Name,
- )
- }
- seen[channel.Name] = true
- }
- return nil
-}
-
-
-
package log
-
-import (
- "fmt"
-
- "github.com/microlib/simple"
-)
-
-// PluggableLoggerInterface - allows us to use other logging systems
-// as long as the interface implementation is adhered to
-type PluggableLoggerInterface interface {
- Error(msg string, val ...interface{})
- Info(msg string, val ...interface{})
- Debug(msg string, val ...interface{})
- Trace(msg string, val ...interface{})
- Level(levele string)
-}
-
-// PluggableLogger
-type PluggableLogger struct {
- Log *simple.Logger
-}
-
-// New - returns a new PluggableLogger instance
-func New(level string) PluggableLoggerInterface {
- return &PluggableLogger{Log: &simple.Logger{Level: level}}
-}
-
-// Error
-func (c *PluggableLogger) Error(msg string, val ...interface{}) {
- c.Log.Error(fmt.Sprintf(msg, val...))
-}
-
-// Info
-func (c *PluggableLogger) Info(msg string, val ...interface{}) {
- c.Log.Info(fmt.Sprintf(msg, val...))
-}
-
-// Debug
-func (c *PluggableLogger) Debug(msg string, val ...interface{}) {
- c.Log.Debug(fmt.Sprintf(msg, val...))
-}
-
-// Trace
-func (c *PluggableLogger) Trace(msg string, val ...interface{}) {
- c.Log.Trace(fmt.Sprintf(msg, val...))
-}
-
-// Level - ovveride log level
-func (c *PluggableLogger) Level(level string) {
- c.Log.Level = level
-}
-
-
-
package manifest
-
-import (
- "archive/tar"
- "compress/gzip"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "strings"
-
- "github.com/blang/semver/v4"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha3"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "k8s.io/klog/v2"
-)
-
-const (
- index string = "index.json"
- catalogJson string = "catalog.json"
- operatorImageExtractDir string = "hold-operator"
- errorSemver string = " semver %v "
-)
-
-type ManifestInterface interface {
- GetImageIndex(dir string) (*v1alpha3.OCISchema, error)
- GetImageManifest(file string) (*v1alpha3.OCISchema, error)
- GetOperatorConfig(file string) (*v1alpha3.OperatorConfigSchema, error)
- GetRelatedImagesFromCatalog(filePath, label string) (map[string][]v1alpha3.RelatedImage, error)
- GetRelatedImagesFromCatalogByFilter(filePath, label string, op v1alpha2.Operator, mp map[string]v1alpha3.ISCPackage) (map[string][]v1alpha3.RelatedImage, error)
- ExtractLayersOCI(filePath, toPath, label string, oci *v1alpha3.OCISchema) error
- GetReleaseSchema(filePath string) ([]v1alpha3.RelatedImage, error)
-}
-
-type Manifest struct {
- Log clog.PluggableLoggerInterface
-}
-
-func New(log clog.PluggableLoggerInterface) ManifestInterface {
- return &Manifest{Log: log}
-}
-
-// GetImageIndex - used to get the oci index.json
-func (o *Manifest) GetImageIndex(dir string) (*v1alpha3.OCISchema, error) {
- var oci *v1alpha3.OCISchema
- indx, err := os.ReadFile(dir + "/" + index)
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(indx, &oci)
- if err != nil {
- return nil, err
- }
- return oci, nil
-}
-
-// GetImageManifest used to ge the manifest in the oci blobs/sha254
-// directory - found in index.json
-func (o *Manifest) GetImageManifest(file string) (*v1alpha3.OCISchema, error) {
- var oci *v1alpha3.OCISchema
- manifest, err := os.ReadFile(file)
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(manifest, &oci)
- if err != nil {
- return nil, err
- }
- return oci, nil
-}
-
-// GetOperatorConfig used to parse the operator json
-func (o *Manifest) GetOperatorConfig(file string) (*v1alpha3.OperatorConfigSchema, error) {
- var ocs *v1alpha3.OperatorConfigSchema
- manifest, err := os.ReadFile(file)
- if err != nil {
- return nil, err
- }
- err = json.Unmarshal(manifest, &ocs)
- if err != nil {
- return nil, err
- }
- return ocs, nil
-}
-
-// operatorImageExtractDir + "/" + label
-// GetRelatedImagesFromCatalog
-func (o *Manifest) GetRelatedImagesFromCatalog(filePath, label string) (map[string][]v1alpha3.RelatedImage, error) {
- relatedImages := make(map[string][]v1alpha3.RelatedImage)
- files, err := os.ReadDir(filePath)
- if err != nil {
- return relatedImages, err
- }
- for _, file := range files {
- // the catalog.json - does not really conform to json standards
- // this needs some thorough testing
- olm, err := readOperatorCatalog(filePath + "/" + file.Name())
- if err != nil {
- return relatedImages, err
- }
- ri, err := getRelatedImageByDefaultChannel(o.Log, olm)
- if err != nil {
- return relatedImages, err
- }
- // append to relatedImages map
- for k, v := range ri {
- relatedImages[k] = v
- }
- }
- return relatedImages, nil
-}
-
-// GetRelatedImagesFromCatalogByFilter
-func (o *Manifest) GetRelatedImagesFromCatalogByFilter(filePath, label string, op v1alpha2.Operator, mp map[string]v1alpha3.ISCPackage) (map[string][]v1alpha3.RelatedImage, error) {
- relatedImages := make(map[string][]v1alpha3.RelatedImage)
- for _, pkg := range op.Packages {
- // the catalog.json - does not really conform to json standards
- // this needs some thorough testing
- olm, err := readOperatorCatalog(filePath + "/" + label + "/" + pkg.Name)
- if err != nil {
- return relatedImages, err
- }
-
- ri, err := getRelatedImageByFilter(o.Log, olm, mp[pkg.Name])
- if err != nil {
- return relatedImages, err
- }
- // append to reletedImages map
- for k, v := range ri {
- relatedImages[k] = v
- }
- o.Log.Trace("related images %v", relatedImages)
- }
- return relatedImages, nil
-}
-
-// ExtractLayersOCI
-func (o *Manifest) ExtractLayersOCI(fromPath, toPath, label string, oci *v1alpha3.OCISchema) error {
- for _, blob := range oci.Layers {
- if !strings.Contains(blob.Digest, "sha256") {
- return fmt.Errorf("the digest format is not correct %s ", blob.Digest)
- }
- f, err := os.Open(fromPath + "/" + strings.Split(blob.Digest, ":")[1])
- if err != nil {
- return err
- }
- err = untar(f, toPath, label)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// GetReleaseSchema
-func (o *Manifest) GetReleaseSchema(filePath string) ([]v1alpha3.RelatedImage, error) {
- var release = v1alpha3.ReleaseSchema{}
-
- file, _ := os.ReadFile(filePath)
- err := json.Unmarshal([]byte(file), &release)
- if err != nil {
- return []v1alpha3.RelatedImage{}, err
- }
-
- var allImages []v1alpha3.RelatedImage
- for _, item := range release.Spec.Tags {
- allImages = append(allImages, v1alpha3.RelatedImage{Image: item.From.Name, Name: item.Name})
- }
- return allImages, nil
-}
-
-// UntarLayers simple function that untars the image layers
-func untar(gzipStream io.Reader, path string, cfgDirName string) error {
- //Remove any separators in cfgDirName as received from the label
- cfgDirName = strings.TrimSuffix(cfgDirName, "/")
- cfgDirName = strings.TrimPrefix(cfgDirName, "/")
- uncompressedStream, err := gzip.NewReader(gzipStream)
- if err != nil {
- return fmt.Errorf("untar: gzipStream - %w", err)
- }
-
- tarReader := tar.NewReader(uncompressedStream)
- for {
- header, err := tarReader.Next()
-
- if err == io.EOF {
- break
- }
-
- if err != nil {
- return fmt.Errorf("untar: Next() failed: %s", err.Error())
- }
-
- if strings.Contains(header.Name, cfgDirName) {
- switch header.Typeflag {
- case tar.TypeDir:
- if header.Name != "./" {
- if err := os.MkdirAll(path+"/"+header.Name, 0755); err != nil {
- return fmt.Errorf("untar: Mkdir() failed: %v", err)
- }
- }
- case tar.TypeReg:
- outFile, err := os.Create(path + "/" + header.Name)
- if err != nil {
- return fmt.Errorf("untar: Create() failed: %v", err)
- }
- if _, err := io.Copy(outFile, tarReader); err != nil {
- return fmt.Errorf("untar: Copy() failed: %v", err)
- }
- outFile.Close()
-
- default:
- // just ignore errors as we are only interested in the FB configs layer
- klog.Warningf("untar: unknown type: %v in %s", header.Typeflag, header.Name)
- }
- }
- }
- return nil
-}
-
-// readOperatorCatalog - simple function tha treads the specific catalog.json file
-// and unmarshals it to DeclarativeConfig struct
-func readOperatorCatalog(path string) ([]v1alpha3.DeclarativeConfig, error) {
- // the catalog.json - dos not really conform to json standards
- // this needs some thorough testing
- // operatorImageExtractDir + "/" + label + "/" + name + "/" + catalogJson
- var olm []v1alpha3.DeclarativeConfig
- data, err := os.ReadFile(path + "/" + catalogJson)
- if err != nil {
- return []v1alpha3.DeclarativeConfig{}, err
- }
- tmp := strings.NewReplacer(" ", "").Replace(string(data))
- updatedJson := "[" + strings.ReplaceAll(tmp, "}\n{", "},{") + "]"
- err = json.Unmarshal([]byte(updatedJson), &olm)
- if err != nil {
- return []v1alpha3.DeclarativeConfig{}, err
- }
- return olm, nil
-}
-
-// getRelatedImageByDefaultChannel - get the DeclarativeConfig for the default channel
-// it returns the HEAD (latest version of the bundles relatedImages)
-func getRelatedImageByDefaultChannel(log clog.PluggableLoggerInterface, olm []v1alpha3.DeclarativeConfig) (map[string][]v1alpha3.RelatedImage, error) {
- // relevant variables
- relatedImages := make(map[string][]v1alpha3.RelatedImage)
- bundles := make(map[string]bool)
- var defaultChannel string
-
- // iterate through the catalog objects
- for i, obj := range olm {
- switch {
- case obj.Schema == "olm.channel":
- if defaultChannel == obj.Name {
- log.Debug("found channel : %v", obj)
- log.Debug("bundle image to use : %v", obj.Entries[0].Name)
- name, err := semverFindMax(obj.Entries)
- if err != nil {
- log.Error(errorSemver, err)
- }
- bundles[name] = true
- }
- case obj.Schema == "olm.bundle":
- if bundles[obj.Name] {
- log.Debug("config bundle: %d %v", i, obj.Name)
- log.Trace("config relatedImages: %d %v", i, obj.RelatedImages)
- relatedImages[obj.Name] = obj.RelatedImages
- }
- case obj.Schema == "olm.package":
- log.Debug("Config package: %v", obj.Name)
- defaultChannel = obj.DefaultChannel
- }
- }
- return relatedImages, nil
-}
-
-// getRelatedImageByFilter - get the DeclarativeConfig for a specifc channel with
-// min,max version if set
-func getRelatedImageByFilter(log clog.PluggableLoggerInterface, olm []v1alpha3.DeclarativeConfig, pkg v1alpha3.ISCPackage) (map[string][]v1alpha3.RelatedImage, error) {
- // relevant variables
- relatedImages := make(map[string][]v1alpha3.RelatedImage)
- bundles := make(map[string]bool)
- // iterate through the catalog objects
- for i, obj := range olm {
- switch {
- case obj.Schema == "olm.channel":
- if len(pkg.Channel) > 0 {
- if pkg.Channel == obj.Name {
- log.Debug("found channel : %v", obj)
- name, err := semverFindRange(obj.Entries, pkg.MinVersion, pkg.MaxVersion)
- if err != nil {
- log.Error(errorSemver, err)
- }
- for _, x := range name {
- bundles[x] = true
- }
- }
- } else {
- name, err := semverFindMax(obj.Entries)
- if err != nil {
- log.Error(errorSemver, err)
- }
- log.Debug("adding channel : %s", name)
- bundles[name] = true
- }
- case obj.Schema == "olm.bundle":
- if bundles[obj.Name] {
- log.Debug("config bundle: %d %v", i, obj.Name)
- log.Trace("config relatedImages: %d %v", i, obj.RelatedImages)
- relatedImages[obj.Name] = obj.RelatedImages
- }
- case obj.Schema == "olm.package":
- log.Debug("config package: %v", obj.Name)
- bundles[obj.DefaultChannel] = true
- }
- }
- return relatedImages, nil
-}
-
-// semverFindMax - finds the max bundle version
-func semverFindMax(entries []v1alpha3.ChannelEntry) (string, error) {
- var max semver.Version
- var index int
- for id, s := range entries {
- hld := strings.Split(s.Name, ".")
- // we are only interested in 1,2,3 positions
- if len(hld) < 4 {
- return "", fmt.Errorf("versioning of string is not correct %s ", s.Name)
- }
- hld[1] = strings.Replace(hld[1], "v", "", -1)
- end := strings.Split(hld[3], "-")
- semStr := strings.Join([]string{hld[1], hld[2], end[0]}, ".")
- version, err := semver.Parse(semStr)
- if err != nil {
- return "", err
- }
-
- if version.Compare(max) == 1 {
- max = version
- index = id
- }
- }
- return entries[index].Name, nil
-}
-
-// semverFindRange - finds the bundles between ranges version
-func semverFindRange(entries []v1alpha3.ChannelEntry, min, max string) ([]string, error) {
-
- var minVersion semver.Version
- var maxVersion semver.Version
- var err error
- var results []string
-
- // parse the min max strings
- if len(min) > 0 {
- minVersion, err = semver.Parse(min)
- if err != nil {
- return []string{}, err
- }
- } else {
- minVersion, _ = semver.Parse("0.0.0")
- }
- if len(max) > 0 {
- maxVersion, err = semver.Parse(max)
- if err != nil {
- return []string{}, err
- }
- } else {
- maxVersion, _ = semver.Parse("9.9.9")
- }
-
- for _, s := range entries {
- hld := strings.Split(s.Name, ".")
- // we are only interested in 1,2,3 positions
- if len(hld) < 4 {
- return []string{}, fmt.Errorf("versioning of string is not correct %s ", s.Name)
- }
- hld[1] = strings.Replace(hld[1], "v", "", -1)
- end := strings.Split(hld[3], "-")
- semStr := strings.Join([]string{hld[1], hld[2], end[0]}, ".")
- version, err := semver.Parse(semStr)
- if err != nil {
- return []string{}, err
- }
- if version.Compare(maxVersion) <= 0 && version.Compare(minVersion) >= 1 {
- results = append(results, s.Name)
- }
- }
- return results, nil
-}
-
-
-
package mirror
-
-import (
- "bufio"
- "context"
- "fmt"
- "io"
- "os"
- "strconv"
-
- "github.com/containers/common/pkg/retry"
- "github.com/containers/image/manifest"
- "github.com/containers/image/v5/copy"
- "github.com/containers/image/v5/pkg/cli"
- "github.com/containers/image/v5/signature"
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/image/v5/types"
- "github.com/docker/distribution/reference"
-)
-
-const (
- mirrorToDisk = "mirrorToDisk"
- diskToMirror = "diskToMirror"
-)
-
-// MirrorInterface used to mirror images with container/images (skopeo)
-type MirrorInterface interface {
- Run(ctx context.Context, src, dest, mode string, opts *CopyOptions, stdout bufio.Writer) (retErr error)
-}
-
-type MirrorCopyInterface interface {
- CopyImage(ctx context.Context, pc *signature.PolicyContext, destRef, srcRef types.ImageReference, opts *copy.Options) ([]byte, error)
-}
-
-type MirrorDeleteInterface interface {
- DeleteImage(ctx context.Context, image string, opts *CopyOptions) error
-}
-
-// Mirror
-type Mirror struct {
- mc MirrorCopyInterface
- md MirrorDeleteInterface
- Mode string
-}
-
-type MirrorCopy struct{}
-type MirrorDelete struct{}
-
-// New returns new Mirror instance
-func New(mc MirrorCopyInterface, md MirrorDeleteInterface) MirrorInterface {
- return &Mirror{mc: mc, md: md}
-}
-
-func NewMirrorCopy() MirrorCopyInterface {
- return &MirrorCopy{}
-}
-
-func NewMirrorDelete() MirrorDeleteInterface {
- return &MirrorDelete{}
-}
-
-// Run - method to copy images from source to destination
-func (o *Mirror) Run(ctx context.Context, src, dest, mode string, opts *CopyOptions, stdout bufio.Writer) (retErr error) {
- if mode == "delete" {
- return o.delete(ctx, src, opts)
- }
- return o.copy(ctx, src, dest, opts, stdout)
-}
-
-func (o *MirrorCopy) CopyImage(ctx context.Context, pc *signature.PolicyContext, destRef, srcRef types.ImageReference, co *copy.Options) ([]byte, error) {
- return copy.Image(ctx, pc, destRef, srcRef, co)
-}
-
-func (o *MirrorDelete) DeleteImage(ctx context.Context, image string, co *CopyOptions) error {
- return nil
-}
-
-// copy - copy images setup and execute
-func (o *Mirror) copy(ctx context.Context, src, dest string, opts *CopyOptions, out bufio.Writer) (retErr error) {
-
- opts.DeprecatedTLSVerify.WarnIfUsed([]string{"--src-tls-verify", "--dest-tls-verify"})
-
- opts.RemoveSignatures, _ = strconv.ParseBool("true")
-
- if err := ReexecIfNecessaryForImages([]string{src, dest}...); err != nil {
- return err
- }
-
- policyContext, err := opts.Global.GetPolicyContext()
- if err != nil {
- return fmt.Errorf("Error loading trust policy: %v", err)
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- retErr = NoteCloseFailure(retErr, "tearing down policy context", err)
- }
- }()
-
- srcRef, err := alltransports.ParseImageName(src)
- if err != nil {
- return fmt.Errorf("Invalid source name %s: %v", src, err)
- }
- destRef, err := alltransports.ParseImageName(dest)
- if err != nil {
- return fmt.Errorf("Invalid destination name %s: %v", dest, err)
- }
-
- sourceCtx, err := opts.SrcImage.NewSystemContext()
- if err != nil {
- return err
- }
- destinationCtx, err := opts.DestImage.NewSystemContext()
- if err != nil {
- return err
- }
-
- var manifestType string
- if len(opts.Format) > 0 {
- manifestType, err = ParseManifestFormat(opts.Format)
- if err != nil {
- return err
- }
- }
-
- /*
- for _, image := range opts.AdditionalTags {
- ref, err := reference.ParseNormalizedNamed(image)
- if err != nil {
- return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
- }
- namedTagged, isNamedTagged := ref.(reference.NamedTagged)
- if !isNamedTagged {
- return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
- }
- destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
- }
- */
-
- ctx, cancel := opts.Global.CommandTimeoutContext()
- defer cancel()
-
- //if opts.Quiet {
- // stdout = nil
- //}
-
- imageListSelection := copy.CopySystemImage
- if len(opts.MultiArch) > 0 && opts.All {
- return fmt.Errorf("Cannot use --all and --multi-arch flags together")
- }
-
- if len(opts.MultiArch) > 0 {
- imageListSelection, err = parseMultiArch(opts.MultiArch)
- if err != nil {
- return err
- }
- }
-
- if opts.All {
- imageListSelection = copy.CopyAllImages
- }
-
- if len(opts.EncryptionKeys) > 0 && len(opts.DecryptionKeys) > 0 {
- return fmt.Errorf("--encryption-key and --decryption-key cannot be specified together")
- }
-
- /*
- var encLayers *[]int
- var encConfig *encconfig.EncryptConfig
- var decConfig *encconfig.DecryptConfig
-
- if len(opts.EncryptLayer) > 0 && len(opts.EncryptionKeys) == 0 {
- return fmt.Errorf("--encrypt-layer can only be used with --encryption-key")
- }
-
- if len(opts.EncryptionKeys) > 0 {
- // encryption
- p := opts.EncryptLayer
- encLayers = &p
- encryptionKeys := opts.EncryptionKeys
- ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
- if err != nil {
- return fmt.Errorf("Invalid encryption keys: %v", err)
- }
- cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{ecc})
- encConfig = cc.EncryptConfig
- }
-
- if len(opts.DecryptionKeys) > 0 {
- // decryption
- decryptionKeys := opts.DecryptionKeys
- dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys)
- if err != nil {
- return fmt.Errorf("Invalid decryption keys: %v", err)
- }
- cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{dcc})
- decConfig = cc.DecryptConfig
- }
- */
-
- // c/image/copy.Image does allow creating both simple signing and sigstore signatures simultaneously,
- // with independent passphrases, but that would make the CLI probably too confusing.
- // For now, use the passphrase with either, but only one of them.
- if opts.SignPassphraseFile != "" && opts.SignByFingerprint != "" && opts.SignBySigstorePrivateKey != "" {
- return fmt.Errorf("Only one of --sign-by and sign-by-sigstore-private-key can be used with sign-passphrase-file")
- }
- var passphrase string
- if opts.SignPassphraseFile != "" {
- p, err := cli.ReadPassphraseFile(opts.SignPassphraseFile)
- if err != nil {
- return err
- }
- passphrase = p
- }
-
- // opts.signByFingerprint triggers a GPG-agent passphrase prompt, possibly using a more secure channel,
- // so we usually shouldn’t prompt ourselves if no passphrase was explicitly provided.
- var signIdentity reference.Named = nil
- if opts.SignIdentity != "" {
- signIdentity, err = reference.ParseNamed(opts.SignIdentity)
- if err != nil {
- return fmt.Errorf("Could not parse --sign-identity: %v", err)
- }
- }
-
- //opts.DigestFile = "test-digest"
- writer := io.Writer(&out)
-
- co := ©.Options{
- RemoveSignatures: opts.RemoveSignatures,
- SignBy: opts.SignByFingerprint,
- SignPassphrase: passphrase,
- SignBySigstorePrivateKeyFile: opts.SignBySigstorePrivateKey,
- SignSigstorePrivateKeyPassphrase: []byte(passphrase),
- SignIdentity: signIdentity,
- ReportWriter: writer,
- SourceCtx: sourceCtx,
- DestinationCtx: destinationCtx,
- ForceManifestMIMEType: manifestType,
- ImageListSelection: imageListSelection,
- PreserveDigests: opts.PreserveDigests,
- //OciDecryptConfig: decConfig,
- //OciEncryptLayers: encLayers,
- //OciEncryptConfig: encConfig,
- }
-
- return retry.IfNecessary(ctx, func() error {
- //manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
- manifestBytes, err := o.mc.CopyImage(ctx, policyContext, destRef, srcRef, co)
- if err != nil {
- return err
- }
- out.Flush()
- if opts.DigestFile != "" {
- manifestDigest, err := manifest.Digest(manifestBytes)
- if err != nil {
- return err
- }
- if err = os.WriteFile(opts.DigestFile, []byte(manifestDigest.String()), 0644); err != nil {
- return fmt.Errorf("Failed to write digest to file %q: %w", opts.DigestFile, err)
- }
- }
- return nil
- }, opts.RetryOpts)
-}
-
-// delete - delete images
-func (o *Mirror) delete(ctx context.Context, image string, opts *CopyOptions) error {
-
- if err := ReexecIfNecessaryForImages([]string{image}...); err != nil {
- return err
- }
-
- imageRef, err := alltransports.ParseImageName(image)
- if err != nil {
- return fmt.Errorf("Invalid source name %s: %v", image, err)
- }
-
- sysCtx, err := opts.DestImage.NewSystemContext()
- if err != nil {
- return err
- }
-
- ctx, cancel := opts.Global.CommandTimeoutContext()
- defer cancel()
-
- return retry.IfNecessary(ctx, func() error {
- err := imageRef.DeleteImage(ctx, sysCtx)
- if err != nil {
- return err
- }
- return nil
- }, opts.RetryOpts)
-}
-
-// parseMultiArch
-func parseMultiArch(multiArch string) (copy.ImageListSelection, error) {
- switch multiArch {
- case "system":
- return copy.CopySystemImage, nil
- case "all":
- return copy.CopyAllImages, nil
- // There is no CopyNoImages value in copy.ImageListSelection, but because we
- // don't provide an option to select a set of images to copy, we can use
- // CopySpecificImages.
- case "index-only":
- return copy.CopySpecificImages, nil
- // We don't expose CopySpecificImages other than index-only above, because
- // we currently don't provide an option to choose the images to copy. That
- // could be added in the future.
- default:
- return copy.CopySystemImage, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', or 'index-only'", multiArch)
- }
-}
-
-
-
package mirror
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- "time"
-
- commonFlag "github.com/containers/common/pkg/flag"
- "github.com/containers/common/pkg/retry"
- "github.com/containers/image/v5/manifest"
- "github.com/containers/image/v5/signature"
- "github.com/containers/image/v5/types"
- "github.com/google/uuid"
- imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
- "github.com/spf13/pflag"
- "golang.org/x/term"
-)
-
-const defaultUserAgent string = "skopeo/v.19.5"
-
-// errorShouldDisplayUsage is a subtype of error used by command handlers to indicate that cli.ShowSubcommandHelp should be called.
-type ErrorShouldDisplayUsage struct {
- Error error
-}
-
-// noteCloseFailure returns (possibly-nil) err modified to account for (non-nil) closeErr.
-// The error for closeErr is annotated with description (which is not a format string)
-// Typical usage:
-//
-// defer func() {
-// if err := something.Close(); err != nil {
-// returnedErr = noteCloseFailure(returnedErr, "closing something", err)
-// }
-// }
-func NoteCloseFailure(err error, description string, closeErr error) error {
- // We don’t accept a Closer() and close it ourselves because signature.PolicyContext has .Destroy(), not .Close().
- // This also makes it harder for a caller to do
- // defer noteCloseFailure(returnedErr, …)
- // which doesn’t use the right value of returnedErr, and doesn’t update it.
- if err == nil {
- return fmt.Errorf("%s: %w", description, closeErr)
- }
- // In this case we prioritize the primary error for use with %w; closeErr is usually less relevant, or might be a consequence of the primary erorr.
- return fmt.Errorf("%w (%s: %v)", err, description, closeErr)
-}
-
-// commandAction intermediates between the RunE interface and the real handler,
-// primarily to ensure that cobra.Command is not available to the handler, which in turn
-// makes sure that the cmd.Flags() etc. flag access functions are not used,
-// and everything is done using the *Options structures and the *Var() methods of cmd.Flag().
-// handler may return errorShouldDisplayUsage to cause c.Help to be called.
-func CommandAction(handler func(args []string, stdout io.Writer) error) func(cmd *cobra.Command, args []string) error {
- return func(c *cobra.Command, args []string) error {
- err := handler(args, c.OutOrStdout())
- //var shouldDisplayUsage = &ErrorShouldDisplayUsage{}
- //if errors.As(err, &ErrorShouldDisplayUsage{}) {
- // return c.Help()
- //}
- return err
- }
-}
-
-// deprecatedTLSVerifyOption represents a deprecated --tls-verify option,
-// which was accepted for all subcommands, for a time.
-// Every user should call deprecatedTLSVerifyOption.warnIfUsed() as part of handling the CLI,
-// whether or not the value actually ends up being used.
-// DO NOT ADD ANY NEW USES OF THIS; just call dockerImageFlags with an appropriate, possibly empty, flagPrefix.
-type DeprecatedTLSVerifyOption struct {
- tlsVerify commonFlag.OptionalBool // FIXME FIXME: Warn if this is used, or even if it is ignored.
-}
-
-// warnIfUsed warns if tlsVerify was set by the user, and suggests alternatives (which should
-// start with "--").
-// Every user should call this as part of handling the CLI, whether or not the value actually
-// ends up being used.
-func (opts *DeprecatedTLSVerifyOption) WarnIfUsed(alternatives []string) {
- if opts.tlsVerify.Present() {
- logrus.Warnf("'--tls-verify' is deprecated, instead use: %s", strings.Join(alternatives, ", "))
- }
-}
-
-// deprecatedTLSVerifyFlags prepares the CLI flag writing into deprecatedTLSVerifyOption, and the managed deprecatedTLSVerifyOption structure.
-// DO NOT ADD ANY NEW USES OF THIS; just call dockerImageFlags with an appropriate, possibly empty, flagPrefix.
-func DeprecatedTLSVerifyFlags() (pflag.FlagSet, *DeprecatedTLSVerifyOption) {
- opts := DeprecatedTLSVerifyOption{}
- fs := pflag.FlagSet{}
- flag := commonFlag.OptionalBoolFlag(&fs, &opts.tlsVerify, "tls-verify", "require HTTPS and verify certificates when accessing the container registry")
- flag.Hidden = true
- return fs, &opts
-}
-
-// sharedImageOptions collects CLI flags which are image-related, but do not change across images.
-// This really should be a part of globalOptions, but that would break existing users of (skopeo copy --authfile=).
-type SharedImageOptions struct {
- authFilePath string // Path to a */containers/auth.json
-}
-
-// sharedImageFlags prepares a collection of CLI flags writing into sharedImageOptions, and the managed sharedImageOptions structure.
-func SharedImageFlags() (pflag.FlagSet, *SharedImageOptions) {
- opts := SharedImageOptions{}
- fs := pflag.FlagSet{}
- fs.StringVar(&opts.authFilePath, "authfile", os.Getenv("REGISTRY_AUTH_FILE"), "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
- return fs, &opts
-}
-
-// dockerImageOptions collects CLI flags specific to the "docker" transport, which are
-// the same across subcommands, but may be different for each image
-// (e.g. may differ between the source and destination of a copy)
-type dockerImageOptions struct {
- global *GlobalOptions // May be shared across several imageOptions instances.
- shared *SharedImageOptions // May be shared across several imageOptions instances.
- deprecatedTLSVerify *DeprecatedTLSVerifyOption // May be shared across several imageOptions instances, or nil.
- authFilePath commonFlag.OptionalString // Path to a */containers/auth.json (prefixed version to override shared image option).
- credsOption commonFlag.OptionalString // username[:password] for accessing a registry
- userName commonFlag.OptionalString // username for accessing a registry
- password commonFlag.OptionalString // password for accessing a registry
- registryToken commonFlag.OptionalString // token to be used directly as a Bearer token when accessing the registry
- dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon
- tlsVerify commonFlag.OptionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
- noCreds bool // Access the registry anonymously
-}
-
-// imageOptions collects CLI flags which are the same across subcommands, but may be different for each image
-// (e.g. may differ between the source and destination of a copy)
-type imageOptions struct {
- dockerImageOptions
- sharedBlobDir string // A directory to use for OCI blobs, shared across repositories
- dockerDaemonHost string // docker-daemon: host to connect to
-}
-
-// dockerImageFlags prepares a collection of docker-transport specific CLI flags
-// writing into imageOptions, and the managed imageOptions structure.
-func dockerImageFlags(global *GlobalOptions, shared *SharedImageOptions, deprecatedTLSVerify *DeprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) {
- flags := imageOptions{
- dockerImageOptions: dockerImageOptions{
- global: global,
- shared: shared,
- deprecatedTLSVerify: deprecatedTLSVerify,
- },
- }
-
- fs := pflag.FlagSet{}
- if flagPrefix != "" {
- // the non-prefixed flag is handled by a shared flag.
- fs.Var(commonFlag.NewOptionalStringValue(&flags.authFilePath), flagPrefix+"authfile", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json")
- }
- fs.Var(commonFlag.NewOptionalStringValue(&flags.credsOption), flagPrefix+"creds", "Use `USERNAME[:PASSWORD]` for accessing the registry")
- fs.Var(commonFlag.NewOptionalStringValue(&flags.userName), flagPrefix+"username", "Username for accessing the registry")
- fs.Var(commonFlag.NewOptionalStringValue(&flags.password), flagPrefix+"password", "Password for accessing the registry")
- if credsOptionAlias != "" {
- // This is horribly ugly, but we need to support the old option forms of (skopeo copy) for compatibility.
- // Don't add any more cases like this.
- f := fs.VarPF(commonFlag.NewOptionalStringValue(&flags.credsOption), credsOptionAlias, "", "Use `USERNAME[:PASSWORD]` for accessing the registry")
- f.Hidden = true
- }
- fs.Var(commonFlag.NewOptionalStringValue(&flags.registryToken), flagPrefix+"registry-token", "Provide a Bearer token for accessing the registry")
- fs.StringVar(&flags.dockerCertPath, flagPrefix+"cert-dir", "", "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry or daemon")
- commonFlag.OptionalBoolFlag(&fs, &flags.tlsVerify, flagPrefix+"tls-verify", "require HTTPS and verify certificates when talking to the container registry or daemon")
- fs.BoolVar(&flags.noCreds, flagPrefix+"no-creds", false, "Access the registry anonymously")
- return fs, &flags
-}
-
-// imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure.
-func ImageFlags(global *GlobalOptions, shared *SharedImageOptions, deprecatedTLSVerify *DeprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageOptions) {
- dockerFlags, opts := dockerImageFlags(global, shared, deprecatedTLSVerify, flagPrefix, credsOptionAlias)
-
- fs := pflag.FlagSet{}
- fs.StringVar(&opts.sharedBlobDir, flagPrefix+"shared-blob-dir", "", "`DIRECTORY` to use to share blobs across OCI repositories")
- fs.StringVar(&opts.dockerDaemonHost, flagPrefix+"daemon-host", "", "use docker daemon host at `HOST` (docker-daemon: only)")
- fs.AddFlagSet(&dockerFlags)
- return fs, opts
-}
-
-func RetryFlags() (pflag.FlagSet, *retry.Options) {
- opts := retry.Options{}
- fs := pflag.FlagSet{}
- fs.IntVar(&opts.MaxRetry, "retry-times", 0, "the number of times to possibly retry")
- return fs, &opts
-}
-
-// getPolicyContext returns a *signature.PolicyContext based on opts.
-func (opts *GlobalOptions) GetPolicyContext() (*signature.PolicyContext, error) {
- var policy *signature.Policy // This could be cached across calls in opts.
- var err error
- if opts.InsecurePolicy {
- policy = &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
- } else if opts.PolicyPath == "" {
- policy, err = signature.DefaultPolicy(nil)
- } else {
- policy, err = signature.NewPolicyFromFile(opts.PolicyPath)
- }
- if err != nil {
- return nil, err
- }
- return signature.NewPolicyContext(policy)
-}
-
-// commandTimeoutContext returns a context.Context and a cancellation callback based on opts.
-// The caller should usually "defer cancel()" immediately after calling this.
-func (opts *GlobalOptions) CommandTimeoutContext() (context.Context, context.CancelFunc) {
- ctx := context.Background()
- var cancel context.CancelFunc = func() {
- // empty function - its ok for now
- }
- if opts.CommandTimeout > 0 {
- ctx, cancel = context.WithTimeout(ctx, opts.CommandTimeout)
- }
- return ctx, cancel
-}
-
-// newSystemContext returns a *types.SystemContext corresponding to opts.
-// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
-func (opts *GlobalOptions) NewSystemContext() *types.SystemContext {
- ctx := &types.SystemContext{
- RegistriesDirPath: opts.RegistriesDirPath,
- ArchitectureChoice: opts.OverrideArch,
- OSChoice: opts.OverrideOS,
- VariantChoice: opts.OverrideVariant,
- SystemRegistriesConfPath: opts.RegistriesConfPath,
- BigFilesTemporaryDir: opts.TmpDir,
- DockerRegistryUserAgent: defaultUserAgent,
- }
- // DEPRECATED: We support this for backward compatibility, but override it if a per-image flag is provided.
- if !opts.TlsVerify {
- ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(true)
- }
- return ctx
-}
-
-// newSystemContext returns a *types.SystemContext corresponding to opts.
-// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
-func (opts *imageOptions) NewSystemContext() (*types.SystemContext, error) {
- // *types.SystemContext instance from globalOptions
- // imageOptions option overrides the instance if both are present.
- ctx := opts.global.NewSystemContext()
- ctx.DockerCertPath = opts.dockerCertPath
- ctx.OCISharedBlobDirPath = opts.sharedBlobDir
- ctx.AuthFilePath = opts.shared.authFilePath
- ctx.DockerDaemonHost = opts.dockerDaemonHost
- ctx.DockerDaemonCertPath = opts.dockerCertPath
- if opts.dockerImageOptions.authFilePath.Present() {
- ctx.AuthFilePath = opts.dockerImageOptions.authFilePath.Value()
- }
- if opts.deprecatedTLSVerify != nil && opts.deprecatedTLSVerify.tlsVerify.Present() {
- // If both this deprecated option and a non-deprecated option is present, we use the latter value.
- ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.deprecatedTLSVerify.tlsVerify.Value())
- }
- if opts.tlsVerify.Present() {
- ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.Value()
- }
- if opts.tlsVerify.Present() {
- ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.Value())
- }
- if opts.credsOption.Present() && opts.noCreds {
- return nil, errors.New("creds and no-creds cannot be specified at the same time")
- }
- if opts.userName.Present() && opts.noCreds {
- return nil, errors.New("username and no-creds cannot be specified at the same time")
- }
- if opts.credsOption.Present() && opts.userName.Present() {
- return nil, errors.New("creds and username cannot be specified at the same time")
- }
- // if any of username or password is present, then both are expected to be present
- if opts.userName.Present() != opts.password.Present() {
- if opts.userName.Present() {
- return nil, errors.New("password must be specified when username is specified")
- }
- return nil, errors.New("username must be specified when password is specified")
- }
- if opts.credsOption.Present() {
- var err error
- ctx.DockerAuthConfig, err = getDockerAuth(opts.credsOption.Value())
- if err != nil {
- return nil, err
- }
- } else if opts.userName.Present() {
- ctx.DockerAuthConfig = &types.DockerAuthConfig{
- Username: opts.userName.Value(),
- Password: opts.password.Value(),
- }
- }
- if opts.registryToken.Present() {
- ctx.DockerBearerRegistryToken = opts.registryToken.Value()
- }
- if opts.noCreds {
- ctx.DockerAuthConfig = &types.DockerAuthConfig{}
- }
-
- return ctx, nil
-}
-
-// imageDestOptions is a superset of imageOptions specialized for image destinations.
-type imageDestOptions struct {
- *imageOptions
- dirForceCompression bool // Compress layers when saving to the dir: transport
- dirForceDecompression bool // Decompress layers when saving to the dir: transport
- ociAcceptUncompressedLayers bool // Whether to accept uncompressed layers in the oci: transport
- compressionFormat string // Format to use for the compression
- compressionLevel commonFlag.OptionalInt // Level to use for the compression
- precomputeDigests bool // Precompute digests to dedup layers when saving to the docker: transport
-}
-
-// imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure.
-func ImageDestFlags(global *GlobalOptions, shared *SharedImageOptions, deprecatedTLSVerify *DeprecatedTLSVerifyOption, flagPrefix, credsOptionAlias string) (pflag.FlagSet, *imageDestOptions) {
- genericFlags, genericOptions := ImageFlags(global, shared, deprecatedTLSVerify, flagPrefix, credsOptionAlias)
- opts := imageDestOptions{imageOptions: genericOptions}
- fs := pflag.FlagSet{}
- fs.AddFlagSet(&genericFlags)
- fs.BoolVar(&opts.dirForceCompression, flagPrefix+"compress", false, "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)")
- fs.BoolVar(&opts.dirForceDecompression, flagPrefix+"decompress", false, "Decompress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)")
- fs.BoolVar(&opts.ociAcceptUncompressedLayers, flagPrefix+"oci-accept-uncompressed-layers", false, "Allow uncompressed image layers when saving to an OCI image using the 'oci' transport. (default is to compress things that aren't compressed)")
- fs.StringVar(&opts.compressionFormat, flagPrefix+"compress-format", "", "`FORMAT` to use for the compression")
- fs.Var(commonFlag.NewOptionalIntValue(&opts.compressionLevel), flagPrefix+"compress-level", "`LEVEL` to use for the compression")
- fs.BoolVar(&opts.precomputeDigests, flagPrefix+"precompute-digests", false, "Precompute digests to prevent uploading layers already on the registry using the 'docker' transport.")
- return fs, &opts
-}
-
-/*
-// newSystemContext returns a *types.SystemContext corresponding to opts.
-// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
-func (opts *imageDestOptions) newSystemContext() (*types.SystemContext, error) {
- ctx, err := opts.imageOptions.NewSystemContext()
- if err != nil {
- return nil, err
- }
-
- ctx.DirForceCompress = opts.dirForceCompression
- ctx.DirForceDecompress = opts.dirForceDecompression
- ctx.OCIAcceptUncompressedLayers = opts.ociAcceptUncompressedLayers
- if opts.compressionFormat != "" {
- cf, err := compression.AlgorithmByName(opts.compressionFormat)
- if err != nil {
- return nil, err
- }
- ctx.CompressionFormat = &cf
- }
- if opts.compressionLevel.Present() {
- value := opts.compressionLevel.Value()
- ctx.CompressionLevel = &value
- }
- ctx.DockerRegistryPushPrecomputeDigests = opts.precomputeDigests
- return ctx, err
-}
-*/
-
-func parseCreds(creds string) (string, string, error) {
- if creds == "" {
- return "", "", errors.New("credentials can't be empty")
- }
- up := strings.SplitN(creds, ":", 2)
- if len(up) == 1 {
- return up[0], "", nil
- }
- if up[0] == "" {
- return "", "", errors.New("username can't be empty")
- }
- return up[0], up[1], nil
-}
-
-func getDockerAuth(creds string) (*types.DockerAuthConfig, error) {
- username, password, err := parseCreds(creds)
- if err != nil {
- return nil, err
- }
- return &types.DockerAuthConfig{
- Username: username,
- Password: password,
- }, nil
-}
-
-/*
-// parseImageSource converts image URL-like string to an ImageSource.
-// The caller must call .Close() on the returned ImageSource.
-func parseImageSource(ctx context.Context, opts *imageOptions, name string) (types.ImageSource, error) {
- ref, err := alltransports.ParseImageName(name)
- if err != nil {
- return nil, err
- }
- sys, err := opts.NewSystemContext()
- if err != nil {
- return nil, err
- }
- return ref.NewImageSource(ctx, sys)
-}
-*/
-
-// parseManifestFormat parses format parameter for copy and sync command.
-// It returns string value to use as manifest MIME type
-func ParseManifestFormat(manifestFormat string) (string, error) {
- switch manifestFormat {
- case "oci":
- return imgspecv1.MediaTypeImageManifest, nil
- case "v2s1":
- return manifest.DockerV2Schema1SignedMediaType, nil
- case "v2s2":
- return manifest.DockerV2Schema2MediaType, nil
- default:
- return "", fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", manifestFormat)
- }
-}
-
-/*
-// usageTemplate returns the usage template for skopeo commands
-// This blocks the displaying of the global options. The main skopeo
-// command should not use this.
-const usageTemplate = `Usage:{{if .Runnable}}
-{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
-{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
-Aliases:
-{{.NameAndAliases}}{{end}}{{if .HasExample}}
-Examples:
-{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
-Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
-{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-{{end}}
-`
-*/
-
-/*
-// adjustUsage uses usageTemplate template to get rid the GlobalOption from usage
-// and disable [flag] at the end of command usage
-func adjustUsage(c *cobra.Command) {
- c.SetUsageTemplate(usageTemplate)
- c.DisableFlagsInUseLine = true
-}
-*/
-
-// promptForPassphrase interactively prompts for a passphrase related to privateKeyFile
-func PromptForPassphrase(privateKeyFile string, stdin, stdout *os.File) (string, error) {
- stdinFd := int(stdin.Fd())
- if !term.IsTerminal(stdinFd) {
- return "", fmt.Errorf("Cannot prompt for a passphrase for key %s, standard input is not a TTY", privateKeyFile)
- }
-
- fmt.Fprintf(stdout, "Passphrase for key %s: ", privateKeyFile)
- passphrase, err := term.ReadPassword(stdinFd)
- if err != nil {
- return "", fmt.Errorf("Error reading password: %w", err)
- }
- fmt.Fprintf(stdout, "\n")
- return string(passphrase), nil
-}
-
-type GlobalOptions struct {
- LogLevel string // one of info, debug, trace
- TlsVerify bool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
- PolicyPath string // Path to a signature verification policy file
- InsecurePolicy bool // Use an "allow everything" signature verification policy
- RegistriesDirPath string // Path to a "registries.d" registry configuration directory
- OverrideArch string // Architecture to use for choosing images, instead of the runtime one
- OverrideOS string // OS to use for choosing images, instead of the runtime one
- OverrideVariant string // Architecture variant to use for choosing images, instead of the runtime one
- CommandTimeout time.Duration // Timeout for the command execution
- RegistriesConfPath string // Path to the "registries.conf" file
- TmpDir string // Path to use for big temporary files
- Dir string // working directory
- ConfigPath string // Path to use for imagesetconfig
- From string // Used for mirroring (diskToMirror)
- Quiet bool // Suppress output information when copying images
- Force bool // Force the copy/mirror even if there is nothing to update
-}
-
-type CopyOptions struct {
- Global *GlobalOptions
- DeprecatedTLSVerify *DeprecatedTLSVerifyOption
- SrcImage *imageOptions
- DestImage *imageDestOptions
- RetryOpts *retry.Options
- AdditionalTags []string // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these
- RemoveSignatures bool // Do not copy signatures from the source image
- SignByFingerprint string // Sign the image using a GPG key with the specified fingerprint
- SignBySigstorePrivateKey string // Sign the image using a sigstore private key
- SignPassphraseFile string // Path pointing to a passphrase file when signing (for either signature format, but only one of them)
- SignIdentity string // Identity of the signed image, must be a fully specified docker reference
- DigestFile string // Write digest to this file
- Format string // Force conversion of the image to a specified format
- All bool // Copy all of the images if the source is a list
- MultiArch string // How to handle multi architecture images
- PreserveDigests bool // Preserve digests during copy
- EncryptLayer []int // The list of layers to encrypt
- EncryptionKeys []string // Keys needed to encrypt the image
- DecryptionKeys []string // Keys needed to decrypt the image
- Mode string // 2 options disktoMirror or mirrorToDisk (for now)
- Dev bool // developer mode - will be removed when completed
- Destination string // what to target to
- UUID uuid.UUID // set uuid
- ImageType string // release, catalog-operator, additionalImage
-}
-
-
-
package mirror
-
-import (
- "fmt"
-
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/storage/pkg/unshare"
- "github.com/syndtr/gocapability/capability"
-)
-
-var neededCapabilities = []capability.Cap{
- capability.CAP_CHOWN,
- capability.CAP_DAC_OVERRIDE,
- capability.CAP_FOWNER,
- capability.CAP_FSETID,
- capability.CAP_MKNOD,
- capability.CAP_SETFCAP,
-}
-
-func maybeReexec() error {
- // With Skopeo we need only the subset of the root capabilities necessary
- // for pulling an image to the storage. Do not attempt to create a namespace
- // if we already have the capabilities we need.
- capabilities, err := capability.NewPid2(0)
- if err != nil {
- return fmt.Errorf("error reading the current capabilities sets: %w", err)
- }
- for _, cap := range neededCapabilities {
- if !capabilities.Get(capability.EFFECTIVE, cap) {
- // We miss a capability we need, create a user namespaces
- unshare.MaybeReexecUsingUserNamespace(true)
- return nil
- }
- }
- return nil
-}
-
-func ReexecIfNecessaryForImages(imageNames ...string) error {
- // Check if container-storage is used before doing unshare
- for _, imageName := range imageNames {
- transport := alltransports.TransportFromImageName(imageName)
- // Hard-code the storage name to avoid a reference on c/image/storage.
- // See https://github.com/containers/skopeo/issues/771#issuecomment-563125006.
- if transport != nil && transport.Name() == "containers-storage" {
- return maybeReexec()
- }
- }
- return nil
-}
-
-
-
package operator
-
-import (
- "bufio"
- "context"
- "crypto/sha256"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- "time"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha3"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/manifest"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
-)
-
-const (
- indexJson string = "index.json"
- operatorImageExtractDir string = "hold-operator"
- workingDir string = "working-dir/"
- dockerProtocol string = "docker://"
- ociProtocol string = "oci://"
- ociProtocolTrimmed string = "oci:"
- releaseImageDir string = "release-images"
- operatorImageDir string = "operator-images"
- releaseImageExtractDir string = "hold-release"
- releaseManifests string = "release-manifests"
- imageReferences string = "image-references"
- releaseImageExtractFullPath string = releaseImageExtractDir + "/" + releaseManifests + "/" + imageReferences
- blobsDir string = "/blobs/sha256/"
- diskToMirror string = "diskToMirror"
- mirrorToDisk string = "mirrorToDisk"
- errMsg string = "[OperatorImageCollector] %v "
- logsFile string = "logs/operator.log"
-)
-
-type CollectorInterface interface {
- OperatorImageCollector(ctx context.Context) ([]string, error)
-}
-
-func New(log clog.PluggableLoggerInterface,
- config v1alpha2.ImageSetConfiguration,
- opts mirror.CopyOptions,
- mirror mirror.MirrorInterface,
- manifest manifest.ManifestInterface,
-) CollectorInterface {
- return &Collector{Log: log, Config: config, Opts: opts, Mirror: mirror, Manifest: manifest}
-}
-
-type Collector struct {
- Log clog.PluggableLoggerInterface
- Mirror mirror.MirrorInterface
- Manifest manifest.ManifestInterface
- Config v1alpha2.ImageSetConfiguration
- Opts mirror.CopyOptions
-}
-
-// OperatorImageCollector - this looks into the operator index image
-// taking into account the mode we are in (mirrorToDisk, diskToMirror)
-// the image is downloaded (oci format) and the index.json is inspected
-// once unmarshalled, the links to manifests are inspected
-func (o *Collector) OperatorImageCollector(ctx context.Context) ([]string, error) {
-
- var allImages []string
- compare := make(map[string]v1alpha3.ISCPackage)
- relatedImages := make(map[string][]v1alpha3.RelatedImage)
- label := "configs"
- if !strings.Contains(o.Opts.Destination, ociProtocol) && !strings.Contains(o.Opts.Destination, dockerProtocol) {
- return []string{}, fmt.Errorf(errMsg, "destination must use oci:// or docker:// prefix")
- }
-
- // compile a map to compare channels,min & max versions
- for _, ops := range o.Config.Mirror.Operators {
- o.Log.Info("isc operators: %s\n", ops.Catalog)
- for _, pkg := range ops.Packages {
- o.Log.Info("catalog packages: %s \n", pkg.Name)
- for _, channel := range pkg.Channels {
- compare[pkg.Name] = v1alpha3.ISCPackage{Channel: channel.Name, MinVersion: channel.MinVersion, MaxVersion: channel.MaxVersion}
- o.Log.Info("channels: %v \n", compare)
- }
- }
- }
-
- // check the mode
- if o.Opts.Mode == mirrorToDisk {
- f, err := os.Create(logsFile)
- if err != nil {
- o.Log.Error(errMsg, err)
- }
- writer := bufio.NewWriter(f)
- defer f.Close()
- for _, op := range o.Config.Mirror.Operators {
-
- if !o.Opts.Dev {
- // download the operator index image
- o.Log.Info("copying operator image %v", op.Catalog)
- src := dockerProtocol + op.Catalog
- dest := ociProtocolTrimmed + o.Opts.Global.Dir + "/" + operatorImageDir
- err := o.Mirror.Run(ctx, src, dest, "copy", &o.Opts, *writer)
- writer.Flush()
- if err != nil {
- o.Log.Error(errMsg, err)
- }
- // read the logs
- f, _ := os.ReadFile(logsFile)
- lines := strings.Split(string(f), "\n")
- for _, s := range lines {
- if len(s) > 0 {
- o.Log.Debug("%s ", strings.ToLower(s))
- }
- }
-
- // it's in oci format so we can go directly to the index.json file
- oci, err := o.Manifest.GetImageIndex(o.Opts.Global.Dir + "/" + operatorImageDir)
- if err != nil {
- return []string{}, err
- }
-
- //read the link to the manifest
- if len(oci.Manifests) == 0 {
- return []string{}, fmt.Errorf("[OperatorImageCollector] no manifests found for %s ", op.Catalog)
- } else {
- if !strings.Contains(oci.Manifests[0].Digest, "sha256") {
- return []string{}, fmt.Errorf("[OperatorImageCollector] the disgets seems to incorrect for %s ", op.Catalog)
- }
- }
- manifest := strings.Split(oci.Manifests[0].Digest, ":")[1]
- o.Log.Info("manifest %v", manifest)
-
- // read the operator image manifest
- oci, err = o.Manifest.GetImageManifest(o.Opts.Global.Dir + "/" + operatorImageDir + blobsDir + manifest)
- if err != nil {
- return []string{}, err
- }
-
- // read the config digest to get the detailed manifest
- // looking for the lable to search for a specific folder
- ocs, err := o.Manifest.GetOperatorConfig(o.Opts.Global.Dir + "/" + operatorImageDir + blobsDir + strings.Split(oci.Config.Digest, ":")[1])
- if err != nil {
- return []string{}, err
- }
-
- label = ocs.Config.Labels.OperatorsOperatorframeworkIoIndexConfigsV1
- o.Log.Info("label %s", label)
-
- // untar all the blobs for the operator
- // if the layer with "label (from previous step) is found to a specific folder"
- err = o.Manifest.ExtractLayersOCI(o.Opts.Global.Dir+"/"+operatorImageDir+blobsDir, o.Opts.Global.Dir+"/"+operatorImageExtractDir, label, oci)
- if err != nil {
- return []string{}, err
- }
- }
-
- // select all packages
- // this is the equivalent of the headOnly mode
- // only the latest version of each operator will be selected
- if len(op.Packages) == 0 {
- relatedImages, err = o.Manifest.GetRelatedImagesFromCatalog(o.Opts.Global.Dir+"/"+operatorImageExtractDir, label)
- if err != nil {
- return []string{}, err
- }
- } else {
- // iterate through each package
- relatedImages, err = o.Manifest.GetRelatedImagesFromCatalogByFilter(o.Opts.Global.Dir+"/"+operatorImageExtractDir, label, op, compare)
- if err != nil {
- return []string{}, err
- }
- }
- }
- o.Log.Info("related images length %d ", len(relatedImages))
- var count = 0
- for _, v := range relatedImages {
- count = count + len(v)
- }
- o.Log.Info("images to copy (before duplicates) %d ", count)
-
- // remove all duplicates
- var cleanedImages []v1alpha3.RelatedImage
- imgs := cleanDuplicates(relatedImages)
- o.Log.Trace("flatenned %v ", imgs)
- o.Log.Debug("images to copy")
- for k, v := range imgs {
- o.Log.Debug(" name %s", v)
- o.Log.Debug(" image %s", k)
- cleanedImages = append(cleanedImages, v1alpha3.RelatedImage{Name: v, Image: k})
- }
- allImages, err = batchWorkerConverter(o.Log, o.Opts.Global.Dir, cleanedImages)
- if err != nil {
- return []string{}, err
- }
- }
-
- if o.Opts.Mode == diskToMirror {
- if len(o.Opts.Global.From) == 0 {
- return []string{}, fmt.Errorf(errMsg, "in diskToMirror mode please use the --from flag")
- }
- // check the directory to copy
- regex, e := regexp.Compile(indexJson)
- if e != nil {
- o.Log.Error("%v", e)
- }
- e = filepath.Walk(workingDir+"/"+o.Opts.Global.From+"/"+operatorImageDir, func(path string, info os.FileInfo, err error) error {
- if err == nil && regex.MatchString(info.Name()) {
- ns := strings.Split(filepath.Dir(path), operatorImageDir)
- if len(ns) == 0 {
- return fmt.Errorf(errMsg+"%s", "no directory found for operator-images ", path)
- } else {
- name := strings.Split(ns[1], "/")
- if len(name) != 3 {
- return fmt.Errorf(errMsg+"%s", "operator name and related compents are incorrect", name)
- }
- src := ociProtocolTrimmed + ns[0] + operatorImageDir + "/" + name[1] + "/" + name[2]
- dest := o.Opts.Destination + "/" + name[1]
- allImages = append(allImages, src+"*"+dest)
- }
- }
- return nil
- })
- if e != nil {
- return []string{}, e
- }
- }
- return allImages, nil
-}
-
-// cleanDuplicates - simple utility to remove duplicates
-func cleanDuplicates(m map[string][]v1alpha3.RelatedImage) map[string]string {
- x := make(map[string]string)
- for _, v := range m {
- for _, ri := range v {
- x[ri.Image] = ri.Name
- }
- }
- return x
-}
-
-// customImageParser - simple image string parser
-func customImageParser(image string) (*v1alpha3.ImageRefSchema, error) {
- var irs *v1alpha3.ImageRefSchema
- var component string
- parts := strings.Split(image, "/")
- if len(parts) < 3 {
- return irs, fmt.Errorf("[customImageParser] image url seems to be wrong %s ", image)
- }
- if strings.Contains(parts[2], "@") {
- component = strings.Split(parts[2], "@")[0]
- } else {
- component = parts[2]
- }
- irs = &v1alpha3.ImageRefSchema{Repository: parts[0], Namespace: parts[1], Component: component}
- return irs, nil
-}
-
-// batchWorkerConverter convert RelatedImages to strings for batch worker
-func batchWorkerConverter(log clog.PluggableLoggerInterface, dir string, images []v1alpha3.RelatedImage) ([]string, error) {
- var result []string
- for _, img := range images {
- irs, err := customImageParser(img.Image)
- if err != nil {
- log.Error("[batchWorkerConverter] %v", err)
- return result, err
- }
- err = os.MkdirAll(dir+"/"+operatorImageDir+"/"+irs.Namespace, 0750)
- if err != nil {
- log.Error("[batchWorkerConverter] %v", err)
- return result, err
- }
- src := dockerProtocol + img.Image
- if len(img.Name) == 0 {
- timestamp := time.Now().Unix()
- s := fmt.Sprintf("%d", timestamp)
- img.Name = fmt.Sprintf("%x", sha256.Sum256([]byte(s)))[:6]
- }
- dest := ociProtocolTrimmed + dir + "/" + operatorImageDir + "/" + irs.Namespace + "/" + img.Name
- log.Debug("source %s ", img.Image)
- log.Debug("destination %s ", dir+"/"+operatorImageDir+"/"+irs.Namespace+"/"+img.Name)
- result = append(result, src+"*"+dest)
- }
- return result, nil
-}
-
-
-
package release
-
-import (
- "context"
- "fmt"
-
- "github.com/blang/semver/v4"
- "github.com/google/uuid"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
-)
-
-type CincinnatiInterface interface {
- GetReleaseReferenceImages(ctx context.Context) map[string]struct{}
- NewOCPClient(uuid uuid.UUID) (Client, error)
- NewOKDClient(uuid uuid.UUID) (Client, error)
-}
-
-func NewCincinnati(log clog.PluggableLoggerInterface, config *v1alpha2.ImageSetConfiguration, opts *mirror.CopyOptions, c Client, b bool) CincinnatiInterface {
- return &CincinnatiSchema{Log: log, Config: config, Opts: opts, Client: c, Fail: b}
-}
-
-type CincinnatiSchema struct {
- Log clog.PluggableLoggerInterface
- Config *v1alpha2.ImageSetConfiguration
- Opts *mirror.CopyOptions
- Client Client
- Fail bool
-}
-
-func (o *CincinnatiSchema) NewOCPClient(uuid uuid.UUID) (Client, error) {
- if o.Fail {
- return o.Client, fmt.Errorf("forced cincinnati error")
- }
- return o.Client, nil
-}
-
-func (o *CincinnatiSchema) NewOKDClient(uuid uuid.UUID) (Client, error) {
- return o.Client, nil
-}
-
-func (o *CincinnatiSchema) GetReleaseReferenceImages(ctx context.Context) map[string]struct{} {
-
- var (
- releaseDownloads = downloads{}
- errs = []error{}
- )
-
- for _, arch := range o.Config.Mirror.Platform.Architectures {
-
- versionsByChannel := make(map[string]v1alpha2.ReleaseChannel, len(o.Config.Mirror.Platform.Channels))
-
- for _, ch := range o.Config.Mirror.Platform.Channels {
-
- var client Client //client := o.Client
- var err error
- switch ch.Type {
- case v1alpha2.TypeOCP:
- client, err = o.NewOCPClient(o.Opts.UUID)
- if err != nil {
- errs = append(errs, err)
- }
- case v1alpha2.TypeOKD:
- client, err = o.NewOKDClient(o.Opts.UUID)
- if err != nil {
- errs = append(errs, err)
- }
- default:
- errs = append(errs, fmt.Errorf("invalid platform type %v", ch.Type))
- continue
- }
- if err != nil {
- errs = append(errs, err)
- continue
- }
-
- if len(ch.MaxVersion) == 0 || len(ch.MinVersion) == 0 {
-
- // Find channel maximum value and only set the minimum as well if heads-only is true
- if len(ch.MaxVersion) == 0 {
- latest, err := GetChannelMinOrMax(ctx, client, arch, ch.Name, false)
- if err != nil {
- errs = append(errs, err)
- continue
- }
-
- // Update version to release channel
- ch.MaxVersion = latest.String()
- o.Log.Info("detected minimum version as %s", ch.MaxVersion)
- if len(ch.MinVersion) == 0 && ch.IsHeadsOnly() {
- //min, found := prevChannels[ch.Name]
- //if !found {
- // Starting at a new headsOnly channels
- min := latest.String()
- //}
- ch.MinVersion = min
- o.Log.Info("detected minimum version as %s\n", ch.MinVersion)
- }
- }
-
- // Find channel minimum if full is true or just the minimum is not set
- // in the config
- if len(ch.MinVersion) == 0 {
- first, err := GetChannelMinOrMax(ctx, client, arch, ch.Name, true)
- if err != nil {
- errs = append(errs, err)
- continue
- }
- ch.MinVersion = first.String()
- o.Log.Info("detected minimum version as %s\n", ch.MinVersion)
- }
- versionsByChannel[ch.Name] = ch
- } else {
- // Range is set. Ensure full is true so this
- // is skipped when processing release metadata.
- o.Log.Info("processing minimum version %s and maximum version %s\n", ch.MinVersion, ch.MaxVersion)
- ch.Full = true
- versionsByChannel[ch.Name] = ch
- }
-
- downloads, err := getChannelDownloads(ctx, o.Log, client, nil, ch, arch)
- if err != nil {
- errs = append(errs, err)
- continue
- }
- releaseDownloads.Merge(downloads)
- }
-
- // Update cfg release channels with maximum and minimum versions
- // if applicable
- for i, ch := range o.Config.Mirror.Platform.Channels {
- ch, found := versionsByChannel[ch.Name]
- if found {
- o.Config.Mirror.Platform.Channels[i] = ch
- }
- }
-
- if len(o.Config.Mirror.Platform.Channels) > 1 {
- client, err := NewOCPClient(o.Opts.UUID)
- if err != nil {
- errs = append(errs, err)
- continue
- }
- newDownloads, err := getCrossChannelDownloads(ctx, o.Log, client, arch, o.Config.Mirror.Platform.Channels)
- if err != nil {
- errs = append(errs, fmt.Errorf("error calculating cross channel upgrades: %v", err))
- continue
- }
- releaseDownloads.Merge(newDownloads)
- }
- }
- for _, e := range errs {
- o.Log.Error("error list %v ", e)
- }
- return releaseDownloads
-}
-
-type downloads map[string]struct{}
-
-func (d downloads) Merge(in downloads) {
- for k, v := range in {
- _, ok := d[k]
- if ok {
- //fmt.Printf("download %s exists", k)
- continue
- }
- d[k] = v
- }
-}
-
-//var b []byte
-
-// getDownloads will prepare the downloads map for mirroring
-func getChannelDownloads(ctx context.Context, log clog.PluggableLoggerInterface, c Client, lastChannels []v1alpha2.ReleaseChannel, channel v1alpha2.ReleaseChannel, arch string) (downloads, error) {
- allDownloads := downloads{}
-
- var prevChannel v1alpha2.ReleaseChannel
- for _, ch := range lastChannels {
- if ch.Name == channel.Name {
- prevChannel = ch
- }
- }
- log.Trace("previous channel %v", prevChannel)
- // Plot between min and max of channel
- first, err := semver.Parse(channel.MinVersion)
- if err != nil {
- return allDownloads, err
- }
- last, err := semver.Parse(channel.MaxVersion)
- if err != nil {
- return allDownloads, err
- }
-
- var newDownloads downloads
- if channel.ShortestPath {
- current, newest, updates, err := CalculateUpgrades(ctx, c, arch, channel.Name, channel.Name, first, last)
- if err != nil {
- return allDownloads, err
- }
- newDownloads = gatherUpdates(log, current, newest, updates)
-
- } else {
- lowRange, err := semver.ParseRange(fmt.Sprintf(">=%s", first))
- if err != nil {
- return allDownloads, err
- }
- highRange, err := semver.ParseRange(fmt.Sprintf("<=%s", last))
- if err != nil {
- return allDownloads, err
- }
- versions, err := GetUpdatesInRange(ctx, c, channel.Name, arch, highRange.AND(lowRange))
- if err != nil {
- return allDownloads, err
- }
- newDownloads = gatherUpdates(log, Update{}, Update{}, versions)
- }
- allDownloads.Merge(newDownloads)
-
- return allDownloads, nil
-}
-
-// getCrossChannelDownloads will determine required downloads between channel versions (for OCP only)
-func getCrossChannelDownloads(ctx context.Context, log clog.PluggableLoggerInterface, ocpClient Client, arch string, channels []v1alpha2.ReleaseChannel) (downloads, error) {
- // Strip any OKD channels from the list
-
- var ocpChannels []v1alpha2.ReleaseChannel
- for _, ch := range channels {
- if ch.Type == v1alpha2.TypeOCP {
- ocpChannels = append(ocpChannels, ch)
- }
- }
- // If no other channels exist, return no downloads
- if len(ocpChannels) == 0 {
- return downloads{}, nil
- }
-
- firstCh, first, err := FindRelease(ocpChannels, true)
- if err != nil {
- return downloads{}, fmt.Errorf("failed to find minimum release version: %v", err)
- }
- lastCh, last, err := FindRelease(ocpChannels, false)
- if err != nil {
- return downloads{}, fmt.Errorf("failed to find maximum release version: %v", err)
- }
- current, newest, updates, err := CalculateUpgrades(ctx, ocpClient, arch, firstCh, lastCh, first, last)
- if err != nil {
- return downloads{}, fmt.Errorf("failed to get upgrade graph: %v", err)
- }
- return gatherUpdates(log, current, newest, updates), nil
-}
-
-// gatherUpdates
-func gatherUpdates(log clog.PluggableLoggerInterface, current, newest Update, updates []Update) downloads {
- releaseDownloads := downloads{}
- for _, update := range updates {
- log.Info("Found update %s\n", update.Version)
- releaseDownloads[update.Image] = struct{}{}
- }
-
- if current.Image != "" {
- releaseDownloads[current.Image] = struct{}{}
- }
-
- if newest.Image != "" {
- releaseDownloads[newest.Image] = struct{}{}
- }
-
- return releaseDownloads
-}
-
-
-
package release
-
-import (
- "crypto/tls"
- "crypto/x509"
- "net/http"
- "net/url"
- "os"
-
- "github.com/google/uuid"
- "k8s.io/klog/v2"
-)
-
-// Client is a Cincinnati client which can be used to fetch update graphs from
-// an upstream Cincinnati stack.
-type Client interface {
- GetURL() *url.URL
- SetQueryParams(arch, channel, version string)
- GetID() uuid.UUID
- GetTransport() *http.Transport
-}
-
-var _ Client = &ocpClient{}
-
-type ocpClient struct {
- id uuid.UUID
- transport *http.Transport
- url url.URL
-}
-
-// NewOCPClient creates a new OCP Cincinnati client with the given client identifier.
-func NewOCPClient(id uuid.UUID) (Client, error) {
- var updateGraphURL string
- if updateURLOverride := os.Getenv("UPDATE_URL_OVERRIDE"); len(updateURLOverride) != 0 {
- klog.Info("Usage of the UPDATE_URL_OVERRIDE environment variable is unsupported")
- updateGraphURL = updateURLOverride
- } else {
- updateGraphURL = UpdateURL
- }
- upstream, err := url.Parse(updateGraphURL)
- if err != nil {
- return &ocpClient{}, err
- }
-
- tls, err := getTLSConfig()
- if err != nil {
- return &ocpClient{}, err
- }
-
- transport := &http.Transport{
- TLSClientConfig: tls,
- Proxy: http.ProxyFromEnvironment,
- }
- return &ocpClient{id: id, transport: transport, url: *upstream}, nil
-}
-
-func (c *ocpClient) GetURL() *url.URL {
- return &c.url
-}
-
-func (c *ocpClient) GetTransport() *http.Transport {
- return c.transport
-}
-
-func (c *ocpClient) GetID() uuid.UUID {
- return c.id
-}
-
-func (c *ocpClient) SetQueryParams(arch, channel, version string) {
- queryParams := c.url.Query()
- queryParams.Add("id", c.id.String())
- params := map[string]string{
- "arch": arch,
- "channel": channel,
- "version": version,
- }
- for key, value := range params {
- if value != "" {
- queryParams.Add(key, value)
- }
- }
- c.url.RawQuery = queryParams.Encode()
-}
-
-var _ Client = &okdClient{}
-
-type okdClient struct {
- id uuid.UUID
- transport *http.Transport
- url url.URL
-}
-
-// NewOKDClient creates a new OKD Cincinnati client with the given client identifier.
-func NewOKDClient(id uuid.UUID) (Client, error) {
- upstream, err := url.Parse(OkdUpdateURL)
- if err != nil {
- return &okdClient{}, err
- }
-
- tls, err := getTLSConfig()
- if err != nil {
- return &okdClient{}, err
- }
-
- transport := &http.Transport{
- TLSClientConfig: tls,
- Proxy: http.ProxyFromEnvironment,
- }
- return &okdClient{id: id, transport: transport, url: *upstream}, nil
-}
-
-func (c *okdClient) GetURL() *url.URL {
- return &c.url
-}
-
-func (c *okdClient) GetID() uuid.UUID {
- return c.id
-}
-
-func (c *okdClient) GetTransport() *http.Transport {
- return c.transport
-}
-
-func (c *okdClient) SetQueryParams(_, _, _ string) {
- // Do nothing
-}
-
-func getTLSConfig() (*tls.Config, error) {
- certPool, err := x509.SystemCertPool()
- if err != nil {
- return nil, err
- }
- config := &tls.Config{
- RootCAs: certPool,
- MinVersion: tls.VersionTLS12,
- }
- return config, nil
-}
-
-
-
package release
-
-import (
- "bufio"
- "context"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha3"
- clog "github.com/lmzuccarelli/golang-oci-mirror/pkg/log"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/manifest"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/mirror"
-)
-
-const (
- indexJson string = "index.json"
- operatorImageExtractDir string = "hold-operator"
- workingDir string = "working-dir/"
- dockerProtocol string = "docker://"
- ociProtocol string = "oci://"
- ociProtocolTrimmed string = "oci:"
- releaseImageDir string = "release-images"
- operatorImageDir string = "operator-images"
- releaseImageExtractDir string = "hold-release"
- releaseManifests string = "release-manifests"
- imageReferences string = "image-references"
- releaseImageExtractFullPath string = releaseImageExtractDir + "/" + releaseManifests + "/" + imageReferences
- blobsDir string = "/blobs/sha256/"
- errMsg string = "[ReleaseImageCollector] %v "
- diskToMirror string = "diskToMirror"
- mirrorToDisk string = "mirrorToDisk"
- logFile string = "logs/release.log"
-)
-
-type CollectorInterface interface {
- ReleaseImageCollector(ctx context.Context) ([]string, error)
-}
-
-func New(log clog.PluggableLoggerInterface,
- config v1alpha2.ImageSetConfiguration,
- opts mirror.CopyOptions,
- mirror mirror.MirrorInterface,
- manifest manifest.ManifestInterface,
- cincinnati CincinnatiInterface,
-) CollectorInterface {
- return &Collector{Log: log, Config: config, Opts: opts, Mirror: mirror, Manifest: manifest, Cincinnati: cincinnati}
-}
-
-type Collector struct {
- Log clog.PluggableLoggerInterface
- Mirror mirror.MirrorInterface
- Manifest manifest.ManifestInterface
- Config v1alpha2.ImageSetConfiguration
- Opts mirror.CopyOptions
- Cincinnati CincinnatiInterface
-}
-
-// ReleaseImageCollector - this looks into the operator index image
-// taking into account the mode we are in (mirrorToDisk, diskToMirror)
-// the image is downloaded (oci format) and the index.json is inspected
-// once unmarshalled, the links to manifests are inspected
-func (o *Collector) ReleaseImageCollector(ctx context.Context) ([]string, error) {
-
- var allImages []string
-
- if o.Opts.Mode == mirrorToDisk {
- releases := o.Cincinnati.GetReleaseReferenceImages(ctx)
- f, err := os.Create(logFile)
- if err != nil {
- o.Log.Error("[ReleaseImageCollector] %v", err)
- }
- if !strings.Contains(o.Opts.Destination, ociProtocol) {
- return []string{}, fmt.Errorf(" [ReleaseImageCollector] destination must use oci: or docker:// prefix")
- }
-
- writer := bufio.NewWriter(f)
- defer f.Close()
- // dev mode debugging
- if !o.Opts.Dev {
- for key := range releases {
- o.Log.Info("copying image %s ", key)
- src := dockerProtocol + key
- dest := ociProtocolTrimmed + o.Opts.Global.Dir + "/" + releaseImageDir
- err := o.Mirror.Run(ctx, src, dest, "copy", &o.Opts, *writer)
- if err != nil {
- return []string{}, fmt.Errorf(errMsg, err)
- }
- o.Log.Debug("copied release index image %s ", key)
-
- // TODO: create common function read the logs
- f, _ := os.ReadFile(logFile)
- lines := strings.Split(string(f), "\n")
- for _, s := range lines {
- if len(s) > 0 {
- o.Log.Debug(" %s ", strings.ToLower(s))
- }
- }
- }
- }
-
- oci, err := o.Manifest.GetImageIndex(o.Opts.Global.Dir + "/" + releaseImageDir)
- if err != nil {
- o.Log.Error("[ReleaseImageCollector] %v ", err)
- return []string{}, fmt.Errorf(errMsg, err)
- }
-
- //read the link to the manifest
- if len(oci.Manifests) == 0 {
- return []string{}, fmt.Errorf(errMsg, "image index not found ")
- }
- manifest := strings.Split(oci.Manifests[0].Digest, ":")[1]
- o.Log.Debug("image index %v", manifest)
-
- oci, err = o.Manifest.GetImageManifest(o.Opts.Global.Dir + "/" + releaseImageDir + blobsDir + manifest)
- if err != nil {
- return []string{}, fmt.Errorf(errMsg, err)
- }
- o.Log.Debug("manifest %v ", oci.Config.Digest)
-
- err = o.Manifest.ExtractLayersOCI(o.Opts.Global.Dir+"/"+releaseImageDir+blobsDir, o.Opts.Global.Dir+"/"+releaseImageExtractDir, releaseManifests, oci)
- if err != nil {
- return []string{}, fmt.Errorf(errMsg, err)
- }
- o.Log.Debug("extracted oci layer %s ", workingDir+releaseImageExtractDir)
-
- allRelatedImages, err := o.Manifest.GetReleaseSchema(o.Opts.Global.Dir + "/" + releaseImageExtractFullPath)
- if err != nil {
- return []string{}, fmt.Errorf(errMsg, err)
- }
- allImages, err = batcWorkerConverter(o.Log, o.Opts.Global.Dir, allRelatedImages)
- if err != nil {
- return []string{}, fmt.Errorf(errMsg, err)
- }
- }
- if o.Opts.Mode == diskToMirror {
- if len(o.Opts.Global.From) == 0 {
- return []string{}, fmt.Errorf(errMsg, "in diskToMirror mode please use the --from flag")
- }
- // check the directory to copy
- regex, e := regexp.Compile(indexJson)
- if e != nil {
- o.Log.Error(errMsg, e)
- }
- e = filepath.Walk(workingDir+"/"+o.Opts.Global.From+"/"+releaseImageDir, func(path string, info os.FileInfo, err error) error {
- if err == nil && regex.MatchString(info.Name()) {
- ns := strings.Split(filepath.Dir(path), releaseImageDir)
- if len(ns) == 0 {
- return fmt.Errorf(errMsg, "no directory found for operator-images - please verify")
- } else {
- name := strings.Split(ns[1], "/")
- if len(name) != 2 {
- return fmt.Errorf(errMsg+" %s ", "operator name and related compents are incorrect", name)
- }
- src := ociProtocolTrimmed + ns[0] + releaseImageDir + "/" + name[1]
- dest := o.Opts.Destination + "/" + name[1]
- allImages = append(allImages, src+"*"+dest)
- }
- }
- return nil
- })
- if e != nil {
- return []string{}, e
- }
- }
-
- return allImages, nil
-
-}
-
-// batchWorkerConverter convert RelatedImages to strings for batch worker
-func batcWorkerConverter(log clog.PluggableLoggerInterface, dir string, images []v1alpha3.RelatedImage) ([]string, error) {
- var result []string
- for _, img := range images {
- src := dockerProtocol + img.Image
- dest := ociProtocolTrimmed + dir + "/" + releaseImageDir + "/" + img.Name
- err := os.MkdirAll(dir+"/"+releaseImageDir+"/"+img.Name, 0750)
- if err != nil {
- log.Error("[batchWorkerConverter] %v", err)
- return []string{}, err
- }
- log.Debug("source %s ", src)
- log.Debug("destination %s ", dest)
- result = append(result, src+"*"+dest)
- }
- return result, nil
-}
-
-
-
package release
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "os"
- "regexp"
- "sort"
- "strings"
- "time"
-
- "github.com/blang/semver/v4"
- "k8s.io/klog/v2"
-)
-
-const (
- // GraphMediaType is the media-type specified in the HTTP Accept header
- // of requests sent to the Cincinnati-v1 Graph API.
- GraphMediaType = "application/json"
-
- // Timeout when calling upstream Cincinnati stack.
- getUpdatesTimeout = time.Minute * 60
- // UpdateURL is the Cincinnati endpoint for the OpenShift platform.
- UpdateURL = "https://api.openshift.com/api/upgrades_info/v1/graph"
- // OkdUpdateURL is the Cincinnati endpoint for the OKD platform.
- OkdUpdateURL = "https://origin-release.ci.openshift.org/graph"
-
- ChannelInfo = "channel %q: %v"
-)
-
-// Error is returned when are unable to get updates.
-type Error struct {
- // Reason is the reason suggested for the Cincinnati calculation error.
- Reason string
-
- // Message is the message suggested for Cincinnati calculation error..
- Message string
-
- // cause is the upstream error, if any, being wrapped by this error.
- cause error
-}
-
-// Error serializes the error as a string, to satisfy the error interface.
-func (err *Error) Error() string {
- return fmt.Sprintf("%s: %s", err.Reason, err.Message)
-}
-
-// Update is a single node from the update graph.
-type Update node
-
-// GetUpdates fetches the requested update payload from the specified
-// upstream Cincinnati stack given the current version, architecture, and channel.
-// The shortest path is calculated between the current and requested version from the graph edge
-// data.
-func GetUpdates(ctx context.Context, c Client, arch string, channel string, version semver.Version, reqVer semver.Version) (Update, Update, []Update, error) {
- var current Update
- var requested Update
- // Prepare parametrized cincinnati query.
- c.SetQueryParams(arch, channel, version.String())
-
- graph, err := getGraphData(ctx, c)
- if err != nil {
- return Update{}, Update{}, nil, &Error{
- Reason: "APIRequestError",
- Message: fmt.Sprintf("version %s in channel %s: %v", version.String(), channel, err),
- cause: err,
- }
- }
-
- // Find the current version within the graph.
- var currentIdx int
- found := false
- for i, node := range graph.Nodes {
- if version.EQ(node.Version) {
- currentIdx = i
- current = Update(graph.Nodes[i])
- found = true
- break
- }
- }
- if !found {
- return current, requested, nil, &Error{
- Reason: "VersionNotFound",
- Message: fmt.Sprintf("current version %s not found in the %q channel", version, channel),
- }
- }
-
- var destinationIdx int
- found = false
- for i, node := range graph.Nodes {
- if reqVer.EQ(node.Version) {
- destinationIdx = i
- requested = Update(graph.Nodes[i])
- found = true
- break
- }
- }
- if !found {
- return current, requested, nil, &Error{
- Reason: "VersionNotFound",
- Message: fmt.Sprintf("requested version %s not found in the %q channel", reqVer, channel),
- }
- }
-
- edgesByOrigin := make(map[int][]int, len(graph.Nodes))
- for _, edge := range graph.Edges {
- edgesByOrigin[edge.Origin] = append(edgesByOrigin[edge.Origin], edge.Destination)
- }
-
- // Sort destination by semver to ensure deterministic result
- for origin, destinations := range edgesByOrigin {
- sort.Slice(destinations, func(i, j int) bool {
- return graph.Nodes[destinations[i]].Version.GT(graph.Nodes[destinations[j]].Version)
- })
- edgesByOrigin[origin] = destinations
- }
-
- shortestPath := func(g map[int][]int, start, end int) []int {
- prev := map[int]int{}
- visited := map[int]struct{}{}
- queue := []int{start}
- visited[start] = struct{}{}
- prev[start] = -1
-
- for len(queue) > 0 {
- node := queue[0]
- queue = queue[1:]
- if node == end {
- break
- }
-
- for _, neighbor := range g[node] {
- if _, ok := visited[neighbor]; !ok {
- prev[neighbor] = node
- queue = append(queue, neighbor)
- visited[neighbor] = struct{}{}
- }
- }
- }
-
- // No path to end
- if _, ok := visited[end]; !ok {
- return []int{}
- }
-
- path := []int{end}
- for next := prev[end]; next != -1; next = prev[next] {
- path = append(path, next)
- }
-
- // Reverse path.
- for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
- path[i], path[j] = path[j], path[i]
- }
-
- return path
- }
-
- nextIdxs := shortestPath(edgesByOrigin, currentIdx, destinationIdx)
-
- var updates []Update
- for _, i := range nextIdxs {
- updates = append(updates, Update(graph.Nodes[i]))
- }
-
- return current, requested, updates, nil
-}
-
-// CalculateUpgrades fetches and calculates all the update payloads from the specified
-// upstream Cincinnati stack given the current and target version and channel.
-func CalculateUpgrades(ctx context.Context, c Client, arch, sourceChannel, targetChannel string, startVer, reqVer semver.Version) (Update, Update, []Update, error) {
- if sourceChannel == targetChannel {
- return GetUpdates(ctx, c, arch, targetChannel, startVer, reqVer)
- }
-
- // Check the major and minor versions are the same with different
- // channel prefixes
- source, target, _, err := getSemverFromChannels(sourceChannel, targetChannel)
- if err != nil {
- return Update{}, Update{}, nil, err
- }
- if source.EQ(target) {
- isBlocked, err := handleBlockedEdges(ctx, c, arch, targetChannel, startVer)
- if err != nil {
- return Update{}, Update{}, nil, err
- }
- if isBlocked {
- // If blocked path is found, just return the requested version and any accumulated
- // upgrades to the caller
- klog.Warningf("No upgrade path for %s in target channel %s", startVer.String(), targetChannel)
- return GetUpdates(ctx, c, arch, targetChannel, reqVer, reqVer)
- }
- return GetUpdates(ctx, c, arch, targetChannel, startVer, reqVer)
- }
-
- // Perform initial calculation for the source channel and
- // recurse through the rest until the target or a blocked
- // edge is hit.
- latest, err := GetChannelMinOrMax(ctx, c, arch, sourceChannel, false)
- if err != nil {
- return Update{}, Update{}, nil, fmt.Errorf(ChannelInfo, sourceChannel, err)
- }
- current, _, upgrades, err := GetUpdates(ctx, c, arch, sourceChannel, startVer, latest)
- if err != nil {
- return Update{}, Update{}, nil, fmt.Errorf(ChannelInfo, sourceChannel, err)
- }
-
- requested, newUpgrades, err := calculate(ctx, c, arch, sourceChannel, targetChannel, latest, reqVer)
- if err != nil {
- return Update{}, Update{}, nil, err
- }
- upgrades = append(upgrades, newUpgrades...)
-
- var finalUpgrades []Update
- seen := make(map[string]struct{}, len(upgrades))
- for _, upgrade := range upgrades {
- if _, ok := seen[upgrade.Image]; !ok {
- finalUpgrades = append(finalUpgrades, upgrade)
- seen[upgrade.Image] = struct{}{}
- }
- }
-
- return current, requested, finalUpgrades, nil
-}
-
-// calculate will calculate Cincinnati upgrades between channels by finding the latest versions in the source channels
-// and incrementing the minor version until the target channel is reached.
-func calculate(ctx context.Context, c Client, arch, sourceChannel, targetChannel string, startVer, reqVer semver.Version) (requested Update, upgrades []Update, err error) {
- source, target, prefix, err := getSemverFromChannels(sourceChannel, targetChannel)
- if err != nil {
- return requested, upgrades, err
- }
- // We immediately bump the source channel since current source channel upgrades have
- // already been calculated
- source.Minor++
- currChannel := fmt.Sprintf("%s-%v.%v", prefix, source.Major, source.Minor)
-
- var targetVer semver.Version
- if source.EQ(target) {
- // If this is the target channel major.minor get
- // requested version, so we don't exceed the maximum version
- // Set the target channel to make sure we have the intended
- // channel prefix
- targetVer = reqVer
- currChannel = targetChannel
- } else {
- targetVer, err = GetChannelMinOrMax(ctx, c, arch, currChannel, false)
- if err != nil {
- return requested, upgrades, err
- }
- }
-
- isBlocked, err := handleBlockedEdges(ctx, c, arch, currChannel, startVer)
- if err != nil {
- return requested, upgrades, err
- }
- if isBlocked {
- // If blocked path is found, just return the requested version and any accumulated
- // upgrades to the caller
- _, requested, _, err = GetUpdates(ctx, c, arch, targetChannel, targetVer, targetVer)
- //Warnf is 5?
- klog.Warningf("No upgrade path for %s in target channel %s", startVer.String(), targetChannel)
- return requested, upgrades, err
- }
-
- klog.V(1).Infof("Getting updates for version %s in channel %s", startVer.String(), currChannel)
- _, requested, upgrades, err = GetUpdates(ctx, c, arch, currChannel, startVer, targetVer)
- if err != nil {
- return requested, upgrades, err
- }
-
- if source.EQ(target) {
- return requested, upgrades, nil
- }
-
- currRequested, currUpgrades, err := calculate(ctx, c, arch, currChannel, targetChannel, targetVer, reqVer)
- if err != nil {
- return requested, upgrades, err
- }
- requested = currRequested
- upgrades = append(upgrades, currUpgrades...)
-
- return requested, upgrades, nil
-}
-
-// handleBlockedEdges will check for the starting version in the current channel
-// if it does not exist the version is blocked.
-func handleBlockedEdges(ctx context.Context, c Client, arch, targetChannel string, startVer semver.Version) (bool, error) {
- chanVersions, err := GetVersions(ctx, c, arch, targetChannel)
- if err != nil {
- return true, err
- }
- for _, v := range chanVersions {
- if v.EQ(startVer) {
- return false, nil
- }
- }
- return true, nil
-}
-
-// getSemverFromChannel will return the major and minor version from the source and target channels. The prefix returned is
-// for the source channels for cross channel calculations.
-func getSemverFromChannels(sourceChannel, targetChannel string) (source, target semver.Version, prefix string, err error) {
- // Get semver representation of source and target channel versions
- sourceIdx := strings.LastIndex(sourceChannel, "-")
- if sourceIdx == -1 {
- return source, target, prefix, fmt.Errorf("invalid channel name %s", sourceChannel)
- }
- targetIdx := strings.LastIndex(targetChannel, "-")
- if targetIdx == -1 {
- return source, target, prefix, fmt.Errorf("invalid channel name %s", targetChannel)
- }
- source, err = semver.Parse(fmt.Sprintf("%s.0", sourceChannel[sourceIdx+1:]))
- if err != nil {
- return source, target, prefix, err
- }
- target, err = semver.Parse(fmt.Sprintf("%s.0", targetChannel[targetIdx+1:]))
- if err != nil {
- return source, target, prefix, err
- }
- prefix = sourceChannel[:sourceIdx]
- return source, target, prefix, nil
-}
-
-// GetChannelMinOrMax fetches the minimum or maximum version from the specified
-// upstream Cincinnati stack given architecture and channel.
-func GetChannelMinOrMax(ctx context.Context, c Client, arch string, channel string, min bool) (semver.Version, error) {
- // Prepare parametrized cincinnati query.
- c.SetQueryParams(arch, channel, "")
-
- graph, err := getGraphData(ctx, c)
- if err != nil {
- return semver.Version{}, &Error{
- Reason: "APIRequestError",
- Message: fmt.Sprintf(ChannelInfo, channel, err),
- cause: err,
- }
- }
-
- // Find the all versions within the graph.
- var versionMatcher *regexp.Regexp
- if versionFilter := os.Getenv("VERSION_FILTER"); len(versionFilter) != 0 {
- klog.Info("Usage of the VERSION_FILTER environment variable is unsupported")
- versionMatcher, err = regexp.Compile(versionFilter)
- if err != nil {
- return semver.Version{}, &Error{
- Reason: "InvalidVersionFilter",
- Message: fmt.Sprintf("Version filter '%s' is not a valid regular expression", versionFilter),
- cause: err,
- }
- }
- }
-
- var Vers []semver.Version
- for _, node := range graph.Nodes {
- if versionMatcher == nil || versionMatcher.MatchString(node.Version.String()) {
- Vers = append(Vers, node.Version)
- }
- }
-
- semver.Sort(Vers)
-
- if len(Vers) == 0 {
- return semver.Version{}, &Error{
- Reason: "NoVersionsFound",
- Message: fmt.Sprintf("no cluster versions found for %q in the %q channel", arch, channel),
- }
- }
-
- if min {
- return Vers[0], nil
- }
-
- return Vers[len(Vers)-1], nil
-}
-
-// GetChannels fetches the channels containing update payloads from the specified
-// upstream Cincinnati stack.
-func GetChannels(ctx context.Context, c Client, channel string) (map[string]struct{}, error) {
- // Prepare parametrized cincinnati query.
- c.SetQueryParams("", channel, "")
-
- graph, err := getGraphData(ctx, c)
- if err != nil {
- return nil, &Error{
- Reason: "APIRequestError",
- Message: fmt.Sprintf(ChannelInfo, channel, err),
- cause: err,
- }
- }
-
- channels := make(map[string]struct{})
-
- for _, node := range graph.Nodes {
- values := node.Metadata["io.openshift.upgrades.graph.release.channels"]
-
- for _, value := range strings.Split(values, ",") {
- channels[value] = struct{}{}
- }
- }
-
- return channels, nil
-}
-
-// GetVersions will return all update payloads from the specified
-// upstream Cincinnati stack given architecture and channel.
-func GetVersions(ctx context.Context, c Client, arch, channel string) ([]semver.Version, error) {
- // Prepare parametrized cincinnati query.
- c.SetQueryParams(arch, channel, "")
-
- graph, err := getGraphData(ctx, c)
- if err != nil {
- return nil, &Error{
- Reason: "APIRequestError",
- Message: fmt.Sprintf(ChannelInfo, channel, err),
- cause: err,
- }
- }
- // Find the all versions within the graph.
- var Vers []semver.Version
- for _, node := range graph.Nodes {
-
- Vers = append(Vers, node.Version)
- }
-
- if len(Vers) == 0 {
- return nil, &Error{
- Reason: "NoVersionsFound",
- Message: fmt.Sprintf("no cluster versions found in the %q channel", channel),
- }
- }
-
- semver.Sort(Vers)
-
- return Vers, nil
-}
-
-// GetUpdatesInRange will return all update payload within a semver range for a specified channel and architecture.
-func GetUpdatesInRange(ctx context.Context, c Client, channel, arch string, updateRange semver.Range) ([]Update, error) {
- // Prepare parametrized cincinnati query.
- c.SetQueryParams(arch, channel, "")
-
- graph, err := getGraphData(ctx, c)
- if err != nil {
- return nil, &Error{
- Reason: "APIRequestError",
- Message: fmt.Sprintf(ChannelInfo, channel, err),
- cause: err,
- }
- }
-
- // Find the all updates within the range
- var updates []Update
- for _, node := range graph.Nodes {
- if updateRange(node.Version) {
- updates = append(updates, Update(node))
- }
-
- }
- return updates, nil
-}
-
-// getGraphData fetches the update graph from the upstream Cincinnati stack given the current version and channel
-func getGraphData(ctx context.Context, c Client) (graph graph, err error) {
- transport := c.GetTransport()
- uri := c.GetURL()
- // Download the update graph.
- req, err := http.NewRequest("GET", uri.String(), nil)
- if err != nil {
- return graph, &Error{Reason: "InvalidRequest", Message: err.Error(), cause: err}
- }
- req.Header.Add("Accept", GraphMediaType)
- if transport != nil && transport.TLSClientConfig != nil {
- if c.GetTransport().TLSClientConfig.ClientCAs == nil {
- klog.V(5).Infof("Using a root CA pool with 0 root CA subjects to request updates from %s", uri)
- } //else {
- //klog.V(5).Infof("Using a root CA pool with %n root CA subjects to request updates from %s", len(transport.TLSClientConfig.RootCAs.Subjects()), uri)
- //}
- }
-
- if transport != nil && transport.Proxy != nil {
- proxy, err := transport.Proxy(req)
- if err == nil && proxy != nil {
- klog.Infof("Using proxy %s to request updates from %s", proxy.Host, uri)
- }
- }
-
- client := http.Client{}
- if transport != nil {
- client.Transport = transport
- }
- timeoutCtx, cancel := context.WithTimeout(ctx, getUpdatesTimeout)
- defer cancel()
- resp, err := client.Do(req.WithContext(timeoutCtx))
- if err != nil {
- return graph, &Error{Reason: "RemoteFailed", Message: err.Error(), cause: err}
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return graph, &Error{Reason: "ResponseFailed", Message: fmt.Sprintf("unexpected HTTP status: %s", resp.Status)}
- }
-
- // Parse the graph.
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return graph, &Error{Reason: "ResponseFailed", Message: err.Error(), cause: err}
- }
-
- if err = json.Unmarshal(body, &graph); err != nil {
- return graph, &Error{Reason: "ResponseInvalid", Message: err.Error(), cause: err}
- }
-
- return graph, nil
-}
-
-type graph struct {
- Nodes []node
- Edges []edge
-}
-
-type node struct {
- Version semver.Version `json:"version"`
- Image string `json:"payload"`
- Metadata map[string]string `json:"metadata,omitempty"`
-}
-
-type edge struct {
- Origin int
- Destination int
-}
-
-// UnmarshalJSON unmarshals an edge in the update graph. The edge's JSON
-// representation is a two-element array of indices, but Go's representation is
-// a struct with two elements so this custom unmarshal method is required.
-func (e *edge) UnmarshalJSON(data []byte) error {
- var fields []int
- if err := json.Unmarshal(data, &fields); err != nil {
- return err
- }
-
- if len(fields) != 2 {
- return fmt.Errorf("expected 2 fields, found %d", len(fields))
- }
-
- e.Origin = fields[0]
- e.Destination = fields[1]
-
- return nil
-}
-
-
-
package release
-
-import (
- "errors"
- "sort"
-
- "github.com/blang/semver/v4"
- "github.com/lmzuccarelli/golang-oci-mirror/pkg/api/v1alpha2"
-)
-
-// ErrNoPreviousRelease is returned when no releases can be found in the
-// release channels.
-var ErrNoPreviousRelease = errors.New("no previous release downloads detected")
-
-// FindRelease will find the minimum or maximum release for a set of ReleaseChannels
-func FindRelease(channels []v1alpha2.ReleaseChannel, min bool) (string, semver.Version, error) {
- vers, err := findReleases(channels, min)
- if err != nil {
- return "", semver.Version{}, err
- }
-
- keys := make([]string, 0, len(vers))
- for k := range vers {
- keys = append(keys, k)
- }
- if min {
- sort.Slice(keys, func(i, j int) bool {
- return vers[keys[i]].GT(vers[keys[j]])
- })
- } else {
- sort.Slice(keys, func(i, j int) bool {
- return vers[keys[i]].LT(vers[keys[j]])
- })
- }
-
- return keys[len(keys)-1], vers[keys[len(keys)-1]], nil
-}
-
-func findReleases(channels []v1alpha2.ReleaseChannel, min bool) (map[string]semver.Version, error) {
- vers := make(map[string]semver.Version, len(channels))
- if len(channels) == 0 {
- return vers, ErrNoPreviousRelease
- }
-
- for _, ch := range channels {
-
- ver := ch.MaxVersion
- if min {
- ver = ch.MinVersion
- }
- parsedVer, err := semver.Parse(ver)
- if err != nil {
- return vers, err
- }
- vers[ch.Name] = parsedVer
- }
-
- return vers, nil
-}
-
-
-
-
-
-
diff --git a/v2/tests/results/cover.out b/v2/tests/results/cover.out
deleted file mode 100644
index 20be54631..000000000
--- a/v2/tests/results/cover.out
+++ /dev/null
@@ -1,877 +0,0 @@
-mode: set
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:45.22,47.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:60.104,64.33 2 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:64.33,65.82 1 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:65.82,67.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:67.18,69.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:70.4,71.67 2 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:71.67,73.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:73.19,75.6 1 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:76.5,81.92 6 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:82.10,84.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:88.2,88.33 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:88.33,90.15 2 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:90.15,92.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:93.3,93.59 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:93.59,95.86 2 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:95.86,96.53 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:96.53,102.6 4 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:103.5,103.15 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:106.3,106.15 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:106.15,108.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:110.2,110.23 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:114.72,118.20 4 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:118.20,120.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:121.2,122.37 2 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:122.37,124.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:125.2,125.37 1 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:125.37,127.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/additional/collector.go:128.2,129.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:31.18,33.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:52.111,60.23 6 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:60.23,62.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:62.8,64.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:66.2,76.31 8 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:76.31,79.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:79.17,81.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:82.3,84.36 3 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:84.36,88.98 4 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:88.98,91.19 3 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:91.19,93.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:96.3,98.23 2 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:98.23,100.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:101.3,102.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:102.18,104.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:105.3,105.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:105.24,106.33 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:106.33,108.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:109.4,109.72 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:112.2,112.21 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:112.21,117.17 4 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:117.17,119.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:121.3,121.25 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:121.25,123.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:124.3,124.60 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:126.2,126.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:130.60,132.24 2 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:132.24,133.43 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:133.43,137.28 4 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:137.28,138.19 1 0
-github.com/openshift/oc-mirror/v2/pkg/batch/worker.go:138.19,141.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:26.49,28.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:31.65,33.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:36.64,38.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:41.65,43.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:46.65,48.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:51.64,53.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/log/logger.go:56.47,58.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/defaults.go:9.52,11.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/defaults.go:13.72,14.91 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/defaults.go:14.91,16.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:22.82,25.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:25.16,27.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:28.2,30.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:30.16,32.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:34.2,34.37 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:35.74,37.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:37.17,39.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:40.10,41.85 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:44.2,46.24 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:50.76,54.51 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:54.51,56.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:58.2,60.39 3 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:60.39,62.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:64.2,66.15 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:70.65,76.39 4 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:76.39,78.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:80.2,82.15 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:85.69,86.56 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:86.56,88.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/load.go:89.2,89.22 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:16.58,18.41 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:18.41,19.36 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:19.36,21.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:23.2,23.38 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:26.73,28.44 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:28.44,30.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:30.17,32.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:33.3,33.21 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:33.21,37.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:38.3,38.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:40.2,40.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:43.73,45.55 2 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:45.55,46.25 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:46.25,50.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:51.3,51.28 1 1
-github.com/openshift/oc-mirror/v2/pkg/config/validate.go:53.2,53.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:53.22,55.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:69.101,80.48 4 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:80.48,82.36 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:82.36,84.41 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:84.41,87.5 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:92.2,92.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:92.33,94.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:94.17,96.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:97.3,99.48 3 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:99.48,106.67 6 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:106.67,108.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:108.19,110.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:111.5,115.19 5 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:115.19,117.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:119.5,121.29 3 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:121.29,122.20 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:122.20,124.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:129.4,130.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:130.18,132.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:135.4,135.31 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:135.31,137.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:137.10,138.61 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:138.61,140.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:142.4,148.18 5 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:148.18,150.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:154.4,156.18 3 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:156.18,158.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:160.4,167.18 5 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:167.18,169.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:174.4,174.29 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:174.29,176.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:176.19,178.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:179.10,182.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:182.19,184.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:188.3,190.35 3 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:190.35,192.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:193.3,196.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:196.17,198.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:201.2,201.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:201.33,204.15 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:204.15,206.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:207.3,207.48 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:207.48,209.36 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:209.36,211.87 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:211.87,212.90 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:212.90,216.24 3 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:216.24,218.8 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:218.13,222.8 3 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:224.6,224.16 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:228.3,228.15 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:228.15,230.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:232.2,232.23 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:236.72,240.20 4 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:240.20,242.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:243.2,243.37 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:243.37,245.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:245.8,247.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:248.2,249.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:253.153,255.42 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:255.42,256.35 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:256.35,258.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:258.18,261.5 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:262.4,264.71 2 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:264.71,266.19 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:266.19,269.6 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:270.5,271.27 2 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:271.27,275.6 3 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:276.5,279.86 4 0
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:280.10,282.5 1 1
-github.com/openshift/oc-mirror/v2/pkg/operator/collector.go:285.2,285.20 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:50.76,52.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:54.42,56.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:58.46,60.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:63.122,64.22 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:64.22,66.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:67.2,67.45 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:70.154,72.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:74.94,76.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:79.114,85.75 3 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:85.75,87.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:89.2,90.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:90.16,92.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:93.2,93.15 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:93.15,94.49 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:94.49,96.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:99.2,100.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:100.16,102.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:103.2,104.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:104.16,106.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:108.2,109.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:109.16,111.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:112.2,113.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:113.16,115.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:117.2,118.26 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:118.26,120.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:120.17,122.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:139.2,147.41 4 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:147.41,149.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:151.2,151.29 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:151.29,153.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:153.17,155.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:158.2,158.14 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:158.14,160.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:162.2,162.66 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:162.66,164.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:203.2,203.106 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:203.106,205.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:206.2,207.35 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:207.35,209.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:209.17,211.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:212.3,212.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:217.2,218.29 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:218.29,220.17 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:220.17,222.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:226.2,246.45 3 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:246.45,250.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:250.17,252.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:253.3,254.28 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:254.28,256.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:256.18,258.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:259.4,259.94 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:259.94,261.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:263.3,263.13 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:268.85,270.71 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:270.71,272.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:274.2,275.16 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:275.16,277.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:279.2,280.16 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:280.16,282.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:284.2,287.45 3 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:287.45,289.17 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:289.17,291.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:292.3,292.13 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:297.72,298.19 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:299.16,300.35 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:301.13,302.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:306.20,307.38 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/mirror.go:311.10,312.156 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:41.76,46.16 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:46.16,48.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:50.2,50.62 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:58.119,59.53 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:59.53,66.3 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:82.74,83.30 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:83.30,85.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:90.77,96.2 5 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:105.62,110.2 4 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:139.190,149.22 3 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:149.22,152.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:153.2,156.28 4 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:156.28,161.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:162.2,166.19 5 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:170.184,178.2 6 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:180.51,185.2 4 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:188.81,191.25 3 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:191.25,193.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:193.8,193.34 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:193.34,195.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:195.8,197.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:198.2,198.16 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:198.16,200.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:201.2,201.43 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:206.90,208.41 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:208.42,210.3 0 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:211.2,211.29 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:211.29,213.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:214.2,214.20 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:219.68,230.21 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:230.21,232.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:233.2,233.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:238.76,247.52 7 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:247.52,249.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:250.2,250.85 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:250.85,253.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:254.2,254.30 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:254.30,256.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:257.2,257.30 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:257.30,259.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:260.2,260.48 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:260.48,262.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:263.2,263.45 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:263.45,265.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:266.2,266.59 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:266.59,268.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:270.2,270.56 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:270.56,271.30 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:271.30,273.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:274.3,274.82 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:276.2,276.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:276.32,279.17 3 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:279.17,281.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:282.8,282.36 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:282.36,287.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:288.2,288.34 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:288.34,290.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:291.2,291.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:291.18,293.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:295.2,295.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:310.192,322.2 11 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:352.55,353.17 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:353.17,355.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:356.2,357.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:357.18,359.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:360.2,360.17 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:360.17,362.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:363.2,363.26 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:366.67,368.16 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:368.16,370.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:371.2,374.8 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:395.65,396.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:397.13,398.47 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:399.14,400.54 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:401.14,402.48 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:403.10,404.124 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:437.89,439.31 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:439.31,441.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:443.2,445.16 3 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:445.16,447.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/options.go:448.2,449.32 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:20.26,25.16 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:25.16,27.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:28.2,28.41 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:28.41,29.51 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:29.51,33.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:35.2,35.12 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:38.61,40.39 1 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:40.39,44.67 2 1
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:44.67,46.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/mirror/unshare_linux.go:48.2,48.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:77.69,113.48 9 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:113.48,115.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:115.18,118.5 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:120.4,123.18 3 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:123.18,126.5 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:130.2,140.12 11 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:144.71,151.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:151.16,154.3 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:156.2,156.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:156.33,159.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:159.17,162.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:165.3,167.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:167.17,170.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:173.3,175.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:175.17,178.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:181.3,183.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:183.17,186.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:189.3,191.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:191.17,194.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:197.2,201.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:201.16,204.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:205.2,211.16 5 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:211.16,214.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:215.2,221.16 5 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:221.16,224.3 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:225.2,230.16 4 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:230.16,233.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:235.2,235.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:239.50,245.16 4 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:245.16,247.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:248.2,260.86 9 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:260.86,264.3 3 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:264.8,267.3 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:268.2,278.84 9 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:283.56,284.40 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:284.40,286.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:288.2,288.47 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:288.47,291.17 2 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:291.17,293.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:294.3,294.44 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:294.44,296.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:297.3,297.42 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:297.42,298.49 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:298.49,300.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:302.3,302.49 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:302.49,303.46 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:303.46,305.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:308.2,308.131 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:308.131,310.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:310.8,312.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:317.82,320.2 2 1
-github.com/openshift/oc-mirror/v2/pkg/cli/executor.go:323.16,326.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:41.63,43.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:46.75,49.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:49.16,51.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:52.2,53.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:53.16,55.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:56.2,56.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:61.79,64.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:64.16,66.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:67.2,68.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:68.16,70.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:71.2,71.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:75.91,78.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:78.16,80.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:81.2,82.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:82.16,84.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:85.2,85.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:90.116,93.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:93.16,95.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:96.2,96.29 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:96.29,100.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:100.17,102.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:103.3,104.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:104.17,106.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:108.3,108.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:108.24,110.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:112.2,112.27 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:116.181,118.34 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:118.34,122.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:122.17,124.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:126.3,127.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:127.17,129.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:131.3,131.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:131.24,133.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:134.3,134.50 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:136.2,136.27 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:140.100,141.77 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:141.77,142.35 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:142.35,143.48 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:143.48,145.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:146.4,147.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:147.18,149.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:150.4,151.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:151.18,153.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:155.8,157.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:158.2,158.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:162.87,166.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:166.16,168.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:170.2,171.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:171.16,173.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:175.2,176.41 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:176.41,178.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:179.2,179.23 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:183.72,188.16 4 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:188.16,190.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:192.2,193.6 2 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:193.6,196.20 2 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:196.20,197.9 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:200.3,200.17 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:200.17,202.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:204.3,204.48 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:204.48,205.27 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:206.21,207.28 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:207.28,208.68 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:208.68,210.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:212.21,214.19 2 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:214.19,216.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:217.5,217.58 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:217.58,219.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:220.5,220.20 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:222.12,224.81 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:228.2,228.12 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:233.77,239.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:239.16,241.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:242.2,245.16 4 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:245.16,247.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:248.2,248.17 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:253.151,260.26 4 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:260.26,261.10 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:262.36,263.34 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:263.34,267.19 4 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:267.19,269.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:270.5,270.25 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:272.35,273.25 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:273.25,277.5 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:278.36,280.39 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:283.2,283.27 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:288.168,293.26 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:293.26,294.10 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:295.36,296.28 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:296.28,297.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:297.32,300.20 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:300.20,302.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:303.6,303.29 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:303.29,305.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:307.10,309.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:309.19,311.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:312.5,313.25 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:315.35,316.38 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:316.38,320.5 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:322.4,322.16 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:322.16,324.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:325.36,327.38 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:330.2,330.27 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:334.69,337.29 3 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:337.29,340.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:340.19,342.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:343.3,347.17 5 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:347.17,349.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:351.3,351.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:351.32,354.4 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:356.2,356.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:360.90,368.18 5 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:368.18,370.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:370.17,372.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:373.8,375.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:376.2,376.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:376.18,378.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:378.17,380.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:381.8,383.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:385.2,385.28 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:385.28,388.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:388.19,390.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:391.3,395.17 5 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:395.17,397.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:398.3,398.75 1 1
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:398.75,400.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/manifest/oci-manifest.go:402.2,402.21 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:43.183,45.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:47.145,49.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:121.73,122.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:122.12,124.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:125.2,125.22 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:128.73,130.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:132.102,139.62 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:139.62,141.56 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:141.56,144.19 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:145.26,147.19 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:147.19,149.6 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:150.26,152.19 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:152.19,154.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:155.12,157.13 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:159.4,159.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:159.18,161.13 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:164.4,164.58 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:164.58,166.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:166.32,168.20 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:168.20,170.15 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:174.6,176.53 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:176.53,184.7 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:189.5,189.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:189.32,191.20 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:191.20,193.15 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:195.6,196.67 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:198.5,198.36 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:199.10,205.5 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:207.4,208.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:208.18,210.13 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:212.4,212.47 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:217.3,217.56 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:217.56,219.13 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:219.13,221.5 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:224.3,224.49 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:224.49,226.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:226.18,228.13 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:230.4,231.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:231.18,233.13 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:235.4,235.50 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:239.2,240.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:240.16,242.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:244.2,244.25 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:244.25,246.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:247.2,247.13 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:251.214,255.34 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:255.34,256.30 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:256.30,258.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:260.2,263.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:263.16,265.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:266.2,267.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:267.16,269.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:271.2,272.26 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:272.26,274.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:274.17,276.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:277.3,277.62 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:279.8,281.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:281.17,283.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:284.3,285.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:285.17,287.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:288.3,289.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:289.17,291.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:292.3,292.66 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:294.2,296.23 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:300.190,304.30 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:304.30,305.34 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:305.34,307.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:310.2,310.27 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:310.27,312.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:314.2,315.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:315.16,317.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:318.2,319.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:319.16,321.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:322.2,323.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:323.16,325.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:326.2,326.58 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:330.124,332.33 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:332.33,335.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:337.2,337.25 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:337.25,339.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:341.2,341.24 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:341.24,343.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:345.2,345.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:349.141,360.27 6 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:360.27,365.17 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:365.17,366.26 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:366.26,368.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:371.3,374.21 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:374.21,379.18 4 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:379.18,381.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:382.4,383.40 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:383.40,387.19 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:387.19,389.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:393.3,393.20 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:393.20,396.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:396.18,398.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:399.4,402.18 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:402.18,404.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:405.4,405.20 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:405.20,407.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:408.4,409.18 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:409.18,411.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:412.4,412.32 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:412.32,414.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:415.4,415.26 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:415.26,417.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:419.4,432.27 13 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:432.27,433.44 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:433.44,435.34 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:435.34,437.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:439.10,439.36 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:439.36,441.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:443.4,447.18 4 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:447.18,450.5 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:451.4,457.19 5 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:457.19,459.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:460.4,460.30 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:461.9,464.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/cincinnati.go:466.2,466.18 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:32.49,34.88 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:34.88,37.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:37.8,39.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:40.2,41.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:41.16,43.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:45.2,46.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:46.16,48.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:50.2,54.70 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:57.39,59.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:61.52,63.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:65.39,67.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:69.67,77.33 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:77.33,78.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:78.18,80.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:82.2,82.39 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:94.49,96.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:96.16,98.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:100.2,101.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:101.16,103.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:105.2,109.70 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:112.39,114.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:116.39,118.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:120.52,122.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:124.53,126.2 0 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:128.42,130.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:130.16,132.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/client.go:133.2,137.20 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:53.22,55.2 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:70.100,75.33 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:75.33,78.17 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:78.17,80.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:82.3,84.34 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:84.34,89.62 5 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:89.62,92.19 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:92.19,94.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:95.5,98.19 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:98.19,100.6 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:101.5,106.29 4 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:106.29,107.20 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:107.20,109.7 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:111.10,113.5 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:115.4,116.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:116.18,119.5 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:122.4,122.31 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:122.31,124.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:125.4,130.18 5 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:130.18,132.5 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:133.4,137.18 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:137.18,139.5 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:140.4,145.18 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:145.18,147.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:149.4,150.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:150.18,152.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:155.4,155.47 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:158.2,158.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:158.33,169.17 4 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:169.17,171.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:174.3,175.15 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:175.15,177.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:181.3,183.90 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:183.90,184.52 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:184.52,187.21 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:187.21,191.6 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:191.11,193.6 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:194.10,194.25 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:194.25,196.5 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:197.4,197.14 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:199.3,199.19 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:199.19,201.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:203.2,203.23 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:207.141,209.29 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:209.29,213.85 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:213.85,215.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:215.18,218.5 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:219.4,221.85 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:222.9,224.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:227.2,227.20 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:231.73,232.27 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:232.27,233.23 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:233.23,236.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/collector.go:238.2,238.11 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:47.34,49.2 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:58.158,65.16 5 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:65.16,71.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:74.2,76.35 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:76.35,77.31 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:77.31,81.9 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:84.2,84.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:84.12,89.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:91.2,93.35 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:93.35,94.30 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:94.30,98.9 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:101.2,101.12 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:101.12,106.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:108.2,109.35 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:109.35,111.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:114.2,114.50 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:114.50,115.48 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:115.48,117.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:118.3,118.39 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:121.2,121.62 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:121.62,128.22 6 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:128.22,131.19 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:131.19,132.10 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:135.4,135.37 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:135.37,136.40 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:136.40,140.6 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:145.3,145.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:145.33,147.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:149.3,150.56 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:150.56,152.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:155.3,155.54 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:155.54,157.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:159.3,159.14 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:162.2,165.29 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:165.29,167.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:169.2,169.41 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:174.165,175.36 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:175.36,177.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:181.2,182.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:182.16,184.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:185.2,185.23 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:185.23,187.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:187.17,189.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:190.3,190.16 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:190.16,195.4 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:196.3,196.67 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:202.2,203.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:203.16,205.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:206.2,207.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:207.16,209.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:211.2,212.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:212.16,214.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:215.2,219.35 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:219.35,220.40 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:220.40,223.4 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:226.2,226.47 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:231.172,233.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:233.16,235.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:238.2,242.23 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:242.23,249.3 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:249.8,251.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:251.17,253.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:256.2,257.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:257.16,259.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:260.2,260.15 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:260.15,267.3 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:269.2,271.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:271.16,273.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:275.2,275.23 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:275.23,277.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:279.2,280.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:280.16,282.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:283.2,286.33 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:291.123,293.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:293.16,295.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:296.2,296.33 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:296.33,297.21 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:297.21,299.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:301.2,301.18 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:306.123,309.21 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:309.21,311.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:312.2,313.21 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:313.21,315.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:316.2,317.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:317.16,319.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:320.2,321.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:321.16,323.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:324.2,325.36 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:330.119,335.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:335.16,341.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:344.2,345.75 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:345.75,348.17 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:348.17,354.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:357.2,358.35 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:358.35,359.81 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:359.81,361.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:364.2,366.20 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:366.20,371.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:373.2,373.9 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:373.9,375.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:377.2,377.31 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:382.94,387.16 3 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:387.16,393.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:395.2,397.35 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:397.35,400.52 2 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:400.52,402.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:405.2,405.22 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:410.97,415.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:415.16,421.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:423.2,424.35 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:424.35,427.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:429.2,429.20 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:429.20,434.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:436.2,438.18 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:442.121,447.16 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:447.16,453.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:456.2,457.35 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:457.35,458.32 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:458.32,460.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:463.2,463.21 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:467.75,472.16 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:472.16,474.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:475.2,476.58 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:476.58,477.56 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:477.56,479.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:484.2,484.48 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:484.48,486.33 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:486.33,488.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:491.2,492.22 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:492.22,494.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:495.2,498.16 4 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:498.16,500.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:501.2,503.38 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:503.38,505.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:508.2,509.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:509.16,511.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:513.2,513.52 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:513.52,515.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:517.2,517.19 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:539.49,541.54 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:541.54,543.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:545.2,545.22 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:545.22,547.3 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/core-cincinnati.go:549.2,552.12 3 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:16.96,18.16 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:18.16,20.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:22.2,23.22 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:23.22,25.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:26.2,26.9 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:26.9,27.40 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:27.40,29.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:30.8,31.40 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:31.40,33.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:36.2,36.56 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:39.100,41.24 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:41.24,43.3 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:45.2,45.30 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:45.30,48.10 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:48.10,50.4 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:51.3,52.17 2 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:52.17,54.4 1 0
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:55.3,55.28 1 1
-github.com/openshift/oc-mirror/v2/pkg/release/find.go:58.2,58.18 1 1
diff --git a/vendor/github.com/openshift/oc-mirror/v2/pkg/mirror/unshare.go b/vendor/github.com/openshift/oc-mirror/v2/pkg/mirror/unshare.go
index 8daf678d6..1f219a34a 100644
--- a/vendor/github.com/openshift/oc-mirror/v2/pkg/mirror/unshare.go
+++ b/vendor/github.com/openshift/oc-mirror/v2/pkg/mirror/unshare.go
@@ -1,4 +1,4 @@
-//go: build !windows
+// go: build !windows
// build !windows
package mirror