api/v1alpha1/packagebundle.go (173 lines of code) (raw):
package v1alpha1
import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"path"
"strconv"
"strings"
"k8s.io/apimachinery/pkg/version"
)
const (
PackageBundleKind = "PackageBundle"
Latest = "latest"
)
func (config *PackageBundle) MetaKind() string {
return config.TypeMeta.Kind
}
func (config *PackageBundle) ExpectedKind() string {
return PackageBundleKind
}
func (config *PackageBundle) FindPackage(pkgName string) (retPkg BundlePackage, err error) {
for _, pkg := range config.Spec.Packages {
if strings.EqualFold(pkg.Name, pkgName) {
return pkg, nil
}
}
return retPkg, fmt.Errorf("package not found in bundle (%s): %s", config.Name, pkgName)
}
func (config *PackageBundle) GetDependencies(version SourceVersion) (dependencies []BundlePackage, err error) {
for _, dep := range version.Dependencies {
pkg, err := config.FindPackage(dep)
if err != nil {
return nil, err
}
dependencies = append(dependencies, pkg)
}
return dependencies, nil
}
func (config *PackageBundle) FindVersion(pkg BundlePackage, pkgVersion string) (ret SourceVersion, err error) {
source := pkg.Source
for _, packageVersion := range source.Versions {
// We do not sort before getting `latest` because there will be only a single packageVersion per release in normal cases. For edge cases which may require multiple
// versions, the order in the file will be ordered according to what we want `latest` to point to
if packageVersion.Name == pkgVersion || packageVersion.Digest == pkgVersion || pkgVersion == Latest {
return packageVersion, nil
}
}
return ret, fmt.Errorf("package version not found in bundle (%s): %s @ %s", config.Name, pkg.Name, pkgVersion)
}
func (config *PackageBundle) FindOCISourceByName(pkgName, pkgVersion string) (retSource PackageOCISource, err error) {
pkg, err := config.FindPackage(pkgName)
if err != nil {
return retSource, err
}
return config.FindOCISource(pkg, pkgVersion)
}
func (config *PackageBundle) FindOCISource(pkg BundlePackage, pkgVersion string) (retSource PackageOCISource, err error) {
packageVersion, err := config.FindVersion(pkg, pkgVersion)
if err != nil {
return retSource, err
}
return config.GetOCISource(pkg, packageVersion), nil
}
func (config *PackageBundle) GetOCISource(pkg BundlePackage, packageVersion SourceVersion) (retSource PackageOCISource) {
source := pkg.Source
return PackageOCISource{Registry: source.Registry, Repository: source.Repository, Digest: packageVersion.Digest, Version: packageVersion.Name}
}
// LessThan evaluates if the left calling bundle is less than the supplied parameter
//
// If the left hand side bundle is older than the right hand side, this
// method returns true. If it is newer (greater) it returns false. If they are
// the same it returns false.
func (config PackageBundle) LessThan(rhsBundle *PackageBundle) bool {
lhsMajor, lhsMinor, lhsBuild, _ := config.getMajorMinorBuild()
rhsMajor, rhsMinor, rhsBuild, _ := rhsBundle.getMajorMinorBuild()
return lhsMajor < rhsMajor || lhsMinor < rhsMinor || lhsBuild < rhsBuild
}
// BundlesByVersion implements sort.Interface for PackageBundles.
type BundlesByVersion []PackageBundle
func (b BundlesByVersion) Len() int {
return len(b)
}
func (b BundlesByVersion) Less(i, j int) bool {
return b[i].LessThan(&b[j])
}
func (b BundlesByVersion) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
// getMajorMinorBuild returns the Kubernetes major version, Kubernetes minor
// version, and bundle build version.
func (config *PackageBundle) getMajorMinorBuild() (major, minor, build int, err error) {
s := strings.Split(config.Name, "-")
s = append(s, "", "", "")
s[0] = strings.TrimPrefix(s[0], "v")
build = 0
minor = 0
major, err = strconv.Atoi(s[0])
if err != nil {
return major, minor, build, fmt.Errorf("invalid major number <%s>", config.Name)
} else {
minor, err = strconv.Atoi(s[1])
if err != nil {
return major, minor, build, fmt.Errorf("invalid minor number <%s>", config.Name)
} else {
build, err = strconv.Atoi(s[2])
if err != nil {
return major, minor, build, fmt.Errorf("invalid build number <%s>", config.Name)
}
}
}
return major, minor, build, err
}
// getMajorMinorFromString returns the Kubernetes major and minor version.
//
// It returns 0, 0 for empty string.
func getMajorMinorFromString(kubeVersion string) (major, minor int) {
s := strings.Split(kubeVersion, "-")
s = append(s, "", "", "")
s[0] = strings.TrimPrefix(s[0], "v")
major, _ = strconv.Atoi(s[0])
minor, _ = strconv.Atoi(s[1])
return major, minor
}
// KubeVersionMatches returns true if the target Kubernetes matches the
// current bundle's Kubernetes version.
//
// Note the method only compares the major and minor versions of Kubernetes, and
// ignore the patch numbers.
func (config *PackageBundle) KubeVersionMatches(targetKubeVersion *version.Info) (matches bool, err error) {
currKubeMajor, currKubeMinor, _, err := config.getMajorMinorBuild()
if err != nil {
return false, err
}
return fmt.Sprint(currKubeMajor) == targetKubeVersion.Major && fmt.Sprint(currKubeMinor) == targetKubeVersion.Minor, nil
}
// IsValidVersion returns true if the bundle version is valid
func (config *PackageBundle) IsValidVersion() bool {
_, _, _, err := config.getMajorMinorBuild()
return err == nil
}
func (s PackageOCISource) GetChartUri() string {
return "oci://" + path.Join(s.Registry, s.Repository)
}
// PackageMatches returns true if the given source locations match one another.
func (s BundlePackageSource) PackageMatches(other BundlePackageSource) bool {
if s.Registry != other.Registry {
return false
}
if s.Repository != other.Repository {
return false
}
myVersions := make(map[string]struct{})
for _, packageVersion := range s.Versions {
myVersions[packageVersion.Key()] = struct{}{}
}
for _, packageVersion := range other.Versions {
if _, ok := myVersions[packageVersion.Key()]; !ok {
return false
}
}
otherVersions := make(map[string]struct{})
for _, packageVersion := range other.Versions {
otherVersions[packageVersion.Key()] = struct{}{}
}
for key := range myVersions {
if _, ok := otherVersions[key]; !ok {
return false
}
}
return true
}
func (bp *BundlePackage) GetJsonSchema(pkgVersion *SourceVersion) ([]byte, error) {
// The package configuration is gzipped and base64 encoded
// When processing the configuration, the reverse occurs: base64 decode, then unzip
configuration := pkgVersion.Schema
decodedConfiguration, err := base64.StdEncoding.DecodeString(configuration)
if err != nil {
return nil, fmt.Errorf("error decoding configurations %v", err)
}
reader := bytes.NewReader(decodedConfiguration)
gzreader, err := gzip.NewReader(reader)
if err != nil {
return nil, fmt.Errorf("error when uncompressing configurations %v", err)
}
output, err := io.ReadAll(gzreader)
if err != nil {
return nil, fmt.Errorf("error reading configurations %v", err)
}
return output, nil
}
func (v SourceVersion) Key() string {
return v.Name + " " + v.Digest
}