diff --git a/PROJECT b/PROJECT index 318f3c3..50b2f08 100644 --- a/PROJECT +++ b/PROJECT @@ -17,4 +17,22 @@ resources: kind: Synapse path: github.com/opdev/synapse-operator/apis/synapse/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: opdev.io + group: synapse + kind: MautrixSignal + path: github.com/opdev/synapse-operator/apis/synapse/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: opdev.io + group: synapse + kind: Heisenbridge + path: github.com/opdev/synapse-operator/apis/synapse/v1alpha1 + version: v1alpha1 version: "3" diff --git a/apis/synapse/v1alpha1/heisenbridge_types.go b/apis/synapse/v1alpha1/heisenbridge_types.go new file mode 100644 index 0000000..06f4c60 --- /dev/null +++ b/apis/synapse/v1alpha1/heisenbridge_types.go @@ -0,0 +1,102 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// HeisenbridgeSpec defines the desired state of Heisenbridge. The user can +// either: +// - enable the bridge, without specifying additional configuration options. +// The bridge will be deployed with a default configuration. +// - enable the bridge and specify an existing ConfigMap by its Name and +// Namespace containing a heisenbridge.yaml. +type HeisenbridgeSpec struct { + // Holds information about the ConfigMap containing the heisenbridge.yaml + // configuration file to be used as input for the configuration of the + // Heisenbridge IRC Bridge. + ConfigMap HeisenbridgeConfigMap `json:"configMap,omitempty"` + + // +kubebuilder:default:=0 + + // Controls the verbosity of the Heisenbrige: + // * 0 corresponds to normal level of logs + // * 1 corresponds to "-v" + // * 2 corresponds to "-vv" + // * 3 corresponds to "-vvv" + VerboseLevel int `json:"verboseLevel,omitempty"` + + // +kubebuilder:validation:Required + + // Name of the Synapse instance, living in the same namespace. + Synapse HeisenbridgeSynapseSpec `json:"synapse"` +} + +type HeisenbridgeSynapseSpec struct { + // Name of the Synapse instance + Name string `json:"name,omitempty"` + + // Namespace of the Synapse instance + // TODO: Complete + Namespace string `json:"namespace,omitempty"` +} + +type HeisenbridgeConfigMap struct { + // +kubebuilder:validation:Required + + // Name of the ConfigMap in the given Namespace. + Name string `json:"name"` + + // Namespace in which the ConfigMap is living. If left empty, the + // Heisenbridge namespace is used. + Namespace string `json:"namespace,omitempty"` +} + +// HeisenbridgeStatus defines the observed state of Heisenbridge +type HeisenbridgeStatus struct { + // State of the Heisenbridge instance + State string `json:"state,omitempty"` + + // Reason for the current Heisenbridge State + Reason string `json:"reason,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Heisenbridge is the Schema for the heisenbridges API +type Heisenbridge struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HeisenbridgeSpec `json:"spec,omitempty"` + Status HeisenbridgeStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// HeisenbridgeList contains a list of Heisenbridge +type HeisenbridgeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Heisenbridge `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Heisenbridge{}, &HeisenbridgeList{}) +} diff --git a/apis/synapse/v1alpha1/mautrixsignal_types.go b/apis/synapse/v1alpha1/mautrixsignal_types.go new file mode 100644 index 0000000..012cea1 --- /dev/null +++ b/apis/synapse/v1alpha1/mautrixsignal_types.go @@ -0,0 +1,102 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// MautrixSignalSpec defines the desired state of MautrixSignal. The user can +// either: +// - enable the bridge, without specifying additional configuration options. +// The bridge will be deployed with a default configuration. +// - enable the bridge and specify an existing ConfigMap by its Name and +// Namespace containing a config.yaml file. +type MautrixSignalSpec struct { + // Holds information about the ConfigMap containing the config.yaml + // configuration file to be used as input for the configuration of the + // mautrix-signal bridge. + ConfigMap MautrixSignalConfigMap `json:"configMap,omitempty"` + + // +kubebuilder:validation:Required + + // Name of the Synapse instance, living in the same namespace. + Synapse MautrixSignalSynapseSpec `json:"synapse"` +} + +type MautrixSignalSynapseSpec struct { + // +kubebuilder:validation:Required + + // Name of the Synapse instance + Name string `json:"name"` + + // Namespace of the Synapse instance + // TODO: Complete + Namespace string `json:"namespace,omitempty"` +} + +type MautrixSignalConfigMap struct { + // +kubebuilder:validation:Required + + // Name of the ConfigMap in the given Namespace. + Name string `json:"name"` + + // Namespace in which the ConfigMap is living. If left empty, the Synapse + // namespace is used. + Namespace string `json:"namespace,omitempty"` +} + +// MautrixSignalStatus defines the observed state of MautrixSignal +type MautrixSignalStatus struct { + // State of the MautrixSignal instance + State string `json:"state,omitempty"` + + // Reason for the current MautrixSignal State + Reason string `json:"reason,omitempty"` + + // Information related to the Synapse instance associated with this bridge + Synapse MautrixSignalStatusSynapse `json:"synapse,omitempty"` +} + +type MautrixSignalStatusSynapse struct { + ServerName string `json:"serverName,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// MautrixSignal is the Schema for the mautrixsignals API +type MautrixSignal struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MautrixSignalSpec `json:"spec,omitempty"` + Status MautrixSignalStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// MautrixSignalList contains a list of MautrixSignal +type MautrixSignalList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []MautrixSignal `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MautrixSignal{}, &MautrixSignalList{}) +} diff --git a/apis/synapse/v1alpha1/synapse_types.go b/apis/synapse/v1alpha1/synapse_types.go index 85ebfa4..8a9d324 100644 --- a/apis/synapse/v1alpha1/synapse_types.go +++ b/apis/synapse/v1alpha1/synapse_types.go @@ -36,9 +36,6 @@ type SynapseSpec struct { // the creation of a configuration file from scratch. Homeserver SynapseHomeserver `json:"homeserver"` - // Configuration options for optional matrix bridges - Bridges SynapseBridges `json:"bridges,omitempty"` - // +kubebuilder:default:=false // Set to true to create a new PostreSQL instance. The homeserver.yaml @@ -80,97 +77,54 @@ type SynapseHomeserverValues struct { ReportStats bool `json:"reportStats"` } -type SynapseBridges struct { - // Configuration options for the IRC bridge Heisenbridge. The user can - // either: - // * disable the deployment of the bridge. - // * enable the bridge, without specifying additional configuration - // options. The bridge will be deployed with a default configuration. - // * enable the bridge and specify an existing ConfigMap by its Name and - // Namespace containing a heisenbridge.yaml. - Heisenbridge SynapseHeisenbridge `json:"heisenbridge,omitempty"` - - // Configuration options for the mautrix-signal bridge. The user can - // either: - // * disable the deployment of the bridge. - // * enable the bridge, without specifying additional configuration - // options. The bridge will be deployed with a default configuration. - // * enable the bridge and specify an existing ConfigMap by its Name and - // Namespace containing a config.yaml file. - MautrixSignal SynapseMautrixSignal `json:"mautrixSignal,omitempty"` -} +// SynapseStatus defines the observed state of Synapse +type SynapseStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file -type SynapseHeisenbridge struct { - // +kubebuilder:default:=false + // Connection information to the external PostgreSQL Database + DatabaseConnectionInfo SynapseStatusDatabaseConnectionInfo `json:"databaseConnectionInfo,omitempty"` - // Whether to deploy Heisenbridge or not - Enabled bool `json:"enabled,omitempty"` + // Holds configuration information for Synapse + HomeserverConfiguration SynapseStatusHomeserverConfiguration `json:"homeserverConfiguration,omitempty"` - // Holds information about the ConfigMap containing the heisenbridge.yaml - // configuration file to be used as input for the configuration of the - // Heisenbridge IRC Bridge. - ConfigMap SynapseHeisenbridgeConfigMap `json:"configMap,omitempty"` + // Information on the bridges deployed alongside Synapse + Bridges SynapseStatusBridges `json:"bridges,omitempty"` - // +kubebuilder:default:=0 + // State of the Synapse instance + State string `json:"state,omitempty"` + + // Reason for the current Synapse State + Reason string `json:"reason,omitempty"` - // Controls the verbosity of the Heisenbrige: - // * 0 corresponds to normal level of logs - // * 1 corresponds to "-v" - // * 2 corresponds to "-vv" - // * 3 corresponds to "-vvv" - VerboseLevel int `json:"verboseLevel,omitempty"` + // +kubebuilder:default:=false + NeedsReconcile bool `json:"needsReconcile,omitempty"` } -type SynapseHeisenbridgeConfigMap struct { - // +kubebuilder:validation:Required +type SynapseStatusBridges struct { + // Information on the Heisenbridge (IRC Bridge). + Heisenbridge SynapseStatusBridgesHeisenbridge `json:"heisenbridge,omitempty"` - // Name of the ConfigMap in the given Namespace. - Name string `json:"name"` - - // Namespace in which the ConfigMap is living. If left empty, the Synapse - // namespace is used. - Namespace string `json:"namespace,omitempty"` + // Information on the mautrix-signal bridge. + MautrixSignal SynapseStatusBridgesMautrixSignal `json:"mautrixsignal,omitempty"` } -type SynapseMautrixSignal struct { +type SynapseStatusBridgesHeisenbridge struct { // +kubebuilder:default:=false - // Whether to deploy mautrix-signal or not + // Whether a Heisenbridge has been deployed for this Synapse instance Enabled bool `json:"enabled,omitempty"` - // Holds information about the ConfigMap containing the config.yaml - // configuration file to be used as input for the configuration of the - // mautrix-signal Bridge. - ConfigMap SynapseMautrixSignalConfigMap `json:"configMap,omitempty"` + // Name of the Heisenbridge object + Name string `json:"name,omitempty"` } -type SynapseMautrixSignalConfigMap struct { - // +kubebuilder:validation:Required - - // Name of the ConfigMap in the given Namespace. - Name string `json:"name"` - - // Namespace in which the ConfigMap is living. If left empty, the Synapse - // namespace is used. - Namespace string `json:"namespace,omitempty"` -} - -// SynapseStatus defines the observed state of Synapse -type SynapseStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Connection information to the external PostgreSQL Database - DatabaseConnectionInfo SynapseStatusDatabaseConnectionInfo `json:"databaseConnectionInfo,omitempty"` - - // Holds configuration information for Synapse - HomeserverConfiguration SynapseStatusHomeserverConfiguration `json:"homeserverConfiguration,omitempty"` - - // State of the Synapse instance - State string `json:"state,omitempty"` +type SynapseStatusBridgesMautrixSignal struct { + // Whether a mautrix-signal has been deployed for this Synapse instance + Enabled bool `json:"enabled,omitempty"` - // Reason for the current Synapse State - Reason string `json:"reason,omitempty"` + // Name of the mautrix-signal bridge object + Name string `json:"name,omitempty"` } type SynapseStatusDatabaseConnectionInfo struct { diff --git a/apis/synapse/v1alpha1/zz_generated.deepcopy.go b/apis/synapse/v1alpha1/zz_generated.deepcopy.go index 40915b6..77917c8 100644 --- a/apis/synapse/v1alpha1/zz_generated.deepcopy.go +++ b/apis/synapse/v1alpha1/zz_generated.deepcopy.go @@ -26,26 +26,26 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Synapse) DeepCopyInto(out *Synapse) { +func (in *Heisenbridge) DeepCopyInto(out *Heisenbridge) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) + out.Spec = in.Spec out.Status = in.Status } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Synapse. -func (in *Synapse) DeepCopy() *Synapse { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Heisenbridge. +func (in *Heisenbridge) DeepCopy() *Heisenbridge { if in == nil { return nil } - out := new(Synapse) + out := new(Heisenbridge) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Synapse) DeepCopyObject() runtime.Object { +func (in *Heisenbridge) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -53,53 +53,263 @@ func (in *Synapse) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseBridges) DeepCopyInto(out *SynapseBridges) { +func (in *HeisenbridgeConfigMap) DeepCopyInto(out *HeisenbridgeConfigMap) { *out = *in - out.Heisenbridge = in.Heisenbridge - out.MautrixSignal = in.MautrixSignal } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseBridges. -func (in *SynapseBridges) DeepCopy() *SynapseBridges { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeisenbridgeConfigMap. +func (in *HeisenbridgeConfigMap) DeepCopy() *HeisenbridgeConfigMap { + if in == nil { + return nil + } + out := new(HeisenbridgeConfigMap) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeisenbridgeList) DeepCopyInto(out *HeisenbridgeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Heisenbridge, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeisenbridgeList. +func (in *HeisenbridgeList) DeepCopy() *HeisenbridgeList { if in == nil { return nil } - out := new(SynapseBridges) + out := new(HeisenbridgeList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HeisenbridgeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseHeisenbridge) DeepCopyInto(out *SynapseHeisenbridge) { +func (in *HeisenbridgeSpec) DeepCopyInto(out *HeisenbridgeSpec) { *out = *in out.ConfigMap = in.ConfigMap + out.Synapse = in.Synapse } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseHeisenbridge. -func (in *SynapseHeisenbridge) DeepCopy() *SynapseHeisenbridge { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeisenbridgeSpec. +func (in *HeisenbridgeSpec) DeepCopy() *HeisenbridgeSpec { if in == nil { return nil } - out := new(SynapseHeisenbridge) + out := new(HeisenbridgeSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseHeisenbridgeConfigMap) DeepCopyInto(out *SynapseHeisenbridgeConfigMap) { +func (in *HeisenbridgeStatus) DeepCopyInto(out *HeisenbridgeStatus) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseHeisenbridgeConfigMap. -func (in *SynapseHeisenbridgeConfigMap) DeepCopy() *SynapseHeisenbridgeConfigMap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeisenbridgeStatus. +func (in *HeisenbridgeStatus) DeepCopy() *HeisenbridgeStatus { if in == nil { return nil } - out := new(SynapseHeisenbridgeConfigMap) + out := new(HeisenbridgeStatus) in.DeepCopyInto(out) return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeisenbridgeSynapseSpec) DeepCopyInto(out *HeisenbridgeSynapseSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeisenbridgeSynapseSpec. +func (in *HeisenbridgeSynapseSpec) DeepCopy() *HeisenbridgeSynapseSpec { + if in == nil { + return nil + } + out := new(HeisenbridgeSynapseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignal) DeepCopyInto(out *MautrixSignal) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignal. +func (in *MautrixSignal) DeepCopy() *MautrixSignal { + if in == nil { + return nil + } + out := new(MautrixSignal) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MautrixSignal) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalConfigMap) DeepCopyInto(out *MautrixSignalConfigMap) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalConfigMap. +func (in *MautrixSignalConfigMap) DeepCopy() *MautrixSignalConfigMap { + if in == nil { + return nil + } + out := new(MautrixSignalConfigMap) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalList) DeepCopyInto(out *MautrixSignalList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MautrixSignal, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalList. +func (in *MautrixSignalList) DeepCopy() *MautrixSignalList { + if in == nil { + return nil + } + out := new(MautrixSignalList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MautrixSignalList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalSpec) DeepCopyInto(out *MautrixSignalSpec) { + *out = *in + out.ConfigMap = in.ConfigMap + out.Synapse = in.Synapse +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalSpec. +func (in *MautrixSignalSpec) DeepCopy() *MautrixSignalSpec { + if in == nil { + return nil + } + out := new(MautrixSignalSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalStatus) DeepCopyInto(out *MautrixSignalStatus) { + *out = *in + out.Synapse = in.Synapse +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalStatus. +func (in *MautrixSignalStatus) DeepCopy() *MautrixSignalStatus { + if in == nil { + return nil + } + out := new(MautrixSignalStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalStatusSynapse) DeepCopyInto(out *MautrixSignalStatusSynapse) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalStatusSynapse. +func (in *MautrixSignalStatusSynapse) DeepCopy() *MautrixSignalStatusSynapse { + if in == nil { + return nil + } + out := new(MautrixSignalStatusSynapse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MautrixSignalSynapseSpec) DeepCopyInto(out *MautrixSignalSynapseSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MautrixSignalSynapseSpec. +func (in *MautrixSignalSynapseSpec) DeepCopy() *MautrixSignalSynapseSpec { + if in == nil { + return nil + } + out := new(MautrixSignalSynapseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Synapse) DeepCopyInto(out *Synapse) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Synapse. +func (in *Synapse) DeepCopy() *Synapse { + if in == nil { + return nil + } + out := new(Synapse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Synapse) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SynapseHomeserver) DeepCopyInto(out *SynapseHomeserver) { *out = *in @@ -188,66 +398,82 @@ func (in *SynapseList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseMautrixSignal) DeepCopyInto(out *SynapseMautrixSignal) { +func (in *SynapseSpec) DeepCopyInto(out *SynapseSpec) { *out = *in - out.ConfigMap = in.ConfigMap + in.Homeserver.DeepCopyInto(&out.Homeserver) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseMautrixSignal. -func (in *SynapseMautrixSignal) DeepCopy() *SynapseMautrixSignal { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseSpec. +func (in *SynapseSpec) DeepCopy() *SynapseSpec { if in == nil { return nil } - out := new(SynapseMautrixSignal) + out := new(SynapseSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseMautrixSignalConfigMap) DeepCopyInto(out *SynapseMautrixSignalConfigMap) { +func (in *SynapseStatus) DeepCopyInto(out *SynapseStatus) { *out = *in + out.DatabaseConnectionInfo = in.DatabaseConnectionInfo + out.HomeserverConfiguration = in.HomeserverConfiguration + out.Bridges = in.Bridges } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseMautrixSignalConfigMap. -func (in *SynapseMautrixSignalConfigMap) DeepCopy() *SynapseMautrixSignalConfigMap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseStatus. +func (in *SynapseStatus) DeepCopy() *SynapseStatus { if in == nil { return nil } - out := new(SynapseMautrixSignalConfigMap) + out := new(SynapseStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseSpec) DeepCopyInto(out *SynapseSpec) { +func (in *SynapseStatusBridges) DeepCopyInto(out *SynapseStatusBridges) { *out = *in - in.Homeserver.DeepCopyInto(&out.Homeserver) - out.Bridges = in.Bridges + out.Heisenbridge = in.Heisenbridge + out.MautrixSignal = in.MautrixSignal } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseSpec. -func (in *SynapseSpec) DeepCopy() *SynapseSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseStatusBridges. +func (in *SynapseStatusBridges) DeepCopy() *SynapseStatusBridges { if in == nil { return nil } - out := new(SynapseSpec) + out := new(SynapseStatusBridges) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SynapseStatus) DeepCopyInto(out *SynapseStatus) { +func (in *SynapseStatusBridgesHeisenbridge) DeepCopyInto(out *SynapseStatusBridgesHeisenbridge) { *out = *in - out.DatabaseConnectionInfo = in.DatabaseConnectionInfo - out.HomeserverConfiguration = in.HomeserverConfiguration } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseStatus. -func (in *SynapseStatus) DeepCopy() *SynapseStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseStatusBridgesHeisenbridge. +func (in *SynapseStatusBridgesHeisenbridge) DeepCopy() *SynapseStatusBridgesHeisenbridge { if in == nil { return nil } - out := new(SynapseStatus) + out := new(SynapseStatusBridgesHeisenbridge) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SynapseStatusBridgesMautrixSignal) DeepCopyInto(out *SynapseStatusBridgesMautrixSignal) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseStatusBridgesMautrixSignal. +func (in *SynapseStatusBridgesMautrixSignal) DeepCopy() *SynapseStatusBridgesMautrixSignal { + if in == nil { + return nil + } + out := new(SynapseStatusBridgesMautrixSignal) in.DeepCopyInto(out) return out } diff --git a/bundle.Dockerfile b/bundle.Dockerfile index c9eba78..6a29a86 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=synapse-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.14.0+git +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.25.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 diff --git a/bundle/manifests/synapse-operator.clusterserviceversion.yaml b/bundle/manifests/synapse-operator.clusterserviceversion.yaml index 2aece18..c85ef5e 100644 --- a/bundle/manifests/synapse-operator.clusterserviceversion.yaml +++ b/bundle/manifests/synapse-operator.clusterserviceversion.yaml @@ -4,6 +4,44 @@ metadata: annotations: alm-examples: |- [ + { + "apiVersion": "synapse.opdev.io/v1alpha1", + "kind": "Heisenbridge", + "metadata": { + "labels": { + "app.kubernetes.io/created-by": "synapse-operator", + "app.kubernetes.io/instance": "heisenbridge-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "heisenbridge", + "app.kubernetes.io/part-of": "synapse-operator" + }, + "name": "heisenbridge-sample" + }, + "spec": { + "synapse": { + "name": "synapse-sample" + } + } + }, + { + "apiVersion": "synapse.opdev.io/v1alpha1", + "kind": "MautrixSignal", + "metadata": { + "labels": { + "app.kubernetes.io/created-by": "synapse-operator", + "app.kubernetes.io/instance": "mautrixsignal-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "mautrixsignal", + "app.kubernetes.io/part-of": "synapse-operator" + }, + "name": "mautrixsignal-sample" + }, + "spec": { + "synapse": { + "name": "synapse-sample" + } + } + }, { "apiVersion": "synapse.opdev.io/v1alpha1", "kind": "Synapse", @@ -15,21 +53,31 @@ metadata: "homeserver": { "values": { "reportStats": true, - "serverName": "example.com" + "serverName": "my.matrix.host" } } } } ] capabilities: Basic Install - operators.operatorframework.io/builder: operator-sdk-v1.14.0+git + operators.operatorframework.io/builder: operator-sdk-v1.25.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 - name: synapse-operator.v0.1.0 + name: synapse-operator.v0.2.0 namespace: placeholder spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: Heisenbridge is the Schema for the heisenbridges API + displayName: Heisenbridge + kind: Heisenbridge + name: heisenbridges.synapse.opdev.io + version: v1alpha1 + - description: MautrixSignal is the Schema for the mautrixsignals API + displayName: Mautrix Signal + kind: MautrixSignal + name: mautrixsignals.synapse.opdev.io + version: v1alpha1 - description: Synapse is the Schema for the synapses API displayName: Synapse kind: Synapse @@ -46,6 +94,58 @@ spec: spec: clusterPermissions: - rules: + - apiGroups: + - synapse.opdev.io + resources: + - heisenbridges + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/finalizers + verbs: + - update + - apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/status + verbs: + - get + - patch + - update + - apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/finalizers + verbs: + - update + - apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/status + verbs: + - get + - patch + - update - apiGroups: - synapse.opdev.io resources: @@ -86,7 +186,9 @@ spec: - create serviceAccountName: synapse-operator-controller-manager deployments: - - name: synapse-operator-controller-manager + - label: + control-plane: controller-manager + name: synapse-operator-controller-manager spec: replicas: 1 selector: @@ -117,7 +219,7 @@ spec: - --leader-elect command: - /manager - image: quay.io/opdev/synapse-operator:v0.1.0 + image: quay.io/opdev/synapse-operator:v0.2.0 livenessProbe: httpGet: path: /healthz @@ -200,4 +302,4 @@ spec: maturity: alpha provider: name: Opdev - version: 0.1.0 + version: 0.2.0 diff --git a/bundle/manifests/synapse.opdev.io_heisenbridges.yaml b/bundle/manifests/synapse.opdev.io_heisenbridges.yaml new file mode 100644 index 0000000..32decd3 --- /dev/null +++ b/bundle/manifests/synapse.opdev.io_heisenbridges.yaml @@ -0,0 +1,95 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: heisenbridges.synapse.opdev.io +spec: + group: synapse.opdev.io + names: + kind: Heisenbridge + listKind: HeisenbridgeList + plural: heisenbridges + singular: heisenbridge + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Heisenbridge is the Schema for the heisenbridges API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: 'HeisenbridgeSpec defines the desired state of Heisenbridge. + The user can either: - enable the bridge, without specifying additional + configuration options. The bridge will be deployed with a default configuration. + - enable the bridge and specify an existing ConfigMap by its Name and + Namespace containing a heisenbridge.yaml.' + properties: + configMap: + description: Holds information about the ConfigMap containing the + heisenbridge.yaml configuration file to be used as input for the + configuration of the Heisenbridge IRC Bridge. + properties: + name: + description: Name of the ConfigMap in the given Namespace. + type: string + namespace: + description: Namespace in which the ConfigMap is living. If left + empty, the Heisenbridge namespace is used. + type: string + required: + - name + type: object + synapse: + description: Name of the Synapse instance, living in the same namespace. + properties: + name: + description: Name of the Synapse instance + type: string + namespace: + description: 'Namespace of the Synapse instance TODO: Complete' + type: string + type: object + verboseLevel: + default: 0 + description: 'Controls the verbosity of the Heisenbrige: * 0 corresponds + to normal level of logs * 1 corresponds to "-v" * 2 corresponds + to "-vv" * 3 corresponds to "-vvv"' + type: integer + required: + - synapse + type: object + status: + description: HeisenbridgeStatus defines the observed state of Heisenbridge + properties: + reason: + description: Reason for the current Heisenbridge State + type: string + state: + description: State of the Heisenbridge instance + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/synapse.opdev.io_mautrixsignals.yaml b/bundle/manifests/synapse.opdev.io_mautrixsignals.yaml new file mode 100644 index 0000000..dfdc262 --- /dev/null +++ b/bundle/manifests/synapse.opdev.io_mautrixsignals.yaml @@ -0,0 +1,98 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: mautrixsignals.synapse.opdev.io +spec: + group: synapse.opdev.io + names: + kind: MautrixSignal + listKind: MautrixSignalList + plural: mautrixsignals + singular: mautrixsignal + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: MautrixSignal is the Schema for the mautrixsignals API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: 'MautrixSignalSpec defines the desired state of MautrixSignal. + The user can either: - enable the bridge, without specifying additional + configuration options. The bridge will be deployed with a default configuration. + - enable the bridge and specify an existing ConfigMap by its Name and + Namespace containing a config.yaml file.' + properties: + configMap: + description: Holds information about the ConfigMap containing the + config.yaml configuration file to be used as input for the configuration + of the mautrix-signal bridge. + properties: + name: + description: Name of the ConfigMap in the given Namespace. + type: string + namespace: + description: Namespace in which the ConfigMap is living. If left + empty, the Synapse namespace is used. + type: string + required: + - name + type: object + synapse: + description: Name of the Synapse instance, living in the same namespace. + properties: + name: + description: Name of the Synapse instance + type: string + namespace: + description: 'Namespace of the Synapse instance TODO: Complete' + type: string + required: + - name + type: object + required: + - synapse + type: object + status: + description: MautrixSignalStatus defines the observed state of MautrixSignal + properties: + reason: + description: Reason for the current MautrixSignal State + type: string + state: + description: State of the MautrixSignal instance + type: string + synapse: + description: Information related to the Synapse instance associated + with this bridge + properties: + serverName: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/synapse.opdev.io_synapses.yaml b/bundle/manifests/synapse.opdev.io_synapses.yaml index 8d1d27e..454850c 100644 --- a/bundle/manifests/synapse.opdev.io_synapses.yaml +++ b/bundle/manifests/synapse.opdev.io_synapses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: synapses.synapse.opdev.io spec: @@ -34,72 +34,6 @@ spec: spec: description: SynapseSpec defines the desired state of Synapse properties: - bridges: - description: Configuration options for optional matrix bridges - properties: - heisenbridge: - description: 'Configuration options for the IRC bridge Heisenbridge. - The user can either: * disable the deployment of the bridge. - * enable the bridge, without specifying additional configuration options. - The bridge will be deployed with a default configuration. * - enable the bridge and specify an existing ConfigMap by its Name - and Namespace containing a heisenbridge.yaml.' - properties: - configMap: - description: Holds information about the ConfigMap containing - the heisenbridge.yaml configuration file to be used as input - for the configuration of the Heisenbridge IRC Bridge. - properties: - name: - description: Name of the ConfigMap in the given Namespace. - type: string - namespace: - description: Namespace in which the ConfigMap is living. - If left empty, the Synapse namespace is used. - type: string - required: - - name - type: object - enabled: - default: false - description: Whether to deploy Heisenbridge or not - type: boolean - verboseLevel: - default: 0 - description: 'Controls the verbosity of the Heisenbrige: * - 0 corresponds to normal level of logs * 1 corresponds to - "-v" * 2 corresponds to "-vv" * 3 corresponds to "-vvv"' - type: integer - type: object - mautrixSignal: - description: 'Configuration options for the mautrix-signal bridge. - The user can either: * disable the deployment of the bridge. - * enable the bridge, without specifying additional configuration options. - The bridge will be deployed with a default configuration. * - enable the bridge and specify an existing ConfigMap by its Name - and Namespace containing a config.yaml file.' - properties: - configMap: - description: Holds information about the ConfigMap containing - the config.yaml configuration file to be used as input for - the configuration of the mautrix-signal Bridge. - properties: - name: - description: Name of the ConfigMap in the given Namespace. - type: string - namespace: - description: Namespace in which the ConfigMap is living. - If left empty, the Synapse namespace is used. - type: string - required: - - name - type: object - enabled: - default: false - description: Whether to deploy mautrix-signal or not - type: boolean - type: object - type: object createNewPostgreSQL: default: false description: Set to true to create a new PostreSQL instance. The homeserver.yaml @@ -153,6 +87,33 @@ spec: status: description: SynapseStatus defines the observed state of Synapse properties: + bridges: + description: Information on the bridges deployed alongside Synapse + properties: + heisenbridge: + description: Information on the Heisenbridge (IRC Bridge). + properties: + enabled: + default: false + description: Whether a Heisenbridge has been deployed for + this Synapse instance + type: boolean + name: + description: Name of the Heisenbridge object + type: string + type: object + mautrixsignal: + description: Information on the mautrix-signal bridge. + properties: + enabled: + description: Whether a mautrix-signal has been deployed for + this Synapse instance + type: boolean + name: + description: Name of the mautrix-signal bridge object + type: string + type: object + type: object databaseConnectionInfo: description: Connection information to the external PostgreSQL Database properties: @@ -183,6 +144,9 @@ spec: description: The public-facing domain of the server type: string type: object + needsReconcile: + default: false + type: boolean reason: description: Reason for the current Synapse State type: string @@ -201,5 +165,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml index 9dba3c9..a85279d 100644 --- a/bundle/metadata/annotations.yaml +++ b/bundle/metadata/annotations.yaml @@ -5,7 +5,7 @@ annotations: operators.operatorframework.io.bundle.metadata.v1: metadata/ operators.operatorframework.io.bundle.package.v1: synapse-operator operators.operatorframework.io.bundle.channels.v1: alpha - operators.operatorframework.io.metrics.builder: operator-sdk-v1.14.0+git + operators.operatorframework.io.metrics.builder: operator-sdk-v1.25.0 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 diff --git a/config/crd/bases/synapse.opdev.io_heisenbridges.yaml b/config/crd/bases/synapse.opdev.io_heisenbridges.yaml new file mode 100644 index 0000000..1578fbb --- /dev/null +++ b/config/crd/bases/synapse.opdev.io_heisenbridges.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: heisenbridges.synapse.opdev.io +spec: + group: synapse.opdev.io + names: + kind: Heisenbridge + listKind: HeisenbridgeList + plural: heisenbridges + singular: heisenbridge + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Heisenbridge is the Schema for the heisenbridges API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: 'HeisenbridgeSpec defines the desired state of Heisenbridge. + The user can either: - enable the bridge, without specifying additional + configuration options. The bridge will be deployed with a default configuration. + - enable the bridge and specify an existing ConfigMap by its Name and + Namespace containing a heisenbridge.yaml.' + properties: + configMap: + description: Holds information about the ConfigMap containing the + heisenbridge.yaml configuration file to be used as input for the + configuration of the Heisenbridge IRC Bridge. + properties: + name: + description: Name of the ConfigMap in the given Namespace. + type: string + namespace: + description: Namespace in which the ConfigMap is living. If left + empty, the Heisenbridge namespace is used. + type: string + required: + - name + type: object + synapse: + description: Name of the Synapse instance, living in the same namespace. + properties: + name: + description: Name of the Synapse instance + type: string + namespace: + description: 'Namespace of the Synapse instance TODO: Complete' + type: string + type: object + verboseLevel: + default: 0 + description: 'Controls the verbosity of the Heisenbrige: * 0 corresponds + to normal level of logs * 1 corresponds to "-v" * 2 corresponds + to "-vv" * 3 corresponds to "-vvv"' + type: integer + required: + - synapse + type: object + status: + description: HeisenbridgeStatus defines the observed state of Heisenbridge + properties: + reason: + description: Reason for the current Heisenbridge State + type: string + state: + description: State of the Heisenbridge instance + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/synapse.opdev.io_mautrixsignals.yaml b/config/crd/bases/synapse.opdev.io_mautrixsignals.yaml new file mode 100644 index 0000000..df70552 --- /dev/null +++ b/config/crd/bases/synapse.opdev.io_mautrixsignals.yaml @@ -0,0 +1,93 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: mautrixsignals.synapse.opdev.io +spec: + group: synapse.opdev.io + names: + kind: MautrixSignal + listKind: MautrixSignalList + plural: mautrixsignals + singular: mautrixsignal + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: MautrixSignal is the Schema for the mautrixsignals API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: 'MautrixSignalSpec defines the desired state of MautrixSignal. + The user can either: - enable the bridge, without specifying additional + configuration options. The bridge will be deployed with a default configuration. + - enable the bridge and specify an existing ConfigMap by its Name and + Namespace containing a config.yaml file.' + properties: + configMap: + description: Holds information about the ConfigMap containing the + config.yaml configuration file to be used as input for the configuration + of the mautrix-signal bridge. + properties: + name: + description: Name of the ConfigMap in the given Namespace. + type: string + namespace: + description: Namespace in which the ConfigMap is living. If left + empty, the Synapse namespace is used. + type: string + required: + - name + type: object + synapse: + description: Name of the Synapse instance, living in the same namespace. + properties: + name: + description: Name of the Synapse instance + type: string + namespace: + description: 'Namespace of the Synapse instance TODO: Complete' + type: string + required: + - name + type: object + required: + - synapse + type: object + status: + description: MautrixSignalStatus defines the observed state of MautrixSignal + properties: + reason: + description: Reason for the current MautrixSignal State + type: string + state: + description: State of the MautrixSignal instance + type: string + synapse: + description: Information related to the Synapse instance associated + with this bridge + properties: + serverName: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/synapse.opdev.io_synapses.yaml b/config/crd/bases/synapse.opdev.io_synapses.yaml index bb67b29..28d01cd 100644 --- a/config/crd/bases/synapse.opdev.io_synapses.yaml +++ b/config/crd/bases/synapse.opdev.io_synapses.yaml @@ -35,72 +35,6 @@ spec: spec: description: SynapseSpec defines the desired state of Synapse properties: - bridges: - description: Configuration options for optional matrix bridges - properties: - heisenbridge: - description: 'Configuration options for the IRC bridge Heisenbridge. - The user can either: * disable the deployment of the bridge. - * enable the bridge, without specifying additional configuration - options. The bridge will be deployed with a default configuration. - * enable the bridge and specify an existing ConfigMap by its - Name and Namespace containing a heisenbridge.yaml.' - properties: - configMap: - description: Holds information about the ConfigMap containing - the heisenbridge.yaml configuration file to be used as input - for the configuration of the Heisenbridge IRC Bridge. - properties: - name: - description: Name of the ConfigMap in the given Namespace. - type: string - namespace: - description: Namespace in which the ConfigMap is living. - If left empty, the Synapse namespace is used. - type: string - required: - - name - type: object - enabled: - default: false - description: Whether to deploy Heisenbridge or not - type: boolean - verboseLevel: - default: 0 - description: 'Controls the verbosity of the Heisenbrige: * - 0 corresponds to normal level of logs * 1 corresponds to - "-v" * 2 corresponds to "-vv" * 3 corresponds to "-vvv"' - type: integer - type: object - mautrixSignal: - description: 'Configuration options for the mautrix-signal bridge. - The user can either: * disable the deployment of the bridge. - * enable the bridge, without specifying additional configuration - options. The bridge will be deployed with a default configuration. - * enable the bridge and specify an existing ConfigMap by its - Name and Namespace containing a config.yaml file.' - properties: - configMap: - description: Holds information about the ConfigMap containing - the config.yaml configuration file to be used as input for - the configuration of the mautrix-signal Bridge. - properties: - name: - description: Name of the ConfigMap in the given Namespace. - type: string - namespace: - description: Namespace in which the ConfigMap is living. - If left empty, the Synapse namespace is used. - type: string - required: - - name - type: object - enabled: - default: false - description: Whether to deploy mautrix-signal or not - type: boolean - type: object - type: object createNewPostgreSQL: default: false description: Set to true to create a new PostreSQL instance. The homeserver.yaml @@ -149,6 +83,33 @@ spec: status: description: SynapseStatus defines the observed state of Synapse properties: + bridges: + description: Information on the bridges deployed alongside Synapse + properties: + heisenbridge: + description: Information on the Heisenbridge (IRC Bridge). + properties: + enabled: + default: false + description: Whether a Heisenbridge has been deployed for + this Synapse instance + type: boolean + name: + description: Name of the Heisenbridge object + type: string + type: object + mautrixsignal: + description: Information on the mautrix-signal bridge. + properties: + enabled: + description: Whether a mautrix-signal has been deployed for + this Synapse instance + type: boolean + name: + description: Name of the mautrix-signal bridge object + type: string + type: object + type: object databaseConnectionInfo: description: Connection information to the external PostgreSQL Database properties: @@ -179,6 +140,9 @@ spec: description: The public-facing domain of the server type: string type: object + needsReconcile: + default: false + type: boolean reason: description: Reason for the current Synapse State type: string diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index d7cfc2e..4438c1a 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,8 @@ # It should be run by config/default resources: - bases/synapse.opdev.io_synapses.yaml +- bases/synapse.opdev.io_mautrixsignals.yaml +- bases/synapse.opdev.io_heisenbridges.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: @@ -17,11 +19,15 @@ patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_synapses.yaml +#- patches/webhook_in_mautrixsignals.yaml +#- patches/webhook_in_heisenbridges.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_synapses.yaml +#- patches/cainjection_in_mautrixsignals.yaml +#- patches/cainjection_in_heisenbridges.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_synapse_heisenbridges.yaml b/config/crd/patches/cainjection_in_synapse_heisenbridges.yaml new file mode 100644 index 0000000..d048b0c --- /dev/null +++ b/config/crd/patches/cainjection_in_synapse_heisenbridges.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: heisenbridges.synapse.opdev.io diff --git a/config/crd/patches/cainjection_in_synapse_mautrixsignals.yaml b/config/crd/patches/cainjection_in_synapse_mautrixsignals.yaml new file mode 100644 index 0000000..4bff4e0 --- /dev/null +++ b/config/crd/patches/cainjection_in_synapse_mautrixsignals.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: mautrixsignals.synapse.opdev.io diff --git a/config/crd/patches/webhook_in_synapse_heisenbridges.yaml b/config/crd/patches/webhook_in_synapse_heisenbridges.yaml new file mode 100644 index 0000000..c889d34 --- /dev/null +++ b/config/crd/patches/webhook_in_synapse_heisenbridges.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: heisenbridges.synapse.opdev.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_synapse_mautrixsignals.yaml b/config/crd/patches/webhook_in_synapse_mautrixsignals.yaml new file mode 100644 index 0000000..4f3fadd --- /dev/null +++ b/config/crd/patches/webhook_in_synapse_mautrixsignals.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: mautrixsignals.synapse.opdev.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/manifests/bases/synapse-operator.clusterserviceversion.yaml b/config/manifests/bases/synapse-operator.clusterserviceversion.yaml index 3845cac..f8c7fa5 100644 --- a/config/manifests/bases/synapse-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/synapse-operator.clusterserviceversion.yaml @@ -10,6 +10,16 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: Heisenbridge is the Schema for the heisenbridges API + displayName: Heisenbridge + kind: Heisenbridge + name: heisenbridges.synapse.opdev.io + version: v1alpha1 + - description: MautrixSignal is the Schema for the mautrixsignals API + displayName: Mautrix Signal + kind: MautrixSignal + name: mautrixsignals.synapse.opdev.io + version: v1alpha1 - description: Synapse is the Schema for the synapses API displayName: Synapse kind: Synapse diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b8f2b0a..ae59655 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,6 +5,58 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/finalizers + verbs: + - update +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/status + verbs: + - get + - patch + - update +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/finalizers + verbs: + - update +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/status + verbs: + - get + - patch + - update - apiGroups: - synapse.opdev.io resources: diff --git a/config/rbac/synapse_heisenbridge_editor_role.yaml b/config/rbac/synapse_heisenbridge_editor_role.yaml new file mode 100644 index 0000000..8bd42d4 --- /dev/null +++ b/config/rbac/synapse_heisenbridge_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit heisenbridges. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: heisenbridge-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: synapse-operator + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + name: heisenbridge-editor-role +rules: +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/status + verbs: + - get diff --git a/config/rbac/synapse_heisenbridge_viewer_role.yaml b/config/rbac/synapse_heisenbridge_viewer_role.yaml new file mode 100644 index 0000000..223501c --- /dev/null +++ b/config/rbac/synapse_heisenbridge_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view heisenbridges. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: heisenbridge-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: synapse-operator + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + name: heisenbridge-viewer-role +rules: +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges + verbs: + - get + - list + - watch +- apiGroups: + - synapse.opdev.io + resources: + - heisenbridges/status + verbs: + - get diff --git a/config/rbac/synapse_mautrixsignal_editor_role.yaml b/config/rbac/synapse_mautrixsignal_editor_role.yaml new file mode 100644 index 0000000..edacdf5 --- /dev/null +++ b/config/rbac/synapse_mautrixsignal_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit mautrixsignals. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: mautrixsignal-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: synapse-operator + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + name: mautrixsignal-editor-role +rules: +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/status + verbs: + - get diff --git a/config/rbac/synapse_mautrixsignal_viewer_role.yaml b/config/rbac/synapse_mautrixsignal_viewer_role.yaml new file mode 100644 index 0000000..d86d935 --- /dev/null +++ b/config/rbac/synapse_mautrixsignal_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view mautrixsignals. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: mautrixsignal-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: synapse-operator + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + name: mautrixsignal-viewer-role +rules: +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals + verbs: + - get + - list + - watch +- apiGroups: + - synapse.opdev.io + resources: + - mautrixsignals/status + verbs: + - get diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 2fbaf58..00091a4 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,6 @@ ## Append samples you want in your CSV to this file as resources ## resources: - synapse_v1alpha1_synapse.yaml +- synapse_v1alpha1_mautrixsignal.yaml +- synapse_v1alpha1_heisenbridge.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/synapse_v1alpha1_heisenbridge.yaml b/config/samples/synapse_v1alpha1_heisenbridge.yaml new file mode 100644 index 0000000..e30d004 --- /dev/null +++ b/config/samples/synapse_v1alpha1_heisenbridge.yaml @@ -0,0 +1,13 @@ +apiVersion: synapse.opdev.io/v1alpha1 +kind: Heisenbridge +metadata: + labels: + app.kubernetes.io/name: heisenbridge + app.kubernetes.io/instance: heisenbridge-sample + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: synapse-operator + name: heisenbridge-sample +spec: + synapse: + name: synapse-sample diff --git a/config/samples/synapse_v1alpha1_mautrixsignal.yaml b/config/samples/synapse_v1alpha1_mautrixsignal.yaml new file mode 100644 index 0000000..49e45cc --- /dev/null +++ b/config/samples/synapse_v1alpha1_mautrixsignal.yaml @@ -0,0 +1,13 @@ +apiVersion: synapse.opdev.io/v1alpha1 +kind: MautrixSignal +metadata: + labels: + app.kubernetes.io/name: mautrixsignal + app.kubernetes.io/instance: mautrixsignal-sample + app.kubernetes.io/part-of: synapse-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: synapse-operator + name: mautrixsignal-sample +spec: + synapse: + name: synapse-sample diff --git a/config/samples/synapse_v1alpha1_synapse.yaml b/config/samples/synapse_v1alpha1_synapse.yaml index e7479b0..1031cae 100644 --- a/config/samples/synapse_v1alpha1_synapse.yaml +++ b/config/samples/synapse_v1alpha1_synapse.yaml @@ -6,5 +6,5 @@ spec: createNewPostgreSQL: false homeserver: values: - serverName: example.com + serverName: my.matrix.host reportStats: true diff --git a/controllers/synapse/heisenbridge/heisenbridge_controller.go b/controllers/synapse/heisenbridge/heisenbridge_controller.go new file mode 100644 index 0000000..4c22644 --- /dev/null +++ b/controllers/synapse/heisenbridge/heisenbridge_controller.go @@ -0,0 +1,222 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package synapse + +import ( + "context" + "reflect" + "strings" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" + "github.com/opdev/synapse-operator/helpers/utils" +) + +// HeisenbridgeReconciler reconciles a Heisenbridge object +type HeisenbridgeReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func GetHeisenbridgeServiceFQDN(h synapsev1alpha1.Heisenbridge) string { + return strings.Join([]string{h.Name, h.Namespace, "svc", "cluster", "local"}, ".") +} + +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=heisenbridges,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=heisenbridges/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=heisenbridges/finalizers,verbs=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. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Heisenbridge object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *HeisenbridgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + + var h synapsev1alpha1.Heisenbridge // The Heisenbridge object being reconciled + + // Load the Heisenbridge by name + if err := r.Get(ctx, req.NamespacedName, &h); err != nil { + if k8serrors.IsNotFound(err) { + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + log.Error( + err, + "Cannot find Heisenbridge - has it been deleted ?", + "Heisenbridge Name", h.Name, + "Heisenbridge Namespace", h.Namespace, + ) + return ctrl.Result{}, nil + } + log.Error( + err, + "Error fetching Heisenbridge", + "Heisenbridge Name", h.Name, + "Heisenbridge Namespace", h.Namespace, + ) + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Build mautrix-signal status + s, err := r.fetchSynapseInstance(ctx, h) + if err != nil { + if k8serrors.IsNotFound(err) { + log.Error( + err, + "Cannot find Synapse instance", + "Synapse Name", h.Spec.Synapse.Name, + "Synapse Namespace", utils.ComputeNamespace(h.Namespace, h.Spec.Synapse.Namespace), + ) + } else { + log.Error( + err, + "Error getting Synapse server name", + "Synapse Name", h.Spec.Synapse.Name, + "Synapse Namespace", utils.ComputeNamespace(h.Namespace, h.Spec.Synapse.Namespace), + ) + } + return ctrl.Result{}, err + } + + if r, err := r.triggerSynapseReconciliation(&s, ctx); reconc.ShouldHaltOrRequeue(r, err) { + return reconc.Evaluate(r, err) + } + + // The list of subreconcilers for Heisenbridge will be built next. + // Heisenbridge is composed of a ConfigMap, a Service and a Deployment. + var subreconcilersForHeisenbridge []reconc.SubreconcilerFuncs + + // The user may specify a ConfigMap, containing the heisenbridge.yaml + // config file, under Spec.Bridges.Heisenbridge.ConfigMap + if h.Spec.ConfigMap.Name != "" { + // If the user provided a custom Heisenbridge configuration via a + // ConfigMap, we need to validate that the ConfigMap exists, and + // create a copy. We also need to edit the heisenbridge + // configuration. + subreconcilersForHeisenbridge = []reconc.SubreconcilerFuncs{ + r.copyInputHeisenbridgeConfigMap, + r.configureHeisenbridgeConfigMap, + } + } else { + // If the user hasn't provided a ConfigMap with a custom + // heisenbridge.yaml, we create a new ConfigMap with a default + // heisenbridge.yaml. + subreconcilersForHeisenbridge = []reconc.SubreconcilerFuncs{ + r.reconcileHeisenbridgeConfigMap, + } + } + + // Reconcile Heisenbridge resources: Service and Deployment + subreconcilersForHeisenbridge = append( + subreconcilersForHeisenbridge, + r.reconcileHeisenbridgeService, + r.reconcileHeisenbridgeDeployment, + ) + + for _, f := range subreconcilersForHeisenbridge { + if r, err := f(&h, ctx); reconc.ShouldHaltOrRequeue(r, err) { + return reconc.Evaluate(r, err) + } + } + + return ctrl.Result{}, nil +} + +func (r *HeisenbridgeReconciler) fetchSynapseInstance( + ctx context.Context, + h synapsev1alpha1.Heisenbridge, +) (synapsev1alpha1.Synapse, error) { + // Validate Synapse instance exists + s := &synapsev1alpha1.Synapse{} + keyForSynapse := types.NamespacedName{ + Name: h.Spec.Synapse.Name, + Namespace: utils.ComputeNamespace(h.Namespace, h.Spec.Synapse.Namespace), + } + if err := r.Get(ctx, keyForSynapse, s); err != nil { + return synapsev1alpha1.Synapse{}, err + } + + return *s, nil +} + +func (r *HeisenbridgeReconciler) triggerSynapseReconciliation(i interface{}, ctx context.Context) (*ctrl.Result, error) { + s := i.(*synapsev1alpha1.Synapse) + s.Status.NeedsReconcile = true + + current := &synapsev1alpha1.Synapse{} + if err := r.Get( + ctx, + types.NamespacedName{Name: s.Name, Namespace: s.Namespace}, + current, + ); err != nil { + return reconc.RequeueWithError(err) + } + + if !reflect.DeepEqual(s.Status, current.Status) { + if err := r.Status().Patch(ctx, s, client.MergeFrom(current)); err != nil { + return reconc.RequeueWithError(err) + } + } + + return reconc.ContinueReconciling() +} + +func (r *HeisenbridgeReconciler) setFailedState(ctx context.Context, h *synapsev1alpha1.Heisenbridge, reason string) error { + h.Status.State = "FAILED" + h.Status.Reason = reason + + return r.updateHeisenbridgeStatus(ctx, h) +} + +func (r *HeisenbridgeReconciler) updateHeisenbridgeStatus(ctx context.Context, h *synapsev1alpha1.Heisenbridge) error { + current := &synapsev1alpha1.Heisenbridge{} + if err := r.Get( + ctx, + types.NamespacedName{Name: h.Name, Namespace: h.Namespace}, + current, + ); err != nil { + return err + } + + if !reflect.DeepEqual(h.Status, current.Status) { + if err := r.Status().Patch(ctx, h, client.MergeFrom(current)); err != nil { + return err + } + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *HeisenbridgeReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&synapsev1alpha1.Heisenbridge{}). + Complete(r) +} diff --git a/controllers/synapse/synapse_heisenbridge_configmap.go b/controllers/synapse/heisenbridge/synapse_heisenbridge_configmap.go similarity index 64% rename from controllers/synapse/synapse_heisenbridge_configmap.go rename to controllers/synapse/heisenbridge/synapse_heisenbridge_configmap.go index e16f4ff..2c6ee26 100644 --- a/controllers/synapse/synapse_heisenbridge_configmap.go +++ b/controllers/synapse/heisenbridge/synapse_heisenbridge_configmap.go @@ -24,11 +24,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" + "github.com/opdev/synapse-operator/helpers/utils" ) // reconcileHeisenbridgeConfigMap is a function of type subreconcilerFuncs, to @@ -36,14 +37,21 @@ import ( // // It reconciles the heisenbridge ConfigMap to its desired state. It is called // only if the user hasn't provided its own ConfigMap for heisenbridge -func (r *SynapseReconciler) reconcileHeisenbridgeConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaHeisenbridge := setObjectMeta(r.GetHeisenbridgeResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *HeisenbridgeReconciler) reconcileHeisenbridgeConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + h := i.(*synapsev1alpha1.Heisenbridge) + + objectMetaHeisenbridge := reconcile.SetObjectMeta(h.Name, h.Namespace, map[string]string{}) + + desiredConfigMap, err := r.configMapForHeisenbridge(h, objectMetaHeisenbridge) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.configMapForHeisenbridge, - synapse, + r.Client, + desiredConfigMap, &corev1.ConfigMap{}, - objectMetaHeisenbridge, ); err != nil { return reconc.RequeueWithError(err) } @@ -52,10 +60,10 @@ func (r *SynapseReconciler) reconcileHeisenbridgeConfigMap(synapse *synapsev1alp } // configMapForSynapse returns a synapse ConfigMap object -func (r *SynapseReconciler) configMapForHeisenbridge(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *HeisenbridgeReconciler) configMapForHeisenbridge(h *synapsev1alpha1.Heisenbridge, objectMeta metav1.ObjectMeta) (*corev1.ConfigMap, error) { heisenbridgeYaml := ` id: heisenbridge -url: http://` + r.GetHeisenbridgeServiceFQDN(*s) + `:9898 +url: http://` + GetHeisenbridgeServiceFQDN(*h) + `:9898 as_token: EUFqSPQusV4mXkPKbwdHyIhthELQ1Xf9S5lSEzTrrlb0uz0ZJRHhwEljT71ByObe hs_token: If6r2GGlsNN4MnoW3djToADNdq0JuIJ1WNM4rKHO73WuG5QvVubj1Q4JHrmQBcS6 rate_limited: false @@ -74,7 +82,7 @@ namespaces: } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, cm, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(h, cm, r.Scheme); err != nil { return &corev1.ConfigMap{}, err } @@ -86,14 +94,13 @@ namespaces: // // It creates a copy of the user-provided ConfigMap for heisenbridge, defined // in synapse.Spec.Bridges.Heisenbridge.ConfigMap -func (r *SynapseReconciler) copyInputHeisenbridgeConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { +func (r *HeisenbridgeReconciler) copyInputHeisenbridgeConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + h := i.(*synapsev1alpha1.Heisenbridge) + log := ctrllog.FromContext(ctx) - inputConfigMapName := synapse.Spec.Bridges.Heisenbridge.ConfigMap.Name - inputConfigMapNamespace := r.getConfigMapNamespace( - *synapse, - synapse.Spec.Bridges.Heisenbridge.ConfigMap.Namespace, - ) + inputConfigMapName := h.Spec.ConfigMap.Name + inputConfigMapNamespace := utils.ComputeNamespace(h.Namespace, h.Spec.ConfigMap.Namespace) keyForConfigMap := types.NamespacedName{ Name: inputConfigMapName, Namespace: inputConfigMapNamespace, @@ -102,8 +109,8 @@ func (r *SynapseReconciler) copyInputHeisenbridgeConfigMap(synapse *synapsev1alp // Get and check the input ConfigMap for Heisenbridge if err := r.Get(ctx, keyForConfigMap, &corev1.ConfigMap{}); err != nil { reason := "ConfigMap " + inputConfigMapName + " does not exist in namespace " + inputConfigMapNamespace - if err := r.setFailedState(ctx, synapse, reason); err != nil { - log.Error(err, "Error updating Synapse State") + if err := r.setFailedState(ctx, h, reason); err != nil { + log.Error(err, "Error updating Heisenbridge State") } log.Error( @@ -118,16 +125,20 @@ func (r *SynapseReconciler) copyInputHeisenbridgeConfigMap(synapse *synapsev1alp return reconc.RequeueWithDelayAndError(time.Duration(30), err) } - objectMetaHeisenbridge := setObjectMeta(r.GetHeisenbridgeResourceName(*synapse), synapse.Namespace, map[string]string{}) + objectMetaHeisenbridge := reconcile.SetObjectMeta(h.Name, h.Namespace, map[string]string{}) + + desiredConfigMap, err := r.configMapForHeisenbridgeCopy(h, objectMetaHeisenbridge) + if err != nil { + return reconc.RequeueWithError(err) + } // Create a copy of the inputHeisenbridgeConfigMap defined in Spec.Bridges.Heisenbridge.ConfigMap // Here we use the configMapForHeisenbridgeCopy function as createResourceFunc - if err := r.reconcileResource( + if err := reconcile.ReconcileResource( ctx, - r.configMapForHeisenbridgeCopy, - synapse, + r.Client, + desiredConfigMap, &corev1.ConfigMap{}, - objectMetaHeisenbridge, ); err != nil { return reconc.RequeueWithError(err) } @@ -140,22 +151,27 @@ func (r *SynapseReconciler) copyInputHeisenbridgeConfigMap(synapse *synapsev1alp // // The ConfigMap returned by configMapForHeisenbridgeCopy is a copy of the ConfigMap // defined in Spec.Bridges.Heisenbridge.ConfigMap. -func (r *SynapseReconciler) configMapForHeisenbridgeCopy( - s *synapsev1alpha1.Synapse, +func (r *HeisenbridgeReconciler) configMapForHeisenbridgeCopy( + h *synapsev1alpha1.Heisenbridge, objectMeta metav1.ObjectMeta, -) (client.Object, error) { +) (*corev1.ConfigMap, error) { var copyConfigMap *corev1.ConfigMap - sourceConfigMapName := s.Spec.Bridges.Heisenbridge.ConfigMap.Name - sourceConfigMapNamespace := r.getConfigMapNamespace(*s, s.Spec.Bridges.Heisenbridge.ConfigMap.Namespace) + sourceConfigMapName := h.Name + sourceConfigMapNamespace := utils.ComputeNamespace(h.Namespace, h.Spec.ConfigMap.Namespace) - copyConfigMap, err := r.getConfigMapCopy(sourceConfigMapName, sourceConfigMapNamespace, objectMeta) + copyConfigMap, err := utils.GetConfigMapCopy( + r.Client, + sourceConfigMapName, + sourceConfigMapNamespace, + objectMeta, + ) if err != nil { return &corev1.ConfigMap{}, err } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, copyConfigMap, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(h, copyConfigMap, r.Scheme); err != nil { return &corev1.ConfigMap{}, err } @@ -167,17 +183,20 @@ func (r *SynapseReconciler) configMapForHeisenbridgeCopy( // // Following the previous copy of the user-provided ConfigMap, it edits the // content of the copy to ensure that heisenbridge is correctly configured. -func (r *SynapseReconciler) configureHeisenbridgeConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { +func (r *HeisenbridgeReconciler) configureHeisenbridgeConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + h := i.(*synapsev1alpha1.Heisenbridge) + keyForConfigMap := types.NamespacedName{ - Name: r.GetHeisenbridgeResourceName(*synapse), - Namespace: synapse.Namespace, + Name: h.Name, + Namespace: h.Namespace, } // Configure correct URL in Heisenbridge ConfigMap - if err := r.updateConfigMap( + if err := utils.UpdateConfigMap( ctx, + r.Client, keyForConfigMap, - *synapse, + h, r.updateHeisenbridgeWithURL, "heisenbridge.yaml", ); err != nil { @@ -192,10 +211,12 @@ func (r *SynapseReconciler) configureHeisenbridgeConfigMap(synapse *synapsev1alp // // It configures the correct Heisenbridge URL, needed for Synapse to reach the // bridge. -func (r *SynapseReconciler) updateHeisenbridgeWithURL( - s synapsev1alpha1.Synapse, +func (r *HeisenbridgeReconciler) updateHeisenbridgeWithURL( + i interface{}, heisenbridge map[string]interface{}, ) error { - heisenbridge["url"] = "http://" + r.GetHeisenbridgeServiceFQDN(s) + ":9898" + h := i.(synapsev1alpha1.Heisenbridge) + + heisenbridge["url"] = "http://" + GetHeisenbridgeServiceFQDN(h) + ":9898" return nil } diff --git a/controllers/synapse/synapse_heisenbridge_deployment.go b/controllers/synapse/heisenbridge/synapse_heisenbridge_deployment.go similarity index 69% rename from controllers/synapse/synapse_heisenbridge_deployment.go rename to controllers/synapse/heisenbridge/synapse_heisenbridge_deployment.go index af17ade..c976e09 100644 --- a/controllers/synapse/synapse_heisenbridge_deployment.go +++ b/controllers/synapse/heisenbridge/synapse_heisenbridge_deployment.go @@ -23,30 +23,38 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" + "github.com/opdev/synapse-operator/helpers/utils" ) // labelsForSynapse returns the labels for selecting the resources // belonging to the given synapse CR name. func labelsForHeisenbridge(name string) map[string]string { - return map[string]string{"app": "heisenbridge", "synapse_cr": name} + return map[string]string{"app": "heisenbridge", "heisenbridge_cr": name} } // reconcileHeisenbridgeDeployment is a function of type subreconcilerFuncs, to // be called in the main reconciliation loop. // // It reconciles the Deployment for Heisenbridge to its desired state. -func (r *SynapseReconciler) reconcileHeisenbridgeDeployment(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaHeisenbridge := setObjectMeta(r.GetHeisenbridgeResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *HeisenbridgeReconciler) reconcileHeisenbridgeDeployment(i interface{}, ctx context.Context) (*ctrl.Result, error) { + h := i.(*synapsev1alpha1.Heisenbridge) + + objectMetaHeisenbridge := reconcile.SetObjectMeta(h.Name, h.Namespace, map[string]string{}) + + desiredDeployment, err := r.deploymentForHeisenbridge(h, objectMetaHeisenbridge) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.deploymentForHeisenbridge, - synapse, + r.Client, + desiredDeployment, &appsv1.Deployment{}, - objectMetaHeisenbridge, ); err != nil { return reconc.RequeueWithError(err) } @@ -55,11 +63,11 @@ func (r *SynapseReconciler) reconcileHeisenbridgeDeployment(synapse *synapsev1al } // deploymentForHeisenbridge returns a Heisenbridge Deployment object -func (r *SynapseReconciler) deploymentForHeisenbridge(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { - ls := labelsForHeisenbridge(s.Name) +func (r *HeisenbridgeReconciler) deploymentForHeisenbridge(h *synapsev1alpha1.Heisenbridge, objectMeta metav1.ObjectMeta) (*appsv1.Deployment, error) { + ls := labelsForHeisenbridge(h.Name) replicas := int32(1) - command := r.craftHeisenbridgeCommad(*s) + command := r.craftHeisenbridgeCommad(*h) // The created Heisenbridge ConfigMap Name share the same name as the // Heisenbridge Deployment heisenbridgeConfigMapName := objectMeta.Name @@ -103,34 +111,37 @@ func (r *SynapseReconciler) deploymentForHeisenbridge(s *synapsev1alpha1.Synapse }, } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, dep, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(h, dep, r.Scheme); err != nil { return &appsv1.Deployment{}, err } return dep, nil } -func (r *SynapseReconciler) craftHeisenbridgeCommad(s synapsev1alpha1.Synapse) []string { +func (r *HeisenbridgeReconciler) craftHeisenbridgeCommad(h synapsev1alpha1.Heisenbridge) []string { command := []string{ "python", "-m", "heisenbridge", } - if s.Spec.Bridges.Heisenbridge.VerboseLevel > 0 { + if h.Spec.VerboseLevel > 0 { verbosity := "-" - for i := 1; i <= s.Spec.Bridges.Heisenbridge.VerboseLevel; i++ { + for i := 1; i <= h.Spec.VerboseLevel; i++ { verbosity = verbosity + "v" } command = append(command, verbosity) } + SynapseName := h.Spec.Synapse.Name + SynapseNamespace := utils.ComputeNamespace(h.Namespace, h.Spec.Synapse.Namespace) + command = append( command, "-c", "/data-heisenbridge/heisenbridge.yaml", "-l", "0.0.0.0", - "http://"+r.GetSynapseServiceFQDN(s)+":8008", + "http://"+utils.ComputeFQDN(SynapseName, SynapseNamespace)+":8008", ) return command diff --git a/controllers/synapse/synapse_heisenbridge_service.go b/controllers/synapse/heisenbridge/synapse_heisenbridge_service.go similarity index 68% rename from controllers/synapse/synapse_heisenbridge_service.go rename to controllers/synapse/heisenbridge/synapse_heisenbridge_service.go index 05b4346..31fabbe 100644 --- a/controllers/synapse/synapse_heisenbridge_service.go +++ b/controllers/synapse/heisenbridge/synapse_heisenbridge_service.go @@ -23,9 +23,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" ) @@ -33,14 +33,21 @@ import ( // called in the main reconciliation loop. // // It reconciles the Service for Heisenbridge to its desired state. -func (r *SynapseReconciler) reconcileHeisenbridgeService(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaHeisenbridge := setObjectMeta(r.GetHeisenbridgeResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *HeisenbridgeReconciler) reconcileHeisenbridgeService(i interface{}, ctx context.Context) (*ctrl.Result, error) { + h := i.(*synapsev1alpha1.Heisenbridge) + + objectMetaHeisenbridge := reconcile.SetObjectMeta(h.Name, h.Namespace, map[string]string{}) + + desiredService, err := r.serviceForHeisenbridge(h, objectMetaHeisenbridge) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.serviceForHeisenbridge, - synapse, + r.Client, + desiredService, &corev1.Service{}, - objectMetaHeisenbridge, ); err != nil { return reconc.RequeueWithError(err) } @@ -49,7 +56,7 @@ func (r *SynapseReconciler) reconcileHeisenbridgeService(synapse *synapsev1alpha } // serviceForSynapse returns a Heisenbridge Service object -func (r *SynapseReconciler) serviceForHeisenbridge(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *HeisenbridgeReconciler) serviceForHeisenbridge(h *synapsev1alpha1.Heisenbridge, objectMeta metav1.ObjectMeta) (*corev1.Service, error) { service := &corev1.Service{ ObjectMeta: objectMeta, Spec: corev1.ServiceSpec{ @@ -59,12 +66,12 @@ func (r *SynapseReconciler) serviceForHeisenbridge(s *synapsev1alpha1.Synapse, o Port: 9898, TargetPort: intstr.FromInt(9898), }}, - Selector: labelsForHeisenbridge(s.Name), + Selector: labelsForHeisenbridge(h.Name), Type: corev1.ServiceTypeClusterIP, }, } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, service, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(h, service, r.Scheme); err != nil { return &corev1.Service{}, err } return service, nil diff --git a/controllers/synapse/synapse_mautrixsignal_configmap.go b/controllers/synapse/mautrixsignal/mautrixsignal_configmap.go similarity index 86% rename from controllers/synapse/synapse_mautrixsignal_configmap.go rename to controllers/synapse/mautrixsignal/mautrixsignal_configmap.go index 014b510..e9115a5 100644 --- a/controllers/synapse/synapse_mautrixsignal_configmap.go +++ b/controllers/synapse/mautrixsignal/mautrixsignal_configmap.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package synapse +package mautrixsignal import ( "context" @@ -25,11 +25,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" + "github.com/opdev/synapse-operator/helpers/utils" ) // reconcileMautrixSignalConfigMap is a function of type subreconcilerFuncs, to @@ -38,14 +39,21 @@ import ( // It reconciles the mautrix-signal ConfigMap to its desired state. It is // called only if the user hasn't provided its own ConfigMap for // mautrix-signal. -func (r *SynapseReconciler) reconcileMautrixSignalConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredConfigMap, err := r.configMapForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.configMapForMautrixSignal, - synapse, + r.Client, + desiredConfigMap, &corev1.ConfigMap{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -54,14 +62,16 @@ func (r *SynapseReconciler) reconcileMautrixSignalConfigMap(synapse *synapsev1al } // configMapForSynapse returns a synapse ConfigMap object -func (r *SynapseReconciler) configMapForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { - synapseServerName := s.Status.HomeserverConfiguration.ServerName +func (r *MautrixSignalReconciler) configMapForMautrixSignal(ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta) (*corev1.ConfigMap, error) { + synapseName := ms.Spec.Synapse.Name + synapseNamespace := utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace) + synapseServerName := ms.Status.Synapse.ServerName configYaml := ` # Homeserver details homeserver: # The address that this appservice can use to connect to the homeserver. - address: http://` + r.GetSynapseServiceFQDN(*s) + `:8008 + address: http://` + utils.ComputeFQDN(synapseName, synapseNamespace) + `:8008 # The domain of the homeserver (for MXIDs, etc). domain: ` + synapseServerName + ` # Whether or not to verify the SSL certificate of the homeserver. @@ -86,7 +96,7 @@ homeserver: # Changing these values requires regeneration of the registration. appservice: # The address that the homeserver can use to connect to this appservice. - address: http://` + r.GetMautrixSignalServiceFQDN(*s) + `:29328 + address: http://` + utils.ComputeFQDN(ms.Name, ms.Namespace) + `:29328 # When using https:// the TLS certificate and key files for the address. tls_cert: false tls_key: false @@ -369,7 +379,7 @@ logging: } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, cm, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, cm, r.Scheme); err != nil { return &corev1.ConfigMap{}, err } @@ -381,24 +391,23 @@ logging: // // It creates a copy of the user-provided ConfigMap for mautrix-signal, defined // in synapse.Spec.Bridges.MautrixSignal.ConfigMap -func (r *SynapseReconciler) copyInputMautrixSignalConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { +func (r *MautrixSignalReconciler) copyInputMautrixSignalConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + log := ctrllog.FromContext(ctx) - inputConfigMapName := synapse.Spec.Bridges.MautrixSignal.ConfigMap.Name - inputConfigMapNamespace := r.getConfigMapNamespace( - *synapse, - synapse.Spec.Bridges.MautrixSignal.ConfigMap.Namespace, - ) + inputConfigMapName := ms.Spec.ConfigMap.Name + inputConfigMapNamespace := utils.ComputeNamespace(ms.Namespace, ms.Spec.ConfigMap.Namespace) keyForInputConfigMap := types.NamespacedName{ Name: inputConfigMapName, Namespace: inputConfigMapNamespace, } - // Get and check the input ConfigMap for Heisenbridge + // Get and check the input ConfigMap for MautrixSignal if err := r.Get(ctx, keyForInputConfigMap, &corev1.ConfigMap{}); err != nil { reason := "ConfigMap " + inputConfigMapName + " does not exist in namespace " + inputConfigMapNamespace - if err := r.setFailedState(ctx, synapse, reason); err != nil { - log.Error(err, "Error updating Synapse State") + if err := r.setFailedState(ctx, ms, reason); err != nil { + log.Error(err, "Error updating mautrix-signal State") } log.Error( @@ -413,16 +422,20 @@ func (r *SynapseReconciler) copyInputMautrixSignalConfigMap(synapse *synapsev1al return reconc.RequeueWithDelayAndError(time.Duration(30), err) } - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredConfigMap, err := r.configMapForMautrixSignalCopy(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } // Create a copy of the inputMautrixSignalConfigMap defined in Spec.Bridges.MautrixSignal.ConfigMap // Here we use the createdMautrixSignalConfigMap function as createResourceFunc - if err := r.reconcileResource( + if err := reconcile.ReconcileResource( ctx, - r.configMapForMautrixSignalCopy, - synapse, + r.Client, + desiredConfigMap, &corev1.ConfigMap{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -435,22 +448,27 @@ func (r *SynapseReconciler) copyInputMautrixSignalConfigMap(synapse *synapsev1al // // The ConfigMap returned by configMapForMautrixSignalCopy is a copy of the ConfigMap // defined in Spec.Bridges.MautrixSignal.ConfigMap. -func (r *SynapseReconciler) configMapForMautrixSignalCopy( - s *synapsev1alpha1.Synapse, +func (r *MautrixSignalReconciler) configMapForMautrixSignalCopy( + ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta, -) (client.Object, error) { +) (*corev1.ConfigMap, error) { var copyConfigMap *corev1.ConfigMap - sourceConfigMapName := s.Spec.Bridges.MautrixSignal.ConfigMap.Name - sourceConfigMapNamespace := r.getConfigMapNamespace(*s, s.Spec.Bridges.MautrixSignal.ConfigMap.Namespace) + sourceConfigMapName := ms.Spec.ConfigMap.Name + sourceConfigMapNamespace := utils.ComputeNamespace(ms.Namespace, ms.Spec.ConfigMap.Namespace) - copyConfigMap, err := r.getConfigMapCopy(sourceConfigMapName, sourceConfigMapNamespace, objectMeta) + copyConfigMap, err := utils.GetConfigMapCopy( + r.Client, + sourceConfigMapName, + sourceConfigMapNamespace, + objectMeta, + ) if err != nil { return &corev1.ConfigMap{}, err } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, copyConfigMap, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, copyConfigMap, r.Scheme); err != nil { return &corev1.ConfigMap{}, err } @@ -462,17 +480,20 @@ func (r *SynapseReconciler) configMapForMautrixSignalCopy( // // Following the previous copy of the user-provided ConfigMap, it edits the // content of the copy to ensure that mautrix-signal is correctly configured. -func (r *SynapseReconciler) configureMautrixSignalConfigMap(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { +func (r *MautrixSignalReconciler) configureMautrixSignalConfigMap(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + keyForConfigMap := types.NamespacedName{ - Name: r.GetMautrixSignalResourceName(*synapse), - Namespace: synapse.Namespace, + Name: ms.Name, + Namespace: ms.Namespace, } // Correct data in mautrix-signal ConfigMap - if err := r.updateConfigMap( + if err := utils.UpdateConfigMap( ctx, + r.Client, keyForConfigMap, - *synapse, + *ms, r.updateMautrixSignalData, "config.yaml", ); err != nil { @@ -488,11 +509,15 @@ func (r *SynapseReconciler) configureMautrixSignalConfigMap(synapse *synapsev1al // It configures the user-provided config.yaml with the correct values. Among // other things, it ensures that the bridge can reach the Synapse homeserver // and knows the correct path to the signald socket. -func (r *SynapseReconciler) updateMautrixSignalData( - s synapsev1alpha1.Synapse, +func (r *MautrixSignalReconciler) updateMautrixSignalData( + i interface{}, config map[string]interface{}, ) error { - synapseServerName := s.Status.HomeserverConfiguration.ServerName + ms := i.(synapsev1alpha1.MautrixSignal) + + synapseName := ms.Spec.Synapse.Name + synapseNamespace := utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace) + synapseServerName := ms.Status.Synapse.ServerName // Update the homeserver section so that the bridge can reach Synapse configHomeserver, ok := config["homeserver"].(map[interface{}]interface{}) @@ -500,7 +525,7 @@ func (r *SynapseReconciler) updateMautrixSignalData( err := errors.New("cannot parse mautrix-signal config.yaml: error parsing 'homeserver' section") return err } - configHomeserver["address"] = "http://" + r.GetSynapseServiceFQDN(s) + ":8008" + configHomeserver["address"] = "http://" + utils.ComputeFQDN(synapseName, synapseNamespace) + ":8008" configHomeserver["domain"] = synapseServerName config["homeserver"] = configHomeserver @@ -510,7 +535,7 @@ func (r *SynapseReconciler) updateMautrixSignalData( err := errors.New("cannot parse mautrix-signal config.yaml: error parsing 'appservice' section") return err } - configAppservice["address"] = "http://" + r.GetMautrixSignalServiceFQDN(s) + ":29328" + configAppservice["address"] = "http://" + utils.ComputeFQDN(ms.Name, ms.Namespace) + ":29328" config["appservice"] = configAppservice // Update the path to the signal socket path diff --git a/controllers/synapse/mautrixsignal/mautrixsignal_controller.go b/controllers/synapse/mautrixsignal/mautrixsignal_controller.go new file mode 100644 index 0000000..dee735e --- /dev/null +++ b/controllers/synapse/mautrixsignal/mautrixsignal_controller.go @@ -0,0 +1,264 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mautrixsignal + +import ( + "context" + "reflect" + "strings" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" + "github.com/opdev/synapse-operator/helpers/utils" +) + +// MautrixSignalReconciler reconciles a MautrixSignal object +type MautrixSignalReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func GetSignaldResourceName(ms synapsev1alpha1.MautrixSignal) string { + return strings.Join([]string{ms.Name, "signald"}, "-") +} + +func GetMautrixSignalServiceFQDN(ms synapsev1alpha1.MautrixSignal) string { + return strings.Join([]string{ms.Name, ms.Namespace, "svc", "cluster", "local"}, ".") +} + +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=mautrixsignals,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=mautrixsignals/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=synapse.opdev.io,resources=mautrixsignals/finalizers,verbs=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. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the MautrixSignal object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *MautrixSignalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + + var ms synapsev1alpha1.MautrixSignal // The mautrix-signal object being reconciled + + // Load the mautrix-signal by name + if err := r.Get(ctx, req.NamespacedName, &ms); err != nil { + if k8serrors.IsNotFound(err) { + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + log.Error( + err, + "Cannot find mautrix-signal - has it been deleted ?", + "mautrix-signal Name", ms.Name, + "mautrix-signal Namespace", ms.Namespace, + ) + return ctrl.Result{}, nil + } + log.Error( + err, + "Error fetching mautrix-signal", + "mautrix-signal Name", ms.Name, + "mautrix-signal Namespace", ms.Namespace, + ) + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Build mautrix-signal status + s, err := r.fetchSynapseInstance(ctx, ms) + if err != nil { + if k8serrors.IsNotFound(err) { + log.Error( + err, + "Cannot find Synapse instance", + "Synapse Name", ms.Spec.Synapse.Name, + "Synapse Namespace", utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace), + ) + } else { + log.Error( + err, + "Error getting Synapse server name", + "Synapse Name", ms.Spec.Synapse.Name, + "Synapse Namespace", utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace), + ) + } + return ctrl.Result{}, err + } + + // Get Synapse Status + // if !isSynapseRunning(s) { + // err = errors.New("Synapse is not ready") + // log.Error( + // err, + // "Synapse is not ready", + // "Synapse Name", ms.Spec.Synapse.Name, + // "Synapse Namespace", utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace), + // ) + + // return ctrl.Result{}, err + // } + + // Get Synapse ServerName + ms.Status.Synapse.ServerName, err = utils.GetSynapseServerName(s) + if err != nil { + log.Error( + err, + "Error getting Synapse ServerName", + "Synapse Name", ms.Spec.Synapse.Name, + "Synapse Namespace", utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace), + ) + } + + if r, err := r.triggerSynapseReconciliation(&s, ctx); reconc.ShouldHaltOrRequeue(r, err) { + return reconc.Evaluate(r, err) + } + + if err := r.updateMautrixSignalStatus(ctx, &ms); err != nil { + log.Error(err, "Error updating mautrix-signal Status") + return ctrl.Result{}, err + } + + // The list of subreconcilers for mautrix-signal will be built next. + // mautrix-signal is composed of a ConfigMap, a Service, a SA, a RB, + // a PVC and a Deployment. + // In addition, a Deployment and a PVC are needed for signald. + var subreconcilersForMautrixSignal []reconc.SubreconcilerFuncs + + // The user may specify a ConfigMap, containing the config.yaml config + // file, under Spec.Bridges.MautrixSignal.ConfigMap + if ms.Spec.ConfigMap.Name != "" { + // If the user provided a custom mautrix-signal configuration via a + // ConfigMap, we need to validate that the ConfigMap exists, and + // create a copy. We also need to edit the mautrix-signal + // configuration. + subreconcilersForMautrixSignal = []reconc.SubreconcilerFuncs{ + r.copyInputMautrixSignalConfigMap, + r.configureMautrixSignalConfigMap, + } + + } else { + // If the user hasn't provided a ConfigMap with a custom + // config.yaml, we create a new ConfigMap with a default + // config.yaml. + subreconcilersForMautrixSignal = []reconc.SubreconcilerFuncs{ + r.reconcileMautrixSignalConfigMap, + } + } + + // Reconcile signald resources: PVC and Deployment + // Reconcile mautrix-signal resources: Service, SA, RB, PVC and Deployment + subreconcilersForMautrixSignal = append( + subreconcilersForMautrixSignal, + r.reconcileSignaldPVC, + r.reconcileSignaldDeployment, + r.reconcileMautrixSignalService, + r.reconcileMautrixSignalServiceAccount, + r.reconcileMautrixSignalRoleBinding, + r.reconcileMautrixSignalPVC, + r.reconcileMautrixSignalDeployment, + ) + + for _, f := range subreconcilersForMautrixSignal { + if r, err := f(&ms, ctx); reconc.ShouldHaltOrRequeue(r, err) { + return reconc.Evaluate(r, err) + } + } + + return ctrl.Result{}, nil +} + +func (r *MautrixSignalReconciler) fetchSynapseInstance( + ctx context.Context, + ms synapsev1alpha1.MautrixSignal, +) (synapsev1alpha1.Synapse, error) { + // Validate Synapse instance exists + s := &synapsev1alpha1.Synapse{} + keyForSynapse := types.NamespacedName{ + Name: ms.Spec.Synapse.Name, + Namespace: utils.ComputeNamespace(ms.Namespace, ms.Spec.Synapse.Namespace), + } + if err := r.Get(ctx, keyForSynapse, s); err != nil { + return synapsev1alpha1.Synapse{}, err + } + + return *s, nil +} + +func (r *MautrixSignalReconciler) triggerSynapseReconciliation(i interface{}, ctx context.Context) (*ctrl.Result, error) { + s := i.(*synapsev1alpha1.Synapse) + s.Status.NeedsReconcile = true + + current := &synapsev1alpha1.Synapse{} + if err := r.Get( + ctx, + types.NamespacedName{Name: s.Name, Namespace: s.Namespace}, + current, + ); err != nil { + return reconc.RequeueWithError(err) + } + + if !reflect.DeepEqual(s.Status, current.Status) { + if err := r.Status().Patch(ctx, s, client.MergeFrom(current)); err != nil { + return reconc.RequeueWithError(err) + } + } + + return reconc.ContinueReconciling() +} + +func (r *MautrixSignalReconciler) setFailedState(ctx context.Context, ms *synapsev1alpha1.MautrixSignal, reason string) error { + ms.Status.State = "FAILED" + ms.Status.Reason = reason + + return r.updateMautrixSignalStatus(ctx, ms) +} + +func (r *MautrixSignalReconciler) updateMautrixSignalStatus(ctx context.Context, ms *synapsev1alpha1.MautrixSignal) error { + current := &synapsev1alpha1.MautrixSignal{} + if err := r.Get( + ctx, + types.NamespacedName{Name: ms.Name, Namespace: ms.Namespace}, + current, + ); err != nil { + return err + } + + if !reflect.DeepEqual(ms.Status, current.Status) { + if err := r.Status().Patch(ctx, ms, client.MergeFrom(current)); err != nil { + return err + } + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *MautrixSignalReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&synapsev1alpha1.MautrixSignal{}). + Complete(r) +} diff --git a/controllers/synapse/mautrixsignal/mautrixsignal_controller_test.go b/controllers/synapse/mautrixsignal/mautrixsignal_controller_test.go new file mode 100644 index 0000000..518d15c --- /dev/null +++ b/controllers/synapse/mautrixsignal/mautrixsignal_controller_test.go @@ -0,0 +1,1222 @@ +package mautrixsignal + +// import ( +// "context" +// "path/filepath" + +// // "strconv" +// "time" + +// . "github.com/onsi/ginkgo/v2" +// . "github.com/onsi/gomega" + +// // appsv1 "k8s.io/api/apps/v1" +// // corev1 "k8s.io/api/core/v1" +// // rbacv1 "k8s.io/api/rbac/v1" + +// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +// "k8s.io/client-go/kubernetes/scheme" +// ctrl "sigs.k8s.io/controller-runtime" +// "sigs.k8s.io/controller-runtime/pkg/client" +// "sigs.k8s.io/controller-runtime/pkg/envtest" +// logf "sigs.k8s.io/controller-runtime/pkg/log" +// "sigs.k8s.io/controller-runtime/pkg/log/zap" + +// synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" +// ) + +// var _ = Describe("Integration tests for the Synapse controller", Ordered, Label("integration"), func() { +// // Define utility constants for object names and testing timeouts/durations and intervals. +// const ( +// MautrixSignalName = "test-mautrixsignal" +// MautrixSignalNamespace = "default" +// InputConfigMapName = "test-configmap" + +// timeout = time.Second * 2 +// duration = time.Second * 2 +// interval = time.Millisecond * 250 +// ) + +// var k8sClient client.Client +// var testEnv *envtest.Environment +// var ctx context.Context +// var cancel context.CancelFunc + +// // var deleteResource func(client.Object, types.NamespacedName, bool) +// // var checkSubresourceAbsence func(string) +// // var checkResourcePresence func(client.Object, types.NamespacedName, metav1.OwnerReference) + +// // Common function to start envTest +// var startenvTest = func() { +// cfg, err := testEnv.Start() +// Expect(err).NotTo(HaveOccurred()) +// Expect(cfg).NotTo(BeNil()) + +// Expect(synapsev1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + +// //+kubebuilder:scaffold:scheme + +// k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) +// Expect(err).NotTo(HaveOccurred()) +// Expect(k8sClient).NotTo(BeNil()) + +// k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ +// Scheme: scheme.Scheme, +// }) +// Expect(err).ToNot(HaveOccurred()) + +// err = (&MautrixSignalReconciler{ +// Client: k8sManager.GetClient(), +// Scheme: k8sManager.GetScheme(), +// }).SetupWithManager(k8sManager) +// Expect(err).ToNot(HaveOccurred()) + +// // deleteResource = utils.DeleteResourceFunc(k8sClient, ctx, timeout, interval) +// // checkSubresourceAbsence = utils.CheckSubresourceAbsenceFunc(MautrixSignalName, MautrixSignalNamespace, k8sClient, ctx, timeout, interval) +// // checkResourcePresence = utils.CheckResourcePresenceFunc(k8sClient, ctx, timeout, interval) + +// go func() { +// defer GinkgoRecover() +// Expect(k8sManager.Start(ctx)).ToNot(HaveOccurred(), "failed to run manager") +// }() +// } + +// Context("When a corectly configured Kubernetes cluster is present", func() { +// var _ = BeforeAll(func() { +// logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + +// ctx, cancel = context.WithCancel(context.TODO()) + +// By("bootstrapping test environment") +// testEnv = &envtest.Environment{ +// CRDDirectoryPaths: []string{ +// filepath.Join("..", "..", "..", "bundle", "manifests", "synapse.opdev.io_synapses.yaml"), +// }, +// ErrorIfCRDPathMissing: true, +// } + +// startenvTest() +// }) + +// var _ = AfterAll(func() { +// cancel() +// By("tearing down the test environment") +// Expect(testEnv.Stop()).NotTo(HaveOccurred()) +// }) + +// Context("Validating MautixSignal CRD Schema", func() { +// var obj map[string]interface{} + +// BeforeEach(func() { +// obj = map[string]interface{}{ +// "apiVersion": "synapse.opdev.io/v1alpha1", +// "kind": "MautrixSignal", +// "metadata": map[string]interface{}{ +// "name": MautrixSignalName, +// "namespace": MautrixSignalNamespace, +// }, +// } +// }) + +// DescribeTable("Creating a misconfigured MautrixSignal instance", +// func(mautrixsignal map[string]interface{}) { +// // Augment base mautrixsignal obj with additional fields +// for key, value := range mautrixsignal { +// obj[key] = value +// } +// // Create Unstructured object from mautrixsignal obj +// u := unstructured.Unstructured{Object: obj} +// Expect(k8sClient.Create(ctx, &u)).ShouldNot(Succeed()) +// }, +// Entry("when MautrixSignal spec is missing", map[string]interface{}{}), +// Entry("when MautrixSignal spec is empty", map[string]interface{}{ +// "spec": map[string]interface{}{}, +// }), +// Entry("when MautrixSignal spec is missing Synapse reference", map[string]interface{}{ +// "spec": map[string]interface{}{ +// "configMap": map[string]interface{}{ +// "name": "dummy", +// }, +// }, +// }), +// Entry("when MautrixSignal spec Synapse doesn't has a name", map[string]interface{}{ +// "spec": map[string]interface{}{ +// "synapse": map[string]interface{}{ +// "namespase": "dummy", +// }, +// }, +// }), +// Entry("when MautrixSignal spec ConfigMap doesn't specify a Name", map[string]interface{}{ +// "spec": map[string]interface{}{ +// "configMap": map[string]interface{}{ +// "namespace": "dummy", +// }, +// "synapse": map[string]interface{}{ +// "name": "dummy", +// }, +// }, +// }), +// // This should not work but passes +// PEntry("when MautrixSignal spec possesses an invalid field", map[string]interface{}{ +// "spec": map[string]interface{}{ +// "synapse": map[string]interface{}{ +// "name": "dummy", +// }, +// "invalidSpecFiels": "random", +// }, +// }), +// ) + +// DescribeTable("Creating a correct MautrixSignal instance", +// func(mautrixsignal map[string]interface{}) { +// // Augment base mautrixsignal obj with additional fields +// for key, value := range mautrixsignal { +// obj[key] = value +// } +// // Create Unstructured object from mautrixsignal obj +// u := unstructured.Unstructured{Object: obj} +// // Use DryRun option to avoid cleaning up resources +// opt := client.CreateOptions{DryRun: []string{"All"}} +// Expect(k8sClient.Create(ctx, &u, &opt)).Should(Succeed()) +// }, +// Entry( +// "when the Configuration file is provided via a ConfigMap", +// map[string]interface{}{ +// "spec": map[string]interface{}{ +// "configMap": map[string]interface{}{ +// "name": "dummy", +// "namespace": "dummy", +// }, +// "synapse": map[string]interface{}{ +// "name": "dummy", +// "namespace": "dummy", +// }, +// }, +// }, +// ), +// Entry( +// "when optional Synapse Namespace and ConfigMap Namespace are missing", +// map[string]interface{}{ +// "spec": map[string]interface{}{ +// "configMap": map[string]interface{}{ +// "name": "dummy", +// }, +// "synapse": map[string]interface{}{ +// "name": "dummy", +// }, +// }, +// }, +// ), +// ) +// }) + +// Context("When creating a valid Synapse instance", func() { +// var synapse *synapsev1alpha1.Synapse +// var createdConfigMap *corev1.ConfigMap +// var createdPVC *corev1.PersistentVolumeClaim +// var createdDeployment *appsv1.Deployment +// var createdService *corev1.Service +// var createdServiceAccount *corev1.ServiceAccount +// var createdRoleBinding *rbacv1.RoleBinding +// var synapseLookupKey types.NamespacedName +// var expectedOwnerReference metav1.OwnerReference +// var synapseSpec synapsev1alpha1.SynapseSpec + +// var initSynapseVariables = func() { +// // Init variables +// synapseLookupKey = types.NamespacedName{Name: SynapseName, Namespace: SynapseNamespace} +// createdConfigMap = &corev1.ConfigMap{} +// createdPVC = &corev1.PersistentVolumeClaim{} +// createdDeployment = &appsv1.Deployment{} +// createdService = &corev1.Service{} +// createdServiceAccount = &corev1.ServiceAccount{} +// createdRoleBinding = &rbacv1.RoleBinding{} +// // The OwnerReference UID must be set after the Synapse instance has been +// // created. See the JustBeforeEach node. +// expectedOwnerReference = metav1.OwnerReference{ +// Kind: "Synapse", +// APIVersion: "synapse.opdev.io/v1alpha1", +// Name: SynapseName, +// Controller: utils.BoolAddr(true), +// BlockOwnerDeletion: utils.BoolAddr(true), +// } +// } + +// var createSynapseInstance = func() { +// By("Creating the Synapse instance") +// synapse = &synapsev1alpha1.Synapse{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: SynapseName, +// Namespace: SynapseNamespace, +// }, +// Spec: synapseSpec, +// } +// Expect(k8sClient.Create(ctx, synapse)).Should(Succeed()) + +// By("Verifying that the Synapse object was created") +// Eventually(func() bool { +// err := k8sClient.Get(ctx, synapseLookupKey, synapse) +// return err == nil +// }, timeout, interval).Should(BeTrue()) + +// expectedOwnerReference.UID = synapse.GetUID() +// } + +// var cleanupSynapseResources = func() { +// By("Cleaning up Synapse CR") +// Expect(k8sClient.Delete(ctx, synapse)).Should(Succeed()) + +// // Child resources must be manually deleted as the controllers responsible of +// // their lifecycle are not running. +// By("Cleaning up Synapse ConfigMap") +// deleteResource(createdConfigMap, synapseLookupKey, false) + +// By("Cleaning up Synapse PVC") +// deleteResource(createdPVC, synapseLookupKey, true) + +// By("Cleaning up Synapse Deployment") +// deleteResource(createdDeployment, synapseLookupKey, false) + +// By("Cleaning up Synapse Service") +// deleteResource(createdService, synapseLookupKey, false) + +// By("Cleaning up Synapse RoleBinding") +// deleteResource(createdRoleBinding, synapseLookupKey, false) + +// By("Cleaning up Synapse ServiceAccount") +// deleteResource(createdServiceAccount, synapseLookupKey, false) +// } + +// When("Specifying the Synapse configuration via Values", func() { +// BeforeAll(func() { +// initSynapseVariables() + +// synapseSpec = synapsev1alpha1.SynapseSpec{ +// Homeserver: synapsev1alpha1.SynapseHomeserver{ +// Values: &synapsev1alpha1.SynapseHomeserverValues{ +// ServerName: ServerName, +// ReportStats: ReportStats, +// }, +// }, +// } + +// createSynapseInstance() +// }) + +// AfterAll(func() { +// cleanupSynapseResources() +// }) + +// It("Should should update the Synapse Status", func() { +// expectedStatus := synapsev1alpha1.SynapseStatus{ +// State: "RUNNING", +// Reason: "", +// HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ +// ServerName: ServerName, +// ReportStats: ReportStats, +// }, +// } +// // Status may need some time to be updated +// Eventually(func() synapsev1alpha1.SynapseStatus { +// _ = k8sClient.Get(ctx, synapseLookupKey, synapse) +// return synapse.Status +// }, timeout, interval).Should(Equal(expectedStatus)) +// }) + +// It("Should create a Synapse ConfigMap", func() { +// checkResourcePresence(createdConfigMap, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse PVC", func() { +// checkResourcePresence(createdPVC, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse Deployment", func() { +// By("Checking that a Synapse Deployment exists and is correctly configured") +// checkResourcePresence(createdDeployment, synapseLookupKey, expectedOwnerReference) + +// By("Checking that initContainers contains the required environment variables") +// envVars := []corev1.EnvVar{{ +// Name: "SYNAPSE_SERVER_NAME", +// Value: ServerName, +// }, { +// Name: "SYNAPSE_REPORT_STATS", +// Value: utils.BoolToYesNo(ReportStats), +// }} +// Expect(createdDeployment.Spec.Template.Spec.InitContainers[0].Env).Should(ContainElements(envVars)) +// }) + +// It("Should create a Synapse Service", func() { +// checkResourcePresence(createdService, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse ServiceAccount", func() { +// checkResourcePresence(createdServiceAccount, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse RoleBinding", func() { +// checkResourcePresence(createdRoleBinding, synapseLookupKey, expectedOwnerReference) +// }) +// }) + +// When("Specifying the Synapse configuration via a ConfigMap", func() { +// var inputConfigMap *corev1.ConfigMap +// var inputConfigmapData map[string]string + +// var createSynapseConfigMap = func() { +// By("Creating a ConfigMap containing a basic homeserver.yaml") +// // Populate the ConfigMap with the minimum data needed +// inputConfigMap = &corev1.ConfigMap{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: InputConfigMapName, +// Namespace: SynapseNamespace, +// }, +// Data: inputConfigmapData, +// } +// Expect(k8sClient.Create(ctx, inputConfigMap)).Should(Succeed()) +// } + +// var cleanupSynapseConfigMap = func() { +// By("Cleaning up ConfigMap") +// Expect(k8sClient.Delete(ctx, inputConfigMap)).Should(Succeed()) +// } + +// When("Creating a simple Synapse instance", func() { +// BeforeAll(func() { +// initSynapseVariables() + +// inputConfigmapData = map[string]string{ +// "homeserver.yaml": "server_name: " + ServerName + "\n" + +// "report_stats: " + strconv.FormatBool(ReportStats), +// } + +// synapseSpec = synapsev1alpha1.SynapseSpec{ +// Homeserver: synapsev1alpha1.SynapseHomeserver{ +// ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// Name: InputConfigMapName, +// }, +// }, +// } + +// createSynapseConfigMap() +// createSynapseInstance() +// }) + +// AfterAll(func() { +// cleanupSynapseResources() +// cleanupSynapseConfigMap() +// }) + +// It("Should should update the Synapse Status", func() { +// expectedStatus := synapsev1alpha1.SynapseStatus{ +// State: "RUNNING", +// Reason: "", +// HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ +// ServerName: ServerName, +// ReportStats: ReportStats, +// }, +// } +// // Status may need some time to be updated +// Eventually(func() synapsev1alpha1.SynapseStatus { +// _ = k8sClient.Get(ctx, synapseLookupKey, synapse) +// return synapse.Status +// }, timeout, interval).Should(Equal(expectedStatus)) +// }) + +// It("Should create a Synapse ConfigMap", func() { +// checkResourcePresence(createdConfigMap, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse PVC", func() { +// checkResourcePresence(createdPVC, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse Deployment", func() { +// By("Checking that a Synapse Deployment exists and is correctly configured") +// checkResourcePresence(createdDeployment, synapseLookupKey, expectedOwnerReference) + +// By("Checking that initContainers contains the required environment variables") +// envVars := []corev1.EnvVar{{ +// Name: "SYNAPSE_SERVER_NAME", +// Value: ServerName, +// }, { +// Name: "SYNAPSE_REPORT_STATS", +// Value: utils.BoolToYesNo(ReportStats), +// }} +// Expect(createdDeployment.Spec.Template.Spec.InitContainers[0].Env).Should(ContainElements(envVars)) +// }) + +// It("Should create a Synapse Service", func() { +// checkResourcePresence(createdService, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse ServiceAccount", func() { +// checkResourcePresence(createdServiceAccount, synapseLookupKey, expectedOwnerReference) +// }) + +// It("Should create a Synapse RoleBinding", func() { +// checkResourcePresence(createdRoleBinding, synapseLookupKey, expectedOwnerReference) +// }) +// }) + +// When("Requesting a new PostgreSQL instance to be created for Synapse", func() { +// var createdPostgresCluster *pgov1beta1.PostgresCluster +// var postgresSecret corev1.Secret +// var postgresLookupKeys types.NamespacedName + +// BeforeAll(func() { +// initSynapseVariables() + +// postgresLookupKeys = types.NamespacedName{ +// Name: synapseLookupKey.Name + "-pgsql", +// Namespace: synapseLookupKey.Namespace, +// } + +// // Init variable +// createdPostgresCluster = &pgov1beta1.PostgresCluster{} + +// inputConfigmapData = map[string]string{ +// "homeserver.yaml": "server_name: " + ServerName + "\n" + +// "report_stats: " + strconv.FormatBool(ReportStats), +// } + +// synapseSpec = synapsev1alpha1.SynapseSpec{ +// Homeserver: synapsev1alpha1.SynapseHomeserver{ +// ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// Name: InputConfigMapName, +// }, +// }, +// CreateNewPostgreSQL: true, +// } + +// createSynapseConfigMap() +// createSynapseInstance() +// }) + +// doPostgresControllerJob := func() { +// // The postgres-operator is responsible for creating a Secret holding +// // information on how to connect to the synapse Database with the synapse +// // user. As this controller is not running during our integration tests, +// // we have to manually create this secret here. +// postgresSecret = corev1.Secret{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: SynapseName + "-pgsql-pguser-synapse", +// Namespace: SynapseNamespace, +// }, +// Data: map[string][]byte{ +// "host": []byte("hostname.postgresql.url"), +// "port": []byte("5432"), +// "dbname": []byte("synapse"), +// "user": []byte("synapse"), +// "password": []byte("VerySecureSyn@psePassword!"), +// }, +// } +// Expect(k8sClient.Create(ctx, &postgresSecret)).Should(Succeed()) + +// // The portgres-operator is responsible for updating the PostgresCluster +// // status, with the number of Pods being ready. This is used a part of +// // the 'isPostgresClusterReady' method in the Synapse controller. +// createdPostgresCluster.Status.InstanceSets = []pgov1beta1.PostgresInstanceSetStatus{{ +// Name: "instance1", +// Replicas: 1, +// ReadyReplicas: 1, +// UpdatedReplicas: 1, +// }} +// Expect(k8sClient.Status().Update(ctx, createdPostgresCluster)).Should(Succeed()) +// } + +// AfterAll(func() { +// By("Cleaning up the Synapse PostgresCluster") +// deleteResource(createdPostgresCluster, postgresLookupKeys, false) + +// cleanupSynapseResources() +// cleanupSynapseConfigMap() +// }) + +// It("Should create a PostgresCluster for Synapse", func() { +// By("Checking that a Synapse PostgresCluster exists") +// checkResourcePresence(createdPostgresCluster, postgresLookupKeys, expectedOwnerReference) +// }) + +// It("Should update the Synapse status", func() { +// By("Checking that the controller detects the Database as not ready") +// Expect(k8sClient.Get(ctx, synapseLookupKey, synapse)).Should(Succeed()) +// Expect(synapse.Status.DatabaseConnectionInfo.State).Should(Equal("NOT READY")) + +// // Once the PostgresCluster has been created, we simulate the +// // postgres-operator reconciliation. +// By("Simulating the postgres-operator controller job") +// doPostgresControllerJob() + +// By("Checking that the Synapse Status is correctly updated") +// Eventually(func(g Gomega) { +// g.Expect(k8sClient.Get(ctx, synapseLookupKey, synapse)).Should(Succeed()) + +// g.Expect(synapse.Status.DatabaseConnectionInfo.ConnectionURL).Should(Equal("hostname.postgresql.url:5432")) +// g.Expect(synapse.Status.DatabaseConnectionInfo.DatabaseName).Should(Equal("synapse")) +// g.Expect(synapse.Status.DatabaseConnectionInfo.User).Should(Equal("synapse")) +// g.Expect(synapse.Status.DatabaseConnectionInfo.Password).Should(Equal(string(base64encode("VerySecureSyn@psePassword!")))) +// g.Expect(synapse.Status.DatabaseConnectionInfo.State).Should(Equal("READY")) +// }, timeout, interval).Should(Succeed()) +// }) + +// It("Should update the ConfigMap Data", func() { +// Eventually(func(g Gomega) { +// // Fetching database section of the homeserver.yaml configuration file +// g.Expect(k8sClient.Get(ctx, +// types.NamespacedName{Name: SynapseName, Namespace: SynapseNamespace}, +// createdConfigMap, +// )).Should(Succeed()) + +// cm_data, ok := createdConfigMap.Data["homeserver.yaml"] +// g.Expect(ok).Should(BeTrue()) + +// homeserver := make(map[string]interface{}) +// g.Expect(yaml.Unmarshal([]byte(cm_data), homeserver)).Should(Succeed()) + +// _, ok = homeserver["database"] +// g.Expect(ok).Should(BeTrue()) + +// marshalled_homeserver_database, err := yaml.Marshal(homeserver["database"]) +// g.Expect(err).ShouldNot(HaveOccurred()) + +// var hs_database HomeserverPgsqlDatabase +// g.Expect(yaml.Unmarshal(marshalled_homeserver_database, &hs_database)).Should(Succeed()) + +// // hs_database, ok := homeserver["database"].(HomeserverPgsqlDatabase) +// // g.Expect(ok).Should(BeTrue()) + +// // Testing that the database section is correctly configured for using +// // the PostgreSQL DB +// g.Expect(hs_database.Name).Should(Equal("psycopg2")) +// g.Expect(hs_database.Args.Host).Should(Equal("hostname.postgresql.url")) + +// g.Expect(hs_database.Args.Port).Should(Equal(int64(5432))) +// g.Expect(hs_database.Args.Database).Should(Equal("synapse")) +// g.Expect(hs_database.Args.User).Should(Equal("synapse")) +// g.Expect(hs_database.Args.Password).Should(Equal("VerySecureSyn@psePassword!")) + +// g.Expect(hs_database.Args.CpMin).Should(Equal(int64(5))) +// g.Expect(hs_database.Args.CpMax).Should(Equal(int64(10))) +// }, timeout, interval).Should(Succeed()) +// }) +// }) + +// // When("Enabling the Heisenbridge", func() { +// // const ( +// // heisenbridgePort = 9898 +// // ) + +// // var createdHeisenbridgeDeployment *appsv1.Deployment +// // var createdHeisenbridgeService *corev1.Service +// // var createdHeisenbridgeConfigMap *corev1.ConfigMap +// // var heisenbridgeLookupKey types.NamespacedName + +// // var initHeisenbridgeVariables = func() { +// // // Init vars +// // createdHeisenbridgeDeployment = &appsv1.Deployment{} +// // createdHeisenbridgeService = &corev1.Service{} +// // createdHeisenbridgeConfigMap = &corev1.ConfigMap{} + +// // heisenbridgeLookupKey = types.NamespacedName{Name: SynapseName + "-heisenbridge", Namespace: SynapseNamespace} +// // } + +// // var cleanupHeisenbridgeResources = func() { +// // By("Cleaning up the Heisenbridge Deployment") +// // deleteResource(createdHeisenbridgeDeployment, heisenbridgeLookupKey, false) + +// // By("Cleaning up the Heisenbridge Service") +// // deleteResource(createdHeisenbridgeService, heisenbridgeLookupKey, false) + +// // By("Cleaning up the Heisenbridge ConfigMap") +// // deleteResource(createdHeisenbridgeConfigMap, heisenbridgeLookupKey, false) +// // } + +// // When("Using the default configuration", func() { +// // BeforeAll(func() { +// // initSynapseVariables() +// // initHeisenbridgeVariables() + +// // inputConfigmapData = map[string]string{ +// // "homeserver.yaml": "server_name: " + ServerName + "\n" + +// // "report_stats: " + strconv.FormatBool(ReportStats), +// // } + +// // synapseSpec = synapsev1alpha1.SynapseSpec{ +// // Homeserver: synapsev1alpha1.SynapseHomeserver{ +// // ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// // Name: InputConfigMapName, +// // }, +// // }, +// // Bridges: synapsev1alpha1.SynapseBridges{ +// // Heisenbridge: synapsev1alpha1.SynapseHeisenbridge{ +// // Enabled: true, +// // }, +// // }, +// // } + +// // createSynapseConfigMap() +// // createSynapseInstance() +// // }) + +// // AfterAll(func() { +// // // Cleanup Heisenbridge resources +// // cleanupSynapseResources() +// // cleanupSynapseConfigMap() +// // cleanupHeisenbridgeResources() +// // }) + +// // It("Should create a ConfigMap for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeConfigMap, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Deployment for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeDeployment, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeService, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should update the Synapse homeserver.yaml", func() { +// // Eventually(func(g Gomega) { +// // g.Expect(k8sClient.Get(ctx, +// // types.NamespacedName{Name: SynapseName, Namespace: SynapseNamespace}, +// // createdConfigMap, +// // )).Should(Succeed()) + +// // cm_data, ok := createdConfigMap.Data["homeserver.yaml"] +// // g.Expect(ok).Should(BeTrue()) + +// // homeserver := make(map[string]interface{}) +// // g.Expect(yaml.Unmarshal([]byte(cm_data), homeserver)).Should(Succeed()) + +// // _, ok = homeserver["app_service_config_files"] +// // g.Expect(ok).Should(BeTrue()) + +// // g.Expect(homeserver["app_service_config_files"]).Should(ContainElement("/data-heisenbridge/heisenbridge.yaml")) +// // }, timeout, interval).Should(Succeed()) +// // }) +// // }) + +// // When("The user provides an input ConfigMap", func() { +// // var inputHeisenbridgeConfigMap *corev1.ConfigMap +// // var inputHeisenbridgeConfigMapData map[string]string + +// // const InputHeisenbridgeConfigMapName = "heisenbridge-input" +// // const heisenbridgeFQDN = SynapseName + "-heisenbridge." + SynapseNamespace + ".svc.cluster.local" + +// // BeforeAll(func() { +// // initSynapseVariables() +// // initHeisenbridgeVariables() + +// // inputConfigmapData = map[string]string{ +// // "homeserver.yaml": "server_name: " + ServerName + "\n" + +// // "report_stats: " + strconv.FormatBool(ReportStats), +// // } + +// // synapseSpec = synapsev1alpha1.SynapseSpec{ +// // Homeserver: synapsev1alpha1.SynapseHomeserver{ +// // ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// // Name: InputConfigMapName, +// // }, +// // }, +// // Bridges: synapsev1alpha1.SynapseBridges{ +// // Heisenbridge: synapsev1alpha1.SynapseHeisenbridge{ +// // Enabled: true, +// // ConfigMap: synapsev1alpha1.SynapseHeisenbridgeConfigMap{ +// // Name: InputHeisenbridgeConfigMapName, +// // }, +// // }, +// // }, +// // } + +// // By("Creating a ConfigMap containing a basic heisenbridge.yaml") +// // // Incomplete heisenbridge.yaml, containing only the +// // // required data for our tests. In particular, we +// // // will test if the URL has been correctly updated +// // inputHeisenbridgeConfigMapData = map[string]string{ +// // "heisenbridge.yaml": "url: http://10.217.5.134:" + strconv.Itoa(heisenbridgePort), +// // } + +// // inputHeisenbridgeConfigMap = &corev1.ConfigMap{ +// // ObjectMeta: metav1.ObjectMeta{ +// // Name: InputHeisenbridgeConfigMapName, +// // Namespace: SynapseNamespace, +// // }, +// // Data: inputHeisenbridgeConfigMapData, +// // } +// // Expect(k8sClient.Create(ctx, inputHeisenbridgeConfigMap)).Should(Succeed()) + +// // createSynapseConfigMap() +// // createSynapseInstance() +// // }) + +// // AfterAll(func() { +// // // Cleanup Heisenbridge resources +// // By("Cleaning up the Heisenbridge ConfigMap") +// // heisenbridgeConfigMapLookupKey := types.NamespacedName{ +// // Name: InputHeisenbridgeConfigMapName, +// // Namespace: SynapseNamespace, +// // } + +// // deleteResource(inputHeisenbridgeConfigMap, heisenbridgeConfigMapLookupKey, false) + +// // cleanupSynapseResources() +// // cleanupSynapseConfigMap() +// // cleanupHeisenbridgeResources() +// // }) + +// // It("Should create a ConfigMap for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeConfigMap, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Deployment for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeDeployment, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service for Heisenbridge", func() { +// // checkResourcePresence(createdHeisenbridgeService, heisenbridgeLookupKey, expectedOwnerReference) +// // }) + +// // It("Should add url value to the created Heisenbridge ConfigMap", func() { +// // Eventually(func(g Gomega) { +// // g.Expect(k8sClient.Get(ctx, heisenbridgeLookupKey, inputHeisenbridgeConfigMap)).Should(Succeed()) + +// // cm_data, ok := inputHeisenbridgeConfigMap.Data["heisenbridge.yaml"] +// // g.Expect(ok).Should(BeTrue()) + +// // heisenbridge := make(map[string]interface{}) +// // g.Expect(yaml.Unmarshal([]byte(cm_data), heisenbridge)).Should(Succeed()) + +// // _, ok = heisenbridge["url"] +// // g.Expect(ok).Should(BeTrue()) + +// // g.Expect(heisenbridge["url"]).To(Equal("http://" + heisenbridgeFQDN + ":" + strconv.Itoa(heisenbridgePort))) +// // }, timeout, interval).Should(Succeed()) +// // }) +// // }) +// // }) + +// // When("Enabling the mautrix-signal", func() { +// // const ( +// // mautrixSignalPort = 29328 +// // ) + +// // var createdSignaldDeployment *appsv1.Deployment +// // var createdSignaldPVC *corev1.PersistentVolumeClaim +// // var createdMautrixSignalServiceAccount *corev1.ServiceAccount +// // var createdMautrixSignalRoleBinding *rbacv1.RoleBinding +// // var createdMautrixSignalDeployment *appsv1.Deployment +// // var createdMautrixSignalPVC *corev1.PersistentVolumeClaim +// // var createdMautrixSignalService *corev1.Service +// // var createdMautrixSignalConfigMap *corev1.ConfigMap +// // var mautrixSignalLookupKey types.NamespacedName +// // var signaldLookupKey types.NamespacedName + +// // var initMautrixSignalVariables = func() { +// // // Init vars +// // createdSignaldDeployment = &appsv1.Deployment{} +// // createdSignaldPVC = &corev1.PersistentVolumeClaim{} +// // createdMautrixSignalServiceAccount = &corev1.ServiceAccount{} +// // createdMautrixSignalRoleBinding = &rbacv1.RoleBinding{} +// // createdMautrixSignalDeployment = &appsv1.Deployment{} +// // createdMautrixSignalPVC = &corev1.PersistentVolumeClaim{} +// // createdMautrixSignalService = &corev1.Service{} +// // createdMautrixSignalConfigMap = &corev1.ConfigMap{} + +// // signaldLookupKey = types.NamespacedName{Name: SynapseName + "-signald", Namespace: SynapseNamespace} +// // mautrixSignalLookupKey = types.NamespacedName{Name: SynapseName + "-mautrixsignal", Namespace: SynapseNamespace} +// // } + +// // var cleanupMautrixSignalResources = func() { +// // By("Cleaning up the signald Deployment") +// // deleteResource(createdSignaldDeployment, signaldLookupKey, false) + +// // By("Cleaning up the mautrix-signal Deployment") +// // deleteResource(createdMautrixSignalDeployment, mautrixSignalLookupKey, false) + +// // By("Cleaning up the signald PVC") +// // deleteResource(createdSignaldPVC, signaldLookupKey, true) + +// // By("Cleaning up the mautrix-signal PVC") +// // deleteResource(createdMautrixSignalPVC, mautrixSignalLookupKey, true) + +// // By("Cleaning up the mautrix-signal Service") +// // deleteResource(createdMautrixSignalService, mautrixSignalLookupKey, false) + +// // By("Cleaning up the mautrix-signal ConfigMap") +// // deleteResource(createdMautrixSignalConfigMap, mautrixSignalLookupKey, false) + +// // By("Cleaning up mautrix-signal RoleBinding") +// // deleteResource(createdMautrixSignalRoleBinding, mautrixSignalLookupKey, false) + +// // By("Cleaning up mautrix-signal ServiceAccount") +// // deleteResource(createdMautrixSignalServiceAccount, mautrixSignalLookupKey, false) +// // } + +// // When("Using the default configuration", func() { +// // BeforeAll(func() { +// // initSynapseVariables() +// // initMautrixSignalVariables() + +// // inputConfigmapData = map[string]string{ +// // "homeserver.yaml": "server_name: " + ServerName + "\n" + +// // "report_stats: " + strconv.FormatBool(ReportStats), +// // } + +// // synapseSpec = synapsev1alpha1.SynapseSpec{ +// // Homeserver: synapsev1alpha1.SynapseHomeserver{ +// // ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// // Name: InputConfigMapName, +// // }, +// // }, +// // Bridges: synapsev1alpha1.SynapseBridges{ +// // MautrixSignal: synapsev1alpha1.SynapseMautrixSignal{ +// // Enabled: true, +// // }, +// // }, +// // } + +// // createSynapseConfigMap() +// // createSynapseInstance() +// // }) + +// // AfterAll(func() { +// // // Cleanup mautrix-signal resources +// // cleanupSynapseResources() +// // cleanupSynapseConfigMap() +// // cleanupMautrixSignalResources() +// // }) + +// // It("Should create a Deployment for signald", func() { +// // checkResourcePresence(createdSignaldDeployment, signaldLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a PVC for signald", func() { +// // checkResourcePresence(createdSignaldPVC, signaldLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a ConfigMap for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalConfigMap, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Role Binding for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalRoleBinding, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service Account for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalServiceAccount, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Deployment for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalDeployment, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a PVC for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalPVC, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalService, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should update the Synapse homeserver.yaml", func() { +// // Eventually(func(g Gomega) { +// // g.Expect(k8sClient.Get(ctx, +// // types.NamespacedName{Name: SynapseName, Namespace: SynapseNamespace}, +// // createdConfigMap, +// // )).Should(Succeed()) + +// // cm_data, ok := createdConfigMap.Data["homeserver.yaml"] +// // g.Expect(ok).Should(BeTrue()) + +// // homeserver := make(map[string]interface{}) +// // g.Expect(yaml.Unmarshal([]byte(cm_data), homeserver)).Should(Succeed()) + +// // _, ok = homeserver["app_service_config_files"] +// // g.Expect(ok).Should(BeTrue()) + +// // g.Expect(homeserver["app_service_config_files"]).Should(ContainElement("/data-mautrixsignal/registration.yaml")) +// // }, timeout, interval).Should(Succeed()) +// // }) +// // }) + +// // When("The user provides an input ConfigMap", func() { +// // var inputMautrixSignalConfigMap *corev1.ConfigMap +// // var inputMautrixSignalConfigMapData map[string]string + +// // const InputMautrixSignalConfigMapName = "mautrix-signal-input" +// // const mautrixSignalFQDN = SynapseName + "-mautrixsignal." + SynapseNamespace + ".svc.cluster.local" +// // const synapseFQDN = SynapseName + "." + SynapseNamespace + ".svc.cluster.local" + +// // BeforeAll(func() { +// // initSynapseVariables() +// // initMautrixSignalVariables() + +// // inputConfigmapData = map[string]string{ +// // "homeserver.yaml": "server_name: " + ServerName + "\n" + +// // "report_stats: " + strconv.FormatBool(ReportStats), +// // } + +// // synapseSpec = synapsev1alpha1.SynapseSpec{ +// // Homeserver: synapsev1alpha1.SynapseHomeserver{ +// // ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// // Name: InputConfigMapName, +// // }, +// // }, +// // Bridges: synapsev1alpha1.SynapseBridges{ +// // MautrixSignal: synapsev1alpha1.SynapseMautrixSignal{ +// // Enabled: true, +// // ConfigMap: synapsev1alpha1.SynapseMautrixSignalConfigMap{ +// // Name: InputMautrixSignalConfigMapName, +// // }, +// // }, +// // }, +// // } + +// // By("Creating a ConfigMap containing a basic config.yaml") +// // // Incomplete config.yaml, containing only the +// // // required data for our tests. We test that those +// // // values are correctly updated +// // configYaml := ` +// // homeserver: +// // address: http://localhost:8008 +// // domain: mydomain.com +// // appservice: +// // address: http://localhost:29328 +// // signal: +// // socket_path: /var/run/signald/signald.sock +// // bridge: +// // permissions: +// // "*": "relay" +// // "mydomain.com": "user" +// // "@admin:mydomain.com": "admin" +// // logging: +// // handlers: +// // file: +// // filename: ./mautrix-signal.log` + +// // inputMautrixSignalConfigMapData = map[string]string{ +// // "config.yaml": configYaml, +// // } + +// // inputMautrixSignalConfigMap = &corev1.ConfigMap{ +// // ObjectMeta: metav1.ObjectMeta{ +// // Name: InputMautrixSignalConfigMapName, +// // Namespace: SynapseNamespace, +// // }, +// // Data: inputMautrixSignalConfigMapData, +// // } +// // Expect(k8sClient.Create(ctx, inputMautrixSignalConfigMap)).Should(Succeed()) + +// // createSynapseConfigMap() +// // createSynapseInstance() +// // }) + +// // AfterAll(func() { +// // // Cleanup mautrix-signal resources +// // By("Cleaning up the mautrix-signal ConfigMap") +// // mautrixSignalConfigMapLookupKey := types.NamespacedName{ +// // Name: InputMautrixSignalConfigMapName, +// // Namespace: SynapseNamespace, +// // } + +// // deleteResource(inputMautrixSignalConfigMap, mautrixSignalConfigMapLookupKey, false) + +// // cleanupSynapseResources() +// // cleanupSynapseConfigMap() +// // cleanupMautrixSignalResources() +// // }) + +// // It("Should create a Deployment for signald", func() { +// // checkResourcePresence(createdMautrixSignalDeployment, signaldLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a PVC for signald", func() { +// // checkResourcePresence(createdSignaldPVC, signaldLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a ConfigMap for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalConfigMap, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Role Binding for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalRoleBinding, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service Account for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalServiceAccount, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Deployment for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalDeployment, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a PVC for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalPVC, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should create a Service for mautrix-signal", func() { +// // checkResourcePresence(createdMautrixSignalService, mautrixSignalLookupKey, expectedOwnerReference) +// // }) + +// // It("Should overwrite necessary values in the created mautrix-signal ConfigMap", func() { +// // Eventually(func(g Gomega) { +// // By("Verifying that the mautrixsignal ConfigMap exists") +// // g.Expect(k8sClient.Get(ctx, mautrixSignalLookupKey, inputMautrixSignalConfigMap)).Should(Succeed()) + +// // cm_data, ok := inputMautrixSignalConfigMap.Data["config.yaml"] +// // g.Expect(ok).Should(BeTrue()) + +// // config := make(map[string]interface{}) +// // g.Expect(yaml.Unmarshal([]byte(cm_data), config)).Should(Succeed()) + +// // By("Verifying that the homeserver configuration has been updated") +// // configHomeserver, ok := config["homeserver"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // g.Expect(configHomeserver["address"]).To(Equal("http://" + synapseFQDN + ":8008")) +// // g.Expect(configHomeserver["domain"]).To(Equal(ServerName)) + +// // By("Verifying that the appservice configuration has been updated") +// // configAppservice, ok := config["appservice"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // g.Expect(configAppservice["address"]).To(Equal("http://" + mautrixSignalFQDN + ":" + strconv.Itoa(mautrixSignalPort))) + +// // By("Verifying that the signal configuration has been updated") +// // configSignal, ok := config["signal"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // g.Expect(configSignal["socket_path"]).To(Equal("/signald/signald.sock")) + +// // By("Verifying that the permissions have been updated") +// // configBridge, ok := config["bridge"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // configBridgePermissions, ok := configBridge["permissions"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // g.Expect(configBridgePermissions).Should(HaveKeyWithValue("*", "relay")) +// // g.Expect(configBridgePermissions).Should(HaveKeyWithValue(ServerName, "user")) +// // g.Expect(configBridgePermissions).Should(HaveKeyWithValue("@admin:"+ServerName, "admin")) + +// // By("Verifying that the log configuration file path have been updated") +// // configLogging, ok := config["logging"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // configLoggingHandlers, ok := configLogging["handlers"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // configLoggingHandlersFile, ok := configLoggingHandlers["file"].(map[interface{}]interface{}) +// // g.Expect(ok).Should(BeTrue()) +// // g.Expect(configLoggingHandlersFile["filename"]).To(Equal("/data/mautrix-signal.log")) +// // }, timeout, interval).Should(Succeed()) +// // }) +// // }) +// // }) + +// }) +// }) + +// Context("When creating an incorrect Synapse instance", func() { +// var synapse *synapsev1alpha1.Synapse + +// BeforeEach(func() { +// By("Creating a Synapse instance which refers an absent ConfigMap") +// synapse = &synapsev1alpha1.Synapse{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: SynapseName, +// Namespace: SynapseNamespace, +// }, +// Spec: synapsev1alpha1.SynapseSpec{ +// Homeserver: synapsev1alpha1.SynapseHomeserver{ +// ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// Name: InputConfigMapName, +// }, +// }, +// }, +// } +// Expect(k8sClient.Create(ctx, synapse)).Should(Succeed()) +// }) + +// AfterEach(func() { +// By("Cleaning up Synapse CR") +// Expect(k8sClient.Delete(ctx, synapse)).Should(Succeed()) +// }) + +// It("Should get in a failed state and not create child objects", func() { +// reason := "ConfigMap " + InputConfigMapName + " does not exist in namespace " + SynapseNamespace +// checkSubresourceAbsence(reason) +// }) +// }) +// }) + +// Context("When the Kubernetes cluster is missing the postgres-operator", func() { +// BeforeAll(func() { +// logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + +// ctx, cancel = context.WithCancel(context.TODO()) + +// By("bootstrapping test environment") +// testEnv = &envtest.Environment{ +// CRDDirectoryPaths: []string{ +// filepath.Join("..", "..", "..", "bundle", "manifests", "synapse.opdev.io_synapses.yaml"), +// }, +// ErrorIfCRDPathMissing: true, +// } + +// startenvTest() +// }) + +// AfterAll(func() { +// cancel() +// By("tearing down the test environment") +// err := testEnv.Stop() +// Expect(err).NotTo(HaveOccurred()) +// }) + +// When("Requesting a new PostgreSQL instance to be created for Synapse", func() { +// var synapse *synapsev1alpha1.Synapse +// var configMap *corev1.ConfigMap + +// BeforeAll(func() { +// configMap = &corev1.ConfigMap{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: InputConfigMapName, +// Namespace: SynapseNamespace, +// }, +// Data: map[string]string{ +// "homeserver.yaml": "server_name: " + ServerName + "\n" + +// "report_stats: " + strconv.FormatBool(ReportStats), +// }, +// } +// Expect(k8sClient.Create(ctx, configMap)).Should(Succeed()) + +// By("Creating the Synapse instance") +// synapse = &synapsev1alpha1.Synapse{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: SynapseName, +// Namespace: SynapseNamespace, +// }, +// Spec: synapsev1alpha1.SynapseSpec{ +// Homeserver: synapsev1alpha1.SynapseHomeserver{ +// ConfigMap: &synapsev1alpha1.SynapseHomeserverConfigMap{ +// Name: InputConfigMapName, +// }, +// }, +// CreateNewPostgreSQL: true, +// }, +// } +// Expect(k8sClient.Create(ctx, synapse)).Should(Succeed()) +// }) + +// AfterAll(func() { +// By("Cleaning up ConfigMap") +// Expect(k8sClient.Delete(ctx, configMap)).Should(Succeed()) + +// By("Cleaning up Synapse CR") +// Expect(k8sClient.Delete(ctx, synapse)).Should(Succeed()) +// }) + +// It("Should not create Synapse sub-resources", func() { +// reason := "Cannot create PostgreSQL instance for synapse. Postgres-operator is not installed." +// checkSubresourceAbsence(reason) +// }) +// }) +// }) +// }) diff --git a/controllers/synapse/synapse_mautrixsignal_deployment.go b/controllers/synapse/mautrixsignal/mautrixsignal_deployment.go similarity index 81% rename from controllers/synapse/synapse_mautrixsignal_deployment.go rename to controllers/synapse/mautrixsignal/mautrixsignal_deployment.go index 83d9016..74ee5a3 100644 --- a/controllers/synapse/synapse_mautrixsignal_deployment.go +++ b/controllers/synapse/mautrixsignal/mautrixsignal_deployment.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package synapse +package mautrixsignal import ( "context" @@ -23,30 +23,37 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" ) // labelsForMautrixSignal returns the labels for selecting the resources // belonging to the given synapse CR name. func labelsForMautrixSignal(name string) map[string]string { - return map[string]string{"app": "mautrix-signal", "synapse_cr": name} + return map[string]string{"app": "mautrix-signal", "mautrixsignal_cr": name} } // reconcileMautrixSignalDeployment is a function of type subreconcilerFuncs, // to be called in the main reconciliation loop. // // It reconciles the Deployment for mautrix-signal to its desired state. -func (r *SynapseReconciler) reconcileMautrixSignalDeployment(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalDeployment(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredDeployment, err := r.deploymentForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.deploymentForMautrixSignal, - synapse, + r.Client, + desiredDeployment, &appsv1.Deployment{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -55,8 +62,8 @@ func (r *SynapseReconciler) reconcileMautrixSignalDeployment(synapse *synapsev1a } // deploymentForMautrixSignal returns a Deployment object for the mautrix-signal bridge -func (r *SynapseReconciler) deploymentForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { - ls := labelsForMautrixSignal(s.Name) +func (r *MautrixSignalReconciler) deploymentForMautrixSignal(ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta) (*appsv1.Deployment, error) { + ls := labelsForMautrixSignal(ms.Name) replicas := int32(1) // The associated mautrix-signal objects (ConfigMap, PVC, SA) share the @@ -66,7 +73,7 @@ func (r *SynapseReconciler) deploymentForMautrixSignal(s *synapsev1alpha1.Synaps mautrixSignalServiceAccountName := objectMeta.Name // The Signald PVC name is the Synapse object name with "-signald" appended - SignaldPVCName := r.GetSignaldResourceName(*s) + SignaldPVCName := GetSignaldResourceName(*ms) dep := &appsv1.Deployment{ ObjectMeta: objectMeta, @@ -141,7 +148,7 @@ func (r *SynapseReconciler) deploymentForMautrixSignal(s *synapsev1alpha1.Synaps }, } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, dep, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, dep, r.Scheme); err != nil { return &appsv1.Deployment{}, err } return dep, nil diff --git a/controllers/synapse/synapse_mautrixsignal_pvc.go b/controllers/synapse/mautrixsignal/mautrixsignal_pvc.go similarity index 69% rename from controllers/synapse/synapse_mautrixsignal_pvc.go rename to controllers/synapse/mautrixsignal/mautrixsignal_pvc.go index 668ec34..d8f039c 100644 --- a/controllers/synapse/synapse_mautrixsignal_pvc.go +++ b/controllers/synapse/mautrixsignal/mautrixsignal_pvc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package synapse +package mautrixsignal import ( "context" @@ -23,9 +23,9 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" ) @@ -33,14 +33,21 @@ import ( // called in the main reconciliation loop. // // It reconciles the PVC for mautrix-signal to its desired state. -func (r *SynapseReconciler) reconcileMautrixSignalPVC(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalPVC(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredPVC, err := r.persistentVolumeClaimForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.persistentVolumeClaimForMautrixSignal, - synapse, + r.Client, + desiredPVC, &corev1.PersistentVolumeClaim{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -49,7 +56,7 @@ func (r *SynapseReconciler) reconcileMautrixSignalPVC(synapse *synapsev1alpha1.S } // persistentVolumeClaimForMautrixSignal returns a mautrix-signal PVC object -func (r *SynapseReconciler) persistentVolumeClaimForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *MautrixSignalReconciler) persistentVolumeClaimForMautrixSignal(ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta) (*corev1.PersistentVolumeClaim, error) { pvcmode := corev1.PersistentVolumeFilesystem pvc := &corev1.PersistentVolumeClaim{ @@ -66,7 +73,7 @@ func (r *SynapseReconciler) persistentVolumeClaimForMautrixSignal(s *synapsev1al } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, pvc, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, pvc, r.Scheme); err != nil { return &corev1.PersistentVolumeClaim{}, err } return pvc, nil diff --git a/controllers/synapse/synapse_mautrixsignal_service.go b/controllers/synapse/mautrixsignal/mautrixsignal_service.go similarity index 67% rename from controllers/synapse/synapse_mautrixsignal_service.go rename to controllers/synapse/mautrixsignal/mautrixsignal_service.go index 3d913b8..f1d113d 100644 --- a/controllers/synapse/synapse_mautrixsignal_service.go +++ b/controllers/synapse/mautrixsignal/mautrixsignal_service.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package synapse +package mautrixsignal import ( "context" @@ -23,9 +23,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" ) @@ -33,14 +33,21 @@ import ( // be called in the main reconciliation loop. // // It reconciles the Service for mautrix-signal to its desired state. -func (r *SynapseReconciler) reconcileMautrixSignalService(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalService(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredService, err := r.serviceForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.serviceForMautrixSignal, - synapse, + r.Client, + desiredService, &corev1.Service{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -49,7 +56,7 @@ func (r *SynapseReconciler) reconcileMautrixSignalService(synapse *synapsev1alph } // serviceForMautrixSignal returns a mautrix-signal Service object -func (r *SynapseReconciler) serviceForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *MautrixSignalReconciler) serviceForMautrixSignal(ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta) (*corev1.Service, error) { service := &corev1.Service{ ObjectMeta: objectMeta, Spec: corev1.ServiceSpec{ @@ -59,12 +66,12 @@ func (r *SynapseReconciler) serviceForMautrixSignal(s *synapsev1alpha1.Synapse, Port: 29328, TargetPort: intstr.FromInt(29328), }}, - Selector: labelsForMautrixSignal(s.Name), + Selector: labelsForMautrixSignal(ms.Name), Type: corev1.ServiceTypeClusterIP, }, } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, service, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, service, r.Scheme); err != nil { return &corev1.Service{}, err } return service, nil diff --git a/controllers/synapse/synapse_mautrixsignal_serviceaccount.go b/controllers/synapse/mautrixsignal/mautrixsignal_serviceaccount.go similarity index 62% rename from controllers/synapse/synapse_mautrixsignal_serviceaccount.go rename to controllers/synapse/mautrixsignal/mautrixsignal_serviceaccount.go index 34223a2..1586da0 100644 --- a/controllers/synapse/synapse_mautrixsignal_serviceaccount.go +++ b/controllers/synapse/mautrixsignal/mautrixsignal_serviceaccount.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package synapse +package mautrixsignal import ( "context" @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" + "github.com/opdev/synapse-operator/helpers/reconcile" reconc "github.com/opdev/synapse-operator/helpers/reconcileresults" ) @@ -33,14 +34,21 @@ import ( // subreconcilerFuncs, to be called in the main reconciliation loop. // // It reconciles the ServiceAccount for mautrix-signal to its desired state. -func (r *SynapseReconciler) reconcileMautrixSignalServiceAccount(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalServiceAccount(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredServiceAccount, err := r.serviceAccountForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.serviceAccountForMautrixSignal, - synapse, + r.Client, + desiredServiceAccount, &corev1.ServiceAccount{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -49,14 +57,16 @@ func (r *SynapseReconciler) reconcileMautrixSignalServiceAccount(synapse *synaps } // serviceAccountForMautrixSignal returns a ServiceAccount object for running the mautrix-signal bridge -func (r *SynapseReconciler) serviceAccountForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *MautrixSignalReconciler) serviceAccountForMautrixSignal(i interface{}, objectMeta metav1.ObjectMeta) (client.Object, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + // TODO: https://github.com/opdev/synapse-operator/issues/19 sa := &corev1.ServiceAccount{ ObjectMeta: objectMeta, } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, sa, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, sa, r.Scheme); err != nil { return &corev1.ServiceAccount{}, err } return sa, nil @@ -66,14 +76,21 @@ func (r *SynapseReconciler) serviceAccountForMautrixSignal(s *synapsev1alpha1.Sy // to be called in the main reconciliation loop. // // It reconciles the RoleBinding for mautrix-signal to its desired state. -func (r *SynapseReconciler) reconcileMautrixSignalRoleBinding(synapse *synapsev1alpha1.Synapse, ctx context.Context) (*ctrl.Result, error) { - objectMetaMautrixSignal := setObjectMeta(r.GetMautrixSignalResourceName(*synapse), synapse.Namespace, map[string]string{}) - if err := r.reconcileResource( +func (r *MautrixSignalReconciler) reconcileMautrixSignalRoleBinding(i interface{}, ctx context.Context) (*ctrl.Result, error) { + ms := i.(*synapsev1alpha1.MautrixSignal) + + objectMetaMautrixSignal := reconcile.SetObjectMeta(ms.Name, ms.Namespace, map[string]string{}) + + desiredRoleBinding, err := r.roleBindingForMautrixSignal(ms, objectMetaMautrixSignal) + if err != nil { + return reconc.RequeueWithError(err) + } + + if err := reconcile.ReconcileResource( ctx, - r.roleBindingForMautrixSignal, - synapse, + r.Client, + desiredRoleBinding, &rbacv1.RoleBinding{}, - objectMetaMautrixSignal, ); err != nil { return reconc.RequeueWithError(err) } @@ -82,7 +99,7 @@ func (r *SynapseReconciler) reconcileMautrixSignalRoleBinding(synapse *synapsev1 } // roleBindingForMautrixSignal returns a RoleBinding object for the mautrix-signal bridge -func (r *SynapseReconciler) roleBindingForMautrixSignal(s *synapsev1alpha1.Synapse, objectMeta metav1.ObjectMeta) (client.Object, error) { +func (r *MautrixSignalReconciler) roleBindingForMautrixSignal(ms *synapsev1alpha1.MautrixSignal, objectMeta metav1.ObjectMeta) (*rbacv1.RoleBinding, error) { // TODO: https://github.com/opdev/synapse-operator/issues/19 rb := &rbacv1.RoleBinding{ ObjectMeta: objectMeta, @@ -99,7 +116,7 @@ func (r *SynapseReconciler) roleBindingForMautrixSignal(s *synapsev1alpha1.Synap } // Set Synapse instance as the owner and controller - if err := ctrl.SetControllerReference(s, rb, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ms, rb, r.Scheme); err != nil { return &rbacv1.RoleBinding{}, err } return rb, nil diff --git a/controllers/synapse/mautrixsignal/mautrixsignal_test.go b/controllers/synapse/mautrixsignal/mautrixsignal_test.go new file mode 100644 index 0000000..67bf7be --- /dev/null +++ b/controllers/synapse/mautrixsignal/mautrixsignal_test.go @@ -0,0 +1,543 @@ +// +//This file contains unit tests for the Synapse package +// + +package mautrixsignal + +// import ( +// "context" + +// . "github.com/onsi/ginkgo/v2" +// . "github.com/onsi/gomega" +// "gopkg.in/yaml.v2" + +// pgov1beta1 "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" +// synapsev1alpha1 "github.com/opdev/synapse-operator/apis/synapse/v1alpha1" +// "github.com/opdev/synapse-operator/helpers/utils" +// corev1 "k8s.io/api/core/v1" +// ) + +// var _ = Describe("Unit tests for Synapse package", Label("unit"), func() { +// // Testing ParseHomeserverConfigMap +// Context("When parsing the homeserver ConfigMap", func() { +// var r SynapseReconciler +// var ctx context.Context +// var s synapsev1alpha1.Synapse +// var cm corev1.ConfigMap +// var data map[string]string + +// BeforeEach(func() { +// // Init variables needed for each test +// r = SynapseReconciler{} +// ctx = context.Background() +// s = synapsev1alpha1.Synapse{} +// }) + +// Context("Extracting the server name and report stats value from ConfigMap", func() { +// JustBeforeEach(func() { +// cm = corev1.ConfigMap{ +// Data: data, +// } +// }) + +// When("when server name is valid and report stat set to true", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "server_name: my-server-name\nreport_stats: true", +// } +// }) + +// It("should accordingly update the Synapse Status", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).Should(Succeed()) +// Expect(s.Status.HomeserverConfiguration.ServerName).Should(Equal("my-server-name")) +// Expect(s.Status.HomeserverConfiguration.ReportStats).Should(BeTrue()) +// }) +// }) + +// When("when server name is valid and report stat set to false", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "server_name: my-server-name\nreport_stats: false", +// } +// }) + +// It("should accordingly update the Synapse Status", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).Should(Succeed()) +// Expect(s.Status.HomeserverConfiguration.ServerName).Should(Equal("my-server-name")) +// Expect(s.Status.HomeserverConfiguration.ReportStats).Should(BeFalse()) +// }) +// }) + +// When("when 'homeserver.yaml' is not present in the ConfigMap data", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "not-homeserver.yaml": "server_name: my-server-name\nreport_stats: true", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) + +// When("when 'server_name' is not present in the 'homeserver.yaml'", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "not_server_name: my-server-name\nreport_stats: true", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) + +// When("when 'server_name' is not a valid string", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "server_name: 1234567890\nreport_stats: true", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) + +// When("when 'report_stats' is not present in the 'homeserver.yaml'", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "server_name: my-server-name\nnot_report_stats: true", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) + +// When("when 'report_stats' is not a valid bool", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "server_name: my-server-name\nreport_stats: not-a-bool", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) + +// When("when 'homeserver.yaml' is not valid YAML", func() { +// BeforeEach(func() { +// data = map[string]string{ +// "homeserver.yaml": "not valid YAML!", +// } +// }) + +// It("should fail to parse the ConfigMap data", func() { +// Expect(r.ParseHomeserverConfigMap(ctx, &s, cm)).ShouldNot(Succeed()) +// }) +// }) +// }) +// }) + +// Context("When checking if the PostreSQL Cluster is ready", func() { +// var postgresCluster pgov1beta1.PostgresCluster +// var postgresInstanceSetSpec []pgov1beta1.PostgresInstanceSetSpec +// var postgresInstanceSetStatus []pgov1beta1.PostgresInstanceSetStatus +// var r SynapseReconciler +// var repl int32 + +// BeforeEach(func() { +// r = SynapseReconciler{} + +// // Default PostgresCluster state, to be overwritten in BeforeEach +// repl = int32(1) +// postgresInstanceSetSpec = []pgov1beta1.PostgresInstanceSetSpec{{ +// Name: "instance1", +// Replicas: &repl, +// }} +// postgresInstanceSetStatus = []pgov1beta1.PostgresInstanceSetStatus{{ +// Name: "instance1", +// ReadyReplicas: 1, +// Replicas: 1, +// UpdatedReplicas: 1, +// }} +// }) + +// JustBeforeEach(func() { +// postgresCluster.Spec.InstanceSets = postgresInstanceSetSpec +// postgresCluster.Status.InstanceSets = postgresInstanceSetStatus +// }) + +// When("when all replicas are ready and have the desired specification", func() { +// // No BeforeEach, use default PostgresCluster state +// It("Should consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeTrue()) +// }) +// }) + +// When("when some replicas are not ready", func() { +// BeforeEach(func() { +// postgresInstanceSetStatus = []pgov1beta1.PostgresInstanceSetStatus{{ +// Name: "instance1", +// ReadyReplicas: 0, +// Replicas: 1, +// UpdatedReplicas: 1, +// }} +// }) + +// It("Should not consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeFalse()) +// }) +// }) + +// When("when some replicas are not updated", func() { +// BeforeEach(func() { +// postgresInstanceSetStatus = []pgov1beta1.PostgresInstanceSetStatus{{ +// Name: "instance1", +// ReadyReplicas: 1, +// Replicas: 1, +// UpdatedReplicas: 0, +// }} +// }) + +// It("Should not consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeFalse()) +// }) +// }) + +// When("when no replicas exist", func() { +// BeforeEach(func() { +// postgresInstanceSetStatus = []pgov1beta1.PostgresInstanceSetStatus{{ +// Name: "instance1", +// ReadyReplicas: 0, +// Replicas: 0, +// UpdatedReplicas: 0, +// }} +// }) + +// It("Should not consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeFalse()) +// }) +// }) + +// When("when no enough replicas exist", func() { +// BeforeEach(func() { +// repl = int32(2) +// postgresInstanceSetSpec = []pgov1beta1.PostgresInstanceSetSpec{{ +// Name: "instance1", +// Replicas: &repl, +// }} +// }) + +// It("Should not consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeFalse()) +// }) +// }) + +// When("when an instance is present in spec but not in status", func() { +// BeforeEach(func() { +// repl = int32(1) +// postgresInstanceSetSpec = []pgov1beta1.PostgresInstanceSetSpec{{ +// Name: "instance1", +// Replicas: &repl, +// }, { +// Name: "instance2", +// Replicas: &repl, +// }} +// }) + +// It("Should not consider the PostgreSQL cluster as ready", func() { +// Expect(r.isPostgresClusterReady(postgresCluster)).Should(BeFalse()) +// }) +// }) + +// }) + +// Context("When updating the Synapse Status with PostgreSQL database information", func() { +// var r SynapseReconciler +// var s synapsev1alpha1.Synapse +// var synapseDatabaseInfo synapsev1alpha1.SynapseStatusDatabaseConnectionInfo +// var postgresSecret corev1.Secret +// var postgresSecretData map[string][]byte + +// // Re-usable test for checking different happy paths +// check_happy_path := func() { +// By("Updating the Synapse Status") +// Expect(r.updateSynapseStatusDatabase(&s, postgresSecret)).Should(Succeed()) + +// By("Checking that the Database information in the Synapse Status are correct") +// Expect(s.Status.DatabaseConnectionInfo.ConnectionURL).Should(Equal("unittestdb-primary.unittest-postgres.svc:5432")) +// Expect(s.Status.DatabaseConnectionInfo.DatabaseName).Should(Equal("synapse")) +// Expect(s.Status.DatabaseConnectionInfo.User).Should(Equal("synapse")) +// Expect(s.Status.DatabaseConnectionInfo.Password).Should(Equal(string(base64encode("iOycqrF;EbyqUo7Z2oma}.