diff --git a/.golangci.yml b/.golangci.yml index 338c2d2d..c70ed67b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -34,3 +34,7 @@ issues: - linters: - lll source: "^//\\+kubebuilder" + - path: "_test\\.go" + linters: + - staticcheck + text: "SA1019" diff --git a/api/config/v2alpha2/opaconfig_types.go b/api/config/v2alpha2/opaconfig_types.go new file mode 100644 index 00000000..1b89510f --- /dev/null +++ b/api/config/v2alpha2/opaconfig_types.go @@ -0,0 +1,76 @@ +/* +Copyright (C) 2025 Bankdata (bankdata@bankdata.dk) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2alpha2 + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/bankdata/styra-controller/pkg/opaconfig" +) + +// OPAConfigSpec mirrors the official OPA configuration schema. +type OPAConfigSpec struct { + Services *runtime.RawExtension `json:"services,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Discovery *runtime.RawExtension `json:"discovery,omitempty"` + Bundle *runtime.RawExtension `json:"bundle,omitempty"` + Bundles *runtime.RawExtension `json:"bundles,omitempty"` + DecisionLogs *runtime.RawExtension `json:"decision_logs,omitempty"` + Status *runtime.RawExtension `json:"status,omitempty"` + Plugins map[string]*runtime.RawExtension `json:"plugins,omitempty"` + Keys *runtime.RawExtension `json:"keys,omitempty"` + DefaultDecision *string `json:"default_decision,omitempty"` + DefaultAuthorizationDecision *string `json:"default_authorization_decision,omitempty"` + Caching *runtime.RawExtension `json:"caching,omitempty"` + NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"` + PersistenceDirectory *string `json:"persistence_directory,omitempty"` + DistributedTracing *runtime.RawExtension `json:"distributed_tracing,omitempty"` + MetricsExport *runtime.RawExtension `json:"metrics_export,omitempty"` + Server *OPAServerConfig `json:"server,omitempty"` + Storage *OPAStorageConfig `json:"storage,omitempty"` +} + +// OPAServerConfig mirrors OPA's server configuration section. +type OPAServerConfig struct { + Metrics *runtime.RawExtension `json:"metrics,omitempty"` + Encoding *runtime.RawExtension `json:"encoding,omitempty"` + Decoding *runtime.RawExtension `json:"decoding,omitempty"` + LoggerPlugin *string `json:"logger_plugin,omitempty"` +} + +// OPAStorageConfig mirrors OPA's storage configuration section. +type OPAStorageConfig struct { + Disk *runtime.RawExtension `json:"disk,omitempty"` +} + +// UnmarshalJSON validates the OPA config against the known schema keys before +// decoding it. +func (c *OPAConfigSpec) UnmarshalJSON(data []byte) error { + if err := opaconfig.ValidateRaw(data); err != nil { + return err + } + + var decoded OPAConfigSpec + if err := json.Unmarshal(data, &decoded); err != nil { + return err + } + + *c = OPAConfigSpec(decoded) + return nil +} diff --git a/api/config/v2alpha2/projectconfig_types.go b/api/config/v2alpha2/projectconfig_types.go index 5c1f25a6..427a4f21 100644 --- a/api/config/v2alpha2/projectconfig_types.go +++ b/api/config/v2alpha2/projectconfig_types.go @@ -54,8 +54,13 @@ type ProjectConfig struct { LeaderElection *LeaderElectionConfig `json:"leaderElection"` + // Deprecated: OPA contains the legacy controller-managed OPA settings. OPA OPAConfig `json:"opa,omitempty"` + // OPAConfig contains the full OPA configuration schema and takes + // precedence over the legacy OPA settings. + OPAConfig *OPAConfigSpec `json:"opaConfig,omitempty"` + // SystemPrefix is a prefix for all the systems that the controller creates. SystemPrefix string `json:"systemPrefix"` @@ -131,6 +136,8 @@ type GitCredentials struct { } // OPAConfig contains default configuration for generated OPA config. +// +// Deprecated: use the typed OPA config schema instead. type OPAConfig struct { DecisionLogs DecisionLog `json:"decisionLogs,omitempty" yaml:"decisionLogs,omitempty"` Metrics MetricsConfig `json:"metrics,omitempty" yaml:"metrics,omitempty"` @@ -168,7 +175,9 @@ type DecisionLog struct { RequestContext RequestContext `json:"requestContext,omitempty"` } -// DecisionAPIConfig contains configuration for decision log dispatch +// DecisionAPIConfig contains configuration for decision log dispatch. +// +// Deprecated: use the typed OPA config schema instead. type DecisionAPIConfig struct { Name string `json:"name,omitempty"` ServiceURL string `json:"serviceUrl,omitempty"` @@ -176,7 +185,9 @@ type DecisionAPIConfig struct { Reporting DecisionLogReporting `json:"reporting,omitempty"` } -// DecisionLogReporting contains configuration for decision log reporting +// DecisionLogReporting contains configuration for decision log reporting. +// +// Deprecated: use the typed OPA config schema instead. type DecisionLogReporting struct { MaxDelaySeconds int `json:"maxDelaySeconds,omitempty" yaml:"maxDelaySeconds,omitempty"` MinDelaySeconds int `json:"minDelaySeconds,omitempty" yaml:"minDelaySeconds,omitempty"` diff --git a/api/config/v2alpha2/zz_generated.deepcopy.go b/api/config/v2alpha2/zz_generated.deepcopy.go index 32032c92..ab8dc370 100644 --- a/api/config/v2alpha2/zz_generated.deepcopy.go +++ b/api/config/v2alpha2/zz_generated.deepcopy.go @@ -242,6 +242,119 @@ func (in *OPAConfig) DeepCopy() *OPAConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAConfigSpec) DeepCopyInto(out *OPAConfigSpec) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Discovery != nil { + in, out := &in.Discovery, &out.Discovery + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Bundle != nil { + in, out := &in.Bundle, &out.Bundle + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Bundles != nil { + in, out := &in.Bundles, &out.Bundles + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.DecisionLogs != nil { + in, out := &in.DecisionLogs, &out.DecisionLogs + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Plugins != nil { + in, out := &in.Plugins, &out.Plugins + *out = make(map[string]*runtime.RawExtension, len(*in)) + for key, val := range *in { + var outVal *runtime.RawExtension + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + if in.Keys != nil { + in, out := &in.Keys, &out.Keys + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.DefaultDecision != nil { + in, out := &in.DefaultDecision, &out.DefaultDecision + *out = new(string) + **out = **in + } + if in.DefaultAuthorizationDecision != nil { + in, out := &in.DefaultAuthorizationDecision, &out.DefaultAuthorizationDecision + *out = new(string) + **out = **in + } + if in.Caching != nil { + in, out := &in.Caching, &out.Caching + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.PersistenceDirectory != nil { + in, out := &in.PersistenceDirectory, &out.PersistenceDirectory + *out = new(string) + **out = **in + } + if in.DistributedTracing != nil { + in, out := &in.DistributedTracing, &out.DistributedTracing + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.MetricsExport != nil { + in, out := &in.MetricsExport, &out.MetricsExport + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Server != nil { + in, out := &in.Server, &out.Server + *out = new(OPAServerConfig) + (*in).DeepCopyInto(*out) + } + if in.Storage != nil { + in, out := &in.Storage, &out.Storage + *out = new(OPAStorageConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAConfigSpec. +func (in *OPAConfigSpec) DeepCopy() *OPAConfigSpec { + if in == nil { + return nil + } + out := new(OPAConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OPAControlPlaneConfig) DeepCopyInto(out *OPAControlPlaneConfig) { *out = *in @@ -278,6 +391,61 @@ func (in *OPAControlPlaneConfig) DeepCopy() *OPAControlPlaneConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAServerConfig) DeepCopyInto(out *OPAServerConfig) { + *out = *in + if in.Metrics != nil { + in, out := &in.Metrics, &out.Metrics + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Encoding != nil { + in, out := &in.Encoding, &out.Encoding + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Decoding != nil { + in, out := &in.Decoding, &out.Decoding + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.LoggerPlugin != nil { + in, out := &in.LoggerPlugin, &out.LoggerPlugin + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAServerConfig. +func (in *OPAServerConfig) DeepCopy() *OPAServerConfig { + if in == nil { + return nil + } + out := new(OPAServerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAStorageConfig) DeepCopyInto(out *OPAStorageConfig) { + *out = *in + if in.Disk != nil { + in, out := &in.Disk, &out.Disk + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAStorageConfig. +func (in *OPAStorageConfig) DeepCopy() *OPAStorageConfig { + if in == nil { + return nil + } + out := new(OPAStorageConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProjectConfig) DeepCopyInto(out *ProjectConfig) { *out = *in @@ -293,6 +461,11 @@ func (in *ProjectConfig) DeepCopyInto(out *ProjectConfig) { **out = **in } in.OPA.DeepCopyInto(&out.OPA) + if in.OPAConfig != nil { + in, out := &in.OPAConfig, &out.OPAConfig + *out = new(OPAConfigSpec) + (*in).DeepCopyInto(*out) + } if in.OPAControlPlaneConfig != nil { in, out := &in.OPAControlPlaneConfig, &out.OPAControlPlaneConfig *out = new(OPAControlPlaneConfig) diff --git a/api/styra/v1beta1/opaconfig_types.go b/api/styra/v1beta1/opaconfig_types.go new file mode 100644 index 00000000..affd590c --- /dev/null +++ b/api/styra/v1beta1/opaconfig_types.go @@ -0,0 +1,72 @@ +/* +Copyright (C) 2025 Bankdata (bankdata@bankdata.dk) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/runtime" +) + +// OPAConfigSpec mirrors the official OPA configuration schema. +type OPAConfigSpec struct { + Services *runtime.RawExtension `json:"services,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Discovery *runtime.RawExtension `json:"discovery,omitempty"` + Bundle *runtime.RawExtension `json:"bundle,omitempty"` + Bundles *runtime.RawExtension `json:"bundles,omitempty"` + DecisionLogs *runtime.RawExtension `json:"decision_logs,omitempty"` + Status *runtime.RawExtension `json:"status,omitempty"` + Plugins map[string]*runtime.RawExtension `json:"plugins,omitempty"` + Keys *runtime.RawExtension `json:"keys,omitempty"` + DefaultDecision *string `json:"default_decision,omitempty"` + DefaultAuthorizationDecision *string `json:"default_authorization_decision,omitempty"` + Caching *runtime.RawExtension `json:"caching,omitempty"` + NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"` + PersistenceDirectory *string `json:"persistence_directory,omitempty"` + DistributedTracing *runtime.RawExtension `json:"distributed_tracing,omitempty"` + MetricsExport *runtime.RawExtension `json:"metrics_export,omitempty"` + Server *OPAServerConfig `json:"server,omitempty"` + Storage *OPAStorageConfig `json:"storage,omitempty"` +} + +// OPAServerConfig mirrors OPA's server configuration section. +type OPAServerConfig struct { + Metrics *runtime.RawExtension `json:"metrics,omitempty"` + Encoding *runtime.RawExtension `json:"encoding,omitempty"` + Decoding *runtime.RawExtension `json:"decoding,omitempty"` + LoggerPlugin *string `json:"logger_plugin,omitempty"` +} + +// OPAStorageConfig mirrors OPA's storage configuration section. +type OPAStorageConfig struct { + Disk *runtime.RawExtension `json:"disk,omitempty"` +} + +// UnmarshalJSON decodes the OPA config. Validation of keys against the OPA +// schema is intentionally not done here so that existing resources with +// non-standard keys can still be read by the controller. Validation is +// performed on write via the admission webhook. +func (c *OPAConfigSpec) UnmarshalJSON(data []byte) error { + var decoded OPAConfigSpec + if err := json.Unmarshal(data, &decoded); err != nil { + return err + } + + *c = OPAConfigSpec(decoded) + return nil +} diff --git a/api/styra/v1beta1/system_types.go b/api/styra/v1beta1/system_types.go index 36587e6a..7babe891 100644 --- a/api/styra/v1beta1/system_types.go +++ b/api/styra/v1beta1/system_types.go @@ -43,17 +43,33 @@ type SystemSpec struct { // Datasources represents a list of datasources to be mounted in the system. Datasources []Datasource `json:"datasources,omitempty"` - // DiscoveryOverrides is an OPA config which will take precedence over the - // configuration supplied by the OPA discovery API. Configuration set here - // will be merged with the configuration supplied by the discovery API. + // Deprecated: DiscoveryOverrides is unused by the controller and will be + // removed in a future version. Use OPA.Config instead. DiscoveryOverrides *DiscoveryOverrides `json:"discoveryOverrides,omitempty"` SourceControl *SourceControl `json:"sourceControl,omitempty"` LocalPlane *LocalPlane `json:"localPlane,omitempty"` - // CustomOPAConfig allows the owner of a System resource to set custom features - // without having to extend the Controller + // Deprecated: CustomOPAConfig allows the owner of a System resource to set + // custom OPA configuration that is merged into the generated OPA config. + // Use OPA.Config instead, which provides the same capability with runtime + // validation of field names against the OPA configuration schema. + // If both are set, OPA.Config takes precedence on conflicting keys. CustomOPAConfig *runtime.RawExtension `json:"customOPAConfig,omitempty"` + + // OPA contains OPA-specific configuration for this system. + OPA *SystemOPASpec `json:"opa,omitempty"` +} + +// SystemOPASpec contains OPA-specific configuration for a System. +type SystemOPASpec struct { + // Config accepts any valid OPA configuration YAML. The top-level keys are + // validated by the OPA configuration schema (see + // https://github.com/open-policy-agent/opa/blob/main/v1/config/config.go). + // + // Configuration set here is merged on top of the generated OPA config and + // takes precedence over the deprecated customOPAConfig field on conflicts. + Config *OPAConfigSpec `json:"config,omitempty"` } // DiscoveryOverrides specifies system specific overrides for the configuration diff --git a/api/styra/v1beta1/zz_generated.deepcopy.go b/api/styra/v1beta1/zz_generated.deepcopy.go index bdcf0a30..f31f5a71 100644 --- a/api/styra/v1beta1/zz_generated.deepcopy.go +++ b/api/styra/v1beta1/zz_generated.deepcopy.go @@ -217,6 +217,119 @@ func (in *OPAConfigDistributedTracing) DeepCopy() *OPAConfigDistributedTracing { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAConfigSpec) DeepCopyInto(out *OPAConfigSpec) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Discovery != nil { + in, out := &in.Discovery, &out.Discovery + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Bundle != nil { + in, out := &in.Bundle, &out.Bundle + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Bundles != nil { + in, out := &in.Bundles, &out.Bundles + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.DecisionLogs != nil { + in, out := &in.DecisionLogs, &out.DecisionLogs + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Plugins != nil { + in, out := &in.Plugins, &out.Plugins + *out = make(map[string]*runtime.RawExtension, len(*in)) + for key, val := range *in { + var outVal *runtime.RawExtension + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + if in.Keys != nil { + in, out := &in.Keys, &out.Keys + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.DefaultDecision != nil { + in, out := &in.DefaultDecision, &out.DefaultDecision + *out = new(string) + **out = **in + } + if in.DefaultAuthorizationDecision != nil { + in, out := &in.DefaultAuthorizationDecision, &out.DefaultAuthorizationDecision + *out = new(string) + **out = **in + } + if in.Caching != nil { + in, out := &in.Caching, &out.Caching + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.PersistenceDirectory != nil { + in, out := &in.PersistenceDirectory, &out.PersistenceDirectory + *out = new(string) + **out = **in + } + if in.DistributedTracing != nil { + in, out := &in.DistributedTracing, &out.DistributedTracing + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.MetricsExport != nil { + in, out := &in.MetricsExport, &out.MetricsExport + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Server != nil { + in, out := &in.Server, &out.Server + *out = new(OPAServerConfig) + (*in).DeepCopyInto(*out) + } + if in.Storage != nil { + in, out := &in.Storage, &out.Storage + *out = new(OPAStorageConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAConfigSpec. +func (in *OPAConfigSpec) DeepCopy() *OPAConfigSpec { + if in == nil { + return nil + } + out := new(OPAConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OPAConfigStatus) DeepCopyInto(out *OPAConfigStatus) { *out = *in @@ -232,6 +345,61 @@ func (in *OPAConfigStatus) DeepCopy() *OPAConfigStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAServerConfig) DeepCopyInto(out *OPAServerConfig) { + *out = *in + if in.Metrics != nil { + in, out := &in.Metrics, &out.Metrics + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Encoding != nil { + in, out := &in.Encoding, &out.Encoding + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.Decoding != nil { + in, out := &in.Decoding, &out.Decoding + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.LoggerPlugin != nil { + in, out := &in.LoggerPlugin, &out.LoggerPlugin + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAServerConfig. +func (in *OPAServerConfig) DeepCopy() *OPAServerConfig { + if in == nil { + return nil + } + out := new(OPAServerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPAStorageConfig) DeepCopyInto(out *OPAStorageConfig) { + *out = *in + if in.Disk != nil { + in, out := &in.Disk, &out.Disk + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAStorageConfig. +func (in *OPAStorageConfig) DeepCopy() *OPAStorageConfig { + if in == nil { + return nil + } + out := new(OPAStorageConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReasonMapping) DeepCopyInto(out *ReasonMapping) { *out = *in @@ -337,6 +505,26 @@ func (in *SystemList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SystemOPASpec) DeepCopyInto(out *SystemOPASpec) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(OPAConfigSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemOPASpec. +func (in *SystemOPASpec) DeepCopy() *SystemOPASpec { + if in == nil { + return nil + } + out := new(SystemOPASpec) + 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 @@ -387,6 +575,11 @@ func (in *SystemSpec) DeepCopyInto(out *SystemSpec) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } + if in.OPA != nil { + in, out := &in.OPA, &out.OPA + *out = new(SystemOPASpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemSpec. diff --git a/cmd/main.go b/cmd/main.go index aa0fc8b3..42f06862 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "strings" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -110,8 +111,7 @@ func main() { ctrlConfig, err := config.Load(configFiles, scheme) if err != nil { - log.Error(err, "unable to load the config file(s)") - exit(err) + exit(errors.Wrap(err, "unable to load the config file(s)")) } ctrl.SetLogger(zap.New( @@ -123,8 +123,7 @@ func main() { mgr, err := ctrl.NewManager(restCfg, options) if err != nil { - log.Error(err, "unable to start manager") - exit(err) + exit(errors.Wrap(err, "unable to start manager")) } var opaControlPlaneClient ocp.ClientInterface @@ -134,14 +133,12 @@ func main() { err := errors.New( "missing OPA Control Plane configuration: address and token are required", ) - log.Error(err, "unable to start manager") - exit(err) + exit(errors.Wrap(err, "unable to start manager")) } if ctrlConfig.OPAControlPlaneConfig.BundleObjectStorage == nil { err := errors.New("missing OPA Control Plane bundle object storage configuration") - log.Error(err, "unable to start manager") - exit(err) + exit(errors.Wrap(err, "unable to start manager")) } ocpHostURL := strings.TrimSuffix(ctrlConfig.OPAControlPlaneConfig.Address, "/") @@ -158,7 +155,6 @@ func main() { if err := metrics.Registry.Register(systemReadyMetric); err != nil { err := errors.Wrap(err, "could not register controller_system_status_ready metric") - log.Error(err, err.Error()) exit(err) } @@ -172,7 +168,6 @@ func main() { if err := metrics.Registry.Register(reconcileSegmentTimeMetric); err != nil { err := errors.Wrap(err, "could not register reconcileSegmentTimeMetric") - log.Error(err, err.Error()) exit(err) } @@ -186,7 +181,6 @@ func main() { if err := metrics.Registry.Register(reconcileTimeMetric); err != nil { err := errors.Wrap(err, "could not register reconcileTimeMetric") - log.Error(err, err.Error()) exit(err) } @@ -212,19 +206,16 @@ func main() { ctrlConfig.OPAControlPlaneConfig.LibraryDatasourceChanged) if err = r1.SetupWithManager(mgr, "styra-controller"); err != nil { - log.Error(err, "unable to create controller", "controller", "System") - exit(err) + exit(errors.Wrap(err, "unable to create System controller")) } if err = r1.CreateDefaultRequirements(context.Background(), log); err != nil { - log.Error(err, "unable to create default requirements") - exit(err) + exit(errors.Wrap(err, "unable to create default requirements")) } if !ctrlConfig.DisableCRDWebhooks { if err = webhookstyrav1beta1.SetupSystemWebhookWithManager(mgr); err != nil { - log.Error(err, "unable to create webhook", "webhook", "System") - os.Exit(1) + exit(errors.Wrap(err, "unable to create System webhook")) } } @@ -241,33 +232,30 @@ func main() { ctrlConfig.OPAControlPlaneConfig.LibraryDatasourceChanged) if err = libraryReconciler.SetupWithManager(mgr); err != nil { - log.Error(err, "unable to create controller", "controller", "Library") - os.Exit(1) + exit(errors.Wrap(err, "unable to create Library controller")) } if !ctrlConfig.DisableCRDWebhooks { if err = webhookstyrav1alpha1.SetupLibraryWebhookWithManager(mgr); err != nil { - log.Error(err, "unable to create webhook", "webhook", "Library") - os.Exit(1) + exit(errors.Wrap(err, "unable to create Library webhook")) } } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - log.Error(err, "unable to set up health check") - exit(err) + exit(errors.Wrap(err, "unable to set up health check")) } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - log.Error(err, "unable to set up ready check") - exit(err) + exit(errors.Wrap(err, "unable to set up ready check")) } log.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - log.Error(err, "problem running manager") - exit(err) + exit(errors.Wrap(err, "problem running manager")) } } -func exit(_ error) { +func exit(err error) { + fmt.Printf("Exiting due to error: %v\n", err) + time.Sleep(10 * time.Second) os.Exit(1) } diff --git a/config/crd/bases/styra.bankdata.dk_systems.yaml b/config/crd/bases/styra.bankdata.dk_systems.yaml index 2a4dc23e..4e17df7d 100644 --- a/config/crd/bases/styra.bankdata.dk_systems.yaml +++ b/config/crd/bases/styra.bankdata.dk_systems.yaml @@ -51,8 +51,11 @@ spec: properties: customOPAConfig: description: |- - CustomOPAConfig allows the owner of a System resource to set custom features - without having to extend the Controller + Deprecated: CustomOPAConfig allows the owner of a System resource to set + custom OPA configuration that is merged into the generated OPA config. + Use OPA.Config instead, which provides the same capability with runtime + validation of field names against the OPA configuration schema. + If both are set, OPA.Config takes precedence on conflicting keys. type: object x-kubernetes-preserve-unknown-fields: true datasources: @@ -159,9 +162,8 @@ spec: type: boolean discoveryOverrides: description: |- - DiscoveryOverrides is an OPA config which will take precedence over the - configuration supplied by the OPA discovery API. Configuration set here - will be merged with the configuration supplied by the discovery API. + Deprecated: DiscoveryOverrides is unused by the controller and will be + removed in a future version. Use OPA.Config instead. properties: distributed_tracing: description: |- @@ -215,6 +217,91 @@ spec: required: - name type: object + opa: + description: OPA contains OPA-specific configuration for this system. + properties: + config: + description: |- + Config accepts any valid OPA configuration YAML. The top-level keys are + validated by the OPA configuration schema (see + https://github.com/open-policy-agent/opa/blob/main/v1/config/config.go). + + Configuration set here is merged on top of the generated OPA config and + takes precedence over the deprecated customOPAConfig field on conflicts. + properties: + bundle: + type: object + x-kubernetes-preserve-unknown-fields: true + bundles: + type: object + x-kubernetes-preserve-unknown-fields: true + caching: + type: object + x-kubernetes-preserve-unknown-fields: true + decision_logs: + type: object + x-kubernetes-preserve-unknown-fields: true + default_authorization_decision: + type: string + default_decision: + type: string + discovery: + type: object + x-kubernetes-preserve-unknown-fields: true + distributed_tracing: + type: object + x-kubernetes-preserve-unknown-fields: true + keys: + type: object + x-kubernetes-preserve-unknown-fields: true + labels: + additionalProperties: + type: string + type: object + metrics_export: + type: object + x-kubernetes-preserve-unknown-fields: true + nd_builtin_cache: + type: boolean + persistence_directory: + type: string + plugins: + additionalProperties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + server: + description: OPAServerConfig mirrors OPA's server configuration + section. + properties: + decoding: + type: object + x-kubernetes-preserve-unknown-fields: true + encoding: + type: object + x-kubernetes-preserve-unknown-fields: true + logger_plugin: + type: string + metrics: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + services: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true + storage: + description: OPAStorageConfig mirrors OPA's storage configuration + section. + properties: + disk: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: object sourceControl: description: SourceControl holds SourceControl configuration. properties: diff --git a/docs/apis/styra/v1alpha1.md b/docs/apis/styra/v1alpha1.md index 61b556c2..7097ffe4 100644 --- a/docs/apis/styra/v1alpha1.md +++ b/docs/apis/styra/v1alpha1.md @@ -458,5 +458,5 @@ GitRepo
Generated with gen-crd-api-reference-docs
-on git commit 4b70bcc.
+on git commit 24c7f90.
+(Appears on:SystemOPASpec) +
+OPAConfigSpec mirrors the official OPA configuration schema.
+| Field | +Description | +
|---|---|
+services+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+labels+ +map[string]string + + |
++ | +
+discovery+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+bundle+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+bundles+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+decision_logs+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+status+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+plugins+ +map[string]*k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+keys+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+default_decision+ +string + + |
++ | +
+default_authorization_decision+ +string + + |
++ | +
+caching+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+nd_builtin_cache+ +bool + + |
++ | +
+persistence_directory+ +string + + |
++ | +
+distributed_tracing+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+metrics_export+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+server+ + +OPAServerConfig + + + |
++ | +
+storage+ + +OPAStorageConfig + + + |
++ | +
@@ -764,6 +966,92 @@ bool +
+(Appears on:OPAConfigSpec) +
+OPAServerConfig mirrors OPA’s server configuration section.
+| Field | +Description | +
|---|---|
+metrics+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+encoding+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+decoding+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
+logger_plugin+ +string + + |
++ | +
+(Appears on:OPAConfigSpec) +
+OPAStorageConfig mirrors OPA’s storage configuration section.
+| Field | +Description | +
|---|---|
+disk+ +k8s.io/apimachinery/pkg/runtime.RawExtension + + |
++ | +
@@ -1003,9 +1291,8 @@ DiscoveryOverrides
DiscoveryOverrides is an OPA config which will take precedence over the -configuration supplied by the OPA discovery API. Configuration set here -will be merged with the configuration supplied by the discovery API.
+Deprecated: DiscoveryOverrides is unused by the controller and will be +removed in a future version. Use OPA.Config instead.
CustomOPAConfig allows the owner of a System resource to set custom features -without having to extend the Controller
+Deprecated: CustomOPAConfig allows the owner of a System resource to set +custom OPA configuration that is merged into the generated OPA config. +Use OPA.Config instead, which provides the same capability with runtime +validation of field names against the OPA configuration schema. +If both are set, OPA.Config takes precedence on conflicting keys.
+opaOPA contains OPA-specific configuration for this system.
+(Appears on:SystemSpec) +
+SystemOPASpec contains OPA-specific configuration for a System.
+| Field | +Description | +
|---|---|
+config+ + +OPAConfigSpec + + + |
+
+ Config accepts any valid OPA configuration YAML. The top-level keys are +validated by the OPA configuration schema (see +https://github.com/open-policy-agent/opa/blob/main/v1/config/config.go). +Configuration set here is merged on top of the generated OPA config and +takes precedence over the deprecated customOPAConfig field on conflicts. + |
+
string alias)@@ -1179,9 +1517,8 @@ DiscoveryOverrides
DiscoveryOverrides is an OPA config which will take precedence over the -configuration supplied by the OPA discovery API. Configuration set here -will be merged with the configuration supplied by the discovery API.
+Deprecated: DiscoveryOverrides is unused by the controller and will be +removed in a future version. Use OPA.Config instead.
CustomOPAConfig allows the owner of a System resource to set custom features -without having to extend the Controller
+Deprecated: CustomOPAConfig allows the owner of a System resource to set +custom OPA configuration that is merged into the generated OPA config. +Use OPA.Config instead, which provides the same capability with runtime +validation of field names against the OPA configuration schema. +If both are set, OPA.Config takes precedence on conflicting keys.
+opaOPA contains OPA-specific configuration for this system.
Generated with gen-crd-api-reference-docs
-on git commit 4b70bcc.
+on git commit 24c7f90.