Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions alpha/declcfg/declcfg_to_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) {
mb.Objects = b.Objects
mb.PropertiesP = props
mb.Version = ver
mb.Release = props.Packages[0].Release
}
}
if !found {
Expand Down
10 changes: 10 additions & 0 deletions alpha/declcfg/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ func withNoBundleData() func(*Bundle) {
}
}

func withReleaseVersion(packageName, version string, rel property.Release) func(*Bundle) {
return func(b *Bundle) {
for i, p := range b.Properties {
if p.Type == property.TypePackage {
b.Properties[i] = property.MustBuildPackageRelease(packageName, version, rel.Label, rel.Version.String())
}
}
}
}

func newTestBundle(packageName, version string, opts ...bundleOpt) Bundle {
csvJSON := fmt.Sprintf(`{"kind": "ClusterServiceVersion", "apiVersion": "operators.coreos.com/v1alpha1", "metadata":{"name":%q}}`, testBundleName(packageName, version))
b := Bundle{
Expand Down
58 changes: 51 additions & 7 deletions alpha/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
import (
"errors"
"fmt"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -103,24 +104,24 @@ func (m *Package) Validate() error {
}

func (m *Package) validateUniqueBundleVersions() error {
versionsMap := map[string]semver.Version{}
versionsMap := map[string]string{}
bundlesWithVersion := map[string]sets.Set[string]{}
for _, ch := range m.Channels {
for _, b := range ch.Bundles {
versionsMap[b.Version.String()] = b.Version
if bundlesWithVersion[b.Version.String()] == nil {
bundlesWithVersion[b.Version.String()] = sets.New[string]()
versionsMap[b.VersionString()] = b.VersionString()
if bundlesWithVersion[b.VersionString()] == nil {
bundlesWithVersion[b.VersionString()] = sets.New[string]()
}
bundlesWithVersion[b.Version.String()].Insert(b.Name)
bundlesWithVersion[b.VersionString()].Insert(b.Name)
}
}

versionsSlice := maps.Values(versionsMap)
semver.Sort(versionsSlice)
slices.Sort(versionsSlice)

var errs []error
for _, v := range versionsSlice {
bundles := sets.List(bundlesWithVersion[v.String()])
bundles := sets.List(bundlesWithVersion[v])
if len(bundles) > 1 {
errs = append(errs, fmt.Errorf("{%s: [%s]}", v, strings.Join(bundles, ", ")))
}
Expand Down Expand Up @@ -327,6 +328,46 @@ type Bundle struct {
// These fields are used to compare bundles in a diff.
PropertiesP *property.Properties
Version semver.Version
Release property.Release
}

func (b *Bundle) VersionString() string {
if b.Release.Label != "" || (b.Release.Version.Major != 0 || b.Release.Version.Minor != 0 || b.Release.Version.Patch != 0) {
return strings.Join([]string{b.Version.String(), b.Release.String()}, "-")
} else {
return b.Version.String()
}
}

func (b *Bundle) normalizeName() string {
// if the bundle has release versioning, then the name must include this in standard form:
// <package-name>-v<version>-<release label>-<release version>
// if no release versioning exists, then just return the bundle name
if b.Release.Label != "" || (b.Release.Version.Major != 0 || b.Release.Version.Minor != 0 || b.Release.Version.Patch != 0) {
return strings.Join([]string{b.Package.Name, "v" + b.Version.String(), b.Release.String()}, "-")
} else {
return b.Name
}
}

// order by version, then
// release, if present
// - label first, if present
// - then version, if present
func (b *Bundle) Compare(other *Bundle) int {
if b.Name == other.Name {
return 0
}
if b.Version.NE(other.Version) {
return b.Version.Compare(other.Version)
}
if b.Release.Label != other.Release.Label {
return strings.Compare(b.Release.Label, other.Release.Label)
}
if b.Release.Version.NE(other.Release.Version) {
return b.Release.Version.Compare(other.Release.Version)
}
return 0
}

func (b *Bundle) Validate() error {
Expand All @@ -335,6 +376,9 @@ func (b *Bundle) Validate() error {
if b.Name == "" {
result.subErrors = append(result.subErrors, errors.New("name must be set"))
}
if b.Name != b.normalizeName() {
result.subErrors = append(result.subErrors, fmt.Errorf("name %q does not match normalized name %q", b.Name, b.normalizeName()))
}
if b.Channel == nil {
result.subErrors = append(result.subErrors, errors.New("channel must be set"))
}
Expand Down
35 changes: 35 additions & 0 deletions alpha/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,41 @@ func TestValidators(t *testing.T) {
},
assertion: hasError(`duplicate versions found in bundles: [{0.0.1: [anakin.v0.0.1, anakin.v0.0.2]} {1.0.1: [anakin.v1.0.1, anakin.v1.0.2]}]`),
},
{
name: "Package/Error/DuplicateBundleVersionsReleases",
v: &Package{
Name: "anakin",
Channels: map[string]*Channel{
"light": {
Package: pkg,
Name: "light",
Bundles: map[string]*Bundle{
"anakin.v0.0.1": {Name: "anakin.v0.0.1", Version: semver.MustParse("0.0.1")},
"anakin.v0.0.2": {Name: "anakin.v0.0.2", Version: semver.MustParse("0.0.1")},
"anakin-v0.0.1-alpha-0.0.1": {Name: "anakin.v0.0.1", Version: semver.MustParse("0.0.1"), Release: property.Release{Label: "alpha", Version: semver.MustParse("0.0.1")}, Package: pkg},
"anakin-v0.0.2-alpha-0.0.1": {Name: "anakin.v0.0.2", Version: semver.MustParse("0.0.1"), Release: property.Release{Label: "alpha", Version: semver.MustParse("0.0.1")}, Package: pkg},
},
},
},
},
assertion: hasError(`duplicate versions found in bundles: [{0.0.1: [anakin.v0.0.1, anakin.v0.0.2]} {0.0.1-alpha-0.0.1: [anakin.v0.0.1, anakin.v0.0.2]}]`),
},
{
name: "Package/Error/BundleReleaseNormalizedName",
v: &Package{
Name: "anakin",
Channels: map[string]*Channel{
"light": {
Package: pkg,
Name: "light",
Bundles: map[string]*Bundle{
"anakin.v0.0.1.alpha.0.0.1": {Name: "anakin.v0.0.1.alpha.0.0.1", Version: semver.MustParse("0.0.1"), Release: property.Release{Label: "alpha", Version: semver.MustParse("0.0.1")}, Package: pkg},
},
},
},
},
assertion: hasError(`name "anakin.v0.0.1.alpha.0.0.1" does not match normalized name "anakin-v0.0.1-alpha-0.0.1"`),
},
{
name: "Package/Error/NoDefaultChannel",
v: &Package{
Expand Down
26 changes: 24 additions & 2 deletions alpha/property/property.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"errors"
"fmt"
"reflect"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/blang/semver/v4"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

Expand All @@ -35,9 +37,15 @@ func (p Property) String() string {
return fmt.Sprintf("type: %q, value: %q", p.Type, p.Value)
}

type Release struct {
Label string `json:"label"`
Version semver.Version `json:"version"`
}

type Package struct {
PackageName string `json:"packageName"`
Version string `json:"version"`
PackageName string `json:"packageName"`
Version string `json:"version"`
Release Release `json:"release,omitzero"`
}

// NOTICE: The Channel properties are for internal use only.
Expand Down Expand Up @@ -247,6 +255,9 @@ func jsonMarshal(p interface{}) ([]byte, error) {
func MustBuildPackage(name, version string) Property {
return MustBuild(&Package{PackageName: name, Version: version})
}
func MustBuildPackageRelease(name, version, relLabel, relVersion string) Property {
return MustBuild(&Package{PackageName: name, Version: version, Release: Release{Label: relLabel, Version: semver.MustParse(relVersion)}})
}
func MustBuildPackageRequired(name, versionRange string) Property {
return MustBuild(&PackageRequired{name, versionRange})
}
Expand Down Expand Up @@ -286,3 +297,14 @@ func MustBuildCSVMetadata(csv v1alpha1.ClusterServiceVersion) Property {
func MustBuildChannelPriority(name string, priority int) Property {
return MustBuild(&Channel{ChannelName: name, Priority: priority})
}

func (r *Release) String() string {
segments := []string{}
if r.Label != "" {
segments = append(segments, r.Label)
}
if r.Version.Major != 0 || r.Version.Minor != 0 || r.Version.Patch != 0 {
segments = append(segments, r.Version.String())
}
return strings.Join(segments, "-")
}
17 changes: 12 additions & 5 deletions alpha/property/property_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"testing"

"github.com/blang/semver/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -132,12 +133,12 @@ func TestParse(t *testing.T) {
},
expectProps: &Properties{
Packages: []Package{
{"package1", "0.1.0"},
{"package2", "0.2.0"},
{PackageName: "package1", Version: "0.1.0"},
{PackageName: "package2", Version: "0.2.0"},
},
PackagesRequired: []PackageRequired{
{"package3", ">=1.0.0 <2.0.0-0"},
{"package4", ">=2.0.0 <3.0.0-0"},
{PackageName: "package3", VersionRange: ">=1.0.0 <2.0.0-0"},
{PackageName: "package4", VersionRange: ">=2.0.0 <3.0.0-0"},
},
GVKs: []GVK{
{"group", "Kind1", "v1"},
Expand Down Expand Up @@ -206,10 +207,16 @@ func TestBuild(t *testing.T) {
specs := []spec{
{
name: "Success/Package",
input: &Package{"name", "0.1.0"},
input: &Package{PackageName: "name", Version: "0.1.0"},
assertion: require.NoError,
expectedProperty: propPtr(MustBuildPackage("name", "0.1.0")),
},
{
name: "Success/Package-ReleaseVersion",
input: &Package{PackageName: "name", Version: "0.1.0", Release: Release{Label: "alpha-whatsit", Version: semver.MustParse("1.1.0-bluefoot")}},
assertion: require.NoError,
expectedProperty: propPtr(MustBuildPackageRelease("name", "0.1.0", "alpha-whatsit", "1.1.0-bluefoot")),
},
{
name: "Success/PackageRequired",
input: &PackageRequired{"name", ">=0.1.0"},
Expand Down
60 changes: 60 additions & 0 deletions alpha/template/substitutes/substitutes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package substitutes

import (
"context"
"encoding/json"
"fmt"
"io"

"k8s.io/apimachinery/pkg/util/yaml"

"github.com/operator-framework/operator-registry/alpha/declcfg"
)

type Template struct {
RenderBundle func(context.Context, string) (*declcfg.DeclarativeConfig, error)
}

type Substitute struct {
Name string `json:"name"`
Base string `json:"base"`
}

type SubstitutesForTemplate struct {
Schema string `json:"schema"`
Entries []*declcfg.Meta `json:"entries"`
Substitutions []Substitute `json:"substitutions"`
}

const schema string = "olm.template.substitutes"

func parseSpec(reader io.Reader) (*SubstitutesForTemplate, error) {
st := &SubstitutesForTemplate{}
stDoc := json.RawMessage{}
stDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
err := stDecoder.Decode(&stDoc)
if err != nil {
return nil, fmt.Errorf("decoding template schema: %v", err)
}
err = json.Unmarshal(stDoc, st)
if err != nil {
return nil, fmt.Errorf("unmarshalling template: %v", err)
}

if st.Schema != schema {
return nil, fmt.Errorf("template has unknown schema (%q), should be %q", st.Schema, schema)
}

return st, nil
}

func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.DeclarativeConfig, error) {
st, err := parseSpec(reader)
if err != nil {
return nil, fmt.Errorf("render: unable to parse template: %v", err)
}

// TODO: Implement the actual rendering logic using st.Entries and st.Substitutes
_ = st
return nil, nil
}
4 changes: 4 additions & 0 deletions cmd/opm/alpha/template/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func NewCmd() *cobra.Command {
// sc.Hidden = true
runCmd.AddCommand(sc)

subs := newSubstitutesForTemplateCmd()
// subs.Hidden = true
runCmd.AddCommand(subs)

runCmd.PersistentFlags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)")

return runCmd
Expand Down
Loading
Loading