Skip to content

Commit

Permalink
Improve Constraints Handling (#91)
Browse files Browse the repository at this point in the history
We already wrap up Masterminds' semver type, so we may as well use their
(far more elaborate) constraints checking too.
  • Loading branch information
spjmurray authored Nov 6, 2024
1 parent 23d5ff0 commit 17d58cf
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 81 deletions.
4 changes: 2 additions & 2 deletions charts/core/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn Core

type: application

version: v0.1.79
appVersion: v0.1.79
version: v0.1.80
appVersion: v0.1.80

icon: https://assets.unikorn-cloud.org/images/logos/dark-on-light/icon.svg

Expand Down
25 changes: 2 additions & 23 deletions charts/core/crds/unikorn-cloud.org_helmapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,29 +92,8 @@ spec:
constraints:
description: |-
Constraints is a set of versioning constraints that must be met
by a SAT solver, the set is composed as a logical AND so all
constraints must be met.
items:
properties:
operator:
description: Operator defines the constraint operation.
enum:
- Equal
- GreaterThan
- LessThan
- GreaterThanOrEqual
- LessThanOrEqual
type: string
version:
description: Version is the version the operator
compares against.
pattern: ^v?[0-9]+(\.[0-9]+)?(\.[0-9]+)?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?$
type: string
required:
- operator
- version
type: object
type: array
by a SAT solver.
type: string
name:
description: Name of the application to depend on.
minLength: 1
Expand Down
23 changes: 2 additions & 21 deletions pkg/apis/unikorn/v1alpha1/helmapplication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,8 @@ type HelmApplicationDependency struct {
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
// Constraints is a set of versioning constraints that must be met
// by a SAT solver, the set is composed as a logical AND so all
// constraints must be met.
Constraints []DependencyConstraint `json:"constraints,omitempty"`
}

// +kubebuilder:validation:Enum=Equal;GreaterThan;LessThan;GreaterThanOrEqual;LessThanOrEqual
type DependencyConstraintOperator string

const (
Equal DependencyConstraintOperator = "Equal"
GreaterThan DependencyConstraintOperator = "GreaterThan"
LessThan DependencyConstraintOperator = "LessThan"
GreaterThanOrEqual DependencyConstraintOperator = "GreaterThanOrEqual"
LessThanOrEqual DependencyConstraintOperator = "LessThanOrEqual"
)

type DependencyConstraint struct {
// Operator defines the constraint operation.
Operator DependencyConstraintOperator `json:"operator"`
// Version is the version the operator compares against.
Version SemanticVersion `json:"version"`
// by a SAT solver.
Constraints *SemanticVersionConstraints `json:"constraints,omitempty"`
}

type HelmApplicationRecommendation struct {
Expand Down
64 changes: 50 additions & 14 deletions pkg/apis/unikorn/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,68 @@ func (v SemanticVersion) Equal(o *SemanticVersion) bool {
return v.Version.Equal(&o.Version)
}

func (v SemanticVersion) GreaterThan(o *SemanticVersion) bool {
return v.Version.GreaterThan(&o.Version)
func (v *SemanticVersion) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &v.Version)
}

func (v SemanticVersion) GreaterThanEqual(o *SemanticVersion) bool {
return v.Version.GreaterThanEqual(&o.Version)
func (v SemanticVersion) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Original())
}

func (v SemanticVersion) LessThan(o *SemanticVersion) bool {
return v.Version.LessThan(&o.Version)
func (v SemanticVersion) ToUnstructured() interface{} {
return v.Original()
}

func (v SemanticVersion) LessThanEqual(o *SemanticVersion) bool {
return v.Version.LessThanEqual(&o.Version)
// SemanticVersionConstraints allows an arbitrary semantic version to be constrained.
// +kubebuilder:validation:Type=string
// +kubebuilder:object:generate=false
type SemanticVersionConstraints struct {
semver.Constraints
}

func (v *SemanticVersion) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &v.Version)
func (c SemanticVersionConstraints) Check(v *SemanticVersion) bool {
return c.Constraints.Check(&v.Version)
}

func (v SemanticVersion) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Original())
func (c SemanticVersionConstraints) String() string {
return c.Constraints.String()
}

func (v SemanticVersion) ToUnstructured() interface{} {
return v.Original()
func (c *SemanticVersionConstraints) UnmarshalJSON(b []byte) error {
var s string

if err := json.Unmarshal(b, &s); err != nil {
return err
}

constraints, err := semver.NewConstraint(s)
if err != nil {
return err
}

c.Constraints = *constraints

return nil
}

func (c SemanticVersionConstraints) MarshalJSON() ([]byte, error) {
return json.Marshal(c.Constraints.String())
}

func (c SemanticVersionConstraints) ToUnstructured() interface{} {
return c.Constraints.String()
}

func (c *SemanticVersionConstraints) DeepCopyInto(out *SemanticVersionConstraints) {
t, _ := c.MarshalText()
_ = out.Constraints.UnmarshalText(t)
}

func (c *SemanticVersionConstraints) DeepCopy() *SemanticVersionConstraints {
out := &SemanticVersionConstraints{}
c.DeepCopyInto(out)

return out
}

// +kubebuilder:validation:Type=string
Expand Down
39 changes: 37 additions & 2 deletions pkg/apis/unikorn/v1alpha1/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,58 @@ var (
func TestSemanticVersionCanonical(t *testing.T) {
t.Parallel()

jsonSemver := `"1.2.3-foo+bar"`

out := &v1alpha1.SemanticVersion{}

require.NoError(t, out.UnmarshalJSON([]byte(`"1.2.3-foo+bar"`)))
require.NoError(t, out.UnmarshalJSON([]byte(jsonSemver)))
require.EqualValues(t, 1, out.Major())
require.EqualValues(t, 2, out.Minor())
require.EqualValues(t, 3, out.Patch())

marshalled, err := out.MarshalJSON()
require.NoError(t, err)
require.Equal(t, jsonSemver, string(marshalled))
}

func TestSemanticVersion(t *testing.T) {
t.Parallel()

jsonSemver := `"v1.2.3-foo+bar"`

out := &v1alpha1.SemanticVersion{}

require.NoError(t, out.UnmarshalJSON([]byte(`"v1.2.3-foo+bar"`)))
require.NoError(t, out.UnmarshalJSON([]byte(jsonSemver)))
require.EqualValues(t, 1, out.Major())
require.EqualValues(t, 2, out.Minor())
require.EqualValues(t, 3, out.Patch())

marshalled, err := out.MarshalJSON()
require.NoError(t, err)
require.Equal(t, jsonSemver, string(marshalled))
}

func TestConstraints(t *testing.T) {
t.Parallel()

good := &v1alpha1.SemanticVersion{}
require.NoError(t, good.UnmarshalJSON([]byte(`"1.5.0"`)))

bad := &v1alpha1.SemanticVersion{}
require.NoError(t, bad.UnmarshalJSON([]byte(`"3.0.0"`)))

jsonConstraints := `">= 1.0.0, < 2.0.0"`

out := &v1alpha1.SemanticVersionConstraints{}

require.NoError(t, out.UnmarshalJSON([]byte(jsonConstraints)))
require.True(t, out.Check(good))
require.False(t, out.Check(bad))

// NOTE: This emits UTF8, which isn't the same as the input.
// We could do some text transformation I guess...
_, err := out.MarshalJSON()
require.NoError(t, err)
}

func TestIPv4AddressUnmarshal(t *testing.T) {
Expand Down
20 changes: 1 addition & 19 deletions pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 17d58cf

Please sign in to comment.