Skip to content

Commit

Permalink
Allow users to specify max ICSP size
Browse files Browse the repository at this point in the history
Problem: It is possible for the `oc adm catalog mirror` command
to generate ICSPs that are greater than 1000000 bytes in size.
ICSPs that exceed 1000000 bytes are likely to fail when applied
to the cluster if the objec already existed in an early state as
the `kubectl.kubernetes.io/last-applied-configuration` annotation
will likely exceed the 2000000 etcd resource byte limit.

Solution: Introduce a the max-icsp-size flag to the
`oc adm catalog mirror` command, allowing users to specify the
maximum byte size of ICSP files generated by the command. If
an ICSP would exceed this limit, create and begin writting mirrors
to a new ICSP.

The default ICSP limit is 1000000 bytes.
  • Loading branch information
awgreene committed May 6, 2021
1 parent a765590 commit da6c904
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 88 deletions.
79 changes: 61 additions & 18 deletions pkg/cli/admin/catalog/mirror.go
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"text/tabwriter"
"time"
Expand Down Expand Up @@ -71,7 +72,9 @@ var (
`)
)

const IndexLocationLabelKey = "operators.operatorframework.io.index.database.v1"
const (
IndexLocationLabelKey = "operators.operatorframework.io.index.database.v1"
)

func init() {
subCommands = append(subCommands, NewMirrorCatalog)
Expand All @@ -87,6 +90,7 @@ type MirrorCatalogOptions struct {

FromFileDir string
FileDir string
MaxICSPSize int

IcspScope string

Expand Down Expand Up @@ -145,6 +149,7 @@ func NewMirrorCatalog(f kcmdutil.Factory, streams genericclioptions.IOStreams) *
flags.StringVar(&o.FromFileDir, "from-dir", o.FromFileDir, "The directory on disk that file:// images will be read from. Overrides --dir")
flags.IntVar(&o.MaxPathComponents, "max-components", 2, "The maximum number of path components allowed in a destination mapping. Example: `quay.io/org/repo` has two path components.")
flags.StringVar(&o.IcspScope, "icsp-scope", o.IcspScope, "Scope of registry mirrors in imagecontentsourcepolicy file. Allowed values: repository, registry. Defaults to: repository")
flags.IntVar(&o.MaxICSPSize, "max-icsp-size", 1000000, "The maximum number of bytes for the generated ICSP yaml(s). Defaults to 1000000")
return cmd
}

Expand Down Expand Up @@ -382,10 +387,48 @@ func (o *MirrorCatalogOptions) Run() error {
fmt.Fprintf(o.IOStreams.ErrOut, "errors during mirroring. the full contents of the catalog may not have been mirrored: %s\n", err.Error())
}

return WriteManifests(o.IOStreams.Out, o.SourceRef, o.DestRef, o.ManifestDir, o.IcspScope, mapping)
return WriteManifests(o.IOStreams.Out, o.SourceRef, o.DestRef, o.ManifestDir, o.IcspScope, o.MaxICSPSize, mapping)
}

func getRegistryMapping(out io.Writer, icspScope string, mapping map[imagesource.TypedImageReference]imagesource.TypedImageReference) map[string]string {
registryMapping := map[string]string{}
for k, v := range mapping {
if len(v.Ref.ID) == 0 {
fmt.Fprintf(out, "no digest mapping available for %s, skip writing to ImageContentSourcePolicy\n", k)
continue
}
if icspScope == "registry" {
registryMapping[k.Ref.Registry] = v.Ref.Registry
} else {
registryMapping[k.Ref.AsRepository().String()] = v.Ref.AsRepository().String()
}
}
return registryMapping
}

func generateICSPs(out io.Writer, source string, icspScope string, maxICSPSize int, mapping map[imagesource.TypedImageReference]imagesource.TypedImageReference) ([][]byte, error) {
registryMapping := getRegistryMapping(out, icspScope, mapping)
icsps := [][]byte{}
for i := 0; len(registryMapping) != 0; i++ {
icsp, err := generateICSP(out, source+"-"+strconv.Itoa(i), maxICSPSize, registryMapping)
if err != nil {
return nil, err
}
icsps = append(icsps, icsp)
}
return icsps, nil
}

func aggregateICSPs(icsps [][]byte) []byte {
aggregation := []byte{}
for _, icsp := range icsps {
aggregation = append(aggregation, []byte("---\n")...)
aggregation = append(aggregation, icsp...)
}
return aggregation
}

func WriteManifests(out io.Writer, source, dest imagesource.TypedImageReference, dir, icspScope string, mapping map[imagesource.TypedImageReference]imagesource.TypedImageReference) error {
func WriteManifests(out io.Writer, source, dest imagesource.TypedImageReference, dir, icspScope string, maxICSPSize int, mapping map[imagesource.TypedImageReference]imagesource.TypedImageReference) error {
f, err := os.Create(filepath.Join(dir, "mapping.txt"))
if err != nil {
return err
Expand All @@ -401,12 +444,12 @@ func WriteManifests(out io.Writer, source, dest imagesource.TypedImageReference,
}

if dest.Type != imagesource.DestinationFile {
icsp, err := generateICSP(out, source.Ref.Name, icspScope, mapping)
icsps, err := generateICSPs(out, source.Ref.Name, icspScope, maxICSPSize, mapping)
if err != nil {
return err
}

if err := ioutil.WriteFile(filepath.Join(dir, "imageContentSourcePolicy.yaml"), icsp, os.ModePerm); err != nil {
if err := ioutil.WriteFile(filepath.Join(dir, "imageContentSourcePolicy.yaml"), aggregateICSPs(icsps), os.ModePerm); err != nil {
return fmt.Errorf("error writing ImageContentSourcePolicy")
}

Expand Down Expand Up @@ -472,7 +515,7 @@ func generateCatalogSource(source imagesource.TypedImageReference, mapping map[i
return csExample, nil
}

func generateICSP(out io.Writer, name string, icspScope string, mapping map[imagesource.TypedImageReference]imagesource.TypedImageReference) ([]byte, error) {
func generateICSP(out io.Writer, name string, byteLimit int, registryMapping map[string]string) ([]byte, error) {
icsp := operatorv1alpha1.ImageContentSourcePolicy{
TypeMeta: metav1.TypeMeta{
APIVersion: operatorv1alpha1.GroupVersion.String(),
Expand All @@ -485,23 +528,23 @@ func generateICSP(out io.Writer, name string, icspScope string, mapping map[imag
},
}

registryMapping := map[string]string{}
for k, v := range mapping {
if len(v.Ref.ID) == 0 {
fmt.Fprintf(out, "no digest mapping available for %s, skip writing to ImageContentSourcePolicy\n", k)
continue
}
if icspScope == "registry" {
registryMapping[k.Ref.Registry] = v.Ref.Registry
} else {
registryMapping[k.Ref.AsRepository().String()] = v.Ref.AsRepository().String()
}
}
for key := range registryMapping {
icsp.Spec.RepositoryDigestMirrors = append(icsp.Spec.RepositoryDigestMirrors, operatorv1alpha1.RepositoryDigestMirrors{
Source: key,
Mirrors: []string{registryMapping[key]},
})
y, err := yaml.Marshal(icsp)
if err != nil {
return nil, fmt.Errorf("unable to marshal ImageContentSourcePolicy yaml: %v", err)
}

if len(y) > byteLimit {
if len(icsp.Spec.RepositoryDigestMirrors) > 0 {
icsp.Spec.RepositoryDigestMirrors = icsp.Spec.RepositoryDigestMirrors[:len(icsp.Spec.RepositoryDigestMirrors)-1]
}
break
}
delete(registryMapping, key)
}

// Create an unstructured object for removing creationTimestamp
Expand Down

0 comments on commit da6c904

Please sign in to comment.