diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4ee75745..31aa497bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -695,7 +695,6 @@ jobs: DOCKER_CONFIG: /tmp run: | export OIDC_ISSUER_URL="http://${{ steps.kind.outputs.oidc_host }}/realms/trusted-artifact-signer" - export REKOR_UI_URL=$(kubectl get rekor -o jsonpath='{.items[0].status.rekorSearchUIUrl}' -n ${{ env.TEST_NAMESPACE }}) export TUF_URL=$(kubectl get tuf -o jsonpath='{.items[0].status.url}' -n ${{ env.TEST_NAMESPACE }}) export REKOR_URL=$(kubectl get rekor -o jsonpath='{.items[0].status.url}' -n ${{ env.TEST_NAMESPACE }}) export FULCIO_URL=$(kubectl get fulcio -o jsonpath='{.items[0].status.url}' -n ${{ env.TEST_NAMESPACE }}) @@ -705,9 +704,7 @@ jobs: cd e2e source ./tas-env-variables.sh - # Install Playwright with dependencies for UI tests - go run github.com/mxschmitt/playwright-go/cmd/playwright install --with-deps - go test -v ./test/... + go test -v $(go list ./test/... | grep -v rekorsearchui) - name: dump the logs of the operator run: | diff --git a/api/v1/rekor_defaults.go b/api/v1/rekor_defaults.go index 851bcda60..7cd087284 100644 --- a/api/v1/rekor_defaults.go +++ b/api/v1/rekor_defaults.go @@ -9,7 +9,6 @@ func (s *RekorSpec) SetDefaults() { s.PodRequirements.SetDefaults() s.Monitoring.SetDefaults() s.Ingress.SetDefaults() - s.RekorSearchUI.SetDefaults() s.Signer.SetDefaults() s.Attestations.SetDefaults() s.SearchIndex.SetDefaults() @@ -17,11 +16,6 @@ func (s *RekorSpec) SetDefaults() { setDefault(&s.MaxRequestBodySize, ptr.To(int64(10485760))) } -func (s *RekorSearchUI) SetDefaults() { - s.PodRequirements.SetDefaults() - setDefault(&s.Enabled, ptr.To(true)) -} - func (s *RekorAttestations) SetDefaults() { setDefault(&s.Enabled, ptr.To(true)) setDefault(&s.Url, "file:///var/run/attestations?no_tmp_dir=true") diff --git a/api/v1/rekor_types.go b/api/v1/rekor_types.go index 36d61be5d..50d99ea3d 100644 --- a/api/v1/rekor_types.go +++ b/api/v1/rekor_types.go @@ -36,8 +36,6 @@ type RekorSpec struct { Ingress Ingress `json:"ingress,omitempty"` //Enable Service monitors for rekor Monitoring MonitoringWithTLogConfig `json:"monitoring,omitempty"` - // Rekor Search UI - RekorSearchUI RekorSearchUI `json:"rekorSearchUI,omitempty"` // Signer configuration Signer RekorSigner `json:"signer,omitempty"` // Attestations configuration @@ -128,17 +126,6 @@ type RekorSigner struct { KeyRef *SecretKeySelector `json:"keyRef,omitempty"` } -type RekorSearchUI struct { - PodRequirements `json:",inline"` - // If set to true, the Operator will deploy a Rekor Search UI - //+kubebuilder:validation:XValidation:rule=(self || !oldSelf),message=Feature cannot be disabled - Enabled *bool `json:"enabled,omitempty"` - // Set hostname for your Ingress. - Host string `json:"host,omitempty"` - // Set labels applied to the created Ingress, e.g. for ingress-controller/route selection when sharding ingress traffic. - Labels map[string]string `json:"labels,omitempty"` -} - // SearchIndex define search index connection // +kubebuilder:validation:XValidation:rule=(!has(self.create) || !(self.create == true) || !has(self.provider) || self.provider == ""),message=Provider can be specified only with external db (create=false) // +kubebuilder:validation:XValidation:rule=(!has(self.create) || !(self.create == false) || self.provider != ""),message=Provider must be defined with external db (create=false) @@ -202,14 +189,13 @@ type RekorSignerStatus struct { type RekorStatus struct { // Reference to secret with Rekor's signer public key. // Public key is automatically generated from signer private key. - PublicKeyRef *SecretKeySelector `json:"publicKeyRef,omitempty"` - ServerConfigRef *LocalObjectReference `json:"serverConfigRef,omitempty"` - Signer RekorSignerStatus `json:"signer,omitempty"` - SearchIndex SearchIndexStatus `json:"searchIndex,omitempty"` - PvcName string `json:"pvcName,omitempty"` - MonitorPvcName string `json:"monitorPvcName,omitempty"` - Url string `json:"url,omitempty"` - RekorSearchUIUrl string `json:"rekorSearchUIUrl,omitempty"` + PublicKeyRef *SecretKeySelector `json:"publicKeyRef,omitempty"` + ServerConfigRef *LocalObjectReference `json:"serverConfigRef,omitempty"` + Signer RekorSignerStatus `json:"signer,omitempty"` + SearchIndex SearchIndexStatus `json:"searchIndex,omitempty"` + PvcName string `json:"pvcName,omitempty"` + MonitorPvcName string `json:"monitorPvcName,omitempty"` + Url string `json:"url,omitempty"` // PEM-encoded public key resolved from the running Rekor service API. // +optional PublicKey string `json:"publicKey,omitempty"` diff --git a/api/v1/rekor_types_test.go b/api/v1/rekor_types_test.go index 93720bbf5..0e4b74d38 100644 --- a/api/v1/rekor_types_test.go +++ b/api/v1/rekor_types_test.go @@ -247,9 +247,6 @@ var _ = Describe("Rekor", func() { Enabled: ptr.To(true), Host: "hostname", }, - RekorSearchUI: RekorSearchUI{ - Enabled: ptr.To(true), - }, BackFillRedis: BackFillRedis{ Enabled: ptr.To(true), Schedule: "* */2 * * 0-3", diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index e65b160bb..111384653 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1273,34 +1273,6 @@ func (in *RekorLogRange) DeepCopy() *RekorLogRange { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RekorSearchUI) DeepCopyInto(out *RekorSearchUI) { - *out = *in - in.PodRequirements.DeepCopyInto(&out.PodRequirements) - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) - **out = **in - } - 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 - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RekorSearchUI. -func (in *RekorSearchUI) DeepCopy() *RekorSearchUI { - if in == nil { - return nil - } - out := new(RekorSearchUI) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RekorService) DeepCopyInto(out *RekorService) { *out = *in @@ -1384,7 +1356,6 @@ func (in *RekorSpec) DeepCopyInto(out *RekorSpec) { in.Trillian.DeepCopyInto(&out.Trillian) in.Ingress.DeepCopyInto(&out.Ingress) in.Monitoring.DeepCopyInto(&out.Monitoring) - in.RekorSearchUI.DeepCopyInto(&out.RekorSearchUI) in.Signer.DeepCopyInto(&out.Signer) in.Attestations.DeepCopyInto(&out.Attestations) in.SearchIndex.DeepCopyInto(&out.SearchIndex) diff --git a/api/v1alpha1/conversion_roundtrip_test.go b/api/v1alpha1/conversion_roundtrip_test.go index bbdf6c54b..097b4e363 100644 --- a/api/v1alpha1/conversion_roundtrip_test.go +++ b/api/v1alpha1/conversion_roundtrip_test.go @@ -23,11 +23,13 @@ import ( rhtasv1 "github.com/securesign/operator/api/v1" utilconversion "github.com/securesign/operator/internal/conversion" + "github.com/securesign/operator/internal/migration" urlfuzz "github.com/securesign/operator/internal/testing/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/conversion" "sigs.k8s.io/randfill" ) @@ -311,6 +313,8 @@ func rekorStatusFuzzerFuncs(_ runtimeserializer.CodecFactory) []interface{} { c.FillNoCustom(s) // no v1 equivalent in RekorSignerStatus s.Signer.KMS = "" + // no v1 equivalent — removed from v1 API + s.RekorSearchUIUrl = "" }, } } @@ -380,10 +384,14 @@ func securesignStatusFuzzerFuncs(_ runtimeserializer.CodecFactory) []interface{} // Tests func TestSecuresignConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.Securesign{}, Spoke: &Securesign{}, + HubAfterMutation: func(hub conversion.Hub) { + migration.StripAll(hub.(*rhtasv1.Securesign)) + }, FuzzerFuncs: []fuzzer.FuzzerFuncs{ securesignStatusFuzzerFuncs, tsaSignerFuzzerFuncs, @@ -401,6 +409,7 @@ func TestSecuresignConversion(t *testing.T) { } func TestCTlogConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.CTlog{}, @@ -415,10 +424,14 @@ func TestCTlogConversion(t *testing.T) { } func TestRekorConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.Rekor{}, Spoke: &Rekor{}, + HubAfterMutation: func(hub conversion.Hub) { + migration.StripAll(hub.(*rhtasv1.Rekor)) + }, FuzzerFuncs: []fuzzer.FuzzerFuncs{ rekorStatusFuzzerFuncs, trillianServiceFuzzerFuncs, @@ -429,6 +442,7 @@ func TestRekorConversion(t *testing.T) { } func TestFulcioConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.Fulcio{}, @@ -441,6 +455,7 @@ func TestFulcioConversion(t *testing.T) { } func TestTrillianConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.Trillian{}, @@ -453,6 +468,7 @@ func TestTrillianConversion(t *testing.T) { } func TestTufConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.Tuf{}, @@ -469,6 +485,7 @@ func TestTufConversion(t *testing.T) { } func TestTimestampAuthorityConversion(t *testing.T) { + t.Parallel() t.Run("roundtrip", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ Scheme: rhtasScheme(), Hub: &rhtasv1.TimestampAuthority{}, diff --git a/api/v1alpha1/conversion_unit_test.go b/api/v1alpha1/conversion_unit_test.go index ee861d0f7..d935e6549 100644 --- a/api/v1alpha1/conversion_unit_test.go +++ b/api/v1alpha1/conversion_unit_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/go-cmp/cmp" rhtasv1 "github.com/securesign/operator/api/v1" utilconversion "github.com/securesign/operator/internal/conversion" + "github.com/securesign/operator/internal/migration" core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" @@ -215,6 +216,7 @@ func TestSecuresignConversionUnit(t *testing.T) { t.Fatalf("ConvertTo failed: %v", err) } + migration.StripAll(gotHub) if !equality.Semantic.DeepEqual(expectedHub, gotHub) { t.Errorf("v1alpha1 -> v1 mismatch (-want +got):\n%s", cmp.Diff(expectedHub, gotHub)) } @@ -489,6 +491,7 @@ func TestRekorConversionUnit(t *testing.T) { if err := tt.spoke.ConvertTo(gotHub); err != nil { t.Fatalf("ConvertTo failed: %v", err) } + migration.StripAll(gotHub) if !equality.Semantic.DeepEqual(tt.hub, gotHub) { t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.hub, gotHub)) } diff --git a/api/v1alpha1/rekor_conversion.go b/api/v1alpha1/rekor_conversion.go index cfdaf3b86..f48ce4315 100644 --- a/api/v1alpha1/rekor_conversion.go +++ b/api/v1alpha1/rekor_conversion.go @@ -3,14 +3,21 @@ package v1alpha1 import ( rhtasv1 "github.com/securesign/operator/api/v1" utilconversion "github.com/securesign/operator/internal/conversion" + "github.com/securesign/operator/internal/migration" apiconversion "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" ) +var MigrationSearchUIData = migration.Key("v1alpha1", "rekorSearchUI") + func Convert_v1_RekorStatus_To_v1alpha1_RekorStatus(in *rhtasv1.RekorStatus, out *RekorStatus, s apiconversion.Scope) error { return autoConvert_v1_RekorStatus_To_v1alpha1_RekorStatus(in, out, s) } +func Convert_v1alpha1_RekorStatus_To_v1_RekorStatus(in *RekorStatus, out *rhtasv1.RekorStatus, s apiconversion.Scope) error { + return autoConvert_v1alpha1_RekorStatus_To_v1_RekorStatus(in, out, s) +} + func Convert_v1alpha1_RekorSpec_To_v1_RekorSpec(in *RekorSpec, out *rhtasv1.RekorSpec, s apiconversion.Scope) error { if err := autoConvert_v1alpha1_RekorSpec_To_v1_RekorSpec(in, out, s); err != nil { return err @@ -18,9 +25,6 @@ func Convert_v1alpha1_RekorSpec_To_v1_RekorSpec(in *RekorSpec, out *rhtasv1.Reko if err := Convert_v1alpha1_ExternalAccess_To_v1_Ingress(&in.ExternalAccess, &out.Ingress, s); err != nil { return err } - if err := Convert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(&in.RekorSearchUI, &out.RekorSearchUI, s); err != nil { - return err - } return Convert_v1alpha1_Pvc_To_v1_Pvc(&in.Pvc, &out.Attestations.Pvc, s) } @@ -31,32 +35,10 @@ func Convert_v1_RekorSpec_To_v1alpha1_RekorSpec(in *rhtasv1.RekorSpec, out *Reko if err := Convert_v1_Ingress_To_v1alpha1_ExternalAccess(&in.Ingress, &out.ExternalAccess, s); err != nil { return err } - if err := Convert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(&in.RekorSearchUI, &out.RekorSearchUI, s); err != nil { - return err - } return Convert_v1_Pvc_To_v1alpha1_Pvc(&in.Attestations.Pvc, &out.Pvc, s) } -// RekorSearchUI had its RouteSelectorLabels field renamed to Labels in v1. - -func Convert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(in *RekorSearchUI, out *rhtasv1.RekorSearchUI, s apiconversion.Scope) error { - if err := autoConvert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(in, out, s); err != nil { - return err - } - out.Labels = in.RouteSelectorLabels - return nil -} - -func Convert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(in *rhtasv1.RekorSearchUI, out *RekorSearchUI, s apiconversion.Scope) error { - if err := autoConvert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(in, out, s); err != nil { - return err - } - out.RouteSelectorLabels = in.Labels - return nil -} - func Convert_v1_RekorAttestations_To_v1alpha1_RekorAttestations(in *rhtasv1.RekorAttestations, out *RekorAttestations, s apiconversion.Scope) error { - // Pvc is handled at the RekorSpec level conversion, not here. return autoConvert_v1_RekorAttestations_To_v1alpha1_RekorAttestations(in, out, s) } @@ -65,6 +47,11 @@ func (src *Rekor) ConvertTo(dstRaw conversion.Hub) error { if err := Convert_v1alpha1_Rekor_To_v1_Rekor(src, dst, nil); err != nil { return err } + + if err := migration.Set(dst, MigrationSearchUIData, src.Spec.RekorSearchUI); err != nil { + return err + } + restored := &rhtasv1.Rekor{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { return err @@ -75,6 +62,7 @@ func (src *Rekor) ConvertTo(dstRaw conversion.Hub) error { if dst.Spec.Trillian.URL == "" { dst.Spec.Trillian.Ref = restored.Spec.Trillian.Ref } + return nil } @@ -83,6 +71,14 @@ func (dst *Rekor) ConvertFrom(srcRaw conversion.Hub) error { if err := Convert_v1_Rekor_To_v1alpha1_Rekor(src, dst, nil); err != nil { return err } + + var searchUI RekorSearchUI + if ok, err := migration.Pop(src, MigrationSearchUIData, &searchUI); err != nil { + return err + } else if ok { + dst.Spec.RekorSearchUI = searchUI + } + return utilconversion.MarshalData(src, dst) } diff --git a/api/v1alpha1/securesign_conversion.go b/api/v1alpha1/securesign_conversion.go index bb7495e50..59dda1f8b 100644 --- a/api/v1alpha1/securesign_conversion.go +++ b/api/v1alpha1/securesign_conversion.go @@ -3,6 +3,7 @@ package v1alpha1 import ( rhtasv1 "github.com/securesign/operator/api/v1" utilconversion "github.com/securesign/operator/internal/conversion" + "github.com/securesign/operator/internal/migration" "k8s.io/apimachinery/pkg/api/equality" apiconversion "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" @@ -39,6 +40,11 @@ func (src *Securesign) ConvertTo(dstRaw conversion.Hub) error { if err := Convert_v1alpha1_Securesign_To_v1_Securesign(src, dst, nil); err != nil { return err } + + if err := migration.Set(dst, MigrationSearchUIData, src.Spec.Rekor.RekorSearchUI); err != nil { + return err + } + restored := &rhtasv1.Securesign{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { return err @@ -104,5 +110,13 @@ func (dst *Securesign) ConvertFrom(srcRaw conversion.Hub) error { if err := Convert_v1_Securesign_To_v1alpha1_Securesign(src, dst, nil); err != nil { return err } + + var searchUI RekorSearchUI + if ok, err := migration.Pop(src, MigrationSearchUIData, &searchUI); err != nil { + return err + } else if ok { + dst.Spec.Rekor.RekorSearchUI = searchUI + } + return utilconversion.MarshalData(src, dst) } diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index e813d2e9f..4334e79dc 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -308,11 +308,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*RekorStatus)(nil), (*v1.RekorStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_RekorStatus_To_v1_RekorStatus(a.(*RekorStatus), b.(*v1.RekorStatus), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*SearchIndex)(nil), (*v1.SearchIndex)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_SearchIndex_To_v1_SearchIndex(a.(*SearchIndex), b.(*v1.SearchIndex), scope) }); err != nil { @@ -633,11 +628,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddConversionFunc((*v1.RekorSearchUI)(nil), (*RekorSearchUI)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(a.(*v1.RekorSearchUI), b.(*RekorSearchUI), scope) - }); err != nil { - return err - } if err := s.AddConversionFunc((*v1.RekorSignerStatus)(nil), (*RekorSigner)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_RekorSignerStatus_To_v1alpha1_RekorSigner(a.(*v1.RekorSignerStatus), b.(*RekorSigner), scope) }); err != nil { @@ -783,11 +773,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddConversionFunc((*RekorSearchUI)(nil), (*v1.RekorSearchUI)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(a.(*RekorSearchUI), b.(*v1.RekorSearchUI), scope) - }); err != nil { - return err - } if err := s.AddConversionFunc((*RekorService)(nil), (*v1.ServiceReference)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_RekorService_To_v1_ServiceReference(a.(*RekorService), b.(*v1.ServiceReference), scope) }); err != nil { @@ -803,6 +788,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*RekorStatus)(nil), (*v1.RekorStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RekorStatus_To_v1_RekorStatus(a.(*RekorStatus), b.(*v1.RekorStatus), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*SecuresignTSAStatus)(nil), (*v1.SecuresignTSAStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_SecuresignTSAStatus_To_v1_SecuresignTSAStatus(a.(*SecuresignTSAStatus), b.(*v1.SecuresignTSAStatus), scope) }); err != nil { @@ -1836,26 +1826,6 @@ func Convert_v1_RekorLogRange_To_v1alpha1_RekorLogRange(in *v1.RekorLogRange, ou return autoConvert_v1_RekorLogRange_To_v1alpha1_RekorLogRange(in, out, s) } -func autoConvert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(in *RekorSearchUI, out *v1.RekorSearchUI, s conversion.Scope) error { - if err := Convert_v1alpha1_PodRequirements_To_v1_PodRequirements(&in.PodRequirements, &out.PodRequirements, s); err != nil { - return err - } - out.Enabled = (*bool)(unsafe.Pointer(in.Enabled)) - out.Host = in.Host - // WARNING: in.RouteSelectorLabels requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(in *v1.RekorSearchUI, out *RekorSearchUI, s conversion.Scope) error { - if err := Convert_v1_PodRequirements_To_v1alpha1_PodRequirements(&in.PodRequirements, &out.PodRequirements, s); err != nil { - return err - } - out.Enabled = (*bool)(unsafe.Pointer(in.Enabled)) - out.Host = in.Host - // WARNING: in.Labels requires manual conversion: does not exist in peer-type - return nil -} - func autoConvert_v1alpha1_RekorService_To_v1_RekorService(in *RekorService, out *v1.RekorService, s conversion.Scope) error { out.Address = in.Address out.Port = (*int32)(unsafe.Pointer(in.Port)) @@ -1914,9 +1884,7 @@ func autoConvert_v1alpha1_RekorSpec_To_v1_RekorSpec(in *RekorSpec, out *v1.Rekor if err := Convert_v1alpha1_MonitoringWithTLogConfig_To_v1_MonitoringWithTLogConfig(&in.Monitoring, &out.Monitoring, s); err != nil { return err } - if err := Convert_v1alpha1_RekorSearchUI_To_v1_RekorSearchUI(&in.RekorSearchUI, &out.RekorSearchUI, s); err != nil { - return err - } + // WARNING: in.RekorSearchUI requires manual conversion: does not exist in peer-type if err := Convert_v1alpha1_RekorSigner_To_v1_RekorSigner(&in.Signer, &out.Signer, s); err != nil { return err } @@ -1950,9 +1918,6 @@ func autoConvert_v1_RekorSpec_To_v1alpha1_RekorSpec(in *v1.RekorSpec, out *Rekor if err := Convert_v1_MonitoringWithTLogConfig_To_v1alpha1_MonitoringWithTLogConfig(&in.Monitoring, &out.Monitoring, s); err != nil { return err } - if err := Convert_v1_RekorSearchUI_To_v1alpha1_RekorSearchUI(&in.RekorSearchUI, &out.RekorSearchUI, s); err != nil { - return err - } if err := Convert_v1_RekorSigner_To_v1alpha1_RekorSigner(&in.Signer, &out.Signer, s); err != nil { return err } @@ -1984,17 +1949,12 @@ func autoConvert_v1alpha1_RekorStatus_To_v1_RekorStatus(in *RekorStatus, out *v1 out.PvcName = in.PvcName out.MonitorPvcName = in.MonitorPvcName out.Url = in.Url - out.RekorSearchUIUrl = in.RekorSearchUIUrl + // WARNING: in.RekorSearchUIUrl requires manual conversion: does not exist in peer-type out.TreeID = (*int64)(unsafe.Pointer(in.TreeID)) out.Conditions = *(*[]metav1.Condition)(unsafe.Pointer(&in.Conditions)) return nil } -// Convert_v1alpha1_RekorStatus_To_v1_RekorStatus is an autogenerated conversion function. -func Convert_v1alpha1_RekorStatus_To_v1_RekorStatus(in *RekorStatus, out *v1.RekorStatus, s conversion.Scope) error { - return autoConvert_v1alpha1_RekorStatus_To_v1_RekorStatus(in, out, s) -} - func autoConvert_v1_RekorStatus_To_v1alpha1_RekorStatus(in *v1.RekorStatus, out *RekorStatus, s conversion.Scope) error { out.PublicKeyRef = (*SecretKeySelector)(unsafe.Pointer(in.PublicKeyRef)) out.ServerConfigRef = (*LocalObjectReference)(unsafe.Pointer(in.ServerConfigRef)) @@ -2007,7 +1967,6 @@ func autoConvert_v1_RekorStatus_To_v1alpha1_RekorStatus(in *v1.RekorStatus, out out.PvcName = in.PvcName out.MonitorPvcName = in.MonitorPvcName out.Url = in.Url - out.RekorSearchUIUrl = in.RekorSearchUIUrl // WARNING: in.PublicKey requires manual conversion: does not exist in peer-type out.TreeID = (*int64)(unsafe.Pointer(in.TreeID)) out.Conditions = *(*[]metav1.Condition)(unsafe.Pointer(&in.Conditions)) diff --git a/cmd/main.go b/cmd/main.go index 151d8d544..3da192c21 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -143,7 +143,6 @@ func main() { utils.RelatedImageFlag("fulcio-server-image", images.FulcioServer, "The image used for the fulcio server.") utils.RelatedImageFlag("rekor-redis-image", images.RekorRedis, "The image used for redis.") utils.RelatedImageFlag("rekor-server-image", images.RekorServer, "The image used for rekor server.") - utils.RelatedImageFlag("rekor-search-ui-image", images.RekorSearchUi, "The image used for rekor search ui.") utils.RelatedImageFlag("backfill-redis-image", images.BackfillRedis, "The image used for backfill redis.") utils.RelatedImageFlag("tuf-image", images.Tuf, "The image used for TUF.") utils.RelatedImageFlag("ctlog-image", images.CTLog, "The image used for ctlog.") diff --git a/config/crd/bases/rhtas.redhat.com_rekors.yaml b/config/crd/bases/rhtas.redhat.com_rekors.yaml index 4a4bb778e..eef9643aa 100644 --- a/config/crd/bases/rhtas.redhat.com_rekors.yaml +++ b/config/crd/bases/rhtas.redhat.com_rekors.yaml @@ -1375,1049 +1375,6 @@ spec: rule: '!has(self.serviceMonitor) || !has(self.serviceMonitor.enabled) || !self.serviceMonitor.enabled || (has(self.metrics) && has(self.metrics.enabled) && self.metrics.enabled)' - rekorSearchUI: - description: Rekor Search UI - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for - the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. - co-locate this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules - (e.g. avoid putting this pod in the same node, zone, etc. - as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and subtracting - "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - enabled: - description: If set to true, the Operator will deploy a Rekor - Search UI - type: boolean - x-kubernetes-validations: - - message: Feature cannot be disabled - rule: (self || !oldSelf) - host: - description: Set hostname for your Ingress. - type: string - labels: - additionalProperties: - type: string - description: Set labels applied to the created Ingress, e.g. for - ingress-controller/route selection when sharding ingress traffic. - type: object - replicas: - description: Number of desired pods. - format: int32 - minimum: 0 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This field depends on the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - tolerations: - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - type: object replicas: description: Number of desired pods. format: int32 @@ -2833,8 +1790,6 @@ spec: x-kubernetes-map-type: atomic pvcName: type: string - rekorSearchUIUrl: - type: string searchIndex: properties: dbPasswordRef: diff --git a/config/crd/bases/rhtas.redhat.com_securesigns.yaml b/config/crd/bases/rhtas.redhat.com_securesigns.yaml index 4ccf9e2d1..83019cba2 100644 --- a/config/crd/bases/rhtas.redhat.com_securesigns.yaml +++ b/config/crd/bases/rhtas.redhat.com_securesigns.yaml @@ -4210,1055 +4210,6 @@ spec: rule: '!has(self.serviceMonitor) || !has(self.serviceMonitor.enabled) || !self.serviceMonitor.enabled || (has(self.metrics) && has(self.metrics.enabled) && self.metrics.enabled)' - rekorSearchUI: - description: Rekor Search UI - properties: - affinity: - description: Affinity is a group of affinity scheduling rules. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules - for the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated - with the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching - the corresponding nodeSelectorTerm, in the - range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector - terms. The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. - co-locate this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, - associated with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules - (e.g. avoid putting this pod in the same node, zone, - etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and subtracting - "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, - associated with the corresponding weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - enabled: - description: If set to true, the Operator will deploy a Rekor - Search UI - type: boolean - x-kubernetes-validations: - - message: Feature cannot be disabled - rule: (self || !oldSelf) - host: - description: Set hostname for your Ingress. - type: string - labels: - additionalProperties: - type: string - description: Set labels applied to the created Ingress, e.g. - for ingress-controller/route selection when sharding ingress - traffic. - type: object - replicas: - description: Number of desired pods. - format: int32 - minimum: 0 - type: integer - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This field depends on the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - tolerations: - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - type: object replicas: description: Number of desired pods. format: int32 diff --git a/config/default/images.env b/config/default/images.env index b50b683cd..3523b34ae 100644 --- a/config/default/images.env +++ b/config/default/images.env @@ -22,9 +22,6 @@ RELATED_IMAGE_REKOR_SERVER=registry.redhat.io/rhtas/rekor-server-rhel9@sha256:99 # Rekor - Redis RELATED_IMAGE_REKOR_REDIS=registry.redhat.io/rhtas/trillian-redis-rhel9@sha256:8b034a306fb8e275b8fb8902d7fa6ba07f0dbe157d2daee232b50079a89198c4 -# Rekor - Search UI -RELATED_IMAGE_REKOR_SEARCH_UI=registry.redhat.io/rhtas/rekor-search-ui-rhel9@sha256:7901d8916e713042a21584a604dded1d5cc83dedff46cd79aaad79d3e15b814b - # Rekor - Backfill Redis RELATED_IMAGE_BACKFILL_REDIS=registry.redhat.io/rhtas/rekor-backfill-redis-rhel9@sha256:d870c1469c09c3ec8fc92be2764caa30ef3f0eaddd102f2cedabd74f3e57da98 @@ -47,7 +44,7 @@ RELATED_IMAGE_REKOR_MONITOR=registry.redhat.io/rhtas/rekor-monitor-rhel9@sha256: RELATED_IMAGE_CTLOG_MONITOR=registry.redhat.io/rhtas/ctlog-monitor-rhel9@sha256:c4cd93c029161f5dd06474911ab26436f26d80b982cb2f201d581412099cdb05 # Console - UI -RELATED_IMAGE_CONSOLE_UI=registry.redhat.io/rhtas/rhtas-console-ui-rhel9@sha256:1248016d3be28c2e48ac2ee9c069556e4e5ab2b404c0a8fd0ab1a426bcec76cd +RELATED_IMAGE_CONSOLE_UI=registry.redhat.io/rhtas/rhtas-console-ui-rhel9@sha256:bc65cc907d93ab67dbadb88841a114e58ace47ad5b1ba0944b95bffb87a2d6b6 # Console - API -RELATED_IMAGE_CONSOLE_API=registry.redhat.io/rhtas/rhtas-console-rhel9@sha256:31b0b341d58440b12561f64db1f2c8dfd3167284d62d60769120b50ce57329cc +RELATED_IMAGE_CONSOLE_API=registry.redhat.io/rhtas/rhtas-console-rhel9@sha256:c4ec630da94847c2307e7038f2571d13b49f4659ec4f7bac925c920446e6dd8e diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index ef7ac4a39..c9b739e81 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -132,17 +132,6 @@ replacements: select: kind: Deployment name: operator-controller-manager -- source: - fieldPath: data.RELATED_IMAGE_REKOR_SEARCH_UI - kind: ConfigMap - name: related-images - version: v1 - targets: - - fieldPaths: - - spec.template.spec.containers.[name=^manager$].env.[name=^RELATED_IMAGE_REKOR_SEARCH_UI$].value - select: - kind: Deployment - name: operator-controller-manager - source: fieldPath: data.RELATED_IMAGE_BACKFILL_REDIS kind: ConfigMap diff --git a/config/default/manager_images_patch.yaml b/config/default/manager_images_patch.yaml index a75084405..820a62666 100644 --- a/config/default/manager_images_patch.yaml +++ b/config/default/manager_images_patch.yaml @@ -26,8 +26,6 @@ spec: value: PLACEHOLDER - name: RELATED_IMAGE_REKOR_SERVER value: PLACEHOLDER - - name: RELATED_IMAGE_REKOR_SEARCH_UI - value: PLACEHOLDER - name: RELATED_IMAGE_BACKFILL_REDIS value: PLACEHOLDER - name: RELATED_IMAGE_TUF diff --git a/internal/controller/console/actions/api/deployment.go b/internal/controller/console/actions/api/deployment.go index 0951a7d26..ed72bab2b 100644 --- a/internal/controller/console/actions/api/deployment.go +++ b/internal/controller/console/actions/api/deployment.go @@ -48,14 +48,9 @@ func (i deployAction) Handle(ctx context.Context, instance *rhtasv1.Console) *ac result controllerutil.OperationResult ) - var tufURL string - if instance.Spec.Api.Tuf.URL != "" || instance.Spec.Api.Tuf.Ref != nil { - tufURL, err = utils.ResolveExternalServiceUrl(ctx, i.Client, instance.Spec.Api.Tuf, instance.Namespace, &rhtasv1.Tuf{}) - if err != nil { - return i.Error(ctx, fmt.Errorf("error resolving TUF URL: %w", err), instance) - } - } else { - tufURL = fmt.Sprintf("http://tuf.%s.svc", instance.Namespace) + tufURL, err := utils.ResolveExternalServiceUrl(ctx, i.Client, instance.Spec.Api.Tuf, instance.Namespace, &rhtasv1.Tuf{}) + if err != nil { + return i.Error(ctx, fmt.Errorf("error resolving TUF URL: %w", err), instance) } l := labels.For(actions.ApiComponentName, actions.ApiDeploymentName, instance.Name) diff --git a/internal/controller/console/console_controller_test.go b/internal/controller/console/console_controller_test.go index feab83102..c32cd5c5a 100644 --- a/internal/controller/console/console_controller_test.go +++ b/internal/controller/console/console_controller_test.go @@ -63,6 +63,23 @@ var _ = Describe("Console controller", func() { }) It("should successfully reconcile a custom resource for Console", func() { + By("creating the TUF resource for autodiscovery") + tuf := &rhtasv1.Tuf{ + ObjectMeta: metav1.ObjectMeta{ + Name: Name, + Namespace: Namespace, + }, + Spec: rhtasv1.TufSpec{ + Keys: []rhtasv1.TufKey{{Name: rhtasv1.TufKeyFulcio}}, + }, + } + Expect(suite.Client().Create(ctx, tuf)).To(Succeed()) + tuf.Status.Url = "http://tuf.default.svc" + tuf.Status.Conditions = []metav1.Condition{ + {Type: constants.ReadyCondition, Status: metav1.ConditionTrue, Reason: "Ready", LastTransitionTime: metav1.Now()}, + } + Expect(suite.Client().Status().Update(ctx, tuf)).To(Succeed()) + By("creating the custom resource for the Kind Console") err := suite.Client().Get(ctx, typeNamespaceName, console) if err != nil && errors.IsNotFound(err) { diff --git a/internal/controller/rekor/actions/constants.go b/internal/controller/rekor/actions/constants.go index 438339d7a..da06c0574 100644 --- a/internal/controller/rekor/actions/constants.go +++ b/internal/controller/rekor/actions/constants.go @@ -12,8 +12,6 @@ const ( RedisDeploymentPort = 6379 MonitorStatefulSetName = "rekor-monitor" SearchUiDeploymentName = "rekor-search-ui" - SearchUiDeploymentPortName = "http" - SearchUiDeploymentPort = 3000 RedisTlsSecret = "%s-rekor-redis-tls" @@ -28,7 +26,6 @@ const ( RedisComponentName = "rekor-redis" MonitorComponentName = "rekor-monitor" MonitorMetricsPort = 9464 - UIComponentName = "rekor-ui" BackfillRedisCronJobName = "backfill-redis" UICondition = "UiAvailable" ServerCondition = "ServerAvailable" diff --git a/internal/controller/rekor/actions/ui/cleanup.go b/internal/controller/rekor/actions/ui/cleanup.go new file mode 100644 index 000000000..dd31e03a2 --- /dev/null +++ b/internal/controller/rekor/actions/ui/cleanup.go @@ -0,0 +1,92 @@ +package ui + +import ( + "context" + "fmt" + + rhtasv1 "github.com/securesign/operator/api/v1" + "github.com/securesign/operator/internal/action" + "github.com/securesign/operator/internal/controller/rekor/actions" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewCleanupAction() action.Action[*rhtasv1.Rekor] { + return &cleanupAction{} +} + +type cleanupAction struct { + action.BaseAction +} + +func (a cleanupAction) Name() string { + return "cleanup-rekorsearchui" +} + +func (a cleanupAction) CanHandle(_ context.Context, instance *rhtasv1.Rekor) bool { + return meta.FindStatusCondition(instance.Status.Conditions, actions.UICondition) != nil +} + +func (a cleanupAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { + namespace := instance.Namespace + + candidates := []client.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.SearchUiDeploymentName, + Namespace: namespace, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.SearchUiDeploymentName, + Namespace: namespace, + }, + }, + &v1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.SearchUiDeploymentName, + Namespace: namespace, + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.RBACUIName, + Namespace: namespace, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.RBACUIName, + Namespace: namespace, + }, + }, + } + + for _, candidate := range candidates { + key := client.ObjectKeyFromObject(candidate) + if err := a.Client.Get(ctx, key, candidate); err != nil { + if errors.IsNotFound(err) { + continue + } + return a.Error(ctx, fmt.Errorf("failed to get resource %s: %w", key.Name, err), instance) + } + if !metav1.IsControlledBy(candidate, instance) { + continue + } + if err := a.Client.Delete(ctx, candidate); err != nil && !errors.IsNotFound(err) { + return a.Error(ctx, fmt.Errorf("failed to delete resource %s: %w", key.Name, err), instance) + } + a.Logger.Info("Deleted orphaned RekorSearchUI resource", "name", key.Name) + } + + meta.RemoveStatusCondition(&instance.Status.Conditions, actions.UICondition) + + return a.ReturnOnChange(a.PersistStatus)(ctx, instance) +} diff --git a/internal/controller/rekor/actions/ui/cleanup_test.go b/internal/controller/rekor/actions/ui/cleanup_test.go new file mode 100644 index 000000000..85d2a7048 --- /dev/null +++ b/internal/controller/rekor/actions/ui/cleanup_test.go @@ -0,0 +1,159 @@ +package ui + +import ( + "testing" + + . "github.com/onsi/gomega" + rhtasv1 "github.com/securesign/operator/api/v1" + "github.com/securesign/operator/internal/controller/rekor/actions" + "github.com/securesign/operator/internal/testing/action" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestCleanupAction_CanHandle(t *testing.T) { + tests := []struct { + name string + instance *rhtasv1.Rekor + want bool + }{ + { + name: "no UiAvailable condition", + instance: &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, + }, + want: false, + }, + { + name: "has UiAvailable condition", + instance: &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, + Status: rhtasv1.RekorStatus{ + Conditions: []metav1.Condition{ + { + Type: actions.UICondition, + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + }, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + a := NewCleanupAction() + g.Expect(a.CanHandle(t.Context(), tt.instance)).To(Equal(tt.want)) + }) + } +} + +func TestCleanupAction_Handle(t *testing.T) { + tests := []struct { + name string + ownedResources []client.Object + unownedResources []client.Object + wantDeleted int + }{ + { + name: "deletes owned resources and removes condition", + wantDeleted: 4, + ownedResources: []client.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: actions.SearchUiDeploymentName, Namespace: "default"}, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: actions.SearchUiDeploymentName, Namespace: "default"}, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{Name: actions.RBACUIName, Namespace: "default"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: actions.RBACUIName, Namespace: "default"}, + }, + }, + }, + { + name: "no resources exist — only removes condition", + wantDeleted: 0, + }, + { + name: "skips resources not owned by this Rekor", + unownedResources: []client.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: actions.SearchUiDeploymentName, Namespace: "default"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + instance := &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + UID: "test-uid", + }, + Status: rhtasv1.RekorStatus{ + Conditions: []metav1.Condition{ + { + Type: actions.UICondition, + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + } + + objects := []client.Object{instance} + for _, r := range tt.ownedResources { + r.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: rhtasv1.GroupVersion.String(), + Kind: "Rekor", + Name: instance.Name, + UID: instance.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }, + }) + objects = append(objects, r) + } + objects = append(objects, tt.unownedResources...) + + cli := action.FakeClientBuilder().WithObjects(objects...).WithStatusSubresource(&rhtasv1.Rekor{}).Build() + a := action.PrepareAction(cli, NewCleanupAction()) + + result := a.Handle(t.Context(), instance) + g.Expect(result).ToNot(BeNil()) + g.Expect(result.Err).ToNot(HaveOccurred()) + + g.Expect(meta.FindStatusCondition(instance.Status.Conditions, actions.UICondition)).To(BeNil()) + + deleted := 0 + for _, r := range tt.ownedResources { + err := cli.Get(t.Context(), client.ObjectKeyFromObject(r), r) + if errors.IsNotFound(err) { + deleted++ + } + } + g.Expect(deleted).To(Equal(tt.wantDeleted)) + + for _, r := range tt.unownedResources { + g.Expect(cli.Get(t.Context(), client.ObjectKeyFromObject(r), r)).To(Succeed()) + } + }) + } +} diff --git a/internal/controller/rekor/actions/ui/deployment.go b/internal/controller/rekor/actions/ui/deployment.go deleted file mode 100644 index 16b76542d..000000000 --- a/internal/controller/rekor/actions/ui/deployment.go +++ /dev/null @@ -1,148 +0,0 @@ -package ui - -import ( - "context" - "fmt" - "maps" - "slices" - - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/constants" - "github.com/securesign/operator/internal/images" - "github.com/securesign/operator/internal/labels" - "github.com/securesign/operator/internal/state" - "github.com/securesign/operator/internal/utils/kubernetes" - "github.com/securesign/operator/internal/utils/kubernetes/ensure" - "github.com/securesign/operator/internal/utils/kubernetes/ensure/deployment" - - "github.com/securesign/operator/internal/controller/rekor/actions" - v2 "k8s.io/api/apps/v1" - core "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - rhtasv1 "github.com/securesign/operator/api/v1" -) - -func NewDeployAction() action.Action[*rhtasv1.Rekor] { - return &deployAction{} -} - -type deployAction struct { - action.BaseAction -} - -func (i deployAction) Name() string { - return "deploy" -} - -func (i deployAction) CanHandle(ctx context.Context, instance *rhtasv1.Rekor) bool { - return enabled(instance) && state.FromInstance(instance, constants.ReadyCondition) >= state.Creating -} - -func (i deployAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { - var ( - err error - result controllerutil.OperationResult - ) - labels := labels.For(actions.UIComponentName, actions.SearchUiDeploymentName, instance.Name) - if result, err = kubernetes.CreateOrUpdate(ctx, i.Client, - &v2.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: actions.SearchUiDeploymentName, - Namespace: instance.Namespace, - }, - }, - i.ensureUIDeployment(instance, actions.RBACUIName, labels), - deployment.PodRequirements(instance.Spec.RekorSearchUI.PodRequirements, actions.SearchUiDeploymentName), - deployment.PodSecurityContext(), - deployment.GODEBUG(instance.GetAnnotations()), - ensure.ControllerReference[*v2.Deployment](instance, i.Client), - ensure.Labels[*v2.Deployment](slices.Collect(maps.Keys(labels)), labels), - ); err != nil { - return i.Error(ctx, fmt.Errorf("could not create Rekor search UI: %w", err), instance, - metav1.Condition{ - Type: actions.UICondition, - Status: metav1.ConditionFalse, - Reason: state.Failure.String(), - Message: err.Error(), - }, - ) - } - - if result != controllerutil.OperationResultNone { - meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ - Type: actions.UICondition, - Status: metav1.ConditionFalse, - Reason: state.Creating.String(), - Message: "Deployment created", - }) - return i.ReturnOnChange(i.PersistStatus)(ctx, instance) - } else { - return i.Continue() - } -} - -func (i deployAction) ensureUIDeployment(instance *rhtasv1.Rekor, sa string, labels map[string]string) func(*v2.Deployment) error { - return func(dp *v2.Deployment) error { - spec := &dp.Spec - spec.Selector = &metav1.LabelSelector{ - MatchLabels: labels, - } - - template := &spec.Template - template.Labels = labels - template.Spec.ServiceAccountName = sa - - container := kubernetes.FindContainerByNameOrCreate(&template.Spec, actions.SearchUiDeploymentName) - container.Image = images.Registry.Get(images.RekorSearchUi) - - env := kubernetes.FindEnvByNameOrCreate(container, "NEXT_PUBLIC_REKOR_DEFAULT_DOMAIN") - env.Value = instance.Status.Url - - serverPort := kubernetes.FindPortByNameOrCreate(container, "3000-tcp") - serverPort.ContainerPort = 3000 - - if container.ReadinessProbe == nil { - container.ReadinessProbe = &core.Probe{} - } - if container.ReadinessProbe.HTTPGet == nil { - container.ReadinessProbe.HTTPGet = &core.HTTPGetAction{} - } - container.ReadinessProbe.HTTPGet.Path = "/" - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(3000) - container.ReadinessProbe.InitialDelaySeconds = 0 - container.ReadinessProbe.PeriodSeconds = 10 - container.ReadinessProbe.TimeoutSeconds = 5 - container.ReadinessProbe.FailureThreshold = 3 - - if container.LivenessProbe == nil { - container.LivenessProbe = &core.Probe{} - } - if container.LivenessProbe.HTTPGet == nil { - container.LivenessProbe.HTTPGet = &core.HTTPGetAction{} - } - container.LivenessProbe.HTTPGet.Path = "/" - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(3000) - container.LivenessProbe.InitialDelaySeconds = 0 - container.LivenessProbe.PeriodSeconds = 10 - container.LivenessProbe.TimeoutSeconds = 5 - container.LivenessProbe.FailureThreshold = 3 - - if container.StartupProbe == nil { - container.StartupProbe = &core.Probe{} - } - if container.StartupProbe.HTTPGet == nil { - container.StartupProbe.HTTPGet = &core.HTTPGetAction{} - } - container.StartupProbe.HTTPGet.Path = "/" - container.StartupProbe.HTTPGet.Port = intstr.FromInt(3000) - container.StartupProbe.PeriodSeconds = 5 - container.StartupProbe.TimeoutSeconds = 5 - container.StartupProbe.FailureThreshold = 12 - - return nil - } -} diff --git a/internal/controller/rekor/actions/ui/helper.go b/internal/controller/rekor/actions/ui/helper.go deleted file mode 100644 index e8c7ea2fa..000000000 --- a/internal/controller/rekor/actions/ui/helper.go +++ /dev/null @@ -1,10 +0,0 @@ -package ui - -import ( - rhtasv1 "github.com/securesign/operator/api/v1" - "github.com/securesign/operator/internal/utils" -) - -func enabled(instance *rhtasv1.Rekor) bool { - return utils.IsEnabled(instance.Spec.RekorSearchUI.Enabled) -} diff --git a/internal/controller/rekor/actions/ui/ingress.go b/internal/controller/rekor/actions/ui/ingress.go deleted file mode 100644 index fea4cc591..000000000 --- a/internal/controller/rekor/actions/ui/ingress.go +++ /dev/null @@ -1,91 +0,0 @@ -package ui - -import ( - "context" - "fmt" - "maps" - "slices" - - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/constants" - "github.com/securesign/operator/internal/labels" - "github.com/securesign/operator/internal/state" - "github.com/securesign/operator/internal/utils" - "github.com/securesign/operator/internal/utils/kubernetes" - "github.com/securesign/operator/internal/utils/kubernetes/ensure" - v2 "k8s.io/api/networking/v1" - "k8s.io/utils/ptr" - - rhtasv1 "github.com/securesign/operator/api/v1" - "github.com/securesign/operator/internal/controller/rekor/actions" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func NewIngressAction() action.Action[*rhtasv1.Rekor] { - return &ingressAction{} -} - -type ingressAction struct { - action.BaseAction -} - -func (i ingressAction) Name() string { - return "ingress" -} - -func (i ingressAction) CanHandle(ctx context.Context, instance *rhtasv1.Rekor) bool { - return enabled(instance) && - utils.IsEnabled(instance.Spec.Ingress.Enabled) && - state.FromInstance(instance, constants.ReadyCondition) >= state.Creating -} - -func (i ingressAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { - var ( - result controllerutil.OperationResult - err error - ) - ok := types.NamespacedName{Name: actions.SearchUiDeploymentName, Namespace: instance.Namespace} - labels := labels.For(actions.UIComponentName, actions.SearchUiDeploymentName, instance.Name) - - svc := &v1.Service{} - if err := i.Client.Get(ctx, ok, svc); err != nil { - return i.Error(ctx, fmt.Errorf("could not find service for ingress: %w", err), instance) - } - - if result, err = kubernetes.CreateOrUpdate(ctx, i.Client, - &v2.Ingress{ - ObjectMeta: metav1.ObjectMeta{Name: svc.Name, Namespace: svc.Namespace}, - }, - kubernetes.EnsureIngressSpec(ctx, i.Client, *svc, - rhtasv1.Ingress{ - Enabled: ptr.To(true), - Host: instance.Spec.RekorSearchUI.Host, - Labels: instance.Spec.RekorSearchUI.Labels, - }, - actions.SearchUiDeploymentPortName), - ensure.Optional(kubernetes.IsOpenShift(), kubernetes.EnsureIngressTLS()), - // add ingress labels - ensure.Labels[*v2.Ingress](slices.Collect(maps.Keys(instance.Spec.RekorSearchUI.Labels)), instance.Spec.RekorSearchUI.Labels), - // add common labels - ensure.Labels[*v2.Ingress](slices.Collect(maps.Keys(labels)), labels), - ensure.ControllerReference[*v2.Ingress](instance, i.Client), - ); err != nil { - return i.Error(ctx, fmt.Errorf("could not create ingress object: %w", err), instance) - } - - if result != controllerutil.OperationResultNone { - meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ - Type: actions.UICondition, - Status: metav1.ConditionFalse, - Reason: state.Creating.String(), - Message: "Ingress created", - }) - return i.ReturnOnChange(i.PersistStatus)(ctx, instance) - } else { - return i.Continue() - } -} diff --git a/internal/controller/rekor/actions/ui/ingress_test.go b/internal/controller/rekor/actions/ui/ingress_test.go deleted file mode 100644 index ca8c478f3..000000000 --- a/internal/controller/rekor/actions/ui/ingress_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package ui - -import ( - "testing" - - . "github.com/onsi/gomega" - "github.com/securesign/operator/internal/state" - - rhtasv1 "github.com/securesign/operator/api/v1" - "github.com/securesign/operator/internal/constants" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" -) - -func TestIngress_CanHandle(t *testing.T) { - t.Parallel() - ctx := t.Context() - tests := []struct { - name string - conditions []metav1.Condition - externalAccess bool - uiEnabled bool - expected bool - }{ - { - name: "ingress is enabled and ui is enabled and ready", - conditions: []metav1.Condition{{Type: constants.ReadyCondition, Status: metav1.ConditionTrue, Reason: state.Ready.String()}}, - externalAccess: true, - uiEnabled: true, - expected: true, - }, - { - name: "ingress is enabled and ui is enabled but not ready", - conditions: []metav1.Condition{{Type: constants.ReadyCondition, Status: metav1.ConditionFalse, Reason: state.Pending.String()}}, - externalAccess: true, - uiEnabled: true, - expected: false, - }, - { - name: "ingress is disabled", - conditions: []metav1.Condition{{Type: constants.ReadyCondition, Status: metav1.ConditionTrue, Reason: state.Ready.String()}}, - externalAccess: false, - uiEnabled: true, - expected: false, - }, - { - name: "ingress is enabled but ui is disabled", - conditions: []metav1.Condition{{Type: constants.ReadyCondition, Status: metav1.ConditionTrue, Reason: state.Ready.String()}}, - externalAccess: true, - uiEnabled: false, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - instance := rhtasv1.Rekor{ - Spec: rhtasv1.RekorSpec{ - Ingress: rhtasv1.Ingress{ - Enabled: ptr.To(tt.externalAccess), - }, - RekorSearchUI: rhtasv1.RekorSearchUI{ - Enabled: &tt.uiEnabled, - }, - }, - Status: rhtasv1.RekorStatus{ - Conditions: tt.conditions, - }, - } - action := NewIngressAction() - g.Expect(tt.expected).To(Equal(action.CanHandle(ctx, &instance))) - }) - } -} diff --git a/internal/controller/rekor/actions/ui/migration.go b/internal/controller/rekor/actions/ui/migration.go new file mode 100644 index 000000000..07a823630 --- /dev/null +++ b/internal/controller/rekor/actions/ui/migration.go @@ -0,0 +1,102 @@ +package ui + +import ( + "context" + "fmt" + + rhtasv1 "github.com/securesign/operator/api/v1" + rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" + "github.com/securesign/operator/internal/action" + "github.com/securesign/operator/internal/migration" + "github.com/securesign/operator/internal/utils" + "github.com/securesign/operator/internal/utils/kubernetes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const consoleNameFormat = "%s-console" + +func NewMigrationAction() action.Action[*rhtasv1.Rekor] { + return &migrationAction{} +} + +type migrationAction struct { + action.BaseAction +} + +func (a migrationAction) Name() string { + return "migrate-rekorsearchui" +} + +func (a migrationAction) CanHandle(ctx context.Context, instance *rhtasv1.Rekor) bool { + return migration.Has(instance, rhtasv1alpha1.MigrationSearchUIData) +} + +func (a migrationAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { + var searchUI rhtasv1alpha1.RekorSearchUI + if ok, err := migration.Read(instance, rhtasv1alpha1.MigrationSearchUIData, &searchUI); err != nil { + return a.Error(ctx, fmt.Errorf("failed to deserialize RekorSearchUI from annotation: %w", err), instance) + } else if !ok { + if err := a.removeMigrationAnnotation(ctx, instance); err != nil { + return a.Error(ctx, err, instance) + } + return a.Continue() + } + + if !utils.IsEnabled(searchUI.Enabled) { + a.Logger.Info("RekorSearchUI not enabled, removing migration annotation") + if err := a.removeMigrationAnnotation(ctx, instance); err != nil { + return a.Error(ctx, err, instance) + } + return a.Continue() + } + + consoleName := fmt.Sprintf(consoleNameFormat, instance.Name) + console := &rhtasv1.Console{ + ObjectMeta: metav1.ObjectMeta{ + Name: consoleName, + Namespace: instance.Namespace, + }, + } + + if _, err := kubernetes.CreateOrUpdate(ctx, a.Client, console, + func(object *rhtasv1.Console) error { + object.Spec.UI = rhtasv1.ConsoleUI{ + PodRequirements: rhtasv1.PodRequirements{ + Replicas: searchUI.Replicas, + Affinity: searchUI.Affinity, + Resources: searchUI.Resources, + Tolerations: searchUI.Tolerations, + }, + Ingress: rhtasv1.Ingress{ + Enabled: searchUI.Enabled, + Host: searchUI.Host, + Labels: searchUI.RouteSelectorLabels, + }, + Rekor: rhtasv1.ServiceReference{ + Ref: &rhtasv1.ServiceReferenceRef{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + }, + } + return nil + }, + ); err != nil { + return a.Error(ctx, fmt.Errorf("failed to create Console CR: %w", err), instance) + } + + a.Logger.Info("Console CR created from RekorSearchUI migration", "name", consoleName) + + if err := a.removeMigrationAnnotation(ctx, instance); err != nil { + return a.Error(ctx, err, instance) + } + + return a.Continue() +} + +func (a migrationAction) removeMigrationAnnotation(ctx context.Context, instance *rhtasv1.Rekor) error { + before := instance.DeepCopy() + migration.Remove(instance, rhtasv1alpha1.MigrationSearchUIData) + return a.Client.Patch(ctx, instance, client.MergeFrom(before)) +} diff --git a/internal/controller/rekor/actions/ui/migration_test.go b/internal/controller/rekor/actions/ui/migration_test.go new file mode 100644 index 000000000..43beb65c1 --- /dev/null +++ b/internal/controller/rekor/actions/ui/migration_test.go @@ -0,0 +1,190 @@ +package ui + +import ( + "testing" + + . "github.com/onsi/gomega" + rhtasv1 "github.com/securesign/operator/api/v1" + rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" + "github.com/securesign/operator/internal/migration" + "github.com/securesign/operator/internal/testing/action" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestMigrationAction_CanHandle(t *testing.T) { + tests := []struct { + name string + instance *rhtasv1.Rekor + want bool + }{ + { + name: "no annotation", + instance: &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, + }, + want: false, + }, + { + name: "has migration annotation", + instance: &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + rhtasv1alpha1.MigrationSearchUIData: `{"enabled":true}`, + }, + }, + }, + want: true, + }, + { + name: "has empty annotation", + instance: &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + rhtasv1alpha1.MigrationSearchUIData: "", + }, + }, + }, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + a := NewMigrationAction() + g.Expect(a.CanHandle(t.Context(), tt.instance)).To(Equal(tt.want)) + }) + } +} + +func TestMigrationAction_Handle(t *testing.T) { + tests := []struct { + name string + searchUI *rhtasv1alpha1.RekorSearchUI + existingConsole *rhtasv1.Console + wantConsole bool + wantAnnotation bool + }{ + { + name: "creates Console CR when enabled", + searchUI: &rhtasv1alpha1.RekorSearchUI{ + Enabled: ptr.To(true), + Host: "rekor-ui.example.com", + RouteSelectorLabels: map[string]string{ + "app": "rekor-ui", + }, + }, + wantConsole: true, + wantAnnotation: false, + }, + { + name: "updates existing Console CR preserving non-UI fields", + searchUI: &rhtasv1alpha1.RekorSearchUI{ + Enabled: ptr.To(true), + Host: "updated-host.example.com", + }, + existingConsole: &rhtasv1.Console{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-console", + Namespace: "default", + }, + Spec: rhtasv1.ConsoleSpec{ + Api: rhtasv1.ConsoleAPI{ + Tuf: rhtasv1.ServiceReference{URL: "https://tuf.example.com"}, + }, + TrustedCA: &rhtasv1.LocalObjectReference{Name: "my-ca"}, + }, + }, + wantConsole: true, + wantAnnotation: false, + }, + { + name: "removes annotation when data is empty", + searchUI: nil, + wantConsole: false, + wantAnnotation: false, + }, + { + name: "skips Console when enabled is false", + searchUI: &rhtasv1alpha1.RekorSearchUI{ + Enabled: ptr.To(false), + Host: "rekor-ui.example.com", + }, + wantConsole: false, + wantAnnotation: false, + }, + { + name: "skips Console when enabled is nil", + searchUI: &rhtasv1alpha1.RekorSearchUI{ + Host: "rekor-ui.example.com", + }, + wantConsole: false, + wantAnnotation: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + instance := &rhtasv1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + } + + if tt.searchUI != nil { + g.Expect(migration.Set(instance, rhtasv1alpha1.MigrationSearchUIData, tt.searchUI)).To(Succeed()) + } else { + if instance.Annotations == nil { + instance.Annotations = map[string]string{} + } + instance.Annotations[rhtasv1alpha1.MigrationSearchUIData] = "" + } + + objects := []client.Object{instance} + if tt.existingConsole != nil { + objects = append(objects, tt.existingConsole) + } + + cli := action.FakeClientBuilder().WithObjects(objects...).WithStatusSubresource(&rhtasv1.Rekor{}).Build() + a := action.PrepareAction(cli, NewMigrationAction()) + + result := a.Handle(t.Context(), instance) + g.Expect(result).To(Equal(action.Continue())) + + g.Expect(migration.Has(instance, rhtasv1alpha1.MigrationSearchUIData)).To(Equal(tt.wantAnnotation)) + + consoleList := &rhtasv1.ConsoleList{} + g.Expect(cli.List(t.Context(), consoleList, client.InNamespace("default"))).To(Succeed()) + + if tt.wantConsole { + g.Expect(consoleList.Items).To(HaveLen(1)) + console := &consoleList.Items[0] + + if tt.searchUI != nil { + g.Expect(console.Name).To(Equal("test-console")) + g.Expect(console.Spec.UI.Ingress.Enabled).To(Equal(tt.searchUI.Enabled)) + g.Expect(console.Spec.UI.Ingress.Host).To(Equal(tt.searchUI.Host)) + g.Expect(console.Spec.UI.Ingress.Labels).To(Equal(tt.searchUI.RouteSelectorLabels)) + g.Expect(console.Spec.UI.Rekor.Ref).ToNot(BeNil()) + g.Expect(console.Spec.UI.Rekor.Ref.Name).To(Equal(instance.Name)) + g.Expect(console.Spec.UI.Rekor.Ref.Namespace).To(Equal(instance.Namespace)) + } + if tt.existingConsole != nil { + g.Expect(console.Spec.Api).To(Equal(tt.existingConsole.Spec.Api)) + g.Expect(console.Spec.TrustedCA).To(Equal(tt.existingConsole.Spec.TrustedCA)) + } + } else { + g.Expect(consoleList.Items).To(BeEmpty()) + } + }) + } +} diff --git a/internal/controller/rekor/actions/ui/rbac.go b/internal/controller/rekor/actions/ui/rbac.go deleted file mode 100644 index dc34df405..000000000 --- a/internal/controller/rekor/actions/ui/rbac.go +++ /dev/null @@ -1,25 +0,0 @@ -package ui - -import ( - "context" - - rhtasv1 "github.com/securesign/operator/api/v1" - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/action/rbac" - "github.com/securesign/operator/internal/constants" - "github.com/securesign/operator/internal/controller/rekor/actions" - "github.com/securesign/operator/internal/state" - v1 "k8s.io/api/core/v1" -) - -func NewRBACAction() action.Action[*rhtasv1.Rekor] { - return rbac.NewAction[*rhtasv1.Rekor]( - actions.SearchUiDeploymentName, actions.RBACUIName, - rbac.WithCanHandle[*rhtasv1.Rekor](func(_ context.Context, instance *rhtasv1.Rekor) bool { - return enabled(instance) && state.FromInstance(instance, constants.ReadyCondition) >= state.Creating - }), - rbac.WithImagePullSecrets(func(instance *rhtasv1.Rekor) []v1.LocalObjectReference { - return instance.Spec.ImagePullSecrets - }), - ) -} diff --git a/internal/controller/rekor/actions/ui/rollout_check.go b/internal/controller/rekor/actions/ui/rollout_check.go deleted file mode 100644 index 64feb023a..000000000 --- a/internal/controller/rekor/actions/ui/rollout_check.go +++ /dev/null @@ -1,18 +0,0 @@ -package ui - -import ( - rhtasv1 "github.com/securesign/operator/api/v1" - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/action/deploymentRollout" - "github.com/securesign/operator/internal/controller/rekor/actions" -) - -func NewRolloutCheckAction() action.Action[*rhtasv1.Rekor] { - return deploymentRollout.NewAction(deploymentRollout.Config[*rhtasv1.Rekor]{ - Name: "ui rollout check", - ConditionType: actions.UICondition, - DeploymentName: actions.SearchUiDeploymentName, - Enabled: enabled, - PromoteOnSuccess: true, - }) -} diff --git a/internal/controller/rekor/actions/ui/status_url.go b/internal/controller/rekor/actions/ui/status_url.go deleted file mode 100644 index 3a686435c..000000000 --- a/internal/controller/rekor/actions/ui/status_url.go +++ /dev/null @@ -1,55 +0,0 @@ -package ui - -import ( - "context" - "fmt" - - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/controller/rekor/actions" - "github.com/securesign/operator/internal/utils" - v1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/types" - - rhtasv1 "github.com/securesign/operator/api/v1" -) - -func NewStatusURLAction() action.Action[*rhtasv1.Rekor] { - return &statusUrlAction{} -} - -type statusUrlAction struct { - action.BaseAction -} - -func (i statusUrlAction) Name() string { - return "status-url" -} - -func (i statusUrlAction) CanHandle(ctx context.Context, instance *rhtasv1.Rekor) bool { - return enabled(instance) -} - -func (i statusUrlAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { - var url string - if utils.IsEnabled(instance.Spec.Ingress.Enabled) { - protocol := "http://" - ingress := &v1.Ingress{} - err := i.Client.Get(ctx, types.NamespacedName{Name: actions.SearchUiDeploymentName, Namespace: instance.Namespace}, ingress) - if err != nil { - return i.Error(ctx, fmt.Errorf("get ingress error: %w", err), instance) - } - if len(ingress.Spec.TLS) > 0 { - protocol = "https://" - } - url = protocol + ingress.Spec.Rules[0].Host - } else { - url = fmt.Sprintf("http://%s.%s.svc", actions.SearchUiDeploymentName, instance.Namespace) - } - - if url == instance.Status.RekorSearchUIUrl { - return i.Continue() - } - - instance.Status.RekorSearchUIUrl = url - return i.ReturnOnChange(i.PersistStatus)(ctx, instance) -} diff --git a/internal/controller/rekor/actions/ui/svc.go b/internal/controller/rekor/actions/ui/svc.go deleted file mode 100644 index 39afae7f8..000000000 --- a/internal/controller/rekor/actions/ui/svc.go +++ /dev/null @@ -1,77 +0,0 @@ -package ui - -import ( - "context" - "fmt" - "maps" - "slices" - - "github.com/securesign/operator/internal/action" - "github.com/securesign/operator/internal/constants" - "github.com/securesign/operator/internal/controller/rekor/actions" - "github.com/securesign/operator/internal/labels" - "github.com/securesign/operator/internal/state" - "github.com/securesign/operator/internal/utils/kubernetes" - "github.com/securesign/operator/internal/utils/kubernetes/ensure" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - rhtasv1 "github.com/securesign/operator/api/v1" -) - -func NewCreateServiceAction() action.Action[*rhtasv1.Rekor] { - return &createServiceAction{} -} - -type createServiceAction struct { - action.BaseAction -} - -func (i createServiceAction) Name() string { - return "create service" -} - -func (i createServiceAction) CanHandle(ctx context.Context, instance *rhtasv1.Rekor) bool { - return enabled(instance) && state.FromInstance(instance, constants.ReadyCondition) >= state.Creating -} - -func (i createServiceAction) Handle(ctx context.Context, instance *rhtasv1.Rekor) *action.Result { - - var ( - err error - result controllerutil.OperationResult - ) - - labels := labels.For(actions.UIComponentName, actions.SearchUiDeploymentName, instance.Name) - - if result, err = kubernetes.CreateOrUpdate(ctx, i.Client, - &v1.Service{ - ObjectMeta: metav1.ObjectMeta{Name: actions.SearchUiDeploymentName, Namespace: instance.Namespace}, - }, - kubernetes.EnsureServiceSpec(labels, v1.ServicePort{ - Name: actions.SearchUiDeploymentPortName, - Protocol: v1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt32(actions.SearchUiDeploymentPort), - }), - ensure.ControllerReference[*v1.Service](instance, i.Client), - ensure.Labels[*v1.Service](slices.Collect(maps.Keys(labels)), labels), - ); err != nil { - return i.Error(ctx, fmt.Errorf("could not create service: %w", err), instance) - } - - if result != controllerutil.OperationResultNone { - meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ - Type: actions.UICondition, - Status: metav1.ConditionFalse, - Reason: state.Creating.String(), - Message: "Service created", - }) - return i.ReturnOnChange(i.PersistStatus)(ctx, instance) - } else { - return i.Continue() - } -} diff --git a/internal/controller/rekor/rekor_controller.go b/internal/controller/rekor/rekor_controller.go index b42a4c9c0..4ca7e6233 100644 --- a/internal/controller/rekor/rekor_controller.go +++ b/internal/controller/rekor/rekor_controller.go @@ -71,6 +71,7 @@ func NewReconciler(c client.Client, scheme *runtime.Scheme, recorder events.Even //+kubebuilder:rbac:groups=rhtas.redhat.com,resources=rekors,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=rhtas.redhat.com,resources=rekors/status,verbs=get;update;patch //+kubebuilder:rbac:groups=rhtas.redhat.com,resources=rekors/finalizers,verbs=update +//+kubebuilder:rbac:groups=rhtas.redhat.com,resources=consoles,verbs=get;create;update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -104,9 +105,6 @@ func (r *rekorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl target := instance.DeepCopy() conditionSupplier := func(rekor *rhtasv1.Rekor) []string { components := fipsutil.AppendFIPSCondition([]string{actions2.ServerCondition, actions2.SignerCondition, trustmaterial.TrustMaterialCondition}) - if utils.OptionalBool(rekor.Spec.RekorSearchUI.Enabled) { - components = append(components, actions2.UICondition) - } if utils.OptionalBool(rekor.Spec.SearchIndex.Create) { components = append(components, actions2.RedisCondition) } @@ -116,6 +114,9 @@ func (r *rekorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl transitions.NewToPendingPhaseAction[*rhtasv1.Rekor](), transitions.NewEnsureConditionsAction[*rhtasv1.Rekor](conditionSupplier), + ui.NewMigrationAction(), + ui.NewCleanupAction(), + redis.NewTlsAction(), redis.NewGeneratePasswordAction(), server.NewFIPSValidationAction(), @@ -124,7 +125,6 @@ func (r *rekorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl transitions.NewToCreatePhaseAction[*rhtasv1.Rekor](), server.NewRBACAction(), - ui.NewRBACAction(), redis.NewRBACAction(), backfillredis.NewRBACAction(), monitor.NewRBACAction(), @@ -141,11 +141,6 @@ func (r *rekorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl redis.NewDeployAction(), redis.NewCreateServiceAction(), - ui.NewDeployAction(), - ui.NewCreateServiceAction(), - ui.NewIngressAction(), - ui.NewStatusURLAction(), - backfillredis.NewBackfillRedisCronJobAction(), monitor.NewStatefulSetAction(), @@ -156,7 +151,6 @@ func (r *rekorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl server.NewRolloutCheckAction(), server.NewResolvePubKeyAction(), - ui.NewRolloutCheckAction(), redis.NewRolloutCheckAction(), transitions.NewToReadyPhaseAction[*rhtasv1.Rekor](), diff --git a/internal/controller/rekor/rekor_controller_test.go b/internal/controller/rekor/rekor_controller_test.go index 3b8c75f4e..aa1582d81 100644 --- a/internal/controller/rekor/rekor_controller_test.go +++ b/internal/controller/rekor/rekor_controller_test.go @@ -130,9 +130,6 @@ var _ = Describe("Rekor controller", func() { Monitoring: rhtasv1.MonitoringWithTLogConfig{ MonitoringConfig: rhtasv1.MonitoringConfig{Metrics: rhtasv1.MetricsConfig{Enabled: ptr.To(false)}, ServiceMonitor: rhtasv1.ServiceMonitorConfig{Enabled: ptr.To(false)}}, }, - RekorSearchUI: rhtasv1.RekorSearchUI{ - Enabled: utils.Pointer(true), - }, BackFillRedis: rhtasv1.BackFillRedis{ Enabled: utils.Pointer(true), Schedule: "0 0 * * *", @@ -191,16 +188,6 @@ var _ = Describe("Rekor controller", func() { return suite.Client().Get(ctx, types.NamespacedName{Name: actions.RedisDeploymentName, Namespace: Namespace}, &corev1.Service{}) }).WithContext(ctx).Should(Succeed()) - By("UI Deployment created") - Eventually(func(ctx context.Context) error { - return suite.Client().Get(ctx, types.NamespacedName{Name: actions.SearchUiDeploymentName, Namespace: Namespace}, &appsv1.Deployment{}) - }).WithContext(ctx).Should(Succeed()) - - By("UI svc created") - Eventually(func(ctx context.Context) error { - return suite.Client().Get(ctx, types.NamespacedName{Name: actions.SearchUiDeploymentName, Namespace: Namespace}, &corev1.Service{}) - }).WithContext(ctx).Should(Succeed()) - By("Backfill Redis Cronjob Created") Eventually(func(ctx context.Context) error { return suite.Client().Get(ctx, types.NamespacedName{Name: actions.BackfillRedisCronJobName, Namespace: Namespace}, &batchv1.CronJob{}) diff --git a/internal/controller/rekor/rekor_hot_update_test.go b/internal/controller/rekor/rekor_hot_update_test.go index 9e31e10ed..be5f4f2dc 100644 --- a/internal/controller/rekor/rekor_hot_update_test.go +++ b/internal/controller/rekor/rekor_hot_update_test.go @@ -127,9 +127,6 @@ var _ = Describe("Rekor hot update test", func() { Monitoring: rhtasv1.MonitoringWithTLogConfig{ MonitoringConfig: rhtasv1.MonitoringConfig{Metrics: rhtasv1.MetricsConfig{Enabled: ptr.To(false)}, ServiceMonitor: rhtasv1.ServiceMonitorConfig{Enabled: ptr.To(false)}}, }, - RekorSearchUI: rhtasv1.RekorSearchUI{ - Enabled: utils.Pointer(false), - }, BackFillRedis: rhtasv1.BackFillRedis{ Enabled: utils.Pointer(false), }, diff --git a/internal/controller/rekor/rekor_migration_test.go b/internal/controller/rekor/rekor_migration_test.go new file mode 100644 index 000000000..2b0f020ce --- /dev/null +++ b/internal/controller/rekor/rekor_migration_test.go @@ -0,0 +1,188 @@ +package rekor + +import ( + "context" + "fmt" + "time" + + rhtasv1 "github.com/securesign/operator/api/v1" + rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" + "github.com/securesign/operator/internal/controller/rekor/actions" + "github.com/securesign/operator/internal/migration" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var _ = Describe("Rekor SearchUI migration", Ordered, func() { + const ( + Name = "migration-test" + Namespace = "migration" + ) + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: Namespace, + }, + } + + typeNamespaceName := types.NamespacedName{Name: Name, Namespace: Namespace} + + BeforeAll(func(ctx SpecContext) { + Expect(suite.Client().Create(ctx, namespace)).To(Succeed()) + }) + + AfterEach(func(ctx SpecContext) { + found := &rhtasv1.Rekor{} + if err := suite.Client().Get(ctx, typeNamespaceName, found); err == nil { + Expect(suite.Client().Delete(ctx, found)).To(Succeed()) + } + }) + + AfterAll(func(ctx SpecContext) { + _ = suite.Client().Delete(ctx, namespace) + }) + + It("creates Console CR when Rekor is created via v1alpha1 with RekorSearchUI enabled", func(ctx SpecContext) { + treeID := int64(456) + instance := &rhtasv1alpha1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: Name, + Namespace: Namespace, + }, + Spec: rhtasv1alpha1.RekorSpec{ + TreeID: &treeID, + Trillian: rhtasv1alpha1.TrillianService{ + Address: "trillian.default.svc", + Port: ptr.To(int32(8091)), + }, + Monitoring: rhtasv1alpha1.MonitoringWithTLogConfig{ + MonitoringConfig: rhtasv1alpha1.MonitoringConfig{Enabled: false}, + TLog: rhtasv1alpha1.TlogMonitoring{Interval: metav1.Duration{Duration: 10 * time.Minute}}, + }, + BackFillRedis: rhtasv1alpha1.BackFillRedis{ + Enabled: ptr.To(true), + Schedule: "0 0 * * *", + }, + RekorSearchUI: rhtasv1alpha1.RekorSearchUI{ + Enabled: ptr.To(true), + Host: "rekor-ui.example.com", + RouteSelectorLabels: map[string]string{ + "router": "internal", + }, + }, + }, + } + + Expect(suite.Client().Create(ctx, instance)).To(Succeed()) + + By("Console CR is created with correct values") + consoleName := types.NamespacedName{Name: Name + "-console", Namespace: Namespace} + Eventually(func(ctx context.Context) error { + return suite.Client().Get(ctx, consoleName, &rhtasv1.Console{}) + }).WithContext(ctx).Should(Succeed()) + + console := &rhtasv1.Console{} + Expect(suite.Client().Get(ctx, consoleName, console)).To(Succeed()) + Expect(console.Spec.UI.Ingress.Enabled).To(Equal(ptr.To(true))) + Expect(console.Spec.UI.Ingress.Host).To(Equal("rekor-ui.example.com")) + Expect(console.Spec.UI.Ingress.Labels).To(Equal(map[string]string{"router": "internal"})) + Expect(console.Spec.UI.Rekor.Ref).ToNot(BeNil()) + Expect(console.Spec.UI.Rekor.Ref.Name).To(Equal(Name)) + Expect(console.Spec.UI.Rekor.Ref.Namespace).To(Equal(Namespace)) + + By("Migration annotation is removed after processing") + Eventually(func(g Gomega, ctx context.Context) { + found := &rhtasv1.Rekor{} + g.Expect(suite.Client().Get(ctx, typeNamespaceName, found)).To(Succeed()) + g.Expect(migration.Has(found, rhtasv1alpha1.MigrationSearchUIData)).To(BeFalse()) + }).WithContext(ctx).Should(Succeed()) + }) + + It("cleans up orphaned resources and removes UiAvailable condition", func(ctx SpecContext) { + treeID := int64(789) + instance := &rhtasv1alpha1.Rekor{ + ObjectMeta: metav1.ObjectMeta{ + Name: Name, + Namespace: Namespace, + }, + Spec: rhtasv1alpha1.RekorSpec{ + TreeID: &treeID, + Trillian: rhtasv1alpha1.TrillianService{ + Address: "trillian.default.svc", + Port: ptr.To(int32(8091)), + }, + Monitoring: rhtasv1alpha1.MonitoringWithTLogConfig{ + MonitoringConfig: rhtasv1alpha1.MonitoringConfig{Enabled: false}, + TLog: rhtasv1alpha1.TlogMonitoring{Interval: metav1.Duration{Duration: 10 * time.Minute}}, + }, + BackFillRedis: rhtasv1alpha1.BackFillRedis{ + Enabled: ptr.To(true), + Schedule: "0 0 * * *", + }, + }, + } + + Expect(suite.Client().Create(ctx, instance)).To(Succeed()) + + By("Simulating pre-existing orphaned deployment owned by this Rekor") + v1Instance := &rhtasv1.Rekor{} + Eventually(func(ctx context.Context) error { + return suite.Client().Get(ctx, typeNamespaceName, v1Instance) + }).WithContext(ctx).Should(Succeed()) + + orphanedDeploy := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: actions.SearchUiDeploymentName, + Namespace: Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "rekor-search-ui"}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "rekor-search-ui"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "ui", Image: "test"}}}, + }, + }, + } + Expect(suite.Client().Create(ctx, orphanedDeploy)).To(Succeed()) + Expect(controllerutil.SetControllerReference(v1Instance, orphanedDeploy, suite.Client().Scheme())).To(Succeed()) + Expect(suite.Client().Update(ctx, orphanedDeploy)).To(Succeed()) + + By("Continuously injecting UiAvailable condition until cleanup fires and deletes deployment") + deployKey := client.ObjectKeyFromObject(orphanedDeploy) + Eventually(func(ctx context.Context) error { + found := &rhtasv1.Rekor{} + if err := suite.Client().Get(ctx, typeNamespaceName, found); err != nil { + return err + } + if meta.FindStatusCondition(found.Status.Conditions, actions.UICondition) == nil { + meta.SetStatusCondition(&found.Status.Conditions, metav1.Condition{ + Type: actions.UICondition, + Status: metav1.ConditionTrue, + Reason: "Ready", + }) + _ = suite.Client().Status().Update(ctx, found) + } + + if err := suite.Client().Get(ctx, deployKey, &appsv1.Deployment{}); err == nil { + return fmt.Errorf("orphaned deployment still exists") + } + return nil + }).WithContext(ctx).WithTimeout(30 * time.Second).WithPolling(500 * time.Millisecond).Should(Succeed()) + + By("UiAvailable condition is removed after cleanup") + Eventually(func(g Gomega, ctx context.Context) { + found := &rhtasv1.Rekor{} + g.Expect(suite.Client().Get(ctx, typeNamespaceName, found)).To(Succeed()) + g.Expect(meta.FindStatusCondition(found.Status.Conditions, actions.UICondition)).To(BeNil()) + }).WithContext(ctx).Should(Succeed()) + }) +}) diff --git a/internal/controller/securesign/actions/ensure_rekor.go b/internal/controller/securesign/actions/ensure_rekor.go index 35ed54229..ab96ee4ee 100644 --- a/internal/controller/securesign/actions/ensure_rekor.go +++ b/internal/controller/securesign/actions/ensure_rekor.go @@ -6,17 +6,20 @@ import ( "maps" "slices" + rhtasv1 "github.com/securesign/operator/api/v1" + v1alpha1 "github.com/securesign/operator/api/v1alpha1" "github.com/securesign/operator/internal/action" "github.com/securesign/operator/internal/annotations" "github.com/securesign/operator/internal/constants" "github.com/securesign/operator/internal/labels" + "github.com/securesign/operator/internal/migration" "github.com/securesign/operator/internal/state" "github.com/securesign/operator/internal/utils/kubernetes" "github.com/securesign/operator/internal/utils/kubernetes/ensure" - rhtasv1 "github.com/securesign/operator/api/v1" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -54,6 +57,7 @@ func (i rekorAction) Handle(ctx context.Context, instance *rhtasv1.Securesign) * ensure.ControllerReference[*rhtasv1.Rekor](instance, i.Client), ensure.Labels[*rhtasv1.Rekor](slices.Collect(maps.Keys(l)), l), ensure.Annotations[*rhtasv1.Rekor](annotations.InheritableAnnotations, instance.Annotations), + migration.Propagate[*rhtasv1.Rekor](v1alpha1.MigrationSearchUIData, instance.Annotations), func(object *rhtasv1.Rekor) error { defaulted := instance.Spec.Rekor.DeepCopy() defaulted.SetDefaults() @@ -70,6 +74,14 @@ func (i rekorAction) Handle(ctx context.Context, instance *rhtasv1.Securesign) * }) } + if migration.Has(instance, v1alpha1.MigrationSearchUIData) { + before := instance.DeepCopy() + migration.Remove(instance, v1alpha1.MigrationSearchUIData) + if err := i.Client.Patch(ctx, instance, client.MergeFrom(before)); err != nil { + return i.Error(ctx, fmt.Errorf("could not remove migration annotation from Securesign: %w", err), instance) + } + } + if result != controllerutil.OperationResultNone { meta.SetStatusCondition(&instance.Status.Conditions, v1.Condition{ Type: RekorCondition, diff --git a/internal/controller/testonly/suite.go b/internal/controller/testonly/suite.go index 250361757..15acd61e1 100644 --- a/internal/controller/testonly/suite.go +++ b/internal/controller/testonly/suite.go @@ -27,8 +27,10 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rhtasv1 "github.com/securesign/operator/api/v1" + rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" "github.com/securesign/operator/internal/controller" _ "github.com/securesign/operator/internal/controller/trillian/serviceresolver" + _ "github.com/securesign/operator/internal/controller/tuf/serviceresolver" testenvhelper "github.com/securesign/operator/internal/testing/envtest" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -66,6 +68,9 @@ func (t *controllerSuite) BeforeSuite() { t.ctx, t.cancel = context.WithCancel(context.Background()) By("bootstrapping test environment") + Expect(rhtasv1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(rhtasv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + t.testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, @@ -77,14 +82,10 @@ func (t *controllerSuite) BeforeSuite() { } var err error - // cfg is defined in this file globally. t.cfg, err = t.testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(t.cfg).NotTo(BeNil()) - err = rhtasv1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme t.k8sClient, err = client.New(t.cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/internal/controller/tuf/serviceresolver/resolver.go b/internal/controller/tuf/serviceresolver/resolver.go new file mode 100644 index 000000000..f05becbe0 --- /dev/null +++ b/internal/controller/tuf/serviceresolver/resolver.go @@ -0,0 +1,16 @@ +package serviceresolver + +import ( + "fmt" + + rhtasv1 "github.com/securesign/operator/api/v1" + "github.com/securesign/operator/internal/controller/tuf/constants" + "github.com/securesign/operator/internal/serviceresolver" +) + +func init() { + serviceresolver.Register( + func(obj *rhtasv1.Tuf) (string, error) { + return fmt.Sprintf("http://%s.%s.svc:%d", constants.DeploymentName, obj.Namespace, constants.Port), nil + }) +} diff --git a/internal/controller/tuf/tuf_controller.go b/internal/controller/tuf/tuf_controller.go index f52cf8c56..4af2b01f7 100644 --- a/internal/controller/tuf/tuf_controller.go +++ b/internal/controller/tuf/tuf_controller.go @@ -28,6 +28,7 @@ import ( "github.com/securesign/operator/internal/controller/predicate" "github.com/securesign/operator/internal/controller/tuf/actions" "github.com/securesign/operator/internal/controller/tuf/constants" + _ "github.com/securesign/operator/internal/controller/tuf/serviceresolver" fipsutil "github.com/securesign/operator/internal/utils/fips" v1 "k8s.io/api/apps/v1" v12 "k8s.io/api/core/v1" diff --git a/internal/images/images.go b/internal/images/images.go index 5234421bf..29f213b48 100644 --- a/internal/images/images.go +++ b/internal/images/images.go @@ -22,7 +22,6 @@ const ( RekorRedis Image = "RELATED_IMAGE_REKOR_REDIS" RekorServer Image = "RELATED_IMAGE_REKOR_SERVER" - RekorSearchUi Image = "RELATED_IMAGE_REKOR_SEARCH_UI" RekorMonitor Image = "RELATED_IMAGE_REKOR_MONITOR" BackfillRedis Image = "RELATED_IMAGE_BACKFILL_REDIS" @@ -48,7 +47,6 @@ var Images = []Image{ FulcioServer, RekorRedis, RekorServer, - RekorSearchUi, RekorMonitor, BackfillRedis, Tuf, diff --git a/internal/migration/migration.go b/internal/migration/migration.go new file mode 100644 index 000000000..df483896c --- /dev/null +++ b/internal/migration/migration.go @@ -0,0 +1,135 @@ +// Package migration provides helpers for preserving v1alpha1-only fields across +// API version conversions using annotations under the migration.rhtas.redhat.com domain. +// +// When a field exists in v1alpha1 but not in v1 (the hub), the conversion webhook +// serializes it into a migration annotation on the v1 object. A controller action +// then reads the annotation and creates the appropriate replacement resource. +// +// Typical lifecycle of a migration annotation: +// +// 1. ConvertTo (spoke→hub): migration.Set(hub, key, spokeField) +// 2. ConvertFrom (hub→spoke): migration.Pop(hub, key, &spokeField) +// — removes it before MarshalData so it doesn't leak into conversion-data +// 3. Propagation: ensure.Annotations or migration.Propagate copies +// the annotation from a parent CR to the child CR that will act on it +// 4. Controller action: migration.Has → migration.Read → create resource → migration.Remove +// +// Annotation key convention: +// +// migration.rhtas.redhat.com/{apiVersion}.{fieldName} +// +// Use [Key] to build keys that follow this convention. +package migration + +import ( + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/json" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const Prefix = "migration.rhtas.redhat.com/" + +// Key builds a migration annotation key following the convention: +// +// migration.rhtas.redhat.com/{apiVersion}.{field} +func Key(apiVersion, field string) string { + return Prefix + apiVersion + "." + field +} + +// Set marshals value as JSON and stores it under key in obj's annotations. +// Use in ConvertTo to preserve a spoke-only field on the hub object. +func Set(obj metav1.Object, key string, value any) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + a := obj.GetAnnotations() + if a == nil { + a = make(map[string]string) + } + a[key] = string(data) + obj.SetAnnotations(a) + return nil +} + +// Read unmarshals the annotation value into target without modifying obj. +// Returns false if the annotation is absent or empty. +// Use in controller actions where removal is a separate step after successful processing. +func Read(obj metav1.Object, key string, target any) (bool, error) { + a := obj.GetAnnotations() + data, ok := a[key] + if !ok || data == "" { + return false, nil + } + if err := json.Unmarshal([]byte(data), target); err != nil { + return false, err + } + return true, nil +} + +// Pop reads and removes the annotation in one step. +// Returns false if the annotation is absent or empty. +// Use in ConvertFrom to consume the annotation before MarshalData runs, +// preventing it from leaking into the conversion-data annotation. +func Pop(obj metav1.Object, key string, target any) (bool, error) { + ok, err := Read(obj, key, target) + if !ok || err != nil { + return ok, err + } + Remove(obj, key) + return true, nil +} + +// Has reports whether the migration annotation exists on obj. +// Use in controller action CanHandle methods. +func Has(obj metav1.Object, key string) bool { + a := obj.GetAnnotations() + _, ok := a[key] + return ok +} + +// Remove deletes the migration annotation from obj. +// Use in controller actions after the migration has been successfully processed. +func Remove(obj metav1.Object, key string) { + a := obj.GetAnnotations() + delete(a, key) + obj.SetAnnotations(a) +} + +// StripAll removes all migration annotations from obj. +// Use in roundtrip and conversion tests where migration data must not +// survive the spoke→hub→spoke cycle. +func StripAll(obj metav1.Object) { + a := obj.GetAnnotations() + for k := range a { + if strings.HasPrefix(k, Prefix) { + delete(a, k) + } + } + if len(a) == 0 { + a = nil + } + obj.SetAnnotations(a) +} + +// Propagate returns an ensure-style function that copies a migration annotation +// from srcAnnotations to the target object. Use in ensure_* actions to forward +// a migration annotation from a parent CR (e.g. Securesign) to the child CR +// (e.g. Rekor) whose controller will act on it. +func Propagate[T client.Object](key string, srcAnnotations map[string]string) func(T) error { + return func(obj T) error { + data, ok := srcAnnotations[key] + if !ok || data == "" { + return nil + } + a := obj.GetAnnotations() + if a == nil { + a = make(map[string]string) + } + a[key] = data + obj.SetAnnotations(a) + return nil + } +} diff --git a/internal/migration/migration_test.go b/internal/migration/migration_test.go new file mode 100644 index 000000000..f988fb35c --- /dev/null +++ b/internal/migration/migration_test.go @@ -0,0 +1,176 @@ +package migration + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newObj(annotations map[string]string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Annotations: annotations, + }, + } +} + +func TestKey(t *testing.T) { + got := Key("v1alpha1", "rekorSearchUI") + want := "migration.rhtas.redhat.com/v1alpha1.rekorSearchUI" + if got != want { + t.Errorf("Key() = %q, want %q", got, want) + } +} + +func TestSetAndRead(t *testing.T) { + obj := newObj(nil) + key := Key("v1alpha1", "field") + + type payload struct { + Name string `json:"name"` + Count int `json:"count"` + } + + if err := Set(obj, key, payload{Name: "test", Count: 42}); err != nil { + t.Fatalf("Set() error: %v", err) + } + + var got payload + ok, err := Read(obj, key, &got) + if err != nil { + t.Fatalf("Read() error: %v", err) + } + if !ok { + t.Fatal("Read() returned false, want true") + } + if got.Name != "test" || got.Count != 42 { + t.Errorf("Read() = %+v, want {Name:test Count:42}", got) + } +} + +func TestReadMissing(t *testing.T) { + obj := newObj(nil) + var target string + ok, err := Read(obj, Key("v1alpha1", "missing"), &target) + if err != nil { + t.Fatalf("Read() error: %v", err) + } + if ok { + t.Error("Read() returned true for missing key") + } +} + +func TestReadEmpty(t *testing.T) { + key := Key("v1alpha1", "empty") + obj := newObj(map[string]string{key: ""}) + var target string + ok, err := Read(obj, key, &target) + if err != nil { + t.Fatalf("Read() error: %v", err) + } + if ok { + t.Error("Read() returned true for empty value") + } +} + +func TestPop(t *testing.T) { + obj := newObj(nil) + key := Key("v1alpha1", "field") + if err := Set(obj, key, "hello"); err != nil { + t.Fatalf("Set() error: %v", err) + } + + var got string + ok, err := Pop(obj, key, &got) + if err != nil { + t.Fatalf("Pop() error: %v", err) + } + if !ok { + t.Fatal("Pop() returned false, want true") + } + if got != "hello" { + t.Errorf("Pop() = %q, want %q", got, "hello") + } + if Has(obj, key) { + t.Error("Pop() did not remove the annotation") + } +} + +func TestPopMissing(t *testing.T) { + obj := newObj(nil) + var target string + ok, err := Pop(obj, Key("v1alpha1", "missing"), &target) + if err != nil { + t.Fatalf("Pop() error: %v", err) + } + if ok { + t.Error("Pop() returned true for missing key") + } +} + +func TestHas(t *testing.T) { + key := Key("v1alpha1", "field") + obj := newObj(map[string]string{key: "data"}) + + if !Has(obj, key) { + t.Error("Has() returned false, want true") + } + if Has(obj, Key("v1alpha1", "other")) { + t.Error("Has() returned true for absent key") + } +} + +func TestRemove(t *testing.T) { + key := Key("v1alpha1", "field") + obj := newObj(map[string]string{key: "data", "other": "keep"}) + + Remove(obj, key) + if Has(obj, key) { + t.Error("Remove() did not delete the key") + } + if obj.GetAnnotations()["other"] != "keep" { + t.Error("Remove() deleted unrelated annotation") + } +} + +func TestStripAll(t *testing.T) { + obj := newObj(map[string]string{ + Key("v1alpha1", "field1"): "a", + Key("v1alpha1", "field2"): "b", + "unrelated": "keep", + }) + + StripAll(obj) + + if Has(obj, Key("v1alpha1", "field1")) || Has(obj, Key("v1alpha1", "field2")) { + t.Error("StripAll() did not remove all migration annotations") + } + if obj.GetAnnotations()["unrelated"] != "keep" { + t.Error("StripAll() removed non-migration annotation") + } +} + +func TestStripAllNilsEmptyMap(t *testing.T) { + obj := newObj(map[string]string{ + Key("v1alpha1", "only"): "data", + }) + + StripAll(obj) + + if obj.GetAnnotations() != nil { + t.Errorf("StripAll() should nil out annotations when empty, got %v", obj.GetAnnotations()) + } +} + +func TestSetNilAnnotations(t *testing.T) { + obj := newObj(nil) + key := Key("v1alpha1", "field") + if err := Set(obj, key, "value"); err != nil { + t.Fatalf("Set() error on nil annotations: %v", err) + } + if !Has(obj, key) { + t.Error("Set() on nil annotations did not create the key") + } +} diff --git a/test/e2e/custom_install/rolling_upgrade_test.go b/test/e2e/custom_install/rolling_upgrade_test.go index d88257671..29fef9aa1 100644 --- a/test/e2e/custom_install/rolling_upgrade_test.go +++ b/test/e2e/custom_install/rolling_upgrade_test.go @@ -48,7 +48,6 @@ func withRelatedImages() optManagerPod { func withReplicas(replicas int32) securesign.Opts { return func(s *rhtasv1.Securesign) { s.Spec.Rekor.Replicas = ptr.To(replicas) - s.Spec.Rekor.RekorSearchUI.Replicas = ptr.To(replicas) s.Spec.Fulcio.Replicas = ptr.To(replicas) s.Spec.Ctlog.Replicas = ptr.To(replicas) s.Spec.Tuf.Replicas = ptr.To(replicas) @@ -145,8 +144,6 @@ var _ = Describe("rolling upgrade with replicas", Ordered, func() { verifyDeploymentHasNewImage(ctx, cli, s.Namespace, rekorAction.RedisDeploymentName) verifyDeploymentHasNewImage(ctx, cli, s.Namespace, rekorAction.ServerDeploymentName) - verifyDeploymentHasNewImage(ctx, cli, s.Namespace, rekorAction.SearchUiDeploymentName) - verifyDeploymentHasNewImage(ctx, cli, s.Namespace, rekorAction.SearchUiDeploymentName) verifyDeploymentHasNewImage(ctx, cli, s.Namespace, trillianAction.LogserverDeploymentName) verifyDeploymentHasNewImage(ctx, cli, s.Namespace, trillianAction.LogsignerDeploymentName) diff --git a/test/e2e/fips/fips_test.go b/test/e2e/fips/fips_test.go index e46c1faf8..74f94ce15 100644 --- a/test/e2e/fips/fips_test.go +++ b/test/e2e/fips/fips_test.go @@ -62,7 +62,6 @@ var _ = Describe("Securesign FIPS Strict Mode (fips140=only)", Ordered, func() { securesign.WithIngress(), securesign.WithDefaultOIDC(), securesign.WithNTPMonitoring(), - securesign.WithSearchUI(), func(v *rhtasv1.Securesign) { v.Spec.Rekor.Attestations.Enabled = ptr.To(false) v.Spec.Ctlog.Monitoring.TLog.Enabled = ptr.To(true) @@ -178,33 +177,6 @@ var _ = Describe("Securesign FIPS Strict Mode (fips140=only)", Ordered, func() { verifyFipsOpenSSL(ctx, redis, rekoractions.RedisDeploymentName, getNamespace(), "/usr/bin/redis-server") }) - It("Verify rekor-ui is running in FIPS mode", func(ctx SpecContext) { - var ui *v1.Pod - Eventually(func(g Gomega, ctx context.Context) { - list := &v1.PodList{} - g.Expect(cli.List(ctx, list, - ctrlclient.InNamespace(getNamespace()), - ctrlclient.MatchingLabels{labels.LabelAppComponent: rekoractions.UIComponentName}, - )).To(Succeed()) - g.Expect(list.Items).To(HaveLen(1)) - ui = &list.Items[0] - }).WithContext(ctx).Should(Succeed()) - - verifyGodebugOnly(ctx, ui, rekoractions.SearchUiDeploymentName, getNamespace()) - - host, err := k8ssupport.ExecInPodWithOutput(ctx, ui.Name, rekoractions.SearchUiDeploymentName, getNamespace(), - "cat", "/proc/sys/crypto/fips_enabled", - ) - Expect(err).ToNot(HaveOccurred()) - Expect(strings.TrimSpace(string(host))).To(Equal("1")) - - node, err := k8ssupport.ExecInPodWithOutput(ctx, ui.Name, rekoractions.SearchUiDeploymentName, getNamespace(), - "node", "-p", "require('crypto').getFips()", - ) - Expect(err).ToNot(HaveOccurred()) - Expect(strings.TrimSpace(string(node))).To(Equal("1")) - }) - It("Verify TUF Server is running in FIPS mode", func(ctx SpecContext) { var server *v1.Pod Eventually(func() *v1.Pod { diff --git a/test/e2e/high_avalability/common_ha_install_test.go b/test/e2e/high_avalability/common_ha_install_test.go index 62d43fcfc..15b1855fe 100644 --- a/test/e2e/high_avalability/common_ha_install_test.go +++ b/test/e2e/high_avalability/common_ha_install_test.go @@ -3,9 +3,6 @@ package ha import ( - "net/http" - "time" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rhtasv1 "github.com/securesign/operator/api/v1" @@ -61,7 +58,6 @@ var _ = Describe("HA Securesign install", Ordered, func() { replicas = ptr.To(int32(2)) s = securesign.Create(namespace.Name, "test", securesign.ChooseDefaults(fipsEnabled, namespace.Name), - securesign.WithSearchUI(), securesign.WithReplicas(replicas), securesign.WithNFSPVC(), ) @@ -102,17 +98,6 @@ var _ = Describe("HA Securesign install", Ordered, func() { }).WithContext(ctx).Should(BeNumerically(">=", *replicas), "rekor server should have at least %d available replicas", *replicas) }) - It("rekor search ui should have the correct replica count", func(ctx SpecContext) { - rekor.VerifySearchUI(ctx, cli, namespace.Name) - Eventually(func(ctx SpecContext) (int32, error) { - var dep appsv1.Deployment - if err := cli.Get(ctx, types.NamespacedName{Namespace: namespace.Name, Name: rekoractions.SearchUiDeploymentName}, &dep); err != nil { - return 0, err - } - return dep.Status.AvailableReplicas, nil - }).WithContext(ctx).Should(BeNumerically(">=", *replicas), "rekor search ui should have at least %d available replicas", *replicas) - }) - It("ctlog should have the correct replica count", func(ctx SpecContext) { ctlog.Verify(ctx, cli, namespace.Name, s.Name) Eventually(func(ctx SpecContext) (int32, error) { @@ -169,7 +154,7 @@ var _ = Describe("HA Securesign install", Ordered, func() { }) It("Services have ready endpoints", func(ctx SpecContext) { - endpointNames := []string{ctlogactions.ComponentName, fulcioactions.DeploymentName, rekoractions.SearchUiDeploymentName, rekoractions.ServerComponentName, trillianactions.LogServerComponentName, trillianactions.LogSignerComponentName, tsaactions.DeploymentName, constants.ComponentName} + endpointNames := []string{ctlogactions.ComponentName, fulcioactions.DeploymentName, rekoractions.ServerComponentName, trillianactions.LogServerComponentName, trillianactions.LogSignerComponentName, tsaactions.DeploymentName, constants.ComponentName} for _, endpointName := range endpointNames { Eventually(kubernetes.ExpectServiceHasAtLeastNReadyEndpoints). WithContext(ctx). @@ -201,25 +186,6 @@ var _ = Describe("HA Securesign install", Ordered, func() { tas.VerifyByCosign(ctx, targetImageName, s.Status.TufStatus.Url, s.Status.FulcioStatus.Url, s.Status.RekorStatus.Url, s.Status.TSAStatus.Url) }) }) - It("rekor-search-ui remains functional when a pod is deleted", func(ctx SpecContext) { - kubernetes.RemainsFunctionalWhenOnePodDeleted(ctx, cli, namespace.Name, rekoractions.SearchUiDeploymentName, func() { - r := rekor.Get(ctx, cli, namespace.Name, s.Name) - Expect(r).ToNot(BeNil()) - Expect(r.Status.RekorSearchUIUrl).NotTo(BeEmpty()) - - httpClient := http.Client{ - Timeout: time.Second * 10, - } - Eventually(func() bool { - resp, err := httpClient.Get(r.Status.RekorSearchUIUrl) - if err != nil { - return false - } - defer func() { _ = resp.Body.Close() }() - return resp.StatusCode == http.StatusOK - }).Should(BeTrue(), "Rekor UI should be accessible and return a status code of 200") - }) - }) It("trillian-logserver remains functional when a pod is deleted", func(ctx SpecContext) { kubernetes.RemainsFunctionalWhenOnePodDeleted(ctx, cli, namespace.Name, trillianactions.LogServerComponentName, func() { s = securesign.Get(ctx, cli, namespace.Name, s.Name) diff --git a/test/e2e/high_avalability/non_ha_to_ha_test.go b/test/e2e/high_avalability/non_ha_to_ha_test.go index 1dd4c7706..be234b943 100644 --- a/test/e2e/high_avalability/non_ha_to_ha_test.go +++ b/test/e2e/high_avalability/non_ha_to_ha_test.go @@ -63,7 +63,6 @@ var _ = Describe("Securesign install with certificate generation", Ordered, func BeforeAll(func(ctx SpecContext) { s = securesign.Create(namespace.Name, "test", securesign.ChooseDefaults(fipsEnabled, namespace.Name), - securesign.WithSearchUI(), func(v *rhtasv1.Securesign) { v.Spec.Rekor.Attestations.Enabled = ptr.To(true) }, @@ -197,17 +196,6 @@ var _ = Describe("Securesign install with certificate generation", Ordered, func }).WithContext(ctx).Should(BeNumerically(">=", *replicas), "rekor server should have at least %d available replicas", *replicas) }) - It("rekor search ui should have the correct replica count", func(ctx SpecContext) { - rekor.VerifySearchUI(ctx, cli, namespace.Name) - Eventually(func(ctx SpecContext) (int32, error) { - var dep appsv1.Deployment - if err := cli.Get(ctx, types.NamespacedName{Namespace: namespace.Name, Name: rekoractions.SearchUiDeploymentName}, &dep); err != nil { - return 0, err - } - return dep.Status.AvailableReplicas, nil - }).WithContext(ctx).Should(BeNumerically(">=", *replicas), "rekor search ui should have at least %d available replicas", *replicas) - }) - It("ctlog should have the correct replica count", func(ctx SpecContext) { ctlog.Verify(ctx, cli, namespace.Name, s.Name) Eventually(func(ctx SpecContext) (int32, error) { @@ -267,7 +255,6 @@ var _ = Describe("Securesign install with certificate generation", Ordered, func endpointNames := []string{ ctlogactions.ComponentName, fulcioactions.DeploymentName, - rekoractions.SearchUiDeploymentName, rekoractions.ServerComponentName, trillianactions.LogServerComponentName, trillianactions.LogSignerComponentName, diff --git a/test/e2e/install/common_install_test.go b/test/e2e/install/common_install_test.go index 008dc2748..cb4ef04b3 100644 --- a/test/e2e/install/common_install_test.go +++ b/test/e2e/install/common_install_test.go @@ -3,9 +3,6 @@ package install import ( - "net/http" - "time" - "github.com/securesign/operator/internal/controller/tuf/constants" "github.com/securesign/operator/internal/labels" "github.com/securesign/operator/internal/utils/kubernetes" @@ -58,7 +55,6 @@ var _ = Describe("Securesign install with certificate generation", Ordered, func BeforeAll(func(ctx SpecContext) { s = securesign.Create(namespace.Name, "test", securesign.ChooseDefaults(fipsEnabled, namespace.Name), - securesign.WithSearchUI(), func(v *rhtasv1.Securesign) { // cover SECURESIGN-2694 v.Spec.Rekor.Attestations.Enabled = ptr.To(false) @@ -238,24 +234,6 @@ var _ = Describe("Securesign install with certificate generation", Ordered, func } }) - It("Verify Rekor Search UI is accessible", func(ctx SpecContext) { - r := rekor.Get(ctx, cli, namespace.Name, s.Name) - Expect(r).ToNot(BeNil()) - Expect(r.Status.RekorSearchUIUrl).NotTo(BeEmpty()) - - httpClient := http.Client{ - Timeout: time.Second * 10, - } - Eventually(func() bool { - resp, err := httpClient.Get(r.Status.RekorSearchUIUrl) - if err != nil { - return false - } - defer func() { _ = resp.Body.Close() }() - return resp.StatusCode == http.StatusOK - }, "30s", "1s").Should(BeTrue(), "Rekor UI should be accessible and return a status code of 200") - }) - It("Use cosign cli", func(ctx SpecContext) { s = securesign.Get(ctx, cli, namespace.Name, s.Name) tas.VerifyByCosign(ctx, targetImageName, s.Status.TufStatus.Url, s.Status.FulcioStatus.Url, s.Status.RekorStatus.Url, s.Status.TSAStatus.Url) diff --git a/test/e2e/lifecycle/key_rotation_test.go b/test/e2e/lifecycle/key_rotation_test.go index 4f5c30f3b..0d43f9ecd 100644 --- a/test/e2e/lifecycle/key_rotation_test.go +++ b/test/e2e/lifecycle/key_rotation_test.go @@ -88,7 +88,6 @@ var _ = Describe("Key rotation test", Ordered, func() { BeforeAll(func(ctx SpecContext) { s = securesign.Create(namespace.Name, "test", securesign.ChooseDefaults(fipsEnabled, namespace.Name), - securesign.WithSearchUI(), ) }) diff --git a/test/e2e/support/tas/rekor/search_ui.go b/test/e2e/support/tas/rekor/search_ui.go deleted file mode 100644 index 9d74ec105..000000000 --- a/test/e2e/support/tas/rekor/search_ui.go +++ /dev/null @@ -1,16 +0,0 @@ -package rekor - -import ( - "context" - - . "github.com/onsi/gomega" - "github.com/securesign/operator/internal/controller/rekor/actions" - "github.com/securesign/operator/test/e2e/support/condition" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func VerifySearchUI(ctx context.Context, cli client.Client, namespace string) { - Eventually(condition.DeploymentIsRunning).WithContext(ctx). - WithArguments(cli, namespace, actions.UIComponentName). - Should(BeTrue()) -} diff --git a/test/e2e/support/tas/securesign/securesign.go b/test/e2e/support/tas/securesign/securesign.go index 2527ae809..0496b1896 100644 --- a/test/e2e/support/tas/securesign/securesign.go +++ b/test/e2e/support/tas/securesign/securesign.go @@ -111,18 +111,6 @@ func WithoutMonitoring() Opts { } } -func WithSearchUI() Opts { - return func(s *rhtasv1.Securesign) { - s.Spec.Rekor.RekorSearchUI.Enabled = ptr.To(true) - } -} - -func WithoutSearchUI() Opts { - return func(s *rhtasv1.Securesign) { - s.Spec.Rekor.RekorSearchUI.Enabled = ptr.To(false) - } -} - func WithDefaultOIDC() Opts { return func(s *rhtasv1.Securesign) { s.Spec.Fulcio.Config = rhtasv1.FulcioConfig{ @@ -403,7 +391,6 @@ func WithReplicas(replicas *int32) Opts { return func(s *rhtasv1.Securesign) { s.Spec.Fulcio.Replicas = replicas s.Spec.Rekor.Replicas = replicas - s.Spec.Rekor.RekorSearchUI.Replicas = replicas s.Spec.Ctlog.Replicas = replicas s.Spec.TimestampAuthority.Replicas = replicas s.Spec.Tuf.Replicas = replicas diff --git a/test/e2e/update/suite_test.go b/test/e2e/update/suite_test.go index be2d3089b..75db30908 100644 --- a/test/e2e/update/suite_test.go +++ b/test/e2e/update/suite_test.go @@ -40,7 +40,6 @@ var fipsEnabled bool func securesignResource(namespace *v1.Namespace) *rhtasv1.Securesign { return securesign.Create(namespace.Name, "test", securesign.ChooseDefaults(fipsEnabled, namespace.Name), - securesign.WithoutSearchUI(), ) } diff --git a/test/e2e/upgrade_test.go b/test/e2e/upgrade_test.go index 58ac20c5b..cb5469b21 100644 --- a/test/e2e/upgrade_test.go +++ b/test/e2e/upgrade_test.go @@ -28,6 +28,7 @@ import ( "github.com/securesign/operator/test/e2e/support/tas/securesign" v13 "k8s.io/api/apps/v1" rbacV1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -306,7 +307,6 @@ var _ = Describe("Operator upgrade", Ordered, func() { ctl.DeploymentName: images.Registry.Get(images.CTLog), tufAction.DeploymentName: images.Registry.Get(images.HttpServer), rekorAction.ServerDeploymentName: images.Registry.Get(images.RekorServer), - rekorAction.SearchUiDeploymentName: images.Registry.Get(images.RekorSearchUi), trillianAction.LogsignerDeploymentName: images.Registry.Get(images.TrillianLogSigner), trillianAction.LogserverDeploymentName: images.Registry.Get(images.TrillianServer), tsaAction.DeploymentName: images.Registry.Get(images.TimestampAuthority), @@ -332,6 +332,39 @@ var _ = Describe("Operator upgrade", Ordered, func() { tas.VerifyAllComponents(ctx, cli, v1Securesign, !fipsEnabled, true) }) + It("Verify RekorSearchUI migrated to Console CR", func(ctx SpecContext) { + gomega.Eventually(func(g gomega.Gomega) { + console := &rhtasv1.Console{} + g.Expect(cli.Get(ctx, types.NamespacedName{ + Name: securesignDeployment.Name + "-console", + Namespace: namespace.Name, + }, console)).To(gomega.Succeed()) + g.Expect(console.Spec.UI.Ingress.Enabled).To(gomega.Equal(ptr.To(true))) + }).Should(gomega.Succeed()) + + orphanedResources := []runtimeCli.Object{ + &v13.Deployment{ObjectMeta: metav1.ObjectMeta{Name: rekorAction.SearchUiDeploymentName, Namespace: namespace.Name}}, + &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: rekorAction.SearchUiDeploymentName, Namespace: namespace.Name}}, + &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: rekorAction.RBACUIName, Namespace: namespace.Name}}, + &rbacV1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: rekorAction.RBACUIName, Namespace: namespace.Name}}, + } + for _, obj := range orphanedResources { + key := runtimeCli.ObjectKeyFromObject(obj) + gomega.Eventually(func() bool { + return errors.IsNotFound(cli.Get(ctx, key, obj)) + }).Should(gomega.BeTrue(), "orphaned resource %s/%s should be deleted", key.Namespace, key.Name) + } + + gomega.Eventually(func(g gomega.Gomega) { + r := &rhtasv1.Rekor{} + g.Expect(cli.Get(ctx, types.NamespacedName{ + Name: securesignDeployment.Name, + Namespace: namespace.Name, + }, r)).To(gomega.Succeed()) + g.Expect(meta.FindStatusCondition(r.Status.Conditions, rekorAction.UICondition)).To(gomega.BeNil()) + }).Should(gomega.Succeed()) + }) + It("Enforce PSA restricted:latest after upgrade", func(ctx SpecContext) { support.EnforcePSARestricted(ctx, cli, namespace) })