Skip to content

Commit

Permalink
Introduce v1alpha2.Operator.TargetCatalog which will later replace Ta…
Browse files Browse the repository at this point in the history
…rgetName

TargetCatalog can contain several component-paths, allowing the user to set the namespace to which the catalog will be copied.
Changes to be committed:
	modified:   pkg/api/v1alpha2/types_config.go
	new file:   pkg/api/v1alpha2/types_config_test.go
	modified:   pkg/cli/mirror/fbc_operators.go
	modified:   pkg/cli/mirror/fbc_operators_test.go
	modified:   pkg/cli/mirror/manifests.go
  • Loading branch information
sherine-k committed Feb 27, 2023
1 parent b4d43ae commit e7c9679
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 54 deletions.
90 changes: 54 additions & 36 deletions pkg/api/v1alpha2/types_config.go
@@ -1,10 +1,8 @@
package v1alpha2

import (
"fmt"
"strings"

"github.com/openshift/library-go/pkg/image/reference"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -107,7 +105,22 @@ type Operator struct {
// TargetName is the target image name the catalog will be built with. If unset,
// the catalog will be published with the provided name in the Catalog
// field.
// Deprecated in oc-mirror 4.13, to be replaced with TargetCatalog.
TargetName string `json:"targetName,omitempty"`
// TargetCatalog replaces TargetName and allows for specifying the exact URL of the target
// catalog, including any path-components (organization, namespace) of the target catalog's location
// on the disconnected registry.
// This answer some customers requests regarding restrictions on where images can be placed.
// The targetCatalog field consists of an optional namespace followed by the target image name,
// described in extended Backus–Naur form below:
// target-catalog = [namespace '/'] target-name
// target-name = path-component
// namespace = path-component ['/' path-component]*
// path-component = alpha-numeric [separator alpha-numeric]*
// alpha-numeric = /[a-z0-9]+/
// separator = /[_.]|__|[-]*/
// TargetCatalog will be preferred over TargetName if both are specified in te ImageSetConfig.
TargetCatalog string `json:"targetCatalog,omitempty"`
// TargetTag is the tag the catalog image will be built with. If unset,
// the catalog will be publish with the provided tag in the Catalog
// field or a tag calculated from the partial digest.
Expand All @@ -126,52 +139,54 @@ type Operator struct {

// GetUniqueName determines the catalog name that will
// be tracked in the metadata and built. This depends on what fields
// are set between Catalog, TargetName, and TargetTag.
// are set between Catalog, TargetCatalog (and soon deprecated
// TargetName), and TargetTag.
func (o Operator) GetUniqueName() (string, error) {
ctlgRef := o.Catalog
if o.TargetName == "" && o.TargetTag == "" {
return ctlgRef, nil
if o.TargetCatalog == "" && o.TargetName == "" && o.TargetTag == "" {
return TrimProtocol(ctlgRef), nil
}
if o.IsFBCOCI() {
reg, ns, name, tag, id := ParseImageReference(ctlgRef)
if o.TargetName != "" {
name = o.TargetName
}
if o.TargetTag != "" {
tag = o.TargetTag
id = ""

reg, ns, name, tag, id := ParseImageReference(ctlgRef)

if o.TargetTag != "" {
tag = o.TargetTag
id = ""
}
uniqueName := ""

if o.TargetCatalog != "" {
// TargetCatalog takes precedence over TargetName, and replaces the catalog component-paths (URL)
if !o.IsFBCOCI() && reg != "" {
// reg is included in the name only in case of registry based catalogs.
// the parsed reg is not relevant in case of OCI, because the parsed ref is simply a filesystem path here
uniqueName += reg + "/"
}
uniqueName := "oci://"
uniqueName += o.TargetCatalog
} else {
if reg != "" {
uniqueName = reg
uniqueName += reg
}
if ns != "" {
uniqueName = strings.Join([]string{uniqueName, ns}, "/")
}

uniqueName = strings.Join([]string{uniqueName, name}, "/")
if tag != "" {
uniqueName = uniqueName + ":" + tag
if o.TargetName != "" {
name = o.TargetName
}
if uniqueName != "" {
uniqueName = strings.Join([]string{uniqueName, name}, "/")
} else {
uniqueName = uniqueName + "@sha256:" + id
uniqueName = name
}
return uniqueName, nil
}
if tag != "" {
uniqueName = uniqueName + ":" + tag
} else {
catalogRef, err := reference.Parse(ctlgRef)
if err != nil {
return "", fmt.Errorf("error parsing source catalog %s: %v", catalogRef, err)
}
if o.TargetName != "" {
catalogRef.Name = o.TargetName
}
if o.TargetTag != "" {
catalogRef.ID = ""
catalogRef.Tag = o.TargetTag
if id != "" {
uniqueName = uniqueName + "@sha256:" + id
}

return catalogRef.Exact(), nil
}

return uniqueName, nil
}

// parseImageName returns the registry, organisation, repository, tag and digest
Expand All @@ -184,7 +199,10 @@ func ParseImageReference(imageName string) (string, string, string, string, stri
imageName = strings.TrimSuffix(imageName, "/")
tmp := strings.Split(imageName, "/")

registry = tmp[0]
if len(tmp) > 1 {
registry = tmp[0]
}

img := strings.Split(tmp[len(tmp)-1], ":")
if len(tmp) > 2 {
org = strings.Join(tmp[1:len(tmp)-1], "/")
Expand All @@ -208,7 +226,7 @@ func ParseImageReference(imageName string) (string, string, string, string, stri
// trimProtocol removes oci://, file:// or docker:// from
// the parameter imageName
func TrimProtocol(imageName string) string {
imageName = strings.TrimPrefix(imageName, "oci:")
imageName = strings.TrimPrefix(imageName, OCITransportPrefix)
imageName = strings.TrimPrefix(imageName, "file:")
imageName = strings.TrimPrefix(imageName, "docker:")
imageName = strings.TrimPrefix(imageName, "//")
Expand Down
78 changes: 78 additions & 0 deletions pkg/api/v1alpha2/types_config_test.go
@@ -0,0 +1,78 @@
package v1alpha2

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestGetUniqueName(t *testing.T) {
type spec struct {
desc string
ref Operator
exp string
expError string
}

specs := []spec{
{
desc: "simple flow",
ref: Operator{
Catalog: "registry.redhat.io/redhat/redhat-operator-index:v4.12",
IncludeConfig: IncludeConfig{
Packages: []IncludePackage{
{
Name: "aws-load-balancer-operator",
},
},
},
},
exp: "registry.redhat.io/redhat/redhat-operator-index:v4.12",
expError: "",
},
{
desc: "simple flow with targetCatalog",
ref: Operator{
Catalog: "registry.redhat.io/redhat/redhat-operator-index:v4.12",
TargetCatalog: "def/redhat-operator-index",
IncludeConfig: IncludeConfig{
Packages: []IncludePackage{
{
Name: "aws-load-balancer-operator",
},
},
},
},
exp: "registry.redhat.io/def/redhat-operator-index:v4.12",
expError: "",
},
{
desc: "OCI FBC flow",
ref: Operator{
Catalog: "oci:///home/myuser/random/folder/redhat-operator-index:v4.12",
TargetCatalog: "def/redhat-operator-index",
IncludeConfig: IncludeConfig{
Packages: []IncludePackage{
{
Name: "aws-load-balancer-operator",
},
},
},
},
exp: "def/redhat-operator-index:v4.12",
expError: "",
},
}

for _, s := range specs {
t.Run(s.desc, func(t *testing.T) {
un, err := s.ref.GetUniqueName()
if s.expError != "" {
require.EqualError(t, err, s.expError)
} else {
require.NoError(t, err)
require.Equal(t, s.exp, un)
}
})
}
}
24 changes: 12 additions & 12 deletions pkg/cli/mirror/fbc_operators.go
Expand Up @@ -210,30 +210,30 @@ func prepareDestCatalogRef(operator v1alpha2.Operator, destReg, namespace string
if destReg == "" {
return "", errors.New("destination registry may not be empty")
}
_, subNamespace, repo, tag, _ := v1alpha2.ParseImageReference(operator.Catalog)
uniqueName, err := operator.GetUniqueName()
if err != nil {
return "", err
}
notAReg, subNamespace, repo, tag, _ := v1alpha2.ParseImageReference(uniqueName)

to := dockerPrefix + destReg

if namespace != "" {
to = strings.Join([]string{to, namespace}, "/")
}
if notAReg != "" {
to = strings.Join([]string{to, notAReg}, "/")
}
if subNamespace != "" {
to = strings.Join([]string{to, subNamespace}, "/")
}

klog.Infof("pushing catalog %s to %s \n", operator.Catalog, to)

if operator.TargetName != "" {
to = strings.Join([]string{to, operator.TargetName}, "/")
} else {
to = strings.Join([]string{to, repo}, "/")
}
if operator.TargetTag != "" {
to += ":" + operator.TargetTag
} else if tag != "" {
to = strings.Join([]string{to, repo}, "/")
if tag != "" {
to += ":" + tag
}
//check if this is a valid reference
_, err := image.ParseReference(v1alpha2.TrimProtocol(to))
_, err = image.ParseReference(v1alpha2.TrimProtocol(to))
return to, err
}

Expand Down
22 changes: 17 additions & 5 deletions pkg/cli/mirror/fbc_operators_test.go
Expand Up @@ -1092,7 +1092,7 @@ func TestPrepareDestCatalogRef(t *testing.T) {
},
destReg: "localhost:5000",
namespace: "disconnected_ocp",
expectedRef: "docker://localhost:5000/disconnected_ocp/artifacts/rhop-ctlg-oci",
expectedRef: "docker://localhost:5000/disconnected_ocp/testdata/artifacts/rhop-ctlg-oci",
expectedErr: "",
},
{
Expand All @@ -1103,7 +1103,7 @@ func TestPrepareDestCatalogRef(t *testing.T) {
},
destReg: "localhost:5000",
namespace: "disconnected_ocp",
expectedRef: "docker://localhost:5000/disconnected_ocp/artifacts/rhopi",
expectedRef: "docker://localhost:5000/disconnected_ocp/testdata/artifacts/rhopi",
expectedErr: "",
},
{
Expand All @@ -1114,7 +1114,7 @@ func TestPrepareDestCatalogRef(t *testing.T) {
},
destReg: "localhost:5000",
namespace: "disconnected_ocp",
expectedRef: "docker://localhost:5000/disconnected_ocp/artifacts/rhop-ctlg-oci:v12",
expectedRef: "docker://localhost:5000/disconnected_ocp/testdata/artifacts/rhop-ctlg-oci:v12",
expectedErr: "",
},
{
Expand All @@ -1126,7 +1126,19 @@ func TestPrepareDestCatalogRef(t *testing.T) {
},
destReg: "localhost:5000",
namespace: "disconnected_ocp",
expectedRef: "docker://localhost:5000/disconnected_ocp/artifacts/rhopi:v12",
expectedRef: "docker://localhost:5000/disconnected_ocp/testdata/artifacts/rhopi:v12",
expectedErr: "",
},
{
desc: "with targetCatalog",
operator: v1alpha2.Operator{
Catalog: "oci://" + testdata,
TargetTag: "v12",
TargetCatalog: "chosen_ns/rhopi",
},
destReg: "localhost:5000",
namespace: "disconnected_ocp",
expectedRef: "docker://localhost:5000/disconnected_ocp/chosen_ns/rhopi:v12",
expectedErr: "",
},
{
Expand All @@ -1146,7 +1158,7 @@ func TestPrepareDestCatalogRef(t *testing.T) {
},
destReg: "localhost:5000",
namespace: "",
expectedRef: "docker://localhost:5000/artifacts/rhop-ctlg-oci",
expectedRef: "docker://localhost:5000/testdata/artifacts/rhop-ctlg-oci",
expectedErr: "",
},
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/cli/mirror/manifests.go
Expand Up @@ -13,6 +13,7 @@ import (
operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
cincinnativ1 "github.com/openshift/cincinnati-operator/api/v1"
"github.com/openshift/library-go/pkg/image/reference"
"github.com/openshift/oc-mirror/pkg/api/v1alpha2"
"github.com/openshift/oc-mirror/pkg/image"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -179,7 +180,8 @@ func getRegistryMapping(icspScope string, mapping image.TypedImageMapping) (map[
registryMapping[k.Ref.AsRepository().String()] = v.Ref.AsRepository().String()
case icspScope == namespaceICSPScope:
source := path.Join(imgRegistry, imgNamespace)
dest := path.Join(v.Ref.Registry, v.Ref.Namespace)
reg, org, _, _, _ := v1alpha2.ParseImageReference(v.Ref.AsRepository().Exact())
dest := path.Join(reg, org)
registryMapping[source] = dest
default:
return registryMapping, fmt.Errorf("invalid ICSP scope %s", icspScope)
Expand Down

0 comments on commit e7c9679

Please sign in to comment.