From c84ddd7174adad4fc45d1e75bcba7a3a61d8c161 Mon Sep 17 00:00:00 2001 From: Artem Bortnikov <82099337+aobort@users.noreply.github.com> Date: Thu, 13 Jun 2024 19:46:05 +0300 Subject: [PATCH] Machine classification (#119) * classification types Signed-off-by: Artem Bortnikov * aggregate controller Signed-off-by: Artem Bortnikov * size controller Signed-off-by: Artem Bortnikov * size controller Signed-off-by: Artem Bortnikov * flags to enable controllers Signed-off-by: Artem Bortnikov * update size labels on machine Signed-off-by: Artem Bortnikov * chore updates for types and controllers Signed-off-by: Artem Bortnikov * fix lint Signed-off-by: Artem Bortnikov --------- Signed-off-by: Artem Bortnikov --- PROJECT | 16 + api/v1alpha1/aggregate_types.go | 131 ++++++ api/v1alpha1/aggregation_results.go | 50 ++ api/v1alpha1/common.go | 6 +- api/v1alpha1/constraint_types.go | 132 ++++++ api/v1alpha1/constraintval_type.go | 74 +++ api/v1alpha1/funcs.go | 262 +++++++++++ api/v1alpha1/inventory_types.go | 2 +- api/v1alpha1/jsonpath_types.go | 79 ++++ api/v1alpha1/machine_types.go | 1 + api/v1alpha1/size_types.go | 134 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 314 +++++++++++- .../api/v1alpha1/aggregate.go | 245 ++++++++++ .../api/v1alpha1/aggregateitem.go | 48 ++ .../api/v1alpha1/aggregatespec.go | 31 ++ .../api/v1alpha1/constraintspec.go | 94 ++++ .../api/v1alpha1/inventorystatus.go | 13 + .../applyconfiguration/api/v1alpha1/size.go | 245 ++++++++++ .../api/v1alpha1/sizespec.go | 31 ++ .../applyconfiguration/internal/internal.go | 162 +++++++ client/applyconfiguration/utils.go | 12 + client/openapi/zz_generated.openapi.go | 445 +++++++++++++++++- cmd/main.go | 40 ++ .../bases/metal.ironcore.dev_aggregates.yaml | 81 ++++ .../bases/metal.ironcore.dev_inventories.yaml | 6 +- .../bases/metal.ironcore.dev_machines.yaml | 3 + .../crd/bases/metal.ironcore.dev_sizes.yaml | 108 +++++ config/crd/kustomization.yaml | 2 + config/rbac/role.yaml | 54 +++ internal/controller/aggregate_controller.go | 155 ++++++ internal/controller/inventory_controller.go | 34 +- internal/controller/size_controller.go | 189 ++++++++ 32 files changed, 3186 insertions(+), 13 deletions(-) create mode 100644 api/v1alpha1/aggregate_types.go create mode 100644 api/v1alpha1/aggregation_results.go create mode 100644 api/v1alpha1/constraint_types.go create mode 100644 api/v1alpha1/constraintval_type.go create mode 100644 api/v1alpha1/funcs.go create mode 100644 api/v1alpha1/jsonpath_types.go create mode 100644 api/v1alpha1/size_types.go create mode 100644 client/applyconfiguration/api/v1alpha1/aggregate.go create mode 100644 client/applyconfiguration/api/v1alpha1/aggregateitem.go create mode 100644 client/applyconfiguration/api/v1alpha1/aggregatespec.go create mode 100644 client/applyconfiguration/api/v1alpha1/constraintspec.go create mode 100644 client/applyconfiguration/api/v1alpha1/size.go create mode 100644 client/applyconfiguration/api/v1alpha1/sizespec.go create mode 100644 config/crd/bases/metal.ironcore.dev_aggregates.yaml create mode 100644 config/crd/bases/metal.ironcore.dev_sizes.yaml create mode 100644 internal/controller/aggregate_controller.go create mode 100644 internal/controller/size_controller.go diff --git a/PROJECT b/PROJECT index 9fb5e429..9d1b0595 100644 --- a/PROJECT +++ b/PROJECT @@ -49,4 +49,20 @@ resources: kind: Inventory path: github.com/ironcore-dev/metal/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: ironcore.dev + group: metal + kind: Size + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: ironcore.dev + group: metal + kind: Aggregate + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/aggregate_types.go b/api/v1alpha1/aggregate_types.go new file mode 100644 index 00000000..3daa7caf --- /dev/null +++ b/api/v1alpha1/aggregate_types.go @@ -0,0 +1,131 @@ +package v1alpha1 + +import ( + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type AggregateType string + +const ( + AverageAggregateType AggregateType = "avg" + SumAggregateType AggregateType = "sum" + MinAggregateType AggregateType = "min" + MaxAggregateType AggregateType = "max" + CountAggregateType AggregateType = "count" +) + +type AggregateItem struct { + // SourcePath is a path in Inventory spec aggregate will be applied to + // +kubebuilder:validation:Required + SourcePath JSONPath `json:"sourcePath"` + // TargetPath is a path in Inventory status `computed` field + // +kubebuilder:validation:Required + TargetPath JSONPath `json:"targetPath"` + // Aggregate defines whether collection values should be aggregated + // for constraint checks, in case if path defines selector for collection + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Enum=avg;sum;min;max;count + Aggregate AggregateType `json:"aggregate,omitempty"` +} + +// AggregateSpec defines the desired state of Aggregate. +type AggregateSpec struct { + // Aggregates is a list of aggregates required to be computed + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + Aggregates []AggregateItem `json:"aggregates"` +} + +// AggregateStatus defines the observed state of Aggregate. +type AggregateStatus struct{} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient + +// Aggregate is the Schema for the aggregates API. +type Aggregate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AggregateSpec `json:"spec,omitempty"` + Status AggregateStatus `json:"status,omitempty"` +} + +func init() { + SchemeBuilder.Register(&Aggregate{}, &AggregateList{}) +} + +// +kubebuilder:object:root=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AggregateList contains a list of Aggregate. +type AggregateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Aggregate `json:"items"` +} + +func (in *Aggregate) Compute(inventory *Inventory) (interface{}, error) { + resultMap := make(map[string]interface{}) + + for _, ai := range in.Spec.Aggregates { + jp, err := ai.SourcePath.ToK8sJSONPath() + if err != nil { + return nil, err + } + + jp.AllowMissingKeys(true) + data, err := jp.FindResults(inventory) + if err != nil { + return nil, err + } + + var aggregatedValue interface{} = nil + tokenizedPath := ai.TargetPath.Tokenize() + + dataLen := len(data) + if dataLen == 0 { + if err := setValueToPath(resultMap, tokenizedPath, aggregatedValue); err != nil { + return nil, err + } + continue + } + if dataLen > 1 { + return nil, errors.New("expected only one value collection to be returned for aggregation") + } + + values := data[0] + valuesLen := len(values) + + if valuesLen == 0 { + if err := setValueToPath(resultMap, tokenizedPath, aggregatedValue); err != nil { + return nil, err + } + continue + } + + if ai.Aggregate == "" { + interfacedValues := make([]interface{}, valuesLen) + for i, value := range values { + interfacedValues[i] = value.Interface() + } + aggregatedValue = interfacedValues + } else { + aggregatedValue, err = makeAggregate(ai.Aggregate, values) + if err != nil { + // If we are failing to calculate one aggregate, we can skip it + // and continue to the next one + continue + } + } + + if err := setValueToPath(resultMap, tokenizedPath, aggregatedValue); err != nil { + return nil, err + } + } + + return resultMap, nil +} diff --git a/api/v1alpha1/aggregation_results.go b/api/v1alpha1/aggregation_results.go new file mode 100644 index 00000000..facc710a --- /dev/null +++ b/api/v1alpha1/aggregation_results.go @@ -0,0 +1,50 @@ +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/json" +) + +// +kubebuilder:validation:Type=object +type AggregationResults struct { + Object map[string]interface{} `json:"-"` +} + +func (in *AggregationResults) MarshalJSON() ([]byte, error) { + if in.Object == nil { + return json.Marshal(map[string]interface{}{}) + } + return json.Marshal(in.Object) +} + +func (in *AggregationResults) UnmarshalJSON(b []byte) error { + stringVal := string(b) + if stringVal == null { + in.Object = nil + return nil + } + if err := json.Unmarshal(b, &in.Object); err != nil { + return err + } + + return nil +} + +func (in *AggregationResults) DeepCopyInto(out *AggregationResults) { + if in == nil { + out = nil + } else if in.Object == nil { + out.Object = nil + } else { + out.Object = runtime.DeepCopyJSON(in.Object) + } +} + +func (in *AggregationResults) DeepCopy() *AggregationResults { + if in == nil { + return nil + } + out := new(AggregationResults) + in.DeepCopyInto(out) + return out +} diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index fe902148..4faf7c36 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -8,13 +8,15 @@ import ( "net/netip" ) +const null = "null" + type Prefix struct { netip.Prefix `json:"-"` } //goland:noinspection GoMixedReceiverTypes func (p *Prefix) UnmarshalJSON(b []byte) error { - if len(b) == 4 && string(b) == "null" { + if len(b) == 4 && string(b) == null { p.Prefix = netip.Prefix{} return nil } @@ -38,7 +40,7 @@ func (p *Prefix) UnmarshalJSON(b []byte) error { //goland:noinspection GoMixedReceiverTypes func (p *Prefix) MarshalJSON() ([]byte, error) { if p.IsZero() { - return []byte("null"), nil + return []byte(null), nil } return json.Marshal(p.String()) diff --git a/api/v1alpha1/constraint_types.go b/api/v1alpha1/constraint_types.go new file mode 100644 index 00000000..684e3f67 --- /dev/null +++ b/api/v1alpha1/constraint_types.go @@ -0,0 +1,132 @@ +package v1alpha1 + +import ( + "reflect" + + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/resource" +) + +// ConstraintSpec contains conditions of constraint that should be applied on resource. +type ConstraintSpec struct { + // Path is a path to the struct field constraint will be applied to + // +kubebuilder:validation:Optional + Path JSONPath `json:"path,omitempty"` + // Aggregate defines whether collection values should be aggregated + // for constraint checks, in case if path defines selector for collection + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Enum=avg;sum;min;max;count + Aggregate AggregateType `json:"agg,omitempty"` + // Equal contains an exact expected value + // +kubebuilder:validation:Optional + Equal *ConstraintValSpec `json:"eq,omitempty"` + // NotEqual contains an exact not expected value + // +kubebuilder:validation:Optional + NotEqual *ConstraintValSpec `json:"neq,omitempty"` + // LessThan contains an highest expected value, exclusive + // +kubebuilder:validation:Optional + LessThan *resource.Quantity `json:"lt,omitempty"` + // LessThan contains an highest expected value, inclusive + // +kubebuilder:validation:Optional + LessThanOrEqual *resource.Quantity `json:"lte,omitempty"` + // LessThan contains an lowest expected value, exclusive + // +kubebuilder:validation:Optional + GreaterThan *resource.Quantity `json:"gt,omitempty"` + // GreaterThanOrEqual contains an lowest expected value, inclusive + // +kubebuilder:validation:Optional + GreaterThanOrEqual *resource.Quantity `json:"gte,omitempty"` +} + +func (in *ConstraintSpec) MatchSingleValue(value *reflect.Value) (bool, error) { + // We do not have special constraints for nil and/or empty values + // so returning false for now if the value is nil + if value.Kind() == reflect.Ptr && value.IsNil() { + return false, nil + } + + if in.Equal != nil { + r, err := in.Equal.Compare(value) + if err != nil { + return false, err + } + return r == 0, nil + } + if in.NotEqual != nil { + r, err := in.NotEqual.Compare(value) + if err != nil { + return false, err + } + return r != 0, nil + } + + matches := true + q, err := valueToQuantity(value) + if err != nil { + return false, err + } + if in.GreaterThanOrEqual != nil { + r := q.Cmp(*in.GreaterThanOrEqual) + matches = r >= 0 + } + if in.GreaterThan != nil { + r := q.Cmp(*in.GreaterThan) + matches = matches && (r > 0) + } + if in.LessThanOrEqual != nil { + r := q.Cmp(*in.LessThanOrEqual) + matches = matches && (r <= 0) + } + if in.LessThan != nil { + r := q.Cmp(*in.LessThan) + matches = matches && (r < 0) + } + + return matches, nil +} + +func (in *ConstraintSpec) MatchMultipleValues(aggregateType AggregateType, values []reflect.Value) (bool, error) { + if aggregateType == "" { + for _, value := range values { + matches, err := in.MatchSingleValue(&value) + if err != nil { + return false, err + } + if !matches { + return false, nil + } + } + return true, nil + } + + agg, err := makeAggregate(aggregateType, values) + if err != nil { + return false, errors.Wrapf(err, "unable to compute aggregate %s", aggregateType) + } + + if in.Equal != nil { + return agg.Cmp(*in.Equal.Numeric) == 0, nil + } + if in.NotEqual != nil { + return agg.Cmp(*in.NotEqual.Numeric) != 0, nil + } + + matches := true + if in.GreaterThanOrEqual != nil { + r := agg.Cmp(*in.GreaterThanOrEqual) + matches = r >= 0 + } + if in.GreaterThan != nil { + r := agg.Cmp(*in.GreaterThan) + matches = matches && (r > 0) + } + if in.LessThanOrEqual != nil { + r := agg.Cmp(*in.LessThanOrEqual) + matches = matches && (r <= 0) + } + if in.LessThan != nil { + r := agg.Cmp(*in.LessThan) + matches = matches && (r < 0) + } + + return matches, nil +} diff --git a/api/v1alpha1/constraintval_type.go b/api/v1alpha1/constraintval_type.go new file mode 100644 index 00000000..1ced7e1b --- /dev/null +++ b/api/v1alpha1/constraintval_type.go @@ -0,0 +1,74 @@ +package v1alpha1 + +import ( + "reflect" + "strings" + + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/json" +) + +// ConstraintValSpec is a wrapper around value for constraint. +// Since it is not possilble to set oneOf/anyOf through kubebuilder +// markers, type is set to number here, and patched with kustomize +// See https://github.com/kubernetes-sigs/kubebuilder/issues/301 +// +kubebuilder:validation:Type=number +type ConstraintValSpec struct { + Literal *string `json:"-"` + Numeric *resource.Quantity `json:"-"` +} + +func (in *ConstraintValSpec) MarshalJSON() ([]byte, error) { + if in.Literal != nil && in.Numeric != nil { + return nil, errors.New("unable to marshal JSON since both numeric and literal fields are set") + } + if in.Literal != nil { + return json.Marshal(in.Literal) + } + if in.Numeric != nil { + return json.Marshal(in.Numeric) + } + return nil, nil +} + +func (in *ConstraintValSpec) UnmarshalJSON(data []byte) error { + if len(data) == 0 || string(data) == null { + in.Literal = nil + in.Numeric = nil + return nil + } + q := resource.Quantity{} + err := q.UnmarshalJSON(data) + if err == nil { + in.Numeric = &q + return nil + } + var str string + err = json.Unmarshal(data, &str) + if err != nil { + return err + } + in.Literal = &str + return nil +} + +func (in *ConstraintValSpec) Compare(value *reflect.Value) (int, error) { + if in.Literal != nil { + s, err := valueToString(value) + if err != nil { + return 0, err + } + return strings.Compare(s, *in.Literal), nil + } + + if in.Numeric != nil { + q, err := valueToQuantity(value) + if err != nil { + return 0, err + } + return q.Cmp(*in.Numeric), nil + } + + return 0, errors.New("both numeric and literal constraints are nil") +} diff --git a/api/v1alpha1/funcs.go b/api/v1alpha1/funcs.go new file mode 100644 index 00000000..36ab5148 --- /dev/null +++ b/api/v1alpha1/funcs.go @@ -0,0 +1,262 @@ +package v1alpha1 + +import ( + "fmt" + "reflect" + "strings" + + "github.com/pkg/errors" + "gopkg.in/inf.v0" + "k8s.io/apimachinery/pkg/api/resource" +) + +func makeAggregate(theType AggregateType, values []reflect.Value) (*resource.Quantity, error) { + switch theType { + case MaxAggregateType: + return maxAggregate(values) + case MinAggregateType: + return minAggregate(values) + case CountAggregateType: + return countAggregate(values), nil + case SumAggregateType: + return sumAggregate(values) + case AverageAggregateType: + return averageAggregate(values) + } + return nil, errors.Errorf("unknown aggregation type %s", theType) +} + +func minAggregate(values []reflect.Value) (*resource.Quantity, error) { + minimum, err := valueToQuantity(&values[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert value to quantity") + } + for i := 1; i < len(values); i++ { + curr, err := valueToQuantity(&values[i]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert value to quantity") + } + if minimum.Cmp(*curr) > 0 { + minimum = curr + } + } + return minimum, nil +} + +func maxAggregate(values []reflect.Value) (*resource.Quantity, error) { + maximum, err := valueToQuantity(&values[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert value to quantity") + } + for i := 1; i < len(values); i++ { + curr, err := valueToQuantity(&values[i]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert value to quantity") + } + if maximum.Cmp(*curr) < 0 { + maximum = curr + } + } + return maximum, nil +} + +func sumAggregate(values []reflect.Value) (*resource.Quantity, error) { + sum := resource.NewScaledQuantity(0, 0) + for _, value := range values { + q, err := valueToQuantity(&value) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert value to quantity") + } + sum.Add(*q) + } + return sum, nil +} + +func averageAggregate(values []reflect.Value) (*resource.Quantity, error) { + sum, err := sumAggregate(values) + if err != nil { + return nil, errors.Wrapf(err, "unable to calculate aggregate") + } + decVal := sum.AsDec() + divInt := len(values) + div := inf.NewDec(int64(divInt), 0) + res := decVal.QuoExact(decVal, div) + if res == nil { + return nil, errors.Errorf("quotient of %s/%s is not finite decimal", decVal.String(), div.String()) + } + agg := resource.MustParse(res.String()) + return &agg, nil +} + +func countAggregate(values []reflect.Value) *resource.Quantity { + return resource.NewScaledQuantity(int64(len(values)), 0) +} + +func setValueToPath(theMap map[string]interface{}, tokenizedPath []string, valueToSet interface{}) error { + lastTokenIdx := len(tokenizedPath) - 1 + var prevVal interface{} = theMap + for idx, token := range tokenizedPath { + currMap, ok := prevVal.(map[string]interface{}) + // if previous value is empty struct, but there are still tokens + // then parent path is used to set value, and it is not possible + // to set value in child path + if !ok { + return errors.Errorf("can not use path %s to set value, as parent path %s already used to set value", strings.Join(tokenizedPath, "."), strings.Join(tokenizedPath[:idx+1], ".")) + } + + currVal, ok := currMap[token] + // if value is not set + if !ok { + // if it is the last token, then set empty struct + if idx == lastTokenIdx { + currMap[token] = valueToSet + // otherwise create a map + } else { + currMap[token] = make(map[string]interface{}) + } + // if value is set + } else { + _, ok := currVal.(map[string]interface{}) + // if value is not map and it is the last token, + // then there is a duplicate path + if !ok && idx == lastTokenIdx { + return errors.Errorf("duplicate path %s", strings.Join(tokenizedPath, ".")) + } + // if it is map and it is the last token, + // then there is a child path used to set value + if ok && idx == lastTokenIdx { + return errors.Errorf("can not use path %s to set value, as there is a child path", strings.Join(tokenizedPath, ".")) + } + // if it is not a map, and it is not the last token, + // then there is parent path used to set value + if !ok && idx != lastTokenIdx { + return errors.Errorf("can not use path %s to set value, as parent path %s already used to set value", strings.Join(tokenizedPath, "."), strings.Join(tokenizedPath[:idx+1], ".")) + } + // if it is a map, and it is not the last token, + // then continue + } + prevVal = currMap[token] + } + + return nil +} + +func valueToString(value *reflect.Value) (string, error) { + nonPointerValue := *value + for { + if nonPointerValue.Kind() == reflect.Ptr { + nonPointerValue = nonPointerValue.Elem() + } else { + break + } + } + + if nonPointerValue.Kind() == reflect.Interface { + nonPointerValue = reflect.ValueOf(nonPointerValue.Interface()) + } + + if nonPointerValue.Kind() != reflect.String { + return "", errors.Errorf("unsupported kind %s for literal comparison", nonPointerValue.Kind().String()) + } + s, ok := nonPointerValue.Interface().(string) + if !ok { + return "", errors.Errorf("valueToString: type assertions failed") + } + return s, nil +} + +// nolint:forcetypeassert +func valueToQuantity(value *reflect.Value) (*resource.Quantity, error) { + nonPointerValue := *value + for { + if nonPointerValue.Kind() == reflect.Ptr { + nonPointerValue = nonPointerValue.Elem() + } else { + break + } + } + + if nonPointerValue.Kind() == reflect.Interface { + nonPointerValue = reflect.ValueOf(nonPointerValue.Interface()) + } + + switch nonPointerValue.Kind() { + case reflect.String: + v := nonPointerValue.Interface().(string) + q, err := resource.ParseQuantity(v) + if err != nil { + return nil, err + } + return &q, nil + case reflect.Int: + v := nonPointerValue.Interface().(int) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Int8: + v := nonPointerValue.Interface().(int8) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Int16: + v := nonPointerValue.Interface().(int16) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Int32: + v := nonPointerValue.Interface().(int32) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Int64: + v := nonPointerValue.Interface().(int64) + q := resource.NewScaledQuantity(v, 0) + return q, nil + case reflect.Uint: + v := nonPointerValue.Interface().(uint) + q := resource.MustParse(fmt.Sprintf("%d", v)) + return &q, nil + case reflect.Uint8: + v := nonPointerValue.Interface().(uint8) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Uint16: + v := nonPointerValue.Interface().(uint16) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Uint32: + v := nonPointerValue.Interface().(uint32) + q := resource.NewScaledQuantity(int64(v), 0) + return q, nil + case reflect.Uint64: + v := nonPointerValue.Interface().(uint64) + q := resource.MustParse(fmt.Sprintf("%d", v)) + return &q, nil + case reflect.Float32: + v := nonPointerValue.Interface().(float32) + q := resource.MustParse(fmt.Sprintf("%f", v)) + return &q, nil + case reflect.Float64: + v := nonPointerValue.Interface().(float64) + q := resource.MustParse(fmt.Sprintf("%f", v)) + return &q, nil + case reflect.Struct: + v, ok := nonPointerValue.Interface().(resource.Quantity) + if !ok { + return nil, errors.Errorf("unsupported struct type %s for numeric comparison", nonPointerValue.Type().String()) + } + return &v, nil + default: + return nil, errors.Errorf("unsupported kind %s for numeric comparison", nonPointerValue.Kind().String()) + } +} + +func normalizeJSONPath(jp string) string { + if strings.HasPrefix(jp, "{.") { + return jp + } + if strings.HasPrefix(jp, ".") { + return fmt.Sprintf("{%s}", jp) + } + return fmt.Sprintf("{.%s}", jp) +} + +type ValidationInventory struct { + Spec InventorySpec `json:"spec"` +} diff --git a/api/v1alpha1/inventory_types.go b/api/v1alpha1/inventory_types.go index d832112b..f5351a5b 100644 --- a/api/v1alpha1/inventory_types.go +++ b/api/v1alpha1/inventory_types.go @@ -390,7 +390,7 @@ type DistroSpec struct { // InventoryStatus defines the observed state of Inventory. type InventoryStatus struct { // +kubebuilder:pruning:PreserveUnknownFields - // Computed AggregationResults `json:"computed"` + Computed AggregationResults `json:"computed"` InventoryStatuses InventoryStatuses `json:"inventoryStatuses,omitempty"` } diff --git a/api/v1alpha1/jsonpath_types.go b/api/v1alpha1/jsonpath_types.go new file mode 100644 index 00000000..37f4ed72 --- /dev/null +++ b/api/v1alpha1/jsonpath_types.go @@ -0,0 +1,79 @@ +package v1alpha1 + +import ( + "strings" + + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/client-go/util/jsonpath" +) + +// +kubebuilder:validation:Type=string +type JSONPath struct { + Path string `json:"-"` +} + +func (in *JSONPath) MarshalJSON() ([]byte, error) { + str := in.String() + return json.Marshal(str) +} + +// nolint:forcetypeassert +func (in *JSONPath) UnmarshalJSON(b []byte) error { + stringVal := string(b) + if stringVal == null { + return nil + } + if err := json.Unmarshal(b, &stringVal); err != nil { + return err + } + + stringVal = normalizeJSONPath(stringVal) + jp := jsonpath.New(stringVal) + if err := jp.Parse(stringVal); err != nil { + return errors.Wrap(err, "unable to parse JSONPath") + } + + parser := jsonpath.NewParser(stringVal) + if err := parser.Parse(stringVal); err != nil { + return errors.Wrap(err, "unable to parse JSONPath") + } + if len(parser.Root.Nodes) != 1 || parser.Root.Nodes[0].Type() != jsonpath.NodeList { + return errors.New("path should have exactly one path expression") + } + + nodeList := parser.Root.Nodes[0].(*jsonpath.ListNode) + for idx, node := range nodeList.Nodes { + nodeType := node.Type() + if !(nodeType == jsonpath.NodeField || nodeType == jsonpath.NodeArray) { + return errors.Errorf("path contains segment %d, %s that is not a field name", idx, node.String()) + } + } + + in.Path = stringVal + + return nil +} + +func JSONPathFromString(s string) *JSONPath { + return &JSONPath{ + Path: normalizeJSONPath(s), + } +} + +func (in *JSONPath) String() string { + return normalizeJSONPath(in.Path) +} + +func (in *JSONPath) ToK8sJSONPath() (*jsonpath.JSONPath, error) { + jp := jsonpath.New(in.Path) + if err := jp.Parse(in.Path); err != nil { + return nil, errors.Wrap(err, "unable to parse JSONPath") + } + return jp, nil +} + +func (in *JSONPath) Tokenize() []string { + trimmed := strings.Trim(in.Path, "{.}") + return strings.Split(trimmed, ".") +} diff --git a/api/v1alpha1/machine_types.go b/api/v1alpha1/machine_types.go index 169262e1..f9589513 100644 --- a/api/v1alpha1/machine_types.go +++ b/api/v1alpha1/machine_types.go @@ -141,6 +141,7 @@ const ( // +kubebuilder:printcolumn:name="SerialNumber",type=string,JSONPath=`.status.serialNumber`,priority=100 // +kubebuilder:printcolumn:name="Power",type=string,JSONPath=`.status.power` // +kubebuilder:printcolumn:name="LocatorLED",type=string,JSONPath=`.status.locatorLED`,priority=100 +// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` // +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // +genclient diff --git a/api/v1alpha1/size_types.go b/api/v1alpha1/size_types.go new file mode 100644 index 00000000..8f8fd31e --- /dev/null +++ b/api/v1alpha1/size_types.go @@ -0,0 +1,134 @@ +package v1alpha1 + +import ( + "strings" + + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + SizeLabelPrefix = "metal.ironcore.dev/size-" + + AggregatePathPrefix = "{.status.computed." + AggregatePathPrefixReplacement = "{." +) + +// SizeSpec defines the desired state of Size. +type SizeSpec struct { + // Constraints is a list of selectors based on machine properties. + // +kubebuilder:validation:Optional + Constraints []ConstraintSpec `json:"constraints,omitempty"` +} + +// SizeStatus defines the observed state of Size. +type SizeStatus struct { +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +genclient + +// Size is the Schema for the sizes API. +type Size struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SizeSpec `json:"spec,omitempty"` + Status SizeStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// SizeList contains a list of Size. +type SizeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Size `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Size{}, &SizeList{}) +} + +func GetSizeMatchLabel(sizeName string) string { + return SizeLabelPrefix + sizeName +} + +func (in *Size) GetMatchLabel() string { + return SizeLabelPrefix + in.Name +} + +func (in *Size) Matches(inventory *Inventory) (bool, error) { + for _, constraint := range in.Spec.Constraints { + // TODO think how or wait to improve on the hot fix + // https://github.com/kubernetes-sigs/controller-tools/issues/287 + // nevertheless #1 is fixed, there is still an issue with kustomize dependency + // https://github.com/kubernetes-sigs/kustomize/blob/f1b191c02fe046a043854092f5f03b4625f5614a/cmd/depprobcheck/README.md + // + // jsonpath library iterates over fields and doesn't care about tags. + // I.e. AggregationResults is serialized into nested map (object), but in go code + // it is represented by structure containing the exact inner map. + // Means, jsonpath expects path to be like "status.computed.object.default..." + // and not like "status.computed.default...". + + var queriedObject interface{} = inventory + localJP := constraint.Path + jpString := localJP.String() + if strings.HasPrefix(jpString, AggregatePathPrefix) { + jpString = AggregatePathPrefixReplacement + strings.TrimPrefix(jpString, AggregatePathPrefix) + localJP = *JSONPathFromString(jpString) + queriedObject = inventory.Status.Computed.Object + } + + jp, err := localJP.ToK8sJSONPath() + if err != nil { + return false, err + } + + // Do not return errors if data is not found + jp.AllowMissingKeys(true) + + data, err := jp.FindResults(queriedObject) + if err != nil { + return false, err + } + + dataLen := len(data) + // If validation data is empty, return "does not match" + if dataLen == 0 { + return false, nil + } + // If data has more than 2 arrays, multiple result sets were returned + // we do not support that case + if dataLen > 1 { + return false, errors.New("multiple selection results are not supported") + } + + validationData := data[0] + validationDataLen := len(validationData) + // If result array is empty for some reason, return "does not match" + if validationDataLen == 0 { + return false, nil + } + + var valid bool + // If result set has only one value, validate it as a single value + // even if it is an aggregate, since result will be the same + if validationDataLen == 1 { + valid, err = constraint.MatchSingleValue(&validationData[0]) + } else { + valid, err = constraint.MatchMultipleValues(constraint.Aggregate, validationData) + } + + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + } + + return true, nil +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index aa4d74ae..6da75c51 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -10,9 +10,120 @@ package v1alpha1 import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Aggregate) DeepCopyInto(out *Aggregate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Aggregate. +func (in *Aggregate) DeepCopy() *Aggregate { + if in == nil { + return nil + } + out := new(Aggregate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Aggregate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregateItem) DeepCopyInto(out *AggregateItem) { + *out = *in + out.SourcePath = in.SourcePath + out.TargetPath = in.TargetPath +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregateItem. +func (in *AggregateItem) DeepCopy() *AggregateItem { + if in == nil { + return nil + } + out := new(AggregateItem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregateList) DeepCopyInto(out *AggregateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Aggregate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregateList. +func (in *AggregateList) DeepCopy() *AggregateList { + if in == nil { + return nil + } + out := new(AggregateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AggregateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregateSpec) DeepCopyInto(out *AggregateSpec) { + *out = *in + if in.Aggregates != nil { + in, out := &in.Aggregates, &out.Aggregates + *out = make([]AggregateItem, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregateSpec. +func (in *AggregateSpec) DeepCopy() *AggregateSpec { + if in == nil { + return nil + } + out := new(AggregateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AggregateStatus) DeepCopyInto(out *AggregateStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregateStatus. +func (in *AggregateStatus) DeepCopy() *AggregateStatus { + if in == nil { + return nil + } + out := new(AggregateStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BlockSpec) DeepCopyInto(out *BlockSpec) { *out = *in @@ -85,6 +196,77 @@ func (in *ConsoleProtocol) DeepCopy() *ConsoleProtocol { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConstraintSpec) DeepCopyInto(out *ConstraintSpec) { + *out = *in + out.Path = in.Path + if in.Equal != nil { + in, out := &in.Equal, &out.Equal + *out = new(ConstraintValSpec) + (*in).DeepCopyInto(*out) + } + if in.NotEqual != nil { + in, out := &in.NotEqual, &out.NotEqual + *out = new(ConstraintValSpec) + (*in).DeepCopyInto(*out) + } + if in.LessThan != nil { + in, out := &in.LessThan, &out.LessThan + x := (*in).DeepCopy() + *out = &x + } + if in.LessThanOrEqual != nil { + in, out := &in.LessThanOrEqual, &out.LessThanOrEqual + x := (*in).DeepCopy() + *out = &x + } + if in.GreaterThan != nil { + in, out := &in.GreaterThan, &out.GreaterThan + x := (*in).DeepCopy() + *out = &x + } + if in.GreaterThanOrEqual != nil { + in, out := &in.GreaterThanOrEqual, &out.GreaterThanOrEqual + x := (*in).DeepCopy() + *out = &x + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConstraintSpec. +func (in *ConstraintSpec) DeepCopy() *ConstraintSpec { + if in == nil { + return nil + } + out := new(ConstraintSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConstraintValSpec) DeepCopyInto(out *ConstraintValSpec) { + *out = *in + if in.Literal != nil { + in, out := &in.Literal, &out.Literal + *out = new(string) + **out = **in + } + if in.Numeric != nil { + in, out := &in.Numeric, &out.Numeric + x := (*in).DeepCopy() + *out = &x + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConstraintValSpec. +func (in *ConstraintValSpec) DeepCopy() *ConstraintValSpec { + if in == nil { + return nil + } + out := new(ConstraintValSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DistroSpec) DeepCopyInto(out *DistroSpec) { *out = *in @@ -136,7 +318,7 @@ func (in *Inventory) DeepCopyInto(out *Inventory) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Inventory. @@ -272,6 +454,7 @@ func (in *InventorySpec) DeepCopy() *InventorySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InventoryStatus) DeepCopyInto(out *InventoryStatus) { *out = *in + in.Computed.DeepCopyInto(&out.Computed) out.InventoryStatuses = in.InventoryStatuses } @@ -300,6 +483,21 @@ func (in *InventoryStatuses) DeepCopy() *InventoryStatuses { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPath) DeepCopyInto(out *JSONPath) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPath. +func (in *JSONPath) DeepCopy() *JSONPath { + if in == nil { + return nil + } + out := new(JSONPath) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LLDPSpec) DeepCopyInto(out *LLDPSpec) { *out = *in @@ -1013,6 +1211,102 @@ func (in *Protocol) DeepCopy() *Protocol { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Size) DeepCopyInto(out *Size) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Size. +func (in *Size) DeepCopy() *Size { + if in == nil { + return nil + } + out := new(Size) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Size) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SizeList) DeepCopyInto(out *SizeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Size, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SizeList. +func (in *SizeList) DeepCopy() *SizeList { + if in == nil { + return nil + } + out := new(SizeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SizeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SizeSpec) DeepCopyInto(out *SizeSpec) { + *out = *in + if in.Constraints != nil { + in, out := &in.Constraints, &out.Constraints + *out = make([]ConstraintSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SizeSpec. +func (in *SizeSpec) DeepCopy() *SizeSpec { + if in == nil { + return nil + } + out := new(SizeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SizeStatus) DeepCopyInto(out *SizeStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SizeStatus. +func (in *SizeStatus) DeepCopy() *SizeStatus { + if in == nil { + return nil + } + out := new(SizeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SystemSpec) DeepCopyInto(out *SystemSpec) { *out = *in @@ -1028,6 +1322,22 @@ func (in *SystemSpec) DeepCopy() *SystemSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidationInventory) DeepCopyInto(out *ValidationInventory) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationInventory. +func (in *ValidationInventory) DeepCopy() *ValidationInventory { + if in == nil { + return nil + } + out := new(ValidationInventory) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtSpec) DeepCopyInto(out *VirtSpec) { *out = *in diff --git a/client/applyconfiguration/api/v1alpha1/aggregate.go b/client/applyconfiguration/api/v1alpha1/aggregate.go new file mode 100644 index 00000000..df4ddf31 --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/aggregate.go @@ -0,0 +1,245 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + internal "github.com/ironcore-dev/metal/client/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// AggregateApplyConfiguration represents an declarative configuration of the Aggregate type for use +// with apply. +type AggregateApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *AggregateSpecApplyConfiguration `json:"spec,omitempty"` + Status *apiv1alpha1.AggregateStatus `json:"status,omitempty"` +} + +// Aggregate constructs an declarative configuration of the Aggregate type for use with +// apply. +func Aggregate(name, namespace string) *AggregateApplyConfiguration { + b := &AggregateApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Aggregate") + b.WithAPIVersion("metal.ironcore.dev/v1alpha1") + return b +} + +// ExtractAggregate extracts the applied configuration owned by fieldManager from +// aggregate. If no managedFields are found in aggregate for fieldManager, a +// AggregateApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// aggregate must be a unmodified Aggregate API object that was retrieved from the Kubernetes API. +// ExtractAggregate provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractAggregate(aggregate *apiv1alpha1.Aggregate, fieldManager string) (*AggregateApplyConfiguration, error) { + return extractAggregate(aggregate, fieldManager, "") +} + +// ExtractAggregateStatus is the same as ExtractAggregate except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractAggregateStatus(aggregate *apiv1alpha1.Aggregate, fieldManager string) (*AggregateApplyConfiguration, error) { + return extractAggregate(aggregate, fieldManager, "status") +} + +func extractAggregate(aggregate *apiv1alpha1.Aggregate, fieldManager string, subresource string) (*AggregateApplyConfiguration, error) { + b := &AggregateApplyConfiguration{} + err := managedfields.ExtractInto(aggregate, internal.Parser().Type("com.github.ironcore-dev.metal.api.v1alpha1.Aggregate"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(aggregate.Name) + b.WithNamespace(aggregate.Namespace) + + b.WithKind("Aggregate") + b.WithAPIVersion("metal.ironcore.dev/v1alpha1") + return b, nil +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithKind(value string) *AggregateApplyConfiguration { + b.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithAPIVersion(value string) *AggregateApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithName(value string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithGenerateName(value string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithNamespace(value string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithUID(value types.UID) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithResourceVersion(value string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithGeneration(value int64) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithCreationTimestamp(value metav1.Time) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *AggregateApplyConfiguration) WithLabels(entries map[string]string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *AggregateApplyConfiguration) WithAnnotations(entries map[string]string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *AggregateApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.OwnerReferences = append(b.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *AggregateApplyConfiguration) WithFinalizers(values ...string) *AggregateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *AggregateApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithSpec(value *AggregateSpecApplyConfiguration) *AggregateApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *AggregateApplyConfiguration) WithStatus(value apiv1alpha1.AggregateStatus) *AggregateApplyConfiguration { + b.Status = &value + return b +} diff --git a/client/applyconfiguration/api/v1alpha1/aggregateitem.go b/client/applyconfiguration/api/v1alpha1/aggregateitem.go new file mode 100644 index 00000000..c94a93e0 --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/aggregateitem.go @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +// AggregateItemApplyConfiguration represents an declarative configuration of the AggregateItem type for use +// with apply. +type AggregateItemApplyConfiguration struct { + SourcePath *v1alpha1.JSONPath `json:"sourcePath,omitempty"` + TargetPath *v1alpha1.JSONPath `json:"targetPath,omitempty"` + Aggregate *v1alpha1.AggregateType `json:"aggregate,omitempty"` +} + +// AggregateItemApplyConfiguration constructs an declarative configuration of the AggregateItem type for use with +// apply. +func AggregateItem() *AggregateItemApplyConfiguration { + return &AggregateItemApplyConfiguration{} +} + +// WithSourcePath sets the SourcePath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SourcePath field is set to the value of the last call. +func (b *AggregateItemApplyConfiguration) WithSourcePath(value v1alpha1.JSONPath) *AggregateItemApplyConfiguration { + b.SourcePath = &value + return b +} + +// WithTargetPath sets the TargetPath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TargetPath field is set to the value of the last call. +func (b *AggregateItemApplyConfiguration) WithTargetPath(value v1alpha1.JSONPath) *AggregateItemApplyConfiguration { + b.TargetPath = &value + return b +} + +// WithAggregate sets the Aggregate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Aggregate field is set to the value of the last call. +func (b *AggregateItemApplyConfiguration) WithAggregate(value v1alpha1.AggregateType) *AggregateItemApplyConfiguration { + b.Aggregate = &value + return b +} diff --git a/client/applyconfiguration/api/v1alpha1/aggregatespec.go b/client/applyconfiguration/api/v1alpha1/aggregatespec.go new file mode 100644 index 00000000..d72a85cf --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/aggregatespec.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// AggregateSpecApplyConfiguration represents an declarative configuration of the AggregateSpec type for use +// with apply. +type AggregateSpecApplyConfiguration struct { + Aggregates []AggregateItemApplyConfiguration `json:"aggregates,omitempty"` +} + +// AggregateSpecApplyConfiguration constructs an declarative configuration of the AggregateSpec type for use with +// apply. +func AggregateSpec() *AggregateSpecApplyConfiguration { + return &AggregateSpecApplyConfiguration{} +} + +// WithAggregates adds the given value to the Aggregates field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Aggregates field. +func (b *AggregateSpecApplyConfiguration) WithAggregates(values ...*AggregateItemApplyConfiguration) *AggregateSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAggregates") + } + b.Aggregates = append(b.Aggregates, *values[i]) + } + return b +} diff --git a/client/applyconfiguration/api/v1alpha1/constraintspec.go b/client/applyconfiguration/api/v1alpha1/constraintspec.go new file mode 100644 index 00000000..87db80c2 --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/constraintspec.go @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// ConstraintSpecApplyConfiguration represents an declarative configuration of the ConstraintSpec type for use +// with apply. +type ConstraintSpecApplyConfiguration struct { + Path *v1alpha1.JSONPath `json:"path,omitempty"` + Aggregate *v1alpha1.AggregateType `json:"agg,omitempty"` + Equal *v1alpha1.ConstraintValSpec `json:"eq,omitempty"` + NotEqual *v1alpha1.ConstraintValSpec `json:"neq,omitempty"` + LessThan *resource.Quantity `json:"lt,omitempty"` + LessThanOrEqual *resource.Quantity `json:"lte,omitempty"` + GreaterThan *resource.Quantity `json:"gt,omitempty"` + GreaterThanOrEqual *resource.Quantity `json:"gte,omitempty"` +} + +// ConstraintSpecApplyConfiguration constructs an declarative configuration of the ConstraintSpec type for use with +// apply. +func ConstraintSpec() *ConstraintSpecApplyConfiguration { + return &ConstraintSpecApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithPath(value v1alpha1.JSONPath) *ConstraintSpecApplyConfiguration { + b.Path = &value + return b +} + +// WithAggregate sets the Aggregate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Aggregate field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithAggregate(value v1alpha1.AggregateType) *ConstraintSpecApplyConfiguration { + b.Aggregate = &value + return b +} + +// WithEqual sets the Equal field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Equal field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithEqual(value v1alpha1.ConstraintValSpec) *ConstraintSpecApplyConfiguration { + b.Equal = &value + return b +} + +// WithNotEqual sets the NotEqual field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NotEqual field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithNotEqual(value v1alpha1.ConstraintValSpec) *ConstraintSpecApplyConfiguration { + b.NotEqual = &value + return b +} + +// WithLessThan sets the LessThan field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LessThan field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithLessThan(value resource.Quantity) *ConstraintSpecApplyConfiguration { + b.LessThan = &value + return b +} + +// WithLessThanOrEqual sets the LessThanOrEqual field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LessThanOrEqual field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithLessThanOrEqual(value resource.Quantity) *ConstraintSpecApplyConfiguration { + b.LessThanOrEqual = &value + return b +} + +// WithGreaterThan sets the GreaterThan field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GreaterThan field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithGreaterThan(value resource.Quantity) *ConstraintSpecApplyConfiguration { + b.GreaterThan = &value + return b +} + +// WithGreaterThanOrEqual sets the GreaterThanOrEqual field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GreaterThanOrEqual field is set to the value of the last call. +func (b *ConstraintSpecApplyConfiguration) WithGreaterThanOrEqual(value resource.Quantity) *ConstraintSpecApplyConfiguration { + b.GreaterThanOrEqual = &value + return b +} diff --git a/client/applyconfiguration/api/v1alpha1/inventorystatus.go b/client/applyconfiguration/api/v1alpha1/inventorystatus.go index 72887754..dc185c7e 100644 --- a/client/applyconfiguration/api/v1alpha1/inventorystatus.go +++ b/client/applyconfiguration/api/v1alpha1/inventorystatus.go @@ -5,9 +5,14 @@ package v1alpha1 +import ( + v1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + // InventoryStatusApplyConfiguration represents an declarative configuration of the InventoryStatus type for use // with apply. type InventoryStatusApplyConfiguration struct { + Computed *v1alpha1.AggregationResults `json:"computed,omitempty"` InventoryStatuses *InventoryStatusesApplyConfiguration `json:"inventoryStatuses,omitempty"` } @@ -17,6 +22,14 @@ func InventoryStatus() *InventoryStatusApplyConfiguration { return &InventoryStatusApplyConfiguration{} } +// WithComputed sets the Computed field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Computed field is set to the value of the last call. +func (b *InventoryStatusApplyConfiguration) WithComputed(value v1alpha1.AggregationResults) *InventoryStatusApplyConfiguration { + b.Computed = &value + return b +} + // WithInventoryStatuses sets the InventoryStatuses field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the InventoryStatuses field is set to the value of the last call. diff --git a/client/applyconfiguration/api/v1alpha1/size.go b/client/applyconfiguration/api/v1alpha1/size.go new file mode 100644 index 00000000..eac5b1de --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/size.go @@ -0,0 +1,245 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + internal "github.com/ironcore-dev/metal/client/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SizeApplyConfiguration represents an declarative configuration of the Size type for use +// with apply. +type SizeApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *SizeSpecApplyConfiguration `json:"spec,omitempty"` + Status *apiv1alpha1.SizeStatus `json:"status,omitempty"` +} + +// Size constructs an declarative configuration of the Size type for use with +// apply. +func Size(name, namespace string) *SizeApplyConfiguration { + b := &SizeApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Size") + b.WithAPIVersion("metal.ironcore.dev/v1alpha1") + return b +} + +// ExtractSize extracts the applied configuration owned by fieldManager from +// size. If no managedFields are found in size for fieldManager, a +// SizeApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// size must be a unmodified Size API object that was retrieved from the Kubernetes API. +// ExtractSize provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractSize(size *apiv1alpha1.Size, fieldManager string) (*SizeApplyConfiguration, error) { + return extractSize(size, fieldManager, "") +} + +// ExtractSizeStatus is the same as ExtractSize except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractSizeStatus(size *apiv1alpha1.Size, fieldManager string) (*SizeApplyConfiguration, error) { + return extractSize(size, fieldManager, "status") +} + +func extractSize(size *apiv1alpha1.Size, fieldManager string, subresource string) (*SizeApplyConfiguration, error) { + b := &SizeApplyConfiguration{} + err := managedfields.ExtractInto(size, internal.Parser().Type("com.github.ironcore-dev.metal.api.v1alpha1.Size"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(size.Name) + b.WithNamespace(size.Namespace) + + b.WithKind("Size") + b.WithAPIVersion("metal.ironcore.dev/v1alpha1") + return b, nil +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithKind(value string) *SizeApplyConfiguration { + b.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithAPIVersion(value string) *SizeApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithName(value string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithGenerateName(value string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithNamespace(value string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithUID(value types.UID) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithResourceVersion(value string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithGeneration(value int64) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *SizeApplyConfiguration) WithLabels(entries map[string]string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *SizeApplyConfiguration) WithAnnotations(entries map[string]string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *SizeApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.OwnerReferences = append(b.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *SizeApplyConfiguration) WithFinalizers(values ...string) *SizeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *SizeApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithSpec(value *SizeSpecApplyConfiguration) *SizeApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *SizeApplyConfiguration) WithStatus(value apiv1alpha1.SizeStatus) *SizeApplyConfiguration { + b.Status = &value + return b +} diff --git a/client/applyconfiguration/api/v1alpha1/sizespec.go b/client/applyconfiguration/api/v1alpha1/sizespec.go new file mode 100644 index 00000000..0292f8a5 --- /dev/null +++ b/client/applyconfiguration/api/v1alpha1/sizespec.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// SizeSpecApplyConfiguration represents an declarative configuration of the SizeSpec type for use +// with apply. +type SizeSpecApplyConfiguration struct { + Constraints []ConstraintSpecApplyConfiguration `json:"constraints,omitempty"` +} + +// SizeSpecApplyConfiguration constructs an declarative configuration of the SizeSpec type for use with +// apply. +func SizeSpec() *SizeSpecApplyConfiguration { + return &SizeSpecApplyConfiguration{} +} + +// WithConstraints adds the given value to the Constraints field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Constraints field. +func (b *SizeSpecApplyConfiguration) WithConstraints(values ...*ConstraintSpecApplyConfiguration) *SizeSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConstraints") + } + b.Constraints = append(b.Constraints, *values[i]) + } + return b +} diff --git a/client/applyconfiguration/internal/internal.go b/client/applyconfiguration/internal/internal.go index 17a5f44c..23327a38 100644 --- a/client/applyconfiguration/internal/internal.go +++ b/client/applyconfiguration/internal/internal.go @@ -26,6 +26,72 @@ func Parser() *typed.Parser { var parserOnce sync.Once var parser *typed.Parser var schemaYAML = typed.YAMLObject(`types: +- name: com.github.ironcore-dev.metal.api.v1alpha1.Aggregate + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.AggregateSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.AggregateStatus + default: {} +- name: com.github.ironcore-dev.metal.api.v1alpha1.AggregateItem + map: + fields: + - name: aggregate + type: + scalar: string + - name: sourcePath + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.JSONPath + - name: targetPath + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.JSONPath +- name: com.github.ironcore-dev.metal.api.v1alpha1.AggregateSpec + map: + fields: + - name: aggregates + type: + list: + elementType: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.AggregateItem + elementRelationship: atomic +- name: com.github.ironcore-dev.metal.api.v1alpha1.AggregateStatus + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +- name: com.github.ironcore-dev.metal.api.v1alpha1.AggregationResults + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: com.github.ironcore-dev.metal.api.v1alpha1.BlockSpec map: fields: @@ -152,6 +218,45 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: numeric default: 0 +- name: com.github.ironcore-dev.metal.api.v1alpha1.ConstraintSpec + map: + fields: + - name: agg + type: + scalar: string + - name: eq + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.ConstraintValSpec + - name: gt + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: gte + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: lt + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: lte + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: neq + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.ConstraintValSpec + - name: path + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.JSONPath +- name: com.github.ironcore-dev.metal.api.v1alpha1.ConstraintValSpec + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: com.github.ironcore-dev.metal.api.v1alpha1.DistroSpec map: fields: @@ -273,6 +378,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.ironcore-dev.metal.api.v1alpha1.InventoryStatus map: fields: + - name: computed + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.AggregationResults - name: inventoryStatuses type: namedType: com.github.ironcore-dev.metal.api.v1alpha1.InventoryStatuses @@ -287,6 +395,18 @@ var schemaYAML = typed.YAMLObject(`types: - name: requestsCount type: scalar: numeric +- name: com.github.ironcore-dev.metal.api.v1alpha1.JSONPath + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: com.github.ironcore-dev.metal.api.v1alpha1.LLDPSpec map: fields: @@ -761,6 +881,48 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: numeric default: 0 +- name: com.github.ironcore-dev.metal.api.v1alpha1.Size + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.SizeSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.SizeStatus + default: {} +- name: com.github.ironcore-dev.metal.api.v1alpha1.SizeSpec + map: + fields: + - name: constraints + type: + list: + elementType: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.ConstraintSpec + elementRelationship: atomic +- name: com.github.ironcore-dev.metal.api.v1alpha1.SizeStatus + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: com.github.ironcore-dev.metal.api.v1alpha1.SystemSpec map: fields: diff --git a/client/applyconfiguration/utils.go b/client/applyconfiguration/utils.go index 84659812..3cd9db86 100644 --- a/client/applyconfiguration/utils.go +++ b/client/applyconfiguration/utils.go @@ -16,10 +16,18 @@ import ( func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { // Group=metal.ironcore.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("Aggregate"): + return &apiv1alpha1.AggregateApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AggregateItem"): + return &apiv1alpha1.AggregateItemApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AggregateSpec"): + return &apiv1alpha1.AggregateSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("BlockSpec"): return &apiv1alpha1.BlockSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ConsoleProtocol"): return &apiv1alpha1.ConsoleProtocolApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ConstraintSpec"): + return &apiv1alpha1.ConstraintSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("CPUSpec"): return &apiv1alpha1.CPUSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DistroSpec"): @@ -82,6 +90,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.PCIDeviceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Protocol"): return &apiv1alpha1.ProtocolApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Size"): + return &apiv1alpha1.SizeApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SizeSpec"): + return &apiv1alpha1.SizeSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SystemSpec"): return &apiv1alpha1.SystemSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("VirtSpec"): diff --git a/client/openapi/zz_generated.openapi.go b/client/openapi/zz_generated.openapi.go index 7eef8d78..d5422d9c 100644 --- a/client/openapi/zz_generated.openapi.go +++ b/client/openapi/zz_generated.openapi.go @@ -18,9 +18,17 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + "github.com/ironcore-dev/metal/api/v1alpha1.Aggregate": schema_ironcore_dev_metal_api_v1alpha1_Aggregate(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateItem": schema_ironcore_dev_metal_api_v1alpha1_AggregateItem(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateList": schema_ironcore_dev_metal_api_v1alpha1_AggregateList(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateSpec": schema_ironcore_dev_metal_api_v1alpha1_AggregateSpec(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateStatus": schema_ironcore_dev_metal_api_v1alpha1_AggregateStatus(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.AggregationResults": schema_ironcore_dev_metal_api_v1alpha1_AggregationResults(ref), "github.com/ironcore-dev/metal/api/v1alpha1.BlockSpec": schema_ironcore_dev_metal_api_v1alpha1_BlockSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.CPUSpec": schema_ironcore_dev_metal_api_v1alpha1_CPUSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.ConsoleProtocol": schema_ironcore_dev_metal_api_v1alpha1_ConsoleProtocol(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.ConstraintSpec": schema_ironcore_dev_metal_api_v1alpha1_ConstraintSpec(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.ConstraintValSpec": schema_ironcore_dev_metal_api_v1alpha1_ConstraintValSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.DistroSpec": schema_ironcore_dev_metal_api_v1alpha1_DistroSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.HostSpec": schema_ironcore_dev_metal_api_v1alpha1_HostSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.IPMISpec": schema_ironcore_dev_metal_api_v1alpha1_IPMISpec(ref), @@ -29,6 +37,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/metal/api/v1alpha1.InventorySpec": schema_ironcore_dev_metal_api_v1alpha1_InventorySpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.InventoryStatus": schema_ironcore_dev_metal_api_v1alpha1_InventoryStatus(ref), "github.com/ironcore-dev/metal/api/v1alpha1.InventoryStatuses": schema_ironcore_dev_metal_api_v1alpha1_InventoryStatuses(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.JSONPath": schema_ironcore_dev_metal_api_v1alpha1_JSONPath(ref), "github.com/ironcore-dev/metal/api/v1alpha1.LLDPSpec": schema_ironcore_dev_metal_api_v1alpha1_LLDPSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.Machine": schema_ironcore_dev_metal_api_v1alpha1_Machine(ref), "github.com/ironcore-dev/metal/api/v1alpha1.MachineClaim": schema_ironcore_dev_metal_api_v1alpha1_MachineClaim(ref), @@ -58,7 +67,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/metal/api/v1alpha1.PartitionTableSpec": schema_ironcore_dev_metal_api_v1alpha1_PartitionTableSpec(ref), "github.com/ironcore-dev/metal/api/v1alpha1.Prefix": schema_ironcore_dev_metal_api_v1alpha1_Prefix(ref), "github.com/ironcore-dev/metal/api/v1alpha1.Protocol": schema_ironcore_dev_metal_api_v1alpha1_Protocol(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.Size": schema_ironcore_dev_metal_api_v1alpha1_Size(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.SizeList": schema_ironcore_dev_metal_api_v1alpha1_SizeList(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.SizeSpec": schema_ironcore_dev_metal_api_v1alpha1_SizeSpec(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.SizeStatus": schema_ironcore_dev_metal_api_v1alpha1_SizeStatus(ref), "github.com/ironcore-dev/metal/api/v1alpha1.SystemSpec": schema_ironcore_dev_metal_api_v1alpha1_SystemSpec(ref), + "github.com/ironcore-dev/metal/api/v1alpha1.ValidationInventory": schema_ironcore_dev_metal_api_v1alpha1_ValidationInventory(ref), "github.com/ironcore-dev/metal/api/v1alpha1.VirtSpec": schema_ironcore_dev_metal_api_v1alpha1_VirtSpec(ref), "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), @@ -341,6 +355,187 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA } } +func schema_ironcore_dev_metal_api_v1alpha1_Aggregate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Aggregate is the Schema for the aggregates API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.AggregateSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.AggregateStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateSpec", "github.com/ironcore-dev/metal/api/v1alpha1.AggregateStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_AggregateItem(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "sourcePath": { + SchemaProps: spec.SchemaProps{ + Description: "SourcePath is a path in Inventory spec aggregate will be applied to", + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.JSONPath"), + }, + }, + "targetPath": { + SchemaProps: spec.SchemaProps{ + Description: "TargetPath is a path in Inventory status `computed` field", + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.JSONPath"), + }, + }, + "aggregate": { + SchemaProps: spec.SchemaProps{ + Description: "Aggregate defines whether collection values should be aggregated for constraint checks, in case if path defines selector for collection", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"sourcePath", "targetPath"}, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.JSONPath"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_AggregateList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AggregateList contains a list of Aggregate.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.Aggregate"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.Aggregate", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_AggregateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AggregateSpec defines the desired state of Aggregate.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "aggregates": { + SchemaProps: spec.SchemaProps{ + Description: "Aggregates is a list of aggregates required to be computed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.AggregateItem"), + }, + }, + }, + }, + }, + }, + Required: []string{"aggregates"}, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.AggregateItem"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_AggregateStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AggregateStatus defines the observed state of Aggregate.", + Type: []string{"object"}, + }, + }, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_AggregationResults(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + }, + } +} + func schema_ironcore_dev_metal_api_v1alpha1_BlockSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -650,6 +845,81 @@ func schema_ironcore_dev_metal_api_v1alpha1_ConsoleProtocol(ref common.Reference } } +func schema_ironcore_dev_metal_api_v1alpha1_ConstraintSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConstraintSpec contains conditions of contraint that should be applied on resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is a path to the struct field constraint will be applied to", + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.JSONPath"), + }, + }, + "agg": { + SchemaProps: spec.SchemaProps{ + Description: "Aggregate defines whether collection values should be aggregated for constraint checks, in case if path defines selector for collection", + Type: []string{"string"}, + Format: "", + }, + }, + "eq": { + SchemaProps: spec.SchemaProps{ + Description: "Equal contains an exact expected value", + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.ConstraintValSpec"), + }, + }, + "neq": { + SchemaProps: spec.SchemaProps{ + Description: "NotEqual contains an exact not expected value", + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.ConstraintValSpec"), + }, + }, + "lt": { + SchemaProps: spec.SchemaProps{ + Description: "LessThan contains an highest expected value, exclusive", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + "lte": { + SchemaProps: spec.SchemaProps{ + Description: "LessThan contains an highest expected value, inclusive", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + "gt": { + SchemaProps: spec.SchemaProps{ + Description: "LessThan contains an lowest expected value, exclusive", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + "gte": { + SchemaProps: spec.SchemaProps{ + Description: "GreaterThanOrEqual contains an lowest expected value, inclusive", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.ConstraintValSpec", "github.com/ironcore-dev/metal/api/v1alpha1.JSONPath", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_ConstraintValSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConstraintValSpec is a wrapper around value for constraint. Since it is not possilble to set oneOf/anyOf through kubebuilder markers, type is set to number here, and patched with kustomize See https://github.com/kubernetes-sigs/kubebuilder/issues/301", + Type: []string{"object"}, + }, + }, + } +} + func schema_ironcore_dev_metal_api_v1alpha1_DistroSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -993,6 +1263,11 @@ func schema_ironcore_dev_metal_api_v1alpha1_InventoryStatus(ref common.Reference Description: "InventoryStatus defines the observed state of Inventory.", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "computed": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.AggregationResults"), + }, + }, "inventoryStatuses": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, @@ -1000,10 +1275,11 @@ func schema_ironcore_dev_metal_api_v1alpha1_InventoryStatus(ref common.Reference }, }, }, + Required: []string{"computed"}, }, }, Dependencies: []string{ - "github.com/ironcore-dev/metal/api/v1alpha1.InventoryStatuses"}, + "github.com/ironcore-dev/metal/api/v1alpha1.AggregationResults", "github.com/ironcore-dev/metal/api/v1alpha1.InventoryStatuses"}, } } @@ -1033,6 +1309,16 @@ func schema_ironcore_dev_metal_api_v1alpha1_InventoryStatuses(ref common.Referen } } +func schema_ironcore_dev_metal_api_v1alpha1_JSONPath(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + }, + } +} + func schema_ironcore_dev_metal_api_v1alpha1_LLDPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2375,6 +2661,142 @@ func schema_ironcore_dev_metal_api_v1alpha1_Protocol(ref common.ReferenceCallbac } } +func schema_ironcore_dev_metal_api_v1alpha1_Size(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Size is the Schema for the sizes API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.SizeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.SizeStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.SizeSpec", "github.com/ironcore-dev/metal/api/v1alpha1.SizeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_SizeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SizeList contains a list of Size.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.Size"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.Size", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_SizeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SizeSpec defines the desired state of Size.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "constraints": { + SchemaProps: spec.SchemaProps{ + Description: "Constraints is a list of selectors based on machine properties.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.ConstraintSpec"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.ConstraintSpec"}, + } +} + +func schema_ironcore_dev_metal_api_v1alpha1_SizeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SizeStatus defines the observed state of Size.", + Type: []string{"object"}, + }, + }, + } +} + func schema_ironcore_dev_metal_api_v1alpha1_SystemSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2416,6 +2838,27 @@ func schema_ironcore_dev_metal_api_v1alpha1_SystemSpec(ref common.ReferenceCallb } } +func schema_ironcore_dev_metal_api_v1alpha1_ValidationInventory(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/metal/api/v1alpha1.InventorySpec"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/metal/api/v1alpha1.InventorySpec"}, + } +} + func schema_ironcore_dev_metal_api_v1alpha1_VirtSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/main.go b/cmd/main.go index f2a140ee..38c94333 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -46,10 +46,12 @@ type params struct { enableHTTP2 bool kubeconfig string systemNamespace string + enableAggregateController bool enableInventoryController bool enableMachineController bool enableMachineClaimController bool enableOOBController bool + enableSizeController bool oobIpLabelSelector string oobMacDB string oobCredsRenewalBeforeExpiry time.Duration @@ -71,10 +73,12 @@ func parseCmdLine() params { pflag.Bool("enable-http2", false, "Enable HTTP2 for the metrics and webhook servers.") pflag.String("kubeconfig", "", "Use a kubeconfig to run out of cluster.") pflag.String("system-namespace", "", "Use a specific namespace for controller state. If blank, use the in-cluster namespace. Required if running out of cluster.") + pflag.Bool("enable-aggregate-controller", true, "Enable the Aggregate controllers.") pflag.Bool("enable-inventory-controller", true, "Enable the Inventory controller") pflag.Bool("enable-machine-controller", true, "Enable the Machine controller.") pflag.Bool("enable-machineclaim-controller", true, "Enable the MachineClaim controller.") pflag.Bool("enable-oob-controller", true, "Enable the OOB controller.") + pflag.Bool("enable-size-controller", true, "Enable the Size controller.") pflag.String("oob-ip-label-selector", "", "OOB: Filter IP objects by labels.") pflag.String("oob-mac-db", "", "OOB: Load MAC DB from file.") pflag.Duration("oob-creds-renewal-before-expiry", time.Hour*24*7, "OOB: Renew expiring credentials this long before they expire.") @@ -107,10 +111,12 @@ func parseCmdLine() params { enableHTTP2: viper.GetBool("enable-http2"), kubeconfig: viper.GetString("kubeconfig"), systemNamespace: viper.GetString("system-namespace"), + enableAggregateController: viper.GetBool("enable-aggregate-controller"), enableInventoryController: viper.GetBool("enable-inventory-controller"), enableMachineController: viper.GetBool("enable-machine-controller"), enableMachineClaimController: viper.GetBool("enable-machineclaim-controller"), enableOOBController: viper.GetBool("enable-oob-controller"), + enableSizeController: viper.GetBool("enable-size-controller"), oobIpLabelSelector: viper.GetString("oob-ip-label-selector"), oobMacDB: viper.GetString("oob-mac-db"), oobCredsRenewalBeforeExpiry: viper.GetDuration("oob-creds-renewal-before-expiry"), @@ -238,6 +244,23 @@ func main() { return } + if p.enableAggregateController { + var aggregateReconciler *controller.AggregateReconciler + aggregateReconciler, err = controller.NewAggregateReconciler() + if err != nil { + log.Error(ctx, fmt.Errorf("cannot create controller: %w", err), "controller", "Aggregate") + exitCode = 1 + return + } + + err = aggregateReconciler.SetupWithManager(mgr) + if err != nil { + log.Error(ctx, fmt.Errorf("cannot create controller: %w", err), "controller", "Aggregate") + exitCode = 1 + return + } + } + if p.enableInventoryController { var inventoryReconciler *controller.InventoryReconciler inventoryReconciler, err = controller.NewInventoryReconciler() @@ -306,6 +329,23 @@ func main() { } } + if p.enableSizeController { + var sizeReconciler *controller.SizeReconciler + sizeReconciler, err = controller.NewSizeReconciler() + if err != nil { + log.Error(ctx, fmt.Errorf("cannot create controller: %w", err), "controller", "Size") + exitCode = 1 + return + } + + err = sizeReconciler.SetupWithManager(mgr) + if err != nil { + log.Error(ctx, fmt.Errorf("cannot create controller: %w", err), "controller", "Size") + exitCode = 1 + return + } + } + // +kubebuilder:scaffold:builder err = mgr.AddHealthzCheck("health", healthz.Ping) diff --git a/config/crd/bases/metal.ironcore.dev_aggregates.yaml b/config/crd/bases/metal.ironcore.dev_aggregates.yaml new file mode 100644 index 00000000..fb06daf9 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_aggregates.yaml @@ -0,0 +1,81 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: aggregates.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: Aggregate + listKind: AggregateList + plural: aggregates + singular: aggregate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Aggregate is the Schema for the aggregates API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: AggregateSpec defines the desired state of Aggregate. + properties: + aggregates: + description: Aggregates is a list of aggregates required to be computed + items: + properties: + aggregate: + description: |- + Aggregate defines whether collection values should be aggregated + for constraint checks, in case if path defines selector for collection + enum: + - avg + - sum + - min + - max + - count + type: string + sourcePath: + description: SourcePath is a path in Inventory spec aggregate + will be applied to + type: string + targetPath: + description: TargetPath is a path in Inventory status `computed` + field + type: string + required: + - sourcePath + - targetPath + type: object + minItems: 1 + type: array + required: + - aggregates + type: object + status: + description: AggregateStatus defines the observed state of Aggregate. + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/metal.ironcore.dev_inventories.yaml b/config/crd/bases/metal.ironcore.dev_inventories.yaml index 18893895..7ff7baf9 100644 --- a/config/crd/bases/metal.ironcore.dev_inventories.yaml +++ b/config/crd/bases/metal.ironcore.dev_inventories.yaml @@ -617,6 +617,9 @@ spec: status: description: InventoryStatus defines the observed state of Inventory. properties: + computed: + type: object + x-kubernetes-preserve-unknown-fields: true inventoryStatuses: properties: ready: @@ -626,7 +629,8 @@ spec: required: - ready type: object - x-kubernetes-preserve-unknown-fields: true + required: + - computed type: object type: object served: true diff --git a/config/crd/bases/metal.ironcore.dev_machines.yaml b/config/crd/bases/metal.ironcore.dev_machines.yaml index d2103687..2a2d4c4d 100644 --- a/config/crd/bases/metal.ironcore.dev_machines.yaml +++ b/config/crd/bases/metal.ironcore.dev_machines.yaml @@ -36,6 +36,9 @@ spec: name: LocatorLED priority: 100 type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string - jsonPath: .status.state name: State type: string diff --git a/config/crd/bases/metal.ironcore.dev_sizes.yaml b/config/crd/bases/metal.ironcore.dev_sizes.yaml new file mode 100644 index 00000000..cb8780a8 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_sizes.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: sizes.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: Size + listKind: SizeList + plural: sizes + singular: size + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Size is the Schema for the sizes API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SizeSpec defines the desired state of Size. + properties: + constraints: + description: Constraints is a list of selectors based on machine properties. + items: + description: ConstraintSpec contains conditions of constraint that + should be applied on resource. + properties: + agg: + description: |- + Aggregate defines whether collection values should be aggregated + for constraint checks, in case if path defines selector for collection + enum: + - avg + - sum + - min + - max + - count + type: string + eq: + description: Equal contains an exact expected value + type: number + gt: + anyOf: + - type: integer + - type: string + description: LessThan contains an lowest expected value, exclusive + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + gte: + anyOf: + - type: integer + - type: string + description: GreaterThanOrEqual contains an lowest expected + value, inclusive + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + lt: + anyOf: + - type: integer + - type: string + description: LessThan contains an highest expected value, exclusive + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + lte: + anyOf: + - type: integer + - type: string + description: LessThan contains an highest expected value, inclusive + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + neq: + description: NotEqual contains an exact not expected value + type: number + path: + description: Path is a path to the struct field constraint will + be applied to + type: string + type: object + type: array + type: object + status: + description: SizeStatus defines the observed state of Size. + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 14d33fa3..b965b22f 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,8 @@ resources: - bases/metal.ironcore.dev_oobs.yaml - bases/metal.ironcore.dev_oobsecrets.yaml - bases/metal.ironcore.dev_inventories.yaml +- bases/metal.ironcore.dev_aggregates.yaml +- bases/metal.ironcore.dev_sizes.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 703b791e..c7419591 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -31,6 +31,32 @@ rules: - ips/status verbs: - get +- apiGroups: + - metal.ironcore.dev + resources: + - aggregates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - aggregates/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - aggregates/status + verbs: + - get + - patch + - update - apiGroups: - metal.ironcore.dev resources: @@ -45,6 +71,8 @@ rules: - inventories/status verbs: - get + - patch + - update - apiGroups: - metal.ironcore.dev resources: @@ -148,3 +176,29 @@ rules: - get - patch - update +- apiGroups: + - metal.ironcore.dev + resources: + - sizes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - sizes/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - sizes/status + verbs: + - get + - patch + - update diff --git a/internal/controller/aggregate_controller.go b/internal/controller/aggregate_controller.go new file mode 100644 index 00000000..9c78ed42 --- /dev/null +++ b/internal/controller/aggregate_controller.go @@ -0,0 +1,155 @@ +package controller + +import ( + "context" + "fmt" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + metalv1alpha1apply "github.com/ironcore-dev/metal/client/applyconfiguration/api/v1alpha1" + "github.com/ironcore-dev/metal/internal/log" + "github.com/ironcore-dev/metal/internal/ssa" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=aggregates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=aggregates/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=aggregates/finalizers,verbs=update +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=inventories,verbs=get;list;watch +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=inventories/status,verbs=get;update;patch + +const ( + AggregateFinalizer = "aggregate.metal.ironcore.dev/finalizer" + AggregateFieldOwner = "metal.ironcore.dev/aggregate" +) + +func NewAggregateReconciler() (*AggregateReconciler, error) { + return &AggregateReconciler{}, nil +} + +type AggregateReconciler struct { + client.Client +} + +func (r *AggregateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var aggregate metalv1alpha1.Aggregate + if err := r.Get(ctx, req.NamespacedName, &aggregate); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(fmt.Errorf("cannot get Aggregate: %w", err)) + } + if !aggregate.DeletionTimestamp.IsZero() { + if !controllerutil.ContainsFinalizer(&aggregate, AggregateFinalizer) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, r.finalize(ctx, aggregate) + } + + return ctrl.Result{}, r.reconcile(ctx, &aggregate) +} + +func (r *AggregateReconciler) reconcile(ctx context.Context, aggregate *metalv1alpha1.Aggregate) error { + if !controllerutil.ContainsFinalizer(aggregate, AggregateFinalizer) { + aggregateApply := metalv1alpha1apply.Aggregate(aggregate.Name, aggregate.Namespace). + WithFinalizers(AggregateFinalizer) + return r.Patch( + ctx, aggregate, ssa.Apply(aggregateApply), client.FieldOwner(AggregateFieldOwner), client.ForceOwnership) + } + + inventories := &metalv1alpha1.InventoryList{} + if err := r.List(ctx, inventories); err != nil { + log.Error(ctx, fmt.Errorf("failed to list inventories: %w", err)) + return err + } + + for _, inventory := range inventories.Items { + if !inventory.DeletionTimestamp.IsZero() { + continue + } + aggregatedValues, err := aggregate.Compute(&inventory) + if err != nil { + log.Error(ctx, fmt.Errorf("failed to compute aggregated values: %w", err), "inventory", inventory.Name) + continue + } + inventoryApply := metalv1alpha1apply.Inventory(inventory.Name, inventory.Namespace) + inventoryStatusApply := metalv1alpha1apply.InventoryStatus() + aggregateResults := inventory.Status.Computed.Object + if aggregateResults == nil { + aggregateResults = make(map[string]interface{}) + } + aggregateResults[aggregate.Name] = aggregatedValues + inventoryStatusApply = inventoryStatusApply. + WithComputed(metalv1alpha1.AggregationResults{Object: aggregateResults}) + inventoryApply = inventoryApply.WithStatus(inventoryStatusApply) + if err := r.Status().Patch( + ctx, &inventory, ssa.Apply(inventoryApply), client.FieldOwner(AggregateFieldOwner), client.ForceOwnership); err != nil { + log.Error(ctx, fmt.Errorf("failed to patch inventory: %w", err), "inventory", inventory.Name) + } + } + + return nil +} + +func (r *AggregateReconciler) finalize(ctx context.Context, aggregate metalv1alpha1.Aggregate) error { + inventories := &metalv1alpha1.InventoryList{} + if err := r.List(ctx, inventories); err != nil { + log.Error(ctx, fmt.Errorf("failed to list inventories: %w", err)) + return err + } + for _, inventory := range inventories.Items { + computed := inventory.Status.Computed.Object + _, ok := computed[aggregate.Name] + if !ok { + continue + } + delete(computed, aggregate.Name) + inventoryApply := metalv1alpha1apply.Inventory(inventory.Name, inventory.Namespace) + inventoryStatusApply := metalv1alpha1apply.InventoryStatus() + inventoryStatusApply = inventoryStatusApply. + WithComputed(metalv1alpha1.AggregationResults{Object: computed}) + inventoryApply = inventoryApply.WithStatus(inventoryStatusApply) + if err := r.Status().Patch( + ctx, &inventory, ssa.Apply(inventoryApply), client.FieldOwner(AggregateFieldOwner), client.ForceOwnership); err != nil { + log.Error(ctx, fmt.Errorf("failed to patch inventory: %w", err), "inventory", inventory.Name) + } + } + + aggregateApply := metalv1alpha1apply.Aggregate(aggregate.Name, aggregate.Namespace). + WithFinalizers() + return r.Patch(ctx, &aggregate, ssa.Apply(aggregateApply), client.FieldOwner(AggregateFieldOwner), client.ForceOwnership) +} + +func (r *AggregateReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.Client = mgr.GetClient() + + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.Aggregate{}). + Watches(&metalv1alpha1.Inventory{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { + requests := make([]reconcile.Request, 0) + inventory, ok := object.(*metalv1alpha1.Inventory) + if !ok { + return requests + } + if !inventory.DeletionTimestamp.IsZero() { + return requests + } + + aggregateList := &metalv1alpha1.AggregateList{} + if err := r.List(ctx, aggregateList); err != nil { + log.Error(ctx, fmt.Errorf("failed to list aggregate: %w", err)) + return requests + } + for _, aggregate := range aggregateList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: aggregate.Namespace, + Name: aggregate.Name, + }, + }) + } + return requests + })). + Complete(r) +} diff --git a/internal/controller/inventory_controller.go b/internal/controller/inventory_controller.go index f3d3ccba..6ac98b6e 100644 --- a/internal/controller/inventory_controller.go +++ b/internal/controller/inventory_controller.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "slices" + "strings" metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" metalv1alpha1apply "github.com/ironcore-dev/metal/client/applyconfiguration/api/v1alpha1" @@ -38,18 +39,39 @@ func (r *InventoryReconciler) reconcile(ctx context.Context, inventory metalv1al return err } idx := slices.IndexFunc(machines.Items, func(machine metalv1alpha1.Machine) bool { - return machine.Spec.InventoryRef == nil && machine.Spec.UUID == inventory.Name + return machine.Spec.UUID == inventory.Name }) if idx == -1 { return nil } + machine := machines.Items[idx].DeepCopy() machineApply := metalv1alpha1apply.Machine(machine.Name, machine.Namespace) - machineSpecApply := metalv1alpha1apply.MachineSpec(). - WithPower(metalv1alpha1.PowerOff). - WithInventoryRef(v1.LocalObjectReference{Name: inventory.Name}) - machineApply = machineApply.WithSpec(machineSpecApply) - return r.Patch(ctx, machine, ssa.Apply(machineApply), client.FieldOwner(MachineFieldOwner), client.ForceOwnership) + + sizeLabels := make(map[string]string) + for k, v := range inventory.GetLabels() { + if !strings.HasPrefix(k, MachineSizeLabelPrefix) { + continue + } + sizeLabels[k] = v + } + if len(sizeLabels) != 0 { + machineApply = machineApply.WithLabels(sizeLabels) + } + + if machine.Spec.InventoryRef == nil { + machineSpecApply := metalv1alpha1apply.MachineSpec(). + WithPower(metalv1alpha1.PowerOff). + WithInventoryRef(v1.LocalObjectReference{Name: inventory.Name}) + machineApply = machineApply.WithSpec(machineSpecApply) + return r.Patch(ctx, machine, ssa.Apply(machineApply), client.FieldOwner(MachineFieldOwner), client.ForceOwnership) + } else { + machineSpecApply := metalv1alpha1apply.MachineSpec(). + WithPower(machine.Spec.Power). + WithInventoryRef(v1.LocalObjectReference{Name: inventory.Name}) + machineApply = machineApply.WithSpec(machineSpecApply) + return r.Patch(ctx, machine, ssa.Apply(machineApply), client.FieldOwner(MachineFieldOwner), client.ForceOwnership) + } } func (r *InventoryReconciler) SetupWithManager(mgr ctrl.Manager) error { diff --git a/internal/controller/size_controller.go b/internal/controller/size_controller.go new file mode 100644 index 00000000..e523007a --- /dev/null +++ b/internal/controller/size_controller.go @@ -0,0 +1,189 @@ +package controller + +import ( + "context" + "fmt" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + metalv1alpha1apply "github.com/ironcore-dev/metal/client/applyconfiguration/api/v1alpha1" + "github.com/ironcore-dev/metal/internal/log" + "github.com/ironcore-dev/metal/internal/ssa" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + SizeFinalizer = "size.metal.ironcore.dev/finalizer" + SizeFieldOwner = "metal.ironcore.dev/size" +) + +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=sizes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=sizes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=metal.ironcore.dev,resources=sizes/finalizers,verbs=update + +func NewSizeReconciler() (*SizeReconciler, error) { + return &SizeReconciler{}, nil +} + +type SizeReconciler struct { + client.Client +} + +func (r *SizeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var size metalv1alpha1.Size + if err := r.Get(ctx, req.NamespacedName, &size); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(fmt.Errorf("cannot get Size: %w", err)) + } + if !size.DeletionTimestamp.IsZero() { + if !controllerutil.ContainsFinalizer(&size, SizeFinalizer) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, r.finalize(ctx, size) + } + + return ctrl.Result{}, r.reconcile(ctx, size) +} + +func (r *SizeReconciler) reconcile(ctx context.Context, size metalv1alpha1.Size) error { + if !controllerutil.ContainsFinalizer(&size, SizeFinalizer) { + sizeApply := metalv1alpha1apply.Size(size.Name, size.Namespace). + WithFinalizers(SizeFinalizer) + return r.Patch(ctx, &size, ssa.Apply(sizeApply), client.FieldOwner(SizeFieldOwner), client.ForceOwnership) + } + + inventories := &metalv1alpha1.InventoryList{} + if err := r.List(ctx, inventories); err != nil { + log.Error(ctx, fmt.Errorf("failed to list inventories: %w", err)) + return err + } + + for _, inventory := range inventories.Items { + if !inventory.DeletionTimestamp.IsZero() { + continue + } + + var labels map[string]string + if inventory.Labels == nil { + labels = make(map[string]string) + } else { + labels = inventory.Labels + } + + matches, err := size.Matches(&inventory) + if err != nil { + log.Error(ctx, fmt.Errorf("failed to match size: %w", err)) + } + sizeLabel := size.GetMatchLabel() + _, labelPresent := labels[sizeLabel] + + switch matches { + case true: + if labelPresent { + log.Debug(ctx, "match between inventory and size found, label present, will not update") + continue + } else { + log.Info(ctx, "match between inventory and size found") + labels[sizeLabel] = "true" + } + case false: + if labelPresent { + log.Info(ctx, "inventory no longer matches to size, will remove label") + delete(labels, sizeLabel) + } else { + log.Debug(ctx, "match between inventory and size is not found, label absent, will not update") + continue + } + } + + inventoryApply := metalv1alpha1apply.Inventory(inventory.Name, inventory.Namespace). + WithLabels(labels) + err = r.Patch( + ctx, &inventory, ssa.Apply(inventoryApply), client.FieldOwner(SizeFieldOwner), client.ForceOwnership) + if err != nil { + log.Error(ctx, fmt.Errorf("failed to patch inventory: %w", err)) + } + } + + return nil +} + +func (r *SizeReconciler) finalize(ctx context.Context, size metalv1alpha1.Size) error { + inventories := &metalv1alpha1.InventoryList{} + if err := r.List(ctx, inventories); err != nil { + log.Error(ctx, fmt.Errorf("failed to list inventories: %w", err)) + return err + } + + sizeLabel := size.GetMatchLabel() + for _, inventory := range inventories.Items { + var labels map[string]string + if inventory.Labels == nil { + labels = make(map[string]string) + } else { + labels = inventory.Labels + } + + if _, labelPresent := labels[sizeLabel]; !labelPresent { + continue + } + + delete(labels, sizeLabel) + inventoryApply := metalv1alpha1apply.Inventory(inventory.Name, inventory.Namespace). + WithLabels(labels) + err := r.Patch( + ctx, &inventory, ssa.Apply(inventoryApply), client.FieldOwner(SizeFieldOwner), client.ForceOwnership) + if err != nil { + log.Error(ctx, fmt.Errorf("failed to patch inventory: %w", err)) + } + } + + sizeApply := metalv1alpha1apply.Size(size.Name, size.Namespace). + WithFinalizers() + return r.Patch(ctx, &size, ssa.Apply(sizeApply), client.FieldOwner(SizeFieldOwner), client.ForceOwnership) +} + +func (r *SizeReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.Client = mgr.GetClient() + + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.Size{}). + Watches(&metalv1alpha1.Inventory{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { + requests := make([]reconcile.Request, 0) + inventory, ok := object.(*metalv1alpha1.Inventory) + if !ok { + return requests + } + if !inventory.DeletionTimestamp.IsZero() { + return requests + } + + sizeList := &metalv1alpha1.SizeList{} + if err := r.List(ctx, sizeList); err != nil { + log.Error(ctx, fmt.Errorf("failed to list size: %w", err)) + return requests + } + for _, size := range sizeList.Items { + matches, err := size.Matches(inventory) + if err != nil { + log.Error(ctx, fmt.Errorf("failed to match size: %w", err)) + continue + } + sizeLabel := size.GetMatchLabel() + _, labelExist := inventory.Labels[sizeLabel] + if matches && !labelExist { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: size.Namespace, + Name: size.Name, + }, + }) + } + } + return requests + })). + Complete(r) +}