diff --git a/api/config/v2alpha2/projectconfig_types.go b/api/config/v2alpha2/projectconfig_types.go index 261ed6d9..260b0897 100644 --- a/api/config/v2alpha2/projectconfig_types.go +++ b/api/config/v2alpha2/projectconfig_types.go @@ -222,25 +222,28 @@ type OPAControlPlaneConfig struct { SystemDatasourceChanged string `json:"systemDatasourceChanged,omitempty"` // LibraryDatasourceChanged is the URL to be called when a library datasource has changed. LibraryDatasourceChanged string `json:"libraryDatasourceChanged,omitempty"` + + // DecisionAPIConfig contains configuration for which api OPAs should use to and how + DecisionAPIConfig *DecisionAPIConfig `json:"decisionAPIConfig,omitempty"` } // UserCredentialHandler defines the structure of possible user credential handlers type UserCredentialHandler struct { - S3 *S3Handler `json:"s3,omitempty"` + S3 *S3Handler `json:"s3,omitempty" yaml:"s3,omitempty"` } // S3Handler defines the structure for S3 handler configuration. type S3Handler struct { - Bucket string `json:"bucket"` - URL string `json:"url"` - Region string `json:"region"` - AccessKeyID string `json:"accessKeyID"` - SecretAccessKey string `json:"secretAccessKey"` + Bucket string `json:"bucket" yaml:"bucket"` + URL string `json:"url" yaml:"url"` + Region string `json:"region" yaml:"region"` + AccessKeyID string `json:"accessKeyID" yaml:"accessKeyID"` + SecretAccessKey string `json:"secretAccessKey" yaml:"secretAccessKey"` } // BundleObjectStorage defines the structure for object storage configuration used by bundles type BundleObjectStorage struct { - S3 *S3ObjectStorage `json:"s3,omitempty"` + S3 *S3ObjectStorage `json:"s3,omitempty" yaml:"s3,omitempty"` } // S3ObjectStorage defines the structure for S3 object storage configuration. @@ -263,14 +266,50 @@ type GitCredentials struct { // OPAConfig contains default configuration for the opa config generated by the styra-controller type OPAConfig struct { - DecisionLogs DecisionLog `json:"decision_logs"` - PersistBundle bool `json:"persist_bundle,omitempty"` - PersistBundleDirectory string `json:"persist_bundle_directory,omitempty"` + DecisionLogs DecisionLog `json:"decisionLogs,omitempty" yaml:"decisionLogs,omitempty"` + Metrics MetricsConfig `json:"metrics,omitempty" yaml:"metrics,omitempty"` + PersistBundle bool `json:"persist_bundle,omitempty" yaml:"persist_bundle,omitempty"` + PersistBundleDirectory string `json:"persist_bundle_directory,omitempty" yaml:"persist_bundle_directory,omitempty"` //nolint:lll + BundleServer *OPABundleServer `json:"bundleServer,omitempty" yaml:"bundleServer,omitempty"` +} + +// OPABundleServer contains configuration for the OPA bundle server +type OPABundleServer struct { + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` +} + +// MetricsConfig contains configuration for OPA metrics +type MetricsConfig struct { + Prometheus PrometheusMetricsConfig `json:"prometheus,omitempty" yaml:"prometheus,omitempty"` +} + +// PrometheusMetricsConfig contains configuration for Prometheus metrics +type PrometheusMetricsConfig struct { + HTTP HTTPMetricsConfig `json:"http,omitempty" yaml:"http,omitempty"` +} + +// HTTPMetricsConfig contains configuration for HTTP metrics +type HTTPMetricsConfig struct { + Buckets []float64 `json:"buckets,omitempty" yaml:"buckets,omitempty"` } // DecisionLog contains configuration for the decision logs type DecisionLog struct { - RequestContext RequestContext `json:"request_context"` + RequestContext RequestContext `json:"requestContext,omitempty"` +} + +// DecisionAPIConfig contains configuration for decision log dispatch +type DecisionAPIConfig struct { + ServiceURL string `json:"serviceUrl,omitempty"` + Reporting DecisionLogReporting `json:"reporting,omitempty"` +} + +// DecisionLogReporting contains configuration for decision log reporting +type DecisionLogReporting struct { + MaxDelaySeconds int `json:"maxDelaySeconds,omitempty" yaml:"maxDelaySeconds,omitempty"` + MinDelaySeconds int `json:"minDelaySeconds,omitempty" yaml:"minDelaySeconds,omitempty"` + UploadSizeLimitBytes int `json:"uploadSizeLimitBytes,omitempty" yaml:"uploadSizeLimitBytes,omitempty"` } // RequestContext contains configuration for the RequestContext in the decision logs diff --git a/api/config/v2alpha2/zz_generated.deepcopy.go b/api/config/v2alpha2/zz_generated.deepcopy.go index 36f45098..eef69be5 100644 --- a/api/config/v2alpha2/zz_generated.deepcopy.go +++ b/api/config/v2alpha2/zz_generated.deepcopy.go @@ -44,6 +44,22 @@ func (in *BundleObjectStorage) DeepCopy() *BundleObjectStorage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecisionAPIConfig) DeepCopyInto(out *DecisionAPIConfig) { + *out = *in + out.Reporting = in.Reporting +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecisionAPIConfig. +func (in *DecisionAPIConfig) DeepCopy() *DecisionAPIConfig { + if in == nil { + return nil + } + out := new(DecisionAPIConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DecisionLog) DeepCopyInto(out *DecisionLog) { *out = *in @@ -60,6 +76,21 @@ func (in *DecisionLog) DeepCopy() *DecisionLog { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DecisionLogReporting) DeepCopyInto(out *DecisionLogReporting) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecisionLogReporting. +func (in *DecisionLogReporting) DeepCopy() *DecisionLogReporting { + if in == nil { + return nil + } + out := new(DecisionLogReporting) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExporterConfig) DeepCopyInto(out *ExporterConfig) { *out = *in @@ -130,6 +161,26 @@ func (in *HTTP) DeepCopy() *HTTP { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPMetricsConfig) DeepCopyInto(out *HTTPMetricsConfig) { + *out = *in + if in.Buckets != nil { + in, out := &in.Buckets, &out.Buckets + *out = make([]float64, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPMetricsConfig. +func (in *HTTPMetricsConfig) DeepCopy() *HTTPMetricsConfig { + if in == nil { + return nil + } + out := new(HTTPMetricsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KafkaConfig) DeepCopyInto(out *KafkaConfig) { *out = *in @@ -173,6 +224,22 @@ func (in *LeaderElectionConfig) DeepCopy() *LeaderElectionConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsConfig) DeepCopyInto(out *MetricsConfig) { + *out = *in + in.Prometheus.DeepCopyInto(&out.Prometheus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsConfig. +func (in *MetricsConfig) DeepCopy() *MetricsConfig { + if in == nil { + return nil + } + out := new(MetricsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NotificationWebhooksConfig) DeepCopyInto(out *NotificationWebhooksConfig) { *out = *in @@ -188,10 +255,31 @@ func (in *NotificationWebhooksConfig) DeepCopy() *NotificationWebhooksConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OPABundleServer) DeepCopyInto(out *OPABundleServer) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPABundleServer. +func (in *OPABundleServer) DeepCopy() *OPABundleServer { + if in == nil { + return nil + } + out := new(OPABundleServer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OPAConfig) DeepCopyInto(out *OPAConfig) { *out = *in in.DecisionLogs.DeepCopyInto(&out.DecisionLogs) + in.Metrics.DeepCopyInto(&out.Metrics) + if in.BundleServer != nil { + in, out := &in.BundleServer, &out.BundleServer + *out = new(OPABundleServer) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAConfig. @@ -228,6 +316,11 @@ func (in *OPAControlPlaneConfig) DeepCopyInto(out *OPAControlPlaneConfig) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.DecisionAPIConfig != nil { + in, out := &in.DecisionAPIConfig, &out.DecisionAPIConfig + *out = new(DecisionAPIConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OPAControlPlaneConfig. @@ -377,6 +470,22 @@ func (in *ProjectConfig) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PrometheusMetricsConfig) DeepCopyInto(out *PrometheusMetricsConfig) { + *out = *in + in.HTTP.DeepCopyInto(&out.HTTP) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusMetricsConfig. +func (in *PrometheusMetricsConfig) DeepCopy() *PrometheusMetricsConfig { + if in == nil { + return nil + } + out := new(PrometheusMetricsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RequestContext) DeepCopyInto(out *RequestContext) { *out = *in diff --git a/config/default/config.yaml b/config/default/config.yaml index 4128c1bd..efc6d78b 100644 --- a/config/default/config.yaml +++ b/config/default/config.yaml @@ -58,6 +58,44 @@ systemUserRoles: #decisionsExporter: +opa: + bundleServer: + url: https://minio-host + path: /ocp + metrics: + prometheus: + http: + buckets: + - 0.0005 + - 0.001 + - 0.002 + - 0.003 + - 0.004 + - 0.005 + - 0.006 + - 0.007 + - 0.008 + - 0.009 + - 0.01 + - 0.02 + - 0.03 + - 0.04 + - 0.05 + - 0.06 + - 0.07 + - 0.08 + - 0.09 + - 0.1 + - 0.2 + - 0.3 + - 0.4 + - 0.5 + - 0.6 + - 0.7 + - 0.8 + - 0.9 + - 1 + #activityExporter: podRestart: @@ -65,8 +103,7 @@ podRestart: enabled: true deploymentType: StatefulSet - -#opa: +# opa: # decision_logs: # request_context: # http: diff --git a/docs/apis/styra/v1alpha1.md b/docs/apis/styra/v1alpha1.md index 510e5a0e..1f0e8fb7 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 b550add7. +on git commit 4901ef1.

diff --git a/docs/apis/styra/v1beta1.md b/docs/apis/styra/v1beta1.md index 5c43bb9b..84a95cd0 100644 --- a/docs/apis/styra/v1beta1.md +++ b/docs/apis/styra/v1beta1.md @@ -246,7 +246,7 @@ the configuration of the System are updated in Styra.

"SystemSourceUpdated"

ConditionTypeSystemSourceUpdated is a ConditionType used when -the datasources of the System are updated in Styra.

+the source for the System is updated in OCP.

@@ -457,6 +457,10 @@ secret referenced by the System resource under Spec.SourceControl.Origin.Credent

EventErrorCredentialsSecretNotFound is an EventType used when the controller gets a 404 when fetching secret referenced by the System resource under Spec.SourceControl.Origin.CredentialsSecretName.

+

"ErrorDeleteBundleInOCP"

+

EventErrorDeleteBundleInOCP is an EventType used when the controller fails +to delete the System’s Bundle in OCP.

+

"ErrorDeleteDatasource"

EventErrorDeleteDatasource is an EventType used when the controller fails to delete a datasource in Styra.

@@ -464,6 +468,10 @@ secret referenced by the System resource under Spec.SourceControl.Origin.Credent

EventErrorDeleteDefaultPolicy is an EventType used when the controller fails to delete the default policy in the System in Styra.

+

"ErrorDeleteSourceInOCP"

+

EventErrorDeleteSourceInOCP is an EventType used when the controller fails +to delete the System’s Source in OCP.

+

"ErrorDeleteSystemInStyra"

EventErrorDeleteSystemInStyra is an EventType used when the controller fails to delete the System in Styra.

@@ -542,9 +550,15 @@ the finalizer on the System resource.

EventErrorStatefulSetNotFound is an EventType used when a system with ‘localPlane’ enabled but which does not have a StatefulSet created for the SLP.

+

"ErrorUpdateBundle"

+

EventErrorUpdateBundle is an EventType used when the controller fails to update the Source in OCP.

+

"ErrorUpdateOPAConfigMap"

EventErrorUpdateOPAConfigMap is an EventType used when the controller fails to update the OPA ConfigMap.

+

"ErrorUpdateOPASecret"

+

EventErrorUpdateOPASecret is an EventType used when the controller fails to update the OPA ConfigMap.

+

"ErrorUpdateOPATokenSecret"

EventErrorUpdateOPATokenSecret is an EventType used when the controller fails to update the OPA token Secret.

@@ -555,6 +569,9 @@ for a user in Styra.

"ErrorUpdateSLPConfigMap"

EventErrorUpdateSLPConfigmap is an EventType used when the controller fails to update the SLP ConfigMap.

+

"ErrorUpdateSource"

+

EventErrorUpdateSource is an EventType used when the controller fails to update the Source in OCP.

+

"ErrorUpdateStatus"

EventErrorUpdateStatus is an EventType used when the controller fails to update the status of the System resource.

@@ -1407,5 +1424,5 @@ System.


Generated with gen-crd-api-reference-docs -on git commit b550add7. +on git commit 4901ef1.

diff --git a/internal/controller/styra/system_controller.go b/internal/controller/styra/system_controller.go index d65791e0..4950f1e2 100644 --- a/internal/controller/styra/system_controller.go +++ b/internal/controller/styra/system_controller.go @@ -115,6 +115,7 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr log = log.WithValues("systemID", system.Status.ID) log = log.WithValues("controlPlane", system.Labels["styra-controller/control-plane"]) + log = log.WithValues("uniqueName", system.OCPUniqueName(r.Config.SystemPrefix, r.Config.SystemSuffix)) if !labels.ControllerClassMatches(&system, r.Config.ControllerClass) { log.Info("This is not a System we are managing. Skipping reconciliation.") @@ -524,17 +525,31 @@ func (r *SystemReconciler) reconcileOPAConfigMapForOCP( } opaconf := ocp.OPAConfig{ - BundleResource: fmt.Sprintf("bundles/%s/bundle.tar.gz", uniqueName), - BundleService: "s3", - ServiceURL: fmt.Sprintf("%s/%s", - r.Config.OPAControlPlaneConfig.BundleObjectStorage.S3.URL, - r.Config.OPAControlPlaneConfig.BundleObjectStorage.S3.Bucket), - ServiceName: "s3", - UniqueName: uniqueName, - Namespace: system.Namespace, + BundleService: &ocp.OPAServiceConfig{ + Name: "s3", + URL: path.Join(r.Config.OPA.BundleServer.URL, r.Config.OPA.BundleServer.Path), + Credentials: &ocp.ServiceCredentials{ + S3: &ocp.S3Signing{ + S3EnvironmentCredentials: map[string]ocp.EmptyStruct{}, + }, + }, + }, + LogService: &ocp.OPAServiceConfig{ + Name: "logs", + URL: r.Config.OPAControlPlaneConfig.DecisionAPIConfig.ServiceURL, + Credentials: &ocp.ServiceCredentials{ + Bearer: &ocp.Bearer{ + TokenPath: "/run/secrets/kubernetes.io/serviceaccount/token", + }, + }, + }, + DecisionLogReporting: r.Config.OPAControlPlaneConfig.DecisionAPIConfig.Reporting, + BundleResource: fmt.Sprintf("bundles/%s/bundle.tar.gz", uniqueName), + UniqueName: uniqueName, + Namespace: system.Namespace, } - expectedOPAConfigMap, err = k8sconv.OpaConfToK8sOPAConfigMapforOCP(opaconf, r.Config.OPA, customConfig) + expectedOPAConfigMap, err = k8sconv.OPAConfToK8sOPAConfigMapforOCP(opaconf, r.Config.OPA, customConfig, log) if err != nil { return ctrl.Result{}, false, ctrlerr.Wrap(err, "Could not convert OPA conf to ConfigMap"). WithEvent(v1beta1.EventErrorConvertOPAConf). @@ -906,7 +921,6 @@ func (r *SystemReconciler) styraReconcile( systemID := system.Status.ID migrationID := system.ObjectMeta.Annotations["styra-controller/migration-id"] - if r.Config.EnableMigrations && systemID == "" && migrationID != "" { log.Info(fmt.Sprintf("Use migrationId(%s) to fetch system from Styra DAS", migrationID)) getSystemStart := time.Now() @@ -1855,10 +1869,10 @@ func (r *SystemReconciler) reconcileOPAConfigMap( if system.Spec.LocalPlane == nil { log.Info("No styra local plane defined for System") - expectedOPAConfigMap, err = k8sconv.OpaConfToK8sOPAConfigMapNoSLP(opaconf, r.Config.OPA, customConfig) + expectedOPAConfigMap, err = k8sconv.OPAConfToK8sOPAConfigMapNoSLP(opaconf, r.Config.OPA, customConfig) } else { slpURL := fmt.Sprintf("http://%s/v1", system.Spec.LocalPlane.Name) - expectedOPAConfigMap, err = k8sconv.OpaConfToK8sOPAConfigMap(opaconf, slpURL, r.Config.OPA, customConfig) + expectedOPAConfigMap, err = k8sconv.OPAConfToK8sOPAConfigMap(opaconf, slpURL, r.Config.OPA, customConfig) } if err != nil { return ctrl.Result{}, false, ctrlerr.Wrap(err, "Could not convert OPA conf to ConfigMap"). @@ -1939,7 +1953,7 @@ func (r *SystemReconciler) reconcileSLPConfigMap( configmapName := fmt.Sprintf("%s-slp", system.Name) log = log.WithValues("configmapName", configmapName) - expectedSLPConfigMap, err := k8sconv.OpaConfToK8sSLPConfigMap(opaconf) + expectedSLPConfigMap, err := k8sconv.OPAConfToK8sSLPConfigMap(opaconf) if err != nil { return ctrl.Result{}, false, ctrlerr.Wrap(err, "Could not convert OPA Conf to SLP ConfigMap"). WithEvent(v1beta1.EventErrorConvertOPAConf). diff --git a/internal/k8sconv/k8sconv.go b/internal/k8sconv/k8sconv.go index e9de636d..438b1244 100644 --- a/internal/k8sconv/k8sconv.go +++ b/internal/k8sconv/k8sconv.go @@ -21,6 +21,7 @@ package k8sconv import ( "fmt" + "github.com/go-logr/logr" "github.com/pkg/errors" "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" @@ -38,14 +39,6 @@ type credentials struct { Bearer bearer `yaml:"bearer"` } -type s3credentials struct { - S3Signing s3signing `yaml:"s3_signing"` -} - -type s3signing struct { - S3EnvironmentCredentials map[string]interface{} `yaml:"environment_credentials"` -} - type authz struct { Service string `yaml:"service"` Resource string `yaml:"resource"` @@ -62,12 +55,6 @@ type service struct { Credentials credentials `yaml:"credentials,omitempty"` } -type s3service struct { - Name string `yaml:"name"` - URL string `yaml:"url"` - S3Credentials s3credentials `yaml:"credentials"` -} - type labels struct { SystemID string `yaml:"system-id"` SystemType string `yaml:"system-type"` @@ -92,71 +79,135 @@ type requestContext struct { HTTP http `yaml:"http"` } -type decisionLogs struct { - RequestContext requestContext `yaml:"request_context"` +// DecisionLogs contains configuration for decision logs +type DecisionLogs struct { + RequestContext requestContext `json:"request_context,omitempty" yaml:"request_context,omitempty"` + ServiceName string `json:"service,omitempty" yaml:"service,omitempty"` + ResourcePath string `json:"resource_path,omitempty" yaml:"resource_path,omitempty"` + Reporting *DecisionLogReporting `json:"reporting,omitempty" yaml:"reporting,omitempty"` +} + +// DecisionLogReporting contains configuration for decision log reporting +type DecisionLogReporting struct { + MaxDelaySeconds int `json:"max_delay_seconds,omitempty" yaml:"max_delay_seconds,omitempty"` + MinDelaySeconds int `json:"min_delay_seconds,omitempty" yaml:"min_delay_seconds,omitempty"` + UploadSizeLimitBytes int `json:"upload_size_limit_bytes,omitempty" yaml:"upload_size_limit_bytes,omitempty"` } -type opaConfigMap struct { +// OPAConfigMap represents the structure of the OPA configuration file +type OPAConfigMap struct { Services []service `yaml:"services"` Labels labels `yaml:"labels"` Discovery discovery `yaml:"discovery"` - DecisionLogs decisionLogs `yaml:"decision_logs,omitempty"` + DecisionLogs DecisionLogs `yaml:"decision_logs,omitempty"` +} + +// OcpOPAConfigMap represents the structure of the OPA configuration file for OCP +type OcpOPAConfigMap struct { + Services []*ocp.OPAServiceConfig `yaml:"services"` + Bundles bundle `yaml:"bundles,omitempty"` + DecisionLogs DecisionLogs `yaml:"decision_logs,omitempty"` + PersistenceDirectory string `yaml:"persistence_directory,omitempty"` + Labels labelsOCP `yaml:"labels,omitempty"` + Server Serverconfig `yaml:"server,omitempty"` + Status StatusConfig `yaml:"status,omitempty"` } -type s3opaConfigMap struct { - Services []s3service `yaml:"services"` - Bundles bundle `yaml:"bundles,omitempty"` - DecisionLogs decisionLogs `yaml:"decision_logs,omitempty"` - PersistenceDirectory string `yaml:"persistence_directory,omitempty"` - Labels labelsOCP `yaml:"labels,omitempty"` +// StatusConfig represents the status configuration for OPA +type StatusConfig struct { + Prometheus bool `yaml:"prometheus,omitempty"` } -// OpaConfToK8sOPAConfigMapforOCP creates a ConfigMap for the OPA. +// Serverconfig represents the server configuration for OPA +type Serverconfig struct { + Metrics Metricsconfig `yaml:"metrics,omitempty"` +} + +// Metricsconfig represents the metrics configuration for OPA +type Metricsconfig struct { + Prometheus PrometheusMetricsConfig `yaml:"prom,omitempty"` +} + +// PrometheusMetricsConfig represents the Prometheus metrics configuration for OPA +type PrometheusMetricsConfig struct { + HTTP HTTPMetricsConfig `yaml:"http_request_duration_seconds,omitempty"` +} + +// HTTPMetricsConfig represents the HTTP metrics configuration for OPA +type HTTPMetricsConfig struct { + Buckets []float64 `yaml:"buckets,omitempty"` +} + +// OPAConfToK8sOPAConfigMapforOCP creates a ConfigMap for the OPA. // It configures OPA to fetch bundle from MinIO. -// OpaConfToK8sOPAConfigMapforOCP merges the information given as input into a ConfigMap for OPA -func OpaConfToK8sOPAConfigMapforOCP( +// OPAConfToK8sOPAConfigMapforOCP merges the information given as input into a ConfigMap for OPA +func OPAConfToK8sOPAConfigMapforOCP( opaconf ocp.OPAConfig, opaDefaultConfig configv2alpha2.OPAConfig, customConfig map[string]interface{}, + _ logr.Logger, ) (corev1.ConfigMap, error) { + var services []*ocp.OPAServiceConfig + + if opaconf.BundleService != nil { + services = append(services, opaconf.BundleService) + } + if opaconf.LogService != nil { + services = append(services, opaconf.LogService) + } - s3opaConfigMap := s3opaConfigMap{ + ocpOPAConfigMap := OcpOPAConfigMap{ Bundles: bundle{ Authz: authz{ - Service: opaconf.BundleService, + Service: opaconf.BundleService.Name, Resource: opaconf.BundleResource, }, }, - Services: []s3service{{ - Name: opaconf.ServiceName, - URL: opaconf.ServiceURL, - S3Credentials: s3credentials{ - S3Signing: s3signing{ - S3EnvironmentCredentials: map[string]interface{}{}, - }, - }, - }}, + Services: services, Labels: labelsOCP{ UniqueName: opaconf.UniqueName, Namespace: opaconf.Namespace, }, + DecisionLogs: DecisionLogs{ + ServiceName: opaconf.LogService.Name, + ResourcePath: "/logs", + Reporting: &DecisionLogReporting{ + MaxDelaySeconds: opaconf.DecisionLogReporting.MaxDelaySeconds, + MinDelaySeconds: opaconf.DecisionLogReporting.MinDelaySeconds, + UploadSizeLimitBytes: opaconf.DecisionLogReporting.UploadSizeLimitBytes, + }, + }, } + + if opaDefaultConfig.Metrics.Prometheus.HTTP.Buckets != nil { + ocpOPAConfigMap.Server = Serverconfig{ + Metrics: Metricsconfig{ + Prometheus: PrometheusMetricsConfig{ + HTTP: HTTPMetricsConfig{ + Buckets: opaDefaultConfig.Metrics.Prometheus.HTTP.Buckets, + }, + }, + }, + } + ocpOPAConfigMap.Status = StatusConfig{ + Prometheus: true, + } + } + if opaDefaultConfig.PersistBundle { - s3opaConfigMap.Bundles.Authz.Persist = opaDefaultConfig.PersistBundle - s3opaConfigMap.PersistenceDirectory = opaDefaultConfig.PersistBundleDirectory + ocpOPAConfigMap.Bundles.Authz.Persist = opaDefaultConfig.PersistBundle + ocpOPAConfigMap.PersistenceDirectory = opaDefaultConfig.PersistBundleDirectory } if opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers != nil { - s3opaConfigMap.DecisionLogs = decisionLogs{ - RequestContext: requestContext{ - HTTP: http{ - Headers: opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers, - }, + ocpOPAConfigMap.DecisionLogs.RequestContext = requestContext{ + HTTP: http{ + Headers: opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers, }, } } - opaConfigMapMapStringInterface, err := opaConfigMapToMap(s3opaConfigMap) + opaConfigMapMapStringInterface, err := opaConfigMapToMap(ocpOPAConfigMap) if err != nil { return corev1.ConfigMap{}, err } @@ -176,17 +227,17 @@ func OpaConfToK8sOPAConfigMapforOCP( return cm, nil } -// OpaConfToK8sOPAConfigMap creates a corev1.ConfigMap for the OPA based on the +// OPAConfToK8sOPAConfigMap creates a corev1.ConfigMap for the OPA based on the // configuration from Styra. The configmap configures the OPA to communicate to // an SLP. -func OpaConfToK8sOPAConfigMap( +func OPAConfToK8sOPAConfigMap( opaconf styra.OPAConfig, slpURL string, opaDefaultConfig configv2alpha2.OPAConfig, customConfig map[string]interface{}, ) (corev1.ConfigMap, error) { - opaConfigMap := opaConfigMap{ + opaConfigMap := OPAConfigMap{ Services: []service{{ Name: "styra", URL: slpURL, @@ -202,7 +253,7 @@ func OpaConfToK8sOPAConfigMap( } if opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers != nil { - opaConfigMap.DecisionLogs = decisionLogs{ + opaConfigMap.DecisionLogs = DecisionLogs{ RequestContext: requestContext{ HTTP: http{ Headers: opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers, @@ -232,9 +283,9 @@ func OpaConfToK8sOPAConfigMap( return cm, nil } -// OpaConfToK8sSLPConfigMap creates a ConfigMap for the SLP based on the +// OPAConfToK8sSLPConfigMap creates a ConfigMap for the SLP based on the // configuration from Styra. -func OpaConfToK8sSLPConfigMap(opaconf styra.OPAConfig) (corev1.ConfigMap, error) { +func OPAConfToK8sSLPConfigMap(opaconf styra.OPAConfig) (corev1.ConfigMap, error) { type Bearer struct { TokenPath string `yaml:"token_path"` } @@ -300,16 +351,16 @@ func OpaConfToK8sSLPConfigMap(opaconf styra.OPAConfig) (corev1.ConfigMap, error) return cm, nil } -// OpaConfToK8sOPAConfigMapNoSLP creates a ConfigMap for the OPA based on the +// OPAConfToK8sOPAConfigMapNoSLP creates a ConfigMap for the OPA based on the // configuration from Styra. The ConfigMap configures the OPA to communicate // directly to Styra and not via an SLP. -func OpaConfToK8sOPAConfigMapNoSLP( +func OPAConfToK8sOPAConfigMapNoSLP( opaconf styra.OPAConfig, opaDefaultConfig configv2alpha2.OPAConfig, customConfig map[string]interface{}, ) (corev1.ConfigMap, error) { - opaConfigMap := opaConfigMap{ + opaConfigMap := OPAConfigMap{ Services: []service{{ Name: "styra", URL: opaconf.HostURL, @@ -341,7 +392,7 @@ func OpaConfToK8sOPAConfigMapNoSLP( } if opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers != nil { - opaConfigMap.DecisionLogs = decisionLogs{ + opaConfigMap.DecisionLogs = DecisionLogs{ RequestContext: requestContext{ HTTP: http{ Headers: opaDefaultConfig.DecisionLogs.RequestContext.HTTP.Headers, @@ -389,6 +440,9 @@ func opaConfigMapToMap(cm interface{}) (map[string]interface{}, error) { // mergeMaps recursively merges two map[string]interface{} variables func mergeMaps(map1, map2 map[string]interface{}) map[string]interface{} { + // TODO: some times, yaml structs have a name as a key and the value under it + // but other times, it is a list, where 'name' is one of the fields. + // This function does not handle that case yet. mergedMap := make(map[string]interface{}) // Copy all key-value pairs from map1 to mergedMap diff --git a/internal/k8sconv/k8sconv_test.go b/internal/k8sconv/k8sconv_test.go index 51ea3d48..02d8d22a 100644 --- a/internal/k8sconv/k8sconv_test.go +++ b/internal/k8sconv/k8sconv_test.go @@ -19,6 +19,7 @@ package k8sconv_test import ( "strings" + "github.com/go-logr/logr" ginkgo "github.com/onsi/ginkgo/v2" gomega "github.com/onsi/gomega" @@ -29,7 +30,7 @@ import ( "gopkg.in/yaml.v2" ) -var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMap", func() { +var _ = ginkgo.Describe("OPAConfToK8sOPAConfigMap", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -38,8 +39,8 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMap", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaConfToK8sOPAConfigMap", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMap(test.opaconf, test.slpURL, test.opaDefaultConfig, nil) + ginkgo.DescribeTable("OPAConfToK8sOPAConfigMap", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMap(test.opaconf, test.slpURL, test.opaDefaultConfig, nil) gomega.Expect(err).To(gomega.BeNil()) @@ -94,15 +95,15 @@ decision_logs: ) }) -var _ = ginkgo.Describe("OpaConfToK8sSLPConfigMap", func() { +var _ = ginkgo.Describe("OPAConfToK8sSLPConfigMap", func() { type test struct { opaconf styra.OPAConfig expectedCMContent string } - ginkgo.DescribeTable("OpaConfToK8sOPAConfigMap", func(test test) { - cm, err := k8sconv.OpaConfToK8sSLPConfigMap(test.opaconf) + ginkgo.DescribeTable("OPAConfToK8sOPAConfigMap", func(test test) { + cm, err := k8sconv.OPAConfToK8sSLPConfigMap(test.opaconf) gomega.Expect(err).To(gomega.BeNil()) @@ -144,7 +145,7 @@ discovery: ) }) -var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapNoSLP", func() { +var _ = ginkgo.Describe("OPAConfToK8sOPAConfigMapNoSLP", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -152,8 +153,8 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapNoSLP", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaConfToK8sOPAConfigMapNoSLP", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMapNoSLP(test.opaconf, test.opaDefaultConfig, nil) + ginkgo.DescribeTable("OPAConfToK8sOPAConfigMapNoSLP", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMapNoSLP(test.opaconf, test.opaDefaultConfig, nil) gomega.Expect(err).To(gomega.BeNil()) @@ -217,7 +218,7 @@ decision_logs: ) }) -var _ = ginkgo.Describe("OpaCustomConfToK8sWithSLP", func() { +var _ = ginkgo.Describe("OPACustomConfToK8sWithSLP", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -227,8 +228,8 @@ var _ = ginkgo.Describe("OpaCustomConfToK8sWithSLP", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaCustomConfToK8sWithSLP", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMap(test.opaconf, test.slpURL, test.opaDefaultConfig, test.customConfig) + ginkgo.DescribeTable("OPACustomConfToK8sWithSLP", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMap(test.opaconf, test.slpURL, test.opaDefaultConfig, test.customConfig) gomega.Expect(err).To(gomega.BeNil()) @@ -292,7 +293,7 @@ distributed_tracing: ) }) -var _ = ginkgo.Describe("OpaCustomConfToK8sNoSLP", func() { +var _ = ginkgo.Describe("OPACustomConfToK8sNoSLP", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -301,8 +302,8 @@ var _ = ginkgo.Describe("OpaCustomConfToK8sNoSLP", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaCustomConfToK8sNoSLP", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMapNoSLP(test.opaconf, test.opaDefaultConfig, test.customConfig) + ginkgo.DescribeTable("OPACustomConfToK8sNoSLP", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMapNoSLP(test.opaconf, test.opaDefaultConfig, test.customConfig) gomega.Expect(err).To(gomega.BeNil()) @@ -375,7 +376,7 @@ distributed_tracing: }) // Test PersistBundle config -var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { +var _ = ginkgo.Describe("OPAConfToK8sOPAConfigMapforOCP", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -384,8 +385,12 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaConfToK8sOPAConfigMapforOCP", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMapforOCP(test.opaconf, test.opaDefaultConfig, test.customConfig) + ginkgo.DescribeTable("OPAConfToK8sOPAConfigMapforOCP", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMapforOCP( + test.opaconf, + test.opaDefaultConfig, + test.customConfig, + logr.Discard()) gomega.Expect(err).To(gomega.BeNil()) @@ -401,7 +406,6 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { gomega.Expect(actualMap).To(gomega.Equal(expectedMap)) }, - ginkgo.Entry("success", test{ opaDefaultConfig: configv2alpha2.OPAConfig{ DecisionLogs: configv2alpha2.DecisionLog{ @@ -416,9 +420,29 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { }, opaconf: ocp.OPAConfig{ BundleResource: "bundles/system/bundle.tar.gz", - ServiceURL: "https://minio/ocp", - ServiceName: "s3", - BundleService: "s3", + BundleService: &ocp.OPAServiceConfig{ + Name: "s3", + URL: "https://minio/ocp", + Credentials: &ocp.ServiceCredentials{ + S3: &ocp.S3Signing{ + S3EnvironmentCredentials: map[string]ocp.EmptyStruct{}, + }, + }, + }, + LogService: &ocp.OPAServiceConfig{ + Name: "logs", + URL: "https://log-service/ocp", + Credentials: &ocp.ServiceCredentials{ + Bearer: &ocp.Bearer{ + TokenPath: "/etc/opa/auth/token", + }, + }, + }, + DecisionLogReporting: configv2alpha2.DecisionLogReporting{ + UploadSizeLimitBytes: 1, + MinDelaySeconds: 2, + MaxDelaySeconds: 3, + }, }, customConfig: map[string]interface{}{ "distributed_tracing": map[string]interface{}{ @@ -437,6 +461,11 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { credentials: s3_signing: environment_credentials: {} +- name: logs + url: https://log-service/ocp + credentials: + bearer: + token_path: /etc/opa/auth/token bundles: authz: resource: bundles/system/bundle.tar.gz @@ -445,11 +474,17 @@ bundles: test: 123 persistence_directory: /opa-bundles decision_logs: + reporting: + upload_size_limit_bytes: 1 + min_delay_seconds: 2 + max_delay_seconds: 3 request_context: http: headers: - header1 - header2 + service: logs + resource_path: /logs distributed_tracing: type: grpc address: localhost:1234 @@ -460,7 +495,7 @@ distributed_tracing: // Test without PersistBundle config // And custom config overriding opaconf -var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { +var _ = ginkgo.Describe("OPAConfToK8sOPAConfigMapforOCP", func() { type test struct { opaDefaultConfig configv2alpha2.OPAConfig @@ -469,8 +504,12 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { expectedCMContent string } - ginkgo.DescribeTable("OpaConfToK8sOPAConfigMapforOCP", func(test test) { - cm, err := k8sconv.OpaConfToK8sOPAConfigMapforOCP(test.opaconf, test.opaDefaultConfig, test.customConfig) + ginkgo.DescribeTable("OPAConfToK8sOPAConfigMapforOCP", func(test test) { + cm, err := k8sconv.OPAConfToK8sOPAConfigMapforOCP( + test.opaconf, + test.opaDefaultConfig, + test.customConfig, + logr.Discard()) gomega.Expect(err).To(gomega.BeNil()) @@ -499,10 +538,30 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { PersistBundle: false, }, opaconf: ocp.OPAConfig{ + LogService: &ocp.OPAServiceConfig{ + Name: "logs", + URL: "https://log-service/ocp", + Credentials: &ocp.ServiceCredentials{ + Bearer: &ocp.Bearer{ + TokenPath: "/etc/opa/auth/token", + }, + }, + }, BundleResource: "bundles/system/bundle.tar.gz", - ServiceURL: "https://minio/ocp", - ServiceName: "s3", - BundleService: "s3", + BundleService: &ocp.OPAServiceConfig{ + Name: "s3", + URL: "https://minio/ocp", + Credentials: &ocp.ServiceCredentials{ + S3: &ocp.S3Signing{ + S3EnvironmentCredentials: map[string]ocp.EmptyStruct{}, + }, + }, + }, + DecisionLogReporting: configv2alpha2.DecisionLogReporting{ + UploadSizeLimitBytes: 1048576, + MinDelaySeconds: 1, + MaxDelaySeconds: 30, + }, }, customConfig: map[string]interface{}{ "distributed_tracing": map[string]interface{}{ @@ -521,11 +580,22 @@ var _ = ginkgo.Describe("OpaConfToK8sOPAConfigMapforOCP", func() { credentials: s3_signing: environment_credentials: {} +- name: logs + url: https://log-service/ocp + credentials: + bearer: + token_path: /etc/opa/auth/token bundles: authz: resource: bundles/system/bundle.tar.gz service: s4 decision_logs: + reporting: + upload_size_limit_bytes: 1048576 + min_delay_seconds: 1 + max_delay_seconds: 30 + service: logs + resource_path: /logs request_context: http: headers: diff --git a/pkg/ocp/opaconfig.go b/pkg/ocp/opaconfig.go index ecacedcc..2b228691 100644 --- a/pkg/ocp/opaconfig.go +++ b/pkg/ocp/opaconfig.go @@ -5,7 +5,7 @@ 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 + 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, @@ -16,12 +16,43 @@ limitations under the License. package ocp +import ( + configv2alpha2 "github.com/bankdata/styra-controller/api/config/v2alpha2" +) + // OPAConfig stores the information going into the ConfigMap for the OPA type OPAConfig struct { - BundleResource string - BundleService string - ServiceURL string - ServiceName string - UniqueName string - Namespace string + BundleService *OPAServiceConfig + LogService *OPAServiceConfig + UniqueName string + Namespace string + BundleResource string + DecisionLogReporting configv2alpha2.DecisionLogReporting +} + +// OPAServiceConfig defines a services added to the OPAs' config files. +type OPAServiceConfig struct { + Name string `json:"name" yaml:"name"` + Credentials *ServiceCredentials `json:"credentials" yaml:"credentials"` + ResponseHeaderTimeoutSeconds int `json:"response_header_timeout_seconds,omitempty" yaml:"response_header_timeout_seconds,omitempty"` //nolint:lll + URL string `json:"url" yaml:"url"` +} + +// ServiceCredentials defines the structure for service credentials. +type ServiceCredentials struct { + Bearer *Bearer `json:"bearer,omitempty" yaml:"bearer,omitempty"` + S3 *S3Signing `json:"s3_signing,omitempty" yaml:"s3_signing,omitempty"` +} + +// S3Signing defines the structure for S3 signing configuration. +type S3Signing struct { + S3EnvironmentCredentials map[string]EmptyStruct `json:"environment_credentials" yaml:"environment_credentials"` +} + +// EmptyStruct is an empty struct used for mapping empty values in S3EnvironmentCredentials +type EmptyStruct struct{} + +// Bearer defines the structure for bearer token credentials. +type Bearer struct { + TokenPath string `json:"token_path" yaml:"token_path"` } diff --git a/test/integration/controller/controller_suite_test.go b/test/integration/controller/controller_suite_test.go index 9cec8e99..65c37c6d 100644 --- a/test/integration/controller/controller_suite_test.go +++ b/test/integration/controller/controller_suite_test.go @@ -18,7 +18,6 @@ package styra import ( "context" - "fmt" "path/filepath" "testing" "time" @@ -28,9 +27,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/mock" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -159,6 +155,14 @@ var _ = ginkgo.BeforeSuite(func() { OCPConfigSecretName: "s3-credentials", }, }, + DecisionAPIConfig: &configv2alpha2.DecisionAPIConfig{ + ServiceURL: "log-api-url", + Reporting: configv2alpha2.DecisionLogReporting{ + MaxDelaySeconds: 60, + MinDelaySeconds: 5, + UploadSizeLimitBytes: 1024, + }, + }, }, UserCredentialHandler: &configv2alpha2.UserCredentialHandler{ S3: &configv2alpha2.S3Handler{ @@ -169,6 +173,12 @@ var _ = ginkgo.BeforeSuite(func() { SecretAccessKey: "secret-access-key", }, }, + OPA: configv2alpha2.OPAConfig{ + BundleServer: &configv2alpha2.OPABundleServer{ + URL: "s3-url2", + Path: "/test-bucket", + }, + }, }, Metrics: &styractrls.SystemReconcilerMetrics{ @@ -295,61 +305,7 @@ var _ = ginkgo.BeforeSuite(func() { managerCtxPodRestart, managerCancelPodRestart = context.WithCancel(context.Background()) go func() { - // Test setup function to systemReconcilerPodRestart that deploys a system with an ID and a Statefulset for a SLP - // and restarts the SLP pods. defer ginkgo.GinkgoRecover() - - systemName := "test-pod-restart" - systemNamespace := "default" - - systemToCreate := &styrav1beta1.System{ - ObjectMeta: metav1.ObjectMeta{ - Name: systemName, - Namespace: systemNamespace, - Labels: map[string]string{ - "styra-controller/class": "styra-controller-pod-restart", - }, - }, - Spec: styrav1beta1.SystemSpec{ - LocalPlane: &styrav1beta1.LocalPlane{ - Name: fmt.Sprintf("%v-slp", systemName), - }, - }, - } - - gomega.Expect(k8sClient.Create(managerCtxPodRestart, systemToCreate)).To(gomega.Succeed()) - patch := client.MergeFrom(systemToCreate.DeepCopy()) - systemToCreate.Status.ID = "system_id" - systemToCreate.Status.Ready = true - gomega.Expect(k8sClient.Status().Patch(managerCtxPodRestart, systemToCreate, patch)).To(gomega.Succeed()) - - sts := &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%v-slp", systemName), - Namespace: systemNamespace, - }, - Spec: appsv1.StatefulSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": fmt.Sprintf("%v-slp", systemName)}, - }, - ServiceName: fmt.Sprintf("%v-slp", systemName), - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"app": fmt.Sprintf("%v-slp", systemName)}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "busybox", - Image: "busybox", - Command: []string{"sleep", "3600"}, - }}, - }, - }, - }, - } - - gomega.Expect(k8sClient.Create(managerCtxPodRestart, sts)).To(gomega.Succeed()) - err = k8sManagerPodRestart.Start(managerCtxPodRestart) gomega.Expect(err).NotTo(gomega.HaveOccurred()) }() diff --git a/test/integration/controller/system_controller_test.go b/test/integration/controller/system_controller_test.go index 2a8a8ed2..6876e2e4 100644 --- a/test/integration/controller/system_controller_test.go +++ b/test/integration/controller/system_controller_test.go @@ -2238,14 +2238,55 @@ distributed_tracing: }) }) -var _ = ginkgo.Describe("SystemReconciler.Reconcile1", ginkgo.Label("integration"), func() { +var _ = ginkgo.Describe("SystemReconciler.WithPodRestart", ginkgo.Label("integration"), func() { ginkgo.It("should reconcile", func() { + + ctx := context.Background() + key := types.NamespacedName{ Name: "test-pod-restart", Namespace: "default", } - ctx := context.Background() + systemToCreate := &styrav1beta1.System{ + ObjectMeta: metav1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + Labels: map[string]string{ + "styra-controller/class": "styra-controller-pod-restart", + }, + }, + Spec: styrav1beta1.SystemSpec{ + LocalPlane: &styrav1beta1.LocalPlane{ + Name: fmt.Sprintf("%v-slp", key.Name), + }, + }, + } + + sts := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v-slp", key.Name), + Namespace: key.Namespace, + }, + Spec: appsv1.StatefulSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": fmt.Sprintf("%v-slp", key.Name)}, + }, + ServiceName: fmt.Sprintf("%v-slp", key.Name), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": fmt.Sprintf("%v-slp", key.Name)}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "busybox", + Image: "busybox", + Command: []string{"sleep", "3600"}, + }}, + }, + }, + }, + } cfg := &styra.SystemConfig{ ID: "system_id", @@ -2253,30 +2294,14 @@ var _ = ginkgo.Describe("SystemReconciler.Reconcile1", ginkgo.Label("integration ReadOnly: true, } - ginkgo.By("Empty System already has ID but does not exist in Styra") + ginkgo.By("Create system and see SLP pods are restarted") - styraClientMock.On("GetSystem", mock.Anything, "system_id").Return(&styra.GetSystemResponse{ - StatusCode: http.StatusNotFound, + styraClientMock.On("GetSystemByName", mock.Anything, key.String()).Return(&styra.GetSystemResponse{ + StatusCode: http.StatusOK, SystemConfig: nil, - }, &httperror.HTTPError{ - StatusCode: http.StatusNotFound, - Body: "nil", - }).Once() + }, nil).Once() - styraClientMock.On("PutSystem", - mock.Anything, - mock.MatchedBy(func(req *styra.PutSystemRequest) bool { - matchesDecisionmapping := len(req.SystemConfig.DecisionMappings) == 0 - matchesDescription := req.SystemConfig.Description == cfg.Description - matchesSourceControl := req.SystemConfig.SourceControl == nil - - return matchesDecisionmapping && - matchesDescription && - matchesSourceControl - }), - "system_id", - map[string]string{"If-None-Match": "*"}, - ).Return(&styra.PutSystemResponse{ + styraClientMock.On("CreateSystem", mock.Anything, mock.Anything).Return(&styra.CreateSystemResponse{ StatusCode: http.StatusOK, SystemConfig: cfg, }, nil).Once() @@ -2424,11 +2449,16 @@ var _ = ginkgo.Describe("SystemReconciler.Reconcile1", ginkgo.Label("integration SystemType: "custom", }, nil).Once() + // Create StatefulSet representing SLP and deploy system + gomega.Expect(k8sClient.Create(ctx, sts)).To(gomega.Succeed()) + gomega.Expect(k8sClient.Create(ctx, systemToCreate)).To(gomega.Succeed()) + gomega.Eventually(func() bool { fetched := &styrav1beta1.System{} if err := k8sClient.Get(ctx, key, fetched); err != nil { return false } + return finalizer.IsSet(fetched) && fetched.Status.ID == "system_id" && fetched.Status.Phase == styrav1beta1.SystemPhaseCreated && @@ -2454,36 +2484,44 @@ var _ = ginkgo.Describe("SystemReconciler.Reconcile1", ginkgo.Label("integration gomega.Eventually(func() bool { var ( - getSystem int - putSystem int + getSystemByName int + createSystem int deletePolicy int + updateSystem int + getSystem int getUsers int rolebindingsListed int getOPAConfig int ) for _, call := range styraClientMock.Calls { switch call.Method { - case "GetSystem": - getSystem++ - case "PutSystem": - putSystem++ + case "GetSystemByName": + getSystemByName++ + case "CreateSystem": + createSystem++ case "DeletePolicy": deletePolicy++ - case "GetUsers": - getUsers++ + case "UpdateSystem": + updateSystem++ case "ListRoleBindingsV2": rolebindingsListed++ + case "GetSystem": + getSystem++ + case "GetUsers": + getUsers++ case "GetOPAConfig": getOPAConfig++ } } - return getSystem == 4 && - putSystem == 1 && + return getSystemByName == 1 && + createSystem == 1 && deletePolicy == 2 && + updateSystem == 1 && getUsers == 4 && rolebindingsListed == 4 && - getOPAConfig == 4 + getOPAConfig == 4 && + getSystem == 3 }, timeout, interval).Should(gomega.BeTrue()) resetMock(&styraClientMock.Mock) @@ -2761,6 +2799,7 @@ var _ = ginkgo.Describe("SystemReconciler.ReconcileOCPSystem", ginkgo.Label("int key := types.NamespacedName{Name: fmt.Sprintf("%s-opa-config", key.Name), Namespace: key.Namespace} if fetchSuceeded := k8sClient.Get(ctx, key, fetched) == nil; !fetchSuceeded { + fmt.Println("Failed to fetch ConfigMap") return false } @@ -2769,6 +2808,13 @@ var _ = ginkgo.Describe("SystemReconciler.ReconcileOCPSystem", ginkgo.Label("int authz: resource: bundles/default-ocp-system/bundle.tar.gz service: s3 +decision_logs: + reporting: + max_delay_seconds: 60 + min_delay_seconds: 5 + upload_size_limit_bytes: 1024 + resource_path: /logs + service: logs labels: namespace: default unique-name: default-ocp-system @@ -2777,20 +2823,30 @@ services: s3_signing: environment_credentials: {} name: s3 - url: s3-url/test-bucket + url: s3-url2/test-bucket +- credentials: + bearer: + token_path: /run/secrets/kubernetes.io/serviceaccount/token + name: logs + url: log-api-url ` if err := yaml.Unmarshal([]byte(actualYAML), &actualMap); err != nil { + fmt.Println("Failed to unmarshal actual YAML:", err) return false } if err := yaml.Unmarshal([]byte(expectedYAML), &expectedMap); err != nil { + fmt.Println("Failed to unmarshal expected YAML:", err) return false } equal := reflect.DeepEqual(expectedMap, actualMap) if !equal { - fmt.Println("Actual", string(actualYAML)) - fmt.Println("Expected", string(expectedYAML)) + fmt.Println("reconciliation failed") + fmt.Println("Actual: \n", string(actualYAML)) + fmt.Println("Expected: \n", string(expectedYAML)) + fmt.Println("Actual Map: \n", actualMap) + fmt.Println("Expected Map: \n", expectedMap) } return equal }, timeout, interval).Should(gomega.BeTrue()) @@ -2885,6 +2941,6 @@ services: return k8serrors.IsNotFound(err) }, timeout, interval).Should(gomega.BeTrue()) - resetMock(&styraClientMock.Mock) + resetMock(&ocpClientMock.Mock) }) })