Skip to content

Commit

Permalink
feat(package-operator): add specifying values for package components (g…
Browse files Browse the repository at this point in the history
…lasskube#1218)

Signed-off-by: Jakob Steiner <[email protected]>
  • Loading branch information
kosmoz authored Sep 11, 2024
1 parent 9c5416e commit ff7c3ce
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 22 deletions.
8 changes: 6 additions & 2 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,15 @@ type ValueReference struct {
PackageRef *PackageValueSource `json:"packageRef,omitempty"`
}

type InlineValueConfiguration struct {
Value *string `json:"value,omitempty"`
}

// +kubebuilder:validation:MinProperties:=1
// +kubebuilder:validation:MaxProperties:=1
type ValueConfiguration struct {
Value *string `json:"value,omitempty"`
ValueFrom *ValueReference `json:"valueFrom,omitempty"`
InlineValueConfiguration `json:",inline"`
ValueFrom *ValueReference `json:"valueFrom,omitempty"`
}

// PackageSpec defines the desired state
Expand Down
12 changes: 12 additions & 0 deletions api/v1alpha1/package_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ type Component struct {
Name string `json:"name" jsonschema:"required"`
InstalledName string `json:"installedName,omitempty"`
Version string `json:"version,omitempty"`
// Specify configuration for this component
Values ComponentValues `json:"values,omitempty"`
}

type ComponentValues map[string]InlineValueConfiguration

func (values ComponentValues) AsPackageValues() map[string]ValueConfiguration {
result := make(map[string]ValueConfiguration, len(values))
for name, value := range values {
result[name] = ValueConfiguration{InlineValueConfiguration: value}
}
return result
}

// +kubebuilder:validation:Enum=Cluster;Namespaced
Expand Down
58 changes: 52 additions & 6 deletions api/v1alpha1/zz_generated.deepcopy.go

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

8 changes: 8 additions & 0 deletions config/crd/bases/packages.glasskube.dev_packageinfos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ spec:
type: string
name:
type: string
values:
additionalProperties:
properties:
value:
type: string
type: object
description: Specify configuration for this component
type: object
version:
type: string
required:
Expand Down
26 changes: 23 additions & 3 deletions internal/controller/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"
"reflect"
"slices"
"strings"

Expand Down Expand Up @@ -329,6 +330,7 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
}

var newPkg ctrlpkg.Package
var pkgValues map[string]packagesv1alpha1.ValueConfiguration

if requirement.ComponentMetadata != nil {
newPkg = &packagesv1alpha1.Package{
Expand All @@ -337,6 +339,11 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
Namespace: requirement.ComponentMetadata.Namespace,
},
}
for _, cmp := range r.pi.Status.Manifest.Components {
if cmp.Name == requirement.Name {
pkgValues = cmp.Values.AsPackageValues()
}
}
} else {
newPkg = &packagesv1alpha1.ClusterPackage{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -374,6 +381,7 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
Version: requirement.Version,
RepositoryName: repositoryName,
}
newPkg.GetSpec().Values = pkgValues
return nil
}); err != nil {
log.Error(err, "Failed to create required package", "required", requirement.Name)
Expand Down Expand Up @@ -401,7 +409,10 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
var ownedPackages []packagesv1alpha1.OwnedResourceRef
var waitingFor []string

var handleRequiredPackage = func(requiredPkg ctrlpkg.Package) error {
var handleRequiredPackage = func(
requiredPkg ctrlpkg.Package,
componentValues map[string]packagesv1alpha1.ValueConfiguration,
) error {
if err := r.Get(ctx, client.ObjectKeyFromObject(requiredPkg), requiredPkg); err != nil {
if apierrors.IsNotFound(err) {
waitingFor = append(waitingFor, requiredPkg.GetName())
Expand All @@ -416,6 +427,15 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
} else {
ownedPackages = append(ownedPackages, owned)
}

if !reflect.DeepEqual(componentValues, requiredPkg.GetSpec().Values) {
requiredPkg.GetSpec().Values = componentValues
if err := r.Update(ctx, requiredPkg); err != nil {
log.Error(err, "Failed to update values of required package", "package", requiredPkg)
failed = append(failed, requiredPkg.GetName())
}
}

if meta.IsStatusConditionTrue(requiredPkg.GetStatus().Conditions, string(condition.Failed)) {
failed = append(failed, requiredPkg.GetName())
} else if !meta.IsStatusConditionTrue(requiredPkg.GetStatus().Conditions, string(condition.Ready)) {
Expand All @@ -430,7 +450,7 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
requiredPkg := packagesv1alpha1.ClusterPackage{
ObjectMeta: metav1.ObjectMeta{Name: dep.Name},
}
if err := handleRequiredPackage(&requiredPkg); err != nil {
if err := handleRequiredPackage(&requiredPkg, nil); err != nil {
r.setShouldUpdate(conditions.SetFailed(ctx, r.EventRecorder, r.pkg, &r.pkg.GetStatus().Conditions,
condition.InstallationFailed, err.Error()))
return false
Expand All @@ -447,7 +467,7 @@ func (r *PackageReconcilationContext) ensureDependencies(ctx context.Context) bo
if requiredPkg.Namespace == "" {
requiredPkg.Namespace = r.pi.Status.Manifest.DefaultNamespace
}
if err := handleRequiredPackage(&requiredPkg); err != nil {
if err := handleRequiredPackage(&requiredPkg, cmp.Values.AsPackageValues()); err != nil {
r.setShouldUpdate(conditions.SetFailed(ctx, r.EventRecorder, r.pkg, &r.pkg.GetStatus().Conditions,
condition.InstallationFailed, err.Error()))
return false
Expand Down
4 changes: 3 additions & 1 deletion internal/manifestvalues/cli/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ func Configure(
}
if options.ShouldUseDefault(name, def) {
fmt.Fprintf(os.Stderr, "Using default value for %v: %v\n", name, def.DefaultValue)
newValues[name] = v1alpha1.ValueConfiguration{Value: util.Pointer(def.DefaultValue)}
newValues[name] = v1alpha1.ValueConfiguration{
InlineValueConfiguration: v1alpha1.InlineValueConfiguration{Value: util.Pointer(def.DefaultValue)},
}
} else {
if newValue, err := ConfigureSingle(name, def, oldValuePtr); err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/manifestvalues/flags/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

var _ = Describe("ParseValues", func() {
foo := "foo"
fooMap := map[string]v1alpha1.ValueConfiguration{"foo": {Value: &foo}}
fooMap := map[string]v1alpha1.ValueConfiguration{"foo": {InlineValueConfiguration: v1alpha1.InlineValueConfiguration{Value: &foo}}}
var opts *ValuesOptions
BeforeEach(func() { opts = &ValuesOptions{} })
DescribeTable("should parse",
Expand Down
2 changes: 1 addition & 1 deletion internal/manifestvalues/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var _ = Describe("resolver", func() {
It("should resolve literal value", func(ctx context.Context) {
resolver := newTestResolver()
result, err := resolver.Resolve(ctx, map[string]v1alpha1.ValueConfiguration{
"test": {Value: &testConst},
"test": {InlineValueConfiguration: v1alpha1.InlineValueConfiguration{Value: &testConst}},
})
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(map[string]string{"test": "test"}))
Expand Down
4 changes: 2 additions & 2 deletions internal/web/configurationvalues.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ func extractValues(r *http.Request, manifest *v1alpha1.PackageManifest) (map[str
if strings.ToLower(formVal) == "on" {
boolStr = strconv.FormatBool(true)
}
values[valueName] = v1alpha1.ValueConfiguration{Value: &boolStr}
values[valueName] = v1alpha1.ValueConfiguration{InlineValueConfiguration: v1alpha1.InlineValueConfiguration{Value: &boolStr}}
} else {
values[valueName] = v1alpha1.ValueConfiguration{Value: &formVal}
values[valueName] = v1alpha1.ValueConfiguration{InlineValueConfiguration: v1alpha1.InlineValueConfiguration{Value: &formVal}}
}
} else {
return nil, fmt.Errorf("cannot extract value %v because of unknown reference kind %v", valueName, refKindVal)
Expand Down
28 changes: 22 additions & 6 deletions website/docs/06_reference/package-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Either `resource` or `chartName` must be specified.
| op | string | required | |
| path | string | required | |

The `value` to create a compete JSON Patch is supplied by the controller.
The `value` to create a complete JSON Patch is supplied by the controller.
See https://jsonpatch.com/ for a complete reference.

### TransformationSource
Expand Down Expand Up @@ -129,11 +129,20 @@ See https://jsonpatch.com/ for a complete reference.

### Component

| Name | Type | Required / Default | Description |
| ------------- | ------ | ------------------ | -------------------------------------- |
| name | string | required | |
| installedName | string | | name suffix for the created `Package` |
| version | string | | a semver constraint for this component |
| Name | Type | Required / Default | Description |
| ------------- | ---------------------------------------------------------------- | ------------------ | -------------------------------------- |
| name | string | required | |
| installedName | string | | name suffix for the created `Package` |
| version | string | | a semver constraint for this component |
| values | map[string][InlineValueConfiguration](#inlinevalueconfiguration) | | specify values for this component |

### InlineValueConfiguration

A stripped down variant of a package's value configuration that only supports directly specified values and no reference values.

| Name | Type | Required / Default | Description |
| ----- | ------ | ------------------ | ----------- |
| value | string | required | |

## Complete Example

Expand Down Expand Up @@ -173,6 +182,9 @@ components:
- name: postgresql
installedName: db
version: '>=1.0.0'
values:
enableSuperuserAccess:
value: 'true'
transitiveResources:
- apiVersion: v1
kind: Secret
Expand Down Expand Up @@ -209,3 +221,7 @@ transformations:
path: /config/database/dbHost
valueTemplate: '"{{.}}-db-rw"'
```
## JSON Schema
An up to date JSON schema file is available at https://glasskube.dev/schemas/v1/package-manifest.json.
18 changes: 18 additions & 0 deletions website/static/schemas/v1/package-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
},
"version": {
"type": "string"
},
"values": {
"$ref": "#/$defs/ComponentValues"
}
},
"additionalProperties": false,
Expand All @@ -20,6 +23,12 @@
"name"
]
},
"ComponentValues": {
"additionalProperties": {
"$ref": "#/$defs/InlineValueConfiguration"
},
"type": "object"
},
"Dependency": {
"properties": {
"name": {
Expand Down Expand Up @@ -58,6 +67,15 @@
"chartVersion"
]
},
"InlineValueConfiguration": {
"properties": {
"value": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"JSON": {
"additionalProperties": true,
"type": "object"
Expand Down

0 comments on commit ff7c3ce

Please sign in to comment.