feat: add decidim app

This commit is contained in:
Hugo Renard 2024-02-12 16:49:20 +01:00
parent 3878edd39f
commit 293e2be821
Signed by: hougo
GPG key ID: 3A285FD470209C59
33 changed files with 2552 additions and 6 deletions

View file

@ -117,4 +117,13 @@ resources:
kind: Forgejo kind: Forgejo
path: libre.sh/api/apps/v1alpha1 path: libre.sh/api/apps/v1alpha1
version: v1alpha1 version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: libre.sh
group: apps
kind: Decidim
path: libre.sh/api/apps/v1alpha1
version: v1alpha1
version: "3" version: "3"

View file

@ -0,0 +1,141 @@
/*
Copyright 2024 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
lshmeta "libre.sh/api/meta/v1alpha1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
const DecidimConfigSuffix = "decidim.libre.sh"
type DecidimLocale struct {
//+kubebuilder:validation:Optional
//+kubebuilder:default="fr"
Default string `json:"default"`
//+kubebuilder:validation:Optional
//+kubebuilder:validation:MinItems=1
//+kubebuilder:default={"fr"}
Available []string `json:"available"`
}
type DecidimFileUpload struct {
//+kubebuilder:validation:Optional
//+kubebuilder:default="en"
Default string `json:"default"`
//+kubebuilder:validation:Optional
//+kubebuilder:validation:MinItems=1
//+kubebuilder:default={"en","fr"}
Available []string `json:"available"`
}
type DecidimAdmin struct {
//+kubebuilder:validation:Required
Email string `json:"email"`
}
type DecidimOrganization struct {
//+kubebuilder:validation:Optional
//+kubebuilder:default=1
ID int `json:"id"`
//+kubebuilder:validation:Optional
Admin DecidimOrganizationAdmin `json:"admin,omitempty"`
}
type DecidimOrganizationAdmin struct {
//+kubebuilder:validation:Required
Email string `json:"email"`
//+kubebuilder:validation:Required
Name string `json:"name"`
//+kubebuilder:validation:Required
Nickname string `json:"nickname"`
}
// DecidimSpec defines the desired state of Decidim
type DecidimSpec struct {
lshmeta.Spec `json:",inline"`
//+kubebuilder:validation:Required
Image string `json:"image"`
//+kubebuilder:validation:Optional
Admin DecidimAdmin `json:"admin,omitempty"`
//+kubebuilder:validation:Required
Organization DecidimOrganization `json:"organization"`
//+kubebuilder:validation:Required
//+kubebuilder:validation:MinLength=3
Host string `json:"host"`
//+kubebuilder:validation:Optional
AdditionalHosts []string `json:"additionalHosts,omitempty"`
//+kubebuilder:validation:Optional
UsersRegistrationMode int `json:"usersRegistrationMode,omitempty"`
//+kubebuilder:validation:Optional
ForceUsersToAuthenticateBeforeAccessOrganization bool `json:"forceUsersToAuthenticateBeforeAccessOrganization,omitempty"`
//+kubebuilder:validation:Optional
AvailableAuthorizations []string `json:"availableAuthorizations,omitempty"`
//+kubebuilder:validation:Optional
FileUploadSettings runtime.RawExtension `json:"fileUploadSettings,omitempty"`
//+kubebuilder:validation:Required
Locale DecidimLocale `json:"locale"`
//+kubebuilder:validation:Optional
//+kubebuilder:default="UTC"
TimeZone string `json:"timeZone,omitempty"`
// OmniAuth DecidimOmniAuth `json:"omniauth,omitempty"`
//+kubebuilder:validation:Optional
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,19,rep,name=envFrom"`
}
// DecidimStatus defines the observed state of Decidim
type DecidimStatus struct {
lshmeta.Status `json:",inline"`
}
//go:generate lsh-gen v1alpha1 Decidim
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
//+kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description=""
//+kubebuilder:printcolumn:name="Suspended",type="boolean",JSONPath=".spec.suspend",description=""
// Decidim is the Schema for the decidims API
type Decidim struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DecidimSpec `json:"spec,omitempty"`
Status DecidimStatus `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
// DecidimList contains a list of Decidim
type DecidimList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Decidim `json:"items"`
}
func init() {
SchemeBuilder.Register(&Decidim{}, &DecidimList{})
}

View file

@ -0,0 +1,35 @@
// Code generated by lsh-gen; DO NOT EDIT.
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (o *Decidim) GetSuspend() bool {
return o.Spec.Suspend
}
func (o *Decidim) SetSuspend(value bool) {
o.Spec.Suspend = value
}
func (o *Decidim) GetVersion() string {
return o.Status.Version
}
func (o *Decidim) SetVersion(value string) {
o.Status.Version = value
}
func (o *Decidim) GetImage() string {
return o.Spec.Image
}
func (o *Decidim) GetConditions() []metav1.Condition {
return o.Status.Conditions
}
func (o *Decidim) SetConditions(conditions []metav1.Condition) {
o.Status.Conditions = conditions
}

View file

@ -23,9 +23,207 @@ package v1alpha1
import ( import (
"encoding/json" "encoding/json"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Decidim) DeepCopyInto(out *Decidim) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Decidim.
func (in *Decidim) DeepCopy() *Decidim {
if in == nil {
return nil
}
out := new(Decidim)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Decidim) 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 *DecidimAdmin) DeepCopyInto(out *DecidimAdmin) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimAdmin.
func (in *DecidimAdmin) DeepCopy() *DecidimAdmin {
if in == nil {
return nil
}
out := new(DecidimAdmin)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimFileUpload) DeepCopyInto(out *DecidimFileUpload) {
*out = *in
if in.Available != nil {
in, out := &in.Available, &out.Available
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimFileUpload.
func (in *DecidimFileUpload) DeepCopy() *DecidimFileUpload {
if in == nil {
return nil
}
out := new(DecidimFileUpload)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimList) DeepCopyInto(out *DecidimList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Decidim, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimList.
func (in *DecidimList) DeepCopy() *DecidimList {
if in == nil {
return nil
}
out := new(DecidimList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DecidimList) 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 *DecidimLocale) DeepCopyInto(out *DecidimLocale) {
*out = *in
if in.Available != nil {
in, out := &in.Available, &out.Available
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimLocale.
func (in *DecidimLocale) DeepCopy() *DecidimLocale {
if in == nil {
return nil
}
out := new(DecidimLocale)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimOrganization) DeepCopyInto(out *DecidimOrganization) {
*out = *in
out.Admin = in.Admin
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimOrganization.
func (in *DecidimOrganization) DeepCopy() *DecidimOrganization {
if in == nil {
return nil
}
out := new(DecidimOrganization)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimOrganizationAdmin) DeepCopyInto(out *DecidimOrganizationAdmin) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimOrganizationAdmin.
func (in *DecidimOrganizationAdmin) DeepCopy() *DecidimOrganizationAdmin {
if in == nil {
return nil
}
out := new(DecidimOrganizationAdmin)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimSpec) DeepCopyInto(out *DecidimSpec) {
*out = *in
out.Spec = in.Spec
out.Admin = in.Admin
out.Organization = in.Organization
if in.AdditionalHosts != nil {
in, out := &in.AdditionalHosts, &out.AdditionalHosts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AvailableAuthorizations != nil {
in, out := &in.AvailableAuthorizations, &out.AvailableAuthorizations
*out = make([]string, len(*in))
copy(*out, *in)
}
in.FileUploadSettings.DeepCopyInto(&out.FileUploadSettings)
in.Locale.DeepCopyInto(&out.Locale)
if in.EnvFrom != nil {
in, out := &in.EnvFrom, &out.EnvFrom
*out = make([]v1.EnvFromSource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimSpec.
func (in *DecidimSpec) DeepCopy() *DecidimSpec {
if in == nil {
return nil
}
out := new(DecidimSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DecidimStatus) DeepCopyInto(out *DecidimStatus) {
*out = *in
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecidimStatus.
func (in *DecidimStatus) DeepCopy() *DecidimStatus {
if in == nil {
return nil
}
out := new(DecidimStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Forgejo) DeepCopyInto(out *Forgejo) { func (in *Forgejo) DeepCopyInto(out *Forgejo) {
*out = *in *out = *in

View file

@ -23,10 +23,12 @@ import (
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them. // to ensure that exec-entrypoint and run can make use of them.
"k8s.io/client-go/discovery"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
imcv1alpha1 "github.com/stakater/IngressMonitorController/v2/api/v1alpha1"
zalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" zalandov1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@ -62,6 +64,7 @@ func init() {
utilruntime.Must(zalandov1.AddToScheme(scheme)) utilruntime.Must(zalandov1.AddToScheme(scheme))
utilruntime.Must(monitoringv1.AddToScheme(scheme)) utilruntime.Must(monitoringv1.AddToScheme(scheme))
utilruntime.Must(certmanagerv1.AddToScheme(scheme)) utilruntime.Must(certmanagerv1.AddToScheme(scheme))
utilruntime.Must(imcv1alpha1.AddToScheme(scheme))
utilruntime.Must(corev1alpha1.AddToScheme(scheme)) utilruntime.Must(corev1alpha1.AddToScheme(scheme))
utilruntime.Must(postgresv1alpha1.AddToScheme(scheme)) utilruntime.Must(postgresv1alpha1.AddToScheme(scheme))
@ -127,6 +130,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig())
endpointMonitorEnabled, err := discovery.IsResourceEnabled(discoveryClient, imcv1alpha1.GroupVersion.WithResource("endpointmonitors"))
if err != nil {
setupLog.Error(err, "unable to check if resource is enabled")
os.Exit(1)
}
if err = (&corecontroller.PostgresReconciler{ if err = (&corecontroller.PostgresReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
@ -214,6 +224,13 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Forgejo") setupLog.Error(err, "unable to create controller", "controller", "Forgejo")
os.Exit(1) os.Exit(1)
} }
if err = (&appscontroller.DecidimReconciler{
Client: mgr.GetClient(),
EndpointMonitorEnabled: endpointMonitorEnabled,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Decidim")
os.Exit(1)
}
//+kubebuilder:scaffold:builder //+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

View file

@ -0,0 +1,239 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.13.0
name: decidims.apps.libre.sh
spec:
group: apps.libre.sh
names:
kind: Decidim
listKind: DecidimList
plural: decidims
singular: decidim
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].message
name: Status
type: string
- jsonPath: .status.version
name: Version
type: string
- jsonPath: .spec.suspend
name: Suspended
type: boolean
name: v1alpha1
schema:
openAPIV3Schema:
description: Decidim is the Schema for the decidims 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: DecidimSpec defines the desired state of Decidim
properties:
additionalHosts:
items:
type: string
type: array
admin:
properties:
email:
type: string
required:
- email
type: object
availableAuthorizations:
items:
type: string
type: array
envFrom:
description: OmniAuth DecidimOmniAuth `json:"omniauth,omitempty"`
items:
description: EnvFromSource represents the source of a set of ConfigMaps
properties:
configMapRef:
description: The ConfigMap to select from
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the ConfigMap must be defined
type: boolean
type: object
x-kubernetes-map-type: atomic
prefix:
description: An optional identifier to prepend to each key in
the ConfigMap. Must be a C_IDENTIFIER.
type: string
secretRef:
description: The Secret to select from
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret must be defined
type: boolean
type: object
x-kubernetes-map-type: atomic
type: object
type: array
fileUploadSettings:
type: object
x-kubernetes-preserve-unknown-fields: true
forceUsersToAuthenticateBeforeAccessOrganization:
type: boolean
host:
minLength: 3
type: string
image:
type: string
locale:
properties:
available:
default:
- fr
items:
type: string
minItems: 1
type: array
default:
default: fr
type: string
type: object
organization:
properties:
admin:
properties:
email:
type: string
name:
type: string
nickname:
type: string
required:
- email
- name
- nickname
type: object
id:
default: 1
type: integer
type: object
suspend:
type: boolean
timeZone:
default: UTC
type: string
usersRegistrationMode:
type: integer
required:
- host
- image
- locale
- organization
type: object
status:
description: DecidimStatus defines the observed state of Decidim
properties:
conditions:
items:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
version:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View file

@ -14,6 +14,7 @@ resources:
- bases/apps.libre.sh_mobilizons.yaml - bases/apps.libre.sh_mobilizons.yaml
- bases/apps.libre.sh_hedgedocs.yaml - bases/apps.libre.sh_hedgedocs.yaml
- bases/apps.libre.sh_forgejoes.yaml - bases/apps.libre.sh_forgejoes.yaml
- bases/apps.libre.sh_decidims.yaml
#+kubebuilder:scaffold:crdkustomizeresource #+kubebuilder:scaffold:crdkustomizeresource
patches: patches:
@ -31,6 +32,7 @@ patches:
#- path: patches/webhook_in_apps_mobilizons.yaml #- path: patches/webhook_in_apps_mobilizons.yaml
#- path: patches/webhook_in_apps_hedgedocs.yaml #- path: patches/webhook_in_apps_hedgedocs.yaml
#- path: patches/webhook_in_apps_forgejoes.yaml #- path: patches/webhook_in_apps_forgejoes.yaml
#- path: patches/webhook_in_apps_decidims.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch #+kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
@ -47,6 +49,7 @@ patches:
#- path: patches/cainjection_in_apps_mobilizons.yaml #- path: patches/cainjection_in_apps_mobilizons.yaml
#- path: patches/cainjection_in_apps_hedgedocs.yaml #- path: patches/cainjection_in_apps_hedgedocs.yaml
#- path: patches/cainjection_in_apps_forgejoes.yaml #- path: patches/cainjection_in_apps_forgejoes.yaml
#- path: patches/cainjection_in_apps_decidims.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch #+kubebuilder:scaffold:crdkustomizecainjectionpatch
# [WEBHOOK] To enable webhook, uncomment the following section # [WEBHOOK] To enable webhook, uncomment the following section

View file

@ -0,0 +1,31 @@
# permissions for end users to edit decidims.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: decidim-editor-role
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: libre-sh
app.kubernetes.io/part-of: libre-sh
app.kubernetes.io/managed-by: kustomize
name: decidim-editor-role
rules:
- apiGroups:
- apps.libre.sh
resources:
- decidims
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps.libre.sh
resources:
- decidims/status
verbs:
- get

View file

@ -0,0 +1,27 @@
# permissions for end users to view decidims.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: decidim-viewer-role
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: libre-sh
app.kubernetes.io/part-of: libre-sh
app.kubernetes.io/managed-by: kustomize
name: decidim-viewer-role
rules:
- apiGroups:
- apps.libre.sh
resources:
- decidims
verbs:
- get
- list
- watch
- apiGroups:
- apps.libre.sh
resources:
- decidims/status
verbs:
- get

View file

@ -145,6 +145,32 @@ rules:
- patch - patch
- update - update
- watch - watch
- apiGroups:
- apps.libre.sh
resources:
- decidims
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps.libre.sh
resources:
- decidims/finalizers
verbs:
- update
- apiGroups:
- apps.libre.sh
resources:
- decidims/status
verbs:
- get
- patch
- update
- apiGroups: - apiGroups:
- apps.libre.sh - apps.libre.sh
resources: resources:
@ -197,6 +223,18 @@ rules:
- get - get
- patch - patch
- update - update
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups: - apiGroups:
- batch - batch
resources: resources:
@ -415,6 +453,18 @@ rules:
- get - get
- patch - patch
- update - update
- apiGroups:
- endpointmonitor.stakater.com
resources:
- endpointmonitors
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups: - apiGroups:
- keycloak.libre.sh - keycloak.libre.sh
resources: resources:

View file

@ -0,0 +1,12 @@
apiVersion: apps.libre.sh/v1alpha1
kind: Decidim
metadata:
labels:
app.kubernetes.io/name: decidim
app.kubernetes.io/instance: decidim-sample
app.kubernetes.io/part-of: libre-sh
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: libre-sh
name: decidim-sample
spec:
# TODO(user): Add fields here

View file

@ -12,4 +12,5 @@ resources:
- apps_v1alpha1_mobilizon.yaml - apps_v1alpha1_mobilizon.yaml
- apps_v1alpha1_hedgedoc.yaml - apps_v1alpha1_hedgedoc.yaml
- apps_v1alpha1_forgejo.yaml - apps_v1alpha1_forgejo.yaml
- apps_v1alpha1_decidim.yaml
#+kubebuilder:scaffold:manifestskustomizesamples #+kubebuilder:scaffold:manifestskustomizesamples

5
go.mod
View file

@ -18,7 +18,7 @@ require (
github.com/fluxcd/pkg/runtime v0.42.0 github.com/fluxcd/pkg/runtime v0.42.0
github.com/fluxcd/pkg/ssa v0.35.0 github.com/fluxcd/pkg/ssa v0.35.0
github.com/fluxcd/source-controller/api v1.2.3 github.com/fluxcd/source-controller/api v1.2.3
github.com/go-logr/logr v1.3.0 github.com/go-logr/logr v1.4.1
github.com/google/uuid v1.5.0 github.com/google/uuid v1.5.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/minio/madmin-go/v3 v3.0.7 github.com/minio/madmin-go/v3 v3.0.7
@ -27,6 +27,7 @@ require (
github.com/onsi/gomega v1.30.0 github.com/onsi/gomega v1.30.0
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.70.0 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.70.0
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stakater/IngressMonitorController/v2 v2.1.50
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.17.0 github.com/tidwall/gjson v1.17.0
github.com/tidwall/sjson v1.2.5 github.com/tidwall/sjson v1.2.5
@ -141,7 +142,7 @@ require (
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect go.uber.org/zap v1.25.0 // indirect
golang.org/x/crypto v0.16.0 // indirect golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect golang.org/x/oauth2 v0.14.0 // indirect

9
go.sum
View file

@ -106,8 +106,9 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@ -325,6 +326,8 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stakater/IngressMonitorController/v2 v2.1.50 h1:7NN/7edU+8VK7+V7NNVgjv5Y35zhRecQ8aTEcEvWGFg=
github.com/stakater/IngressMonitorController/v2 v2.1.50/go.mod h1:oDNe9M+84mS2IvicLf39dloF9BIt2sAf1JcI0drwpw4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
@ -384,8 +387,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=

View file

@ -0,0 +1,264 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/patch"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
lshr "libre.sh/pkg/controller-runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshcore "libre.sh/api/core/v1alpha1"
lshmeta "libre.sh/api/meta/v1alpha1"
internal "libre.sh/internal/decidim"
)
// DecidimReconciler reconciles a Decidim object
type DecidimReconciler struct {
client.Client
EndpointMonitorEnabled bool
}
type decidimResources struct {
postgres *lshcore.Postgres
redis *lshcore.Redis
bucket *lshcore.Bucket
secret *corev1.Secret
}
//+kubebuilder:rbac:groups=apps.libre.sh,resources=decidims,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps.libre.sh,resources=decidims/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps.libre.sh,resources=decidims/finalizers,verbs=update
//+kubebuilder:rbac:groups=core.libre.sh,resources=postgres;buckets;redis,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=secrets;configmaps;services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=endpointmonitor.stakater.com,resources=endpointmonitors,verbs=get;list;watch;create;update;patch;delete
// 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 Decidim 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 *DecidimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var decidim appsv1alpha1.Decidim
patcher, result := lshr.Initialize(ctx, r, req, &decidim)
if result != nil {
return result.Unwrap()
}
if lshr.IsFinalizing(&decidim) {
return lshr.Finalize(ctx, r, patcher, &decidim, func() error {
var bucket lshcore.Bucket
lshr.SetResourceNamespacedName(&decidim, &bucket)
err := r.Get(ctx, client.ObjectKeyFromObject(&bucket), &bucket)
if err != nil {
return client.IgnoreNotFound(err)
}
return lshr.UnsetControllerReference(ctx, r, &decidim, &bucket)
})
}
jm := lshr.NewJobManager(r, &decidim, patcher)
resources := decidimResources{}
err := r.reconcilePostgres(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
err = r.reconcileRedis(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
err = r.reconcileBucket(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
lshr.SetDependencyCondition(&decidim, resources.postgres, resources.redis, resources.bucket)
if err := lshr.Patch(ctx, r, patcher, &decidim, lshr.PatchOpts{}); err != nil {
return ctrl.Result{}, err
}
if lshr.IsDependencyNotReady(&decidim) || lshr.IsImporting(&decidim) {
log.Info("Waiting for dependencies or importation")
return ctrl.Result{}, nil
}
err = r.reconcileSecret(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
err = lshr.OnInstall(&decidim, func() error {
log.Info("Installing")
job, err := r.reconcileInstallJob(ctx, &decidim, &resources)
if err != nil {
return err
}
return r.completeHook(ctx, patcher, &decidim, jm, job)
})
if err != nil {
return ctrl.Result{}, err
}
err = lshr.OnUpgrade(&decidim, func() error {
log.Info("Upgrading")
job, err := r.reconcileUpgradeJob(ctx, &decidim, &resources)
if err != nil {
return err
}
return r.completeHook(ctx, patcher, &decidim, jm, job)
})
if err != nil {
return ctrl.Result{}, err
}
if lshr.IsJobNotComleted(&decidim) {
log.Info("Waiting for lifecycle job")
return ctrl.Result{}, nil
}
err = r.reconcileMemcachedDeployment(ctx, &decidim)
if err != nil {
return ctrl.Result{}, err
}
err = r.reconcileMemcachedService(ctx, &decidim)
if err != nil {
return ctrl.Result{}, err
}
err = r.reconcileMaintenance(ctx, &decidim, patcher, &resources)
if err != nil {
return ctrl.Result{}, err
}
sidekiqDep, err := r.reconcileSidekiqDeployment(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
_, err = r.reconcileHpaSidekiq(ctx, &decidim, sidekiqDep)
if err != nil {
return ctrl.Result{}, err
}
dep, err := r.reconcileDeployment(ctx, &decidim, &resources)
if err != nil {
return ctrl.Result{}, err
}
err = r.reconcilePodDisruptionBudget(ctx, &decidim)
if err != nil {
return ctrl.Result{}, err
}
_, err = r.reconcileHpa(ctx, &decidim, dep)
if err != nil {
return ctrl.Result{}, err
}
svc, err := r.reconcileService(ctx, &decidim)
if err != nil {
return ctrl.Result{}, err
}
_, err = r.reconcileIngress(ctx, &decidim, svc)
if err != nil {
return ctrl.Result{}, err
}
if r.EndpointMonitorEnabled {
err = r.reconcileIMC(ctx, &decidim)
if err != nil {
return ctrl.Result{}, err
}
}
if !internal.HealthCheck(ctx, svc) {
return ctrl.Result{RequeueAfter: 2 * time.Second}, nil
}
return ctrl.Result{}, lshr.Complete(ctx, r, patcher, &decidim, lshr.PatchOpts{})
}
// todo : move to runtime
func (r *DecidimReconciler) completeHook(ctx context.Context, patcher *patch.SerialPatcher, decidim *appsv1alpha1.Decidim, jm lshr.JobManager, job *batchv1.Job) error {
jm.Add(job)
isJobNotComleted, err := jm.UpdateCondition(ctx)
if err != nil {
return err
}
if !isJobNotComleted {
lshr.SetCurrentVersion(decidim)
delete(decidim.Annotations, "core.libre.sh/lifecycle")
err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
if err != nil {
return err
}
}
return lshr.Patch(ctx, r, patcher, decidim, lshr.PatchOpts{})
}
// SetupWithManager sets up the controller with the Manager.
func (r *DecidimReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appsv1alpha1.Decidim{}).
Owns(&lshcore.Postgres{}).
Owns(&lshcore.Bucket{}).
Owns(&lshcore.Redis{}).
Owns(&batchv1.Job{}).
Owns(&appsv1.Deployment{}).
Complete(r)
}
func (r *DecidimReconciler) Name() string {
return "decidim-controller"
}
func (r *DecidimReconciler) OwnedConditions() []string {
return []string{
meta.ReconcilingCondition,
meta.StalledCondition,
lshmeta.DependenciesNotReady,
lshmeta.HookJobNotComleted,
}
}

View file

@ -0,0 +1,65 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
corev1 "k8s.io/api/core/v1"
)
func (r *DecidimReconciler) setAffinity(podSpec *corev1.PodSpec) {
podSpec.Affinity = &corev1.Affinity{
NodeAffinity: &corev1.NodeAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.PreferredSchedulingTerm{
{
Weight: 20,
Preference: corev1.NodeSelectorTerm{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "node.libre.sh/roles",
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
},
{
Weight: 100,
Preference: corev1.NodeSelectorTerm{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "node.libre.sh/roles",
Operator: corev1.NodeSelectorOpNotIn,
Values: []string{"postgres"},
},
},
},
},
{
Weight: 80,
Preference: corev1.NodeSelectorTerm{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "node.libre.sh/roles",
Operator: corev1.NodeSelectorOpNotIn,
Values: []string{"minio"},
},
},
},
},
},
},
}
}

View file

@ -0,0 +1,40 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshcore "libre.sh/api/core/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileBucket(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) error {
var bucket lshcore.Bucket
resources.bucket = &bucket
lshr.SetResourceNamespacedName(decidim, &bucket)
return lshr.CreateOrPatch(ctx, r, &bucket, func() error {
lshr.ApplyLabels(decidim, &bucket, nil)
bucket.Spec.Suspend = decidim.GetSuspend()
bucket.Spec.Policy = lshcore.BucketPolicy{Preset: lshcore.BucketPrivatePreset}
bucket.Spec.Provider = lshcore.BucketDataProvider
return controllerutil.SetControllerReference(decidim, &bucket, r.Scheme())
})
}

View file

@ -0,0 +1,44 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/rand"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileSecret(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) error {
var secret corev1.Secret
resources.secret = &secret
secret.Name = lshr.GetResourceName(decidim)
secret.Namespace = decidim.Namespace
return lshr.CreateOrPatch(ctx, r, &secret, func() error {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
if len(secret.Data["SECRET_KEY_BASE"]) == 0 {
secret.Data["SECRET_KEY_BASE"] = []byte(rand.String(32))
}
return controllerutil.SetControllerReference(decidim, &secret, r.Scheme())
})
}

View file

@ -0,0 +1,125 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileDeployment(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) (*appsv1.Deployment, error) {
var deployment appsv1.Deployment
lshr.SetResourceNamespacedName(decidim, &deployment, "app")
err := lshr.CreateOrPatch(ctx, r, &deployment, func() error {
lshr.ApplyLabels(decidim, &deployment, &lshr.LabelOpts{
Component: "app",
Version: decidim.GetVersion(),
})
deployment.Spec.Selector = &metav1.LabelSelector{
MatchLabels: lshr.ExtractLabelSelector(&deployment),
}
deployment.Spec.Template.ObjectMeta.Labels = deployment.Labels
deployment.Spec.Template.Spec.TopologySpreadConstraints = []corev1.TopologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "kubernetes.io/hostname",
WhenUnsatisfiable: corev1.ScheduleAnyway,
LabelSelector: deployment.Spec.Selector,
},
}
if deployment.Spec.Replicas == nil {
replicas := int32(2)
deployment.Spec.Replicas = &replicas
}
r.setAffinity(&deployment.Spec.Template.Spec)
probeHandler := corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/health_check",
Port: intstr.FromString("http"),
},
}
appContainer := corev1.Container{
Image: decidim.Spec.Image,
ImagePullPolicy: corev1.PullAlways,
Name: "app",
Ports: []corev1.ContainerPort{
{
ContainerPort: 3000,
Name: "http",
Protocol: corev1.ProtocolTCP,
},
},
// TODO no endpoint pour readiness and liveness ?
// TODO no securityContext ?
EnvFrom: append(decidim.Spec.EnvFrom, corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.secret.Name,
},
},
}),
StartupProbe: &corev1.Probe{
ProbeHandler: probeHandler,
InitialDelaySeconds: 10,
FailureThreshold: 11,
PeriodSeconds: 10,
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: probeHandler,
PeriodSeconds: 10,
TimeoutSeconds: 30,
FailureThreshold: 3,
},
LivenessProbe: &corev1.Probe{
ProbeHandler: probeHandler,
PeriodSeconds: 60,
TimeoutSeconds: 30,
FailureThreshold: 3,
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
}
envVars := r.getEnvVars(decidim, resources)
appContainer.Env = envVars
deployment.Spec.Template.Spec.Containers = []corev1.Container{appContainer /* , webContainer */}
return controllerutil.SetControllerReference(decidim, &deployment, r.Scheme())
})
return &deployment, err
}

View file

@ -0,0 +1,99 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileSidekiqDeployment(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) (*appsv1.Deployment, error) {
const component = "sidekiq"
var deployment appsv1.Deployment
lshr.SetResourceNamespacedName(decidim, &deployment, component)
err := lshr.CreateOrPatch(ctx, r, &deployment, func() error {
lshr.ApplyLabels(decidim, &deployment, &lshr.LabelOpts{
Component: component,
Version: decidim.GetVersion(),
})
deployment.Spec.Selector = &metav1.LabelSelector{
MatchLabels: lshr.ExtractLabelSelector(&deployment),
}
deployment.Spec.Template.ObjectMeta.Labels = deployment.Labels
deployment.Spec.Template.ObjectMeta = metav1.ObjectMeta{
Labels: deployment.Labels,
}
r.setAffinity(&deployment.Spec.Template.Spec)
container := corev1.Container{
Image: decidim.Spec.Image,
ImagePullPolicy: corev1.PullAlways,
Name: "sidekiq",
Ports: []corev1.ContainerPort{
{
ContainerPort: 7433,
Name: "api",
Protocol: corev1.ProtocolTCP,
},
},
Command: []string{"sidekiq", "-C", "config/sidekiq.yml"},
Lifecycle: &corev1.Lifecycle{
PreStop: &corev1.LifecycleHandler{
Exec: &corev1.ExecAction{
Command: []string{"./sidekiq_quiet.sh"},
},
},
},
// TODO no securityContext ?
EnvFrom: append(decidim.Spec.EnvFrom, corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.secret.Name,
},
},
}),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
}
envVars := r.getEnvVars(decidim, resources)
container.Env = envVars
deployment.Spec.Template.Spec.Containers = []corev1.Container{container}
return controllerutil.SetControllerReference(decidim, &deployment, r.Scheme())
})
return &deployment, err
}

View file

@ -0,0 +1,183 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshmeta "libre.sh/api/meta/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) getEnvVars(decidim *appsv1alpha1.Decidim, resources *decidimResources) []corev1.EnvVar {
return []corev1.EnvVar{
/*
FRANCE_CONNECT_PROFILE_IDENTIFIER
FRANCE_CONNECT_PROFILE_SECRET
FRANCE_CONNECT_UID_IDENTIFIER
FRANCE_CONNECT_UID_SECRET
RAILS_ENV
SECRET_KEY_BASE
SESSION_DAYS_TRIM_THRESHOLD
*/
{Name: "RAILS_ENV", Value: "production"},
{Name: "APP_NAME", Value: decidim.Name},
{Name: "WEB_CONCURRENCY", Value: "0"},
{Name: "CACHE_ASSETS", Value: "true"},
{Name: "RAILS_LOG_TO_STDOUT", Value: "true"},
{Name: "RAILS_SERVE_STATIC_FILES", Value: "true"},
{Name: "NEW_RELIC_ENABLED", Value: "false"},
{Name: "FORCE_SSL", Value: "1"},
{Name: "DEFAULT_LOCALE", Value: decidim.Spec.Locale.Default},
{Name: "AVAILABLE_LOCALES", Value: strings.Join(decidim.Spec.Locale.Available, ",")},
{Name: "ENABLE_RACK_ATTACK", Value: "0"},
{Name: "RACK_ATTACK_FAIL2BAN", Value: "0"},
{Name: "ASSET_HOST", Value: decidim.Spec.Host},
{
Name: "MEMCACHE_SERVERS",
Value: fmt.Sprintf("%s:%d", lshr.GetResourceName(decidim, "memcached"), MEMCACHED_PORT),
},
{
Name: "POD_IP",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIP",
},
},
},
{
Name: "SCALEWAY_ID",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.bucket.SecretName(),
},
Key: "accessKey",
},
},
},
{
Name: "SCALEWAY_TOKEN",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.bucket.SecretName(),
},
Key: "secretKey",
},
},
},
{
Name: "OBJECTSTORE_S3_HOST",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.bucket.SecretName(),
},
Key: "endpoint",
},
},
},
{
Name: "SCALEWAY_BUCKET_NAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.bucket.SecretName(),
},
Key: "bucket",
},
},
},
{
Name: "DATABASE_USERNAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.postgres.SecretName(),
},
Key: lshmeta.SecretUsernameKey,
},
},
},
{
Name: "DATABASE_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.postgres.SecretName(),
},
Key: lshmeta.SecretPasswordKey,
},
},
},
{
Name: "DATABASE_HOST",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.postgres.SecretName(),
},
Key: lshmeta.SecretHostKey,
},
},
},
{
Name: "DATABASE_PORT",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.postgres.SecretName(),
},
Key: lshmeta.SecretPortKey,
},
},
},
{
Name: "DATABASE_NAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.postgres.SecretName(),
},
Key: "database",
},
},
},
{
Name: "REDIS_URL",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.redis.SecretName(),
},
Key: "url",
},
},
},
// TODO no port for redis need to be specified ?
// TODO add smtp
// TODO check all secrets/configmap that should be set
}
}

View file

@ -0,0 +1,95 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileHpa(ctx context.Context, decidim *appsv1alpha1.Decidim, deployment *appsv1.Deployment) (*autoscalingv2.HorizontalPodAutoscaler, error) {
var hpa autoscalingv2.HorizontalPodAutoscaler
lshr.SetResourceNamespacedName(decidim, &hpa, "app")
err := lshr.CreateOrPatch(ctx, r, &hpa, func() error {
lshr.ApplyLabels(decidim, &hpa, &lshr.LabelOpts{
Component: "app",
Version: decidim.GetVersion(),
})
hpa.Spec.ScaleTargetRef = autoscalingv2.CrossVersionObjectReference{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: "Deployment",
Name: deployment.Name,
}
min := int32(2)
hpa.Spec.MinReplicas = &min
hpa.Spec.MaxReplicas = 40
var averageUtilization int32 = 150
hpa.Spec.Metrics = []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: &averageUtilization,
},
},
},
}
var stabilizationWindowSeconds int32 = 300
hpa.Spec.Behavior = &autoscalingv2.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindowSeconds,
Policies: []autoscalingv2.HPAScalingPolicy{
{
Type: autoscalingv2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 15,
},
{
Type: autoscalingv2.PercentScalingPolicy,
Value: 50,
PeriodSeconds: 15,
},
},
},
ScaleDown: &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindowSeconds,
Policies: []autoscalingv2.HPAScalingPolicy{
{
Type: autoscalingv2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 15,
},
{
Type: autoscalingv2.PodsScalingPolicy,
Value: 25,
PeriodSeconds: 15,
},
},
},
}
return controllerutil.SetControllerReference(decidim, &hpa, r.Scheme())
})
return &hpa, err
}

View file

@ -0,0 +1,85 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileHpaSidekiq(ctx context.Context, decidim *appsv1alpha1.Decidim, deployment *appsv1.Deployment) (*autoscalingv2.HorizontalPodAutoscaler, error) {
var hpa autoscalingv2.HorizontalPodAutoscaler
lshr.SetResourceNamespacedName(decidim, &hpa, "sidekiq")
err := lshr.CreateOrPatch(ctx, r, &hpa, func() error {
lshr.ApplyLabels(decidim, &hpa, &lshr.LabelOpts{
Component: "sidekiq",
Version: decidim.GetVersion(),
})
hpa.Spec.ScaleTargetRef = autoscalingv2.CrossVersionObjectReference{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: "Deployment",
Name: deployment.Name,
}
min := int32(1)
hpa.Spec.MinReplicas = &min
hpa.Spec.MaxReplicas = 5
var averageUtilization int32 = 150
hpa.Spec.Metrics = []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: &averageUtilization,
},
},
},
}
var stabilizationWindowSeconds int32 = 300
hpa.Spec.Behavior = &autoscalingv2.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindowSeconds,
Policies: []autoscalingv2.HPAScalingPolicy{
{
Type: autoscalingv2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 30,
},
},
},
ScaleDown: &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindowSeconds,
Policies: []autoscalingv2.HPAScalingPolicy{
{
Type: autoscalingv2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 15,
},
},
},
}
return controllerutil.SetControllerReference(decidim, &hpa, r.Scheme())
})
return &hpa, err
}

View file

@ -0,0 +1,104 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"fmt"
imcv1alpha1 "github.com/stakater/IngressMonitorController/v2/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) getIngressRule(decidim *appsv1alpha1.Decidim, svc *corev1.Service, host string) networkingv1.IngressRule {
pathType := networkingv1.PathTypePrefix
return networkingv1.IngressRule{
Host: host,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: "/",
PathType: &pathType,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: svc.Name,
Port: networkingv1.ServiceBackendPort{
Name: "http",
},
},
},
},
},
},
},
}
}
func (r *DecidimReconciler) reconcileIngress(ctx context.Context, decidim *appsv1alpha1.Decidim, svc *corev1.Service) (*networkingv1.Ingress, error) {
var ingress networkingv1.Ingress
lshr.SetResourceNamespacedName(decidim, &ingress)
err := lshr.CreateOrPatch(ctx, r, &ingress, func() error {
ingress.ObjectMeta.Annotations = map[string]string{
"kubernetes.io/tls-acme": "true",
"nginx.ingress.kubernetes.io/proxy-body-size": "100g",
"nginx.ingress.kubernetes.io/proxy-request-buffering": "off",
}
ingressTLS := networkingv1.IngressTLS{
Hosts: []string{decidim.Spec.Host},
SecretName: fmt.Sprintf("%s-tls", decidim.Name),
}
ingress.Spec.Rules = []networkingv1.IngressRule{
r.getIngressRule(decidim, svc, decidim.Spec.Host),
}
for _, additionalHost := range decidim.Spec.AdditionalHosts {
ingressTLS.Hosts = append(ingressTLS.Hosts, additionalHost)
ingress.Spec.Rules = append(ingress.Spec.Rules, r.getIngressRule(decidim, svc, additionalHost))
}
ingress.Spec.TLS = []networkingv1.IngressTLS{ingressTLS}
return controllerutil.SetControllerReference(decidim, &ingress, r.Scheme())
})
return &ingress, err
}
func (r *DecidimReconciler) reconcileIMC(ctx context.Context, decidim *appsv1alpha1.Decidim) error {
var imc imcv1alpha1.EndpointMonitor
lshr.SetResourceNamespacedName(decidim, &imc)
return lshr.CreateOrPatch(ctx, r, &imc, func() error {
imc.Spec.ForceHTTPS = true
imc.Spec.HealthEndpoint = "/"
imc.Spec.URLFrom = &imcv1alpha1.URLSource{
IngressRef: &imcv1alpha1.IngressURLSource{
Name: imc.Name,
},
}
imc.Spec.UptimeRobotConfig = &imcv1alpha1.UptimeRobotConfig{
Interval: 60,
}
return controllerutil.SetControllerReference(decidim, &imc, r.Scheme())
})
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) mutateJob(decidim *appsv1alpha1.Decidim, resources *decidimResources, job *batchv1.Job, command ...string) {
r.setAffinity(&job.Spec.Template.Spec)
container := corev1.Container{
Image: decidim.Spec.Image,
ImagePullPolicy: corev1.PullAlways,
Command: command,
Name: "decidim",
Env: r.getEnvVars(decidim, resources),
EnvFrom: append(decidim.Spec.EnvFrom, corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.secret.Name,
},
},
}),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
},
}
backoffLimit := int32(0)
job.Spec.BackoffLimit = &backoffLimit
/* job.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
RunAsUser: &UserID,
RunAsGroup: &UserID,
FSGroup: &UserID,
} */
job.Spec.Template.Spec.Containers = []corev1.Container{container}
job.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever
}
func (r *DecidimReconciler) reconcileInstallJob(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) (*batchv1.Job, error) {
var job batchv1.Job
job.Name = lshr.GetResourceName(decidim, "install")
job.Namespace = decidim.Namespace
return &job, lshr.CreateOrPatch(ctx, r, &job, func() error {
r.mutateJob(decidim, resources, &job,
"bundle", "exec", "rails", "db:create", "db:migrate")
return controllerutil.SetControllerReference(decidim, &job, r.Scheme())
})
}
func (r *DecidimReconciler) reconcileUpgradeJob(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) (*batchv1.Job, error) {
var job batchv1.Job
job.Name = lshr.GetResourceName(decidim, "upgrade")
job.Namespace = decidim.Namespace
return &job, lshr.CreateOrPatch(ctx, r, &job, func() error {
r.mutateJob(decidim, resources, &job,
"rake", "decidim_app:k8s:upgrade")
return controllerutil.SetControllerReference(decidim, &job, r.Scheme())
})
}

View file

@ -0,0 +1,106 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"fmt"
"strconv"
"github.com/fluxcd/pkg/runtime/patch"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
const maintenanceAnnotation = "decidim.libre.sh/maintenance"
func (r *DecidimReconciler) reconcileMaintenance(ctx context.Context, decidim *appsv1alpha1.Decidim, patcher *patch.SerialPatcher, resources *decidimResources) error {
log := log.FromContext(ctx)
var job batchv1.Job
lshr.SetResourceNamespacedName(decidim, &job, "maintenance")
if decidim.Annotations[maintenanceAnnotation] == "" {
err := r.Delete(ctx, &job, client.PropagationPolicy(metav1.DeletePropagationForeground))
if err == nil {
log.Info("Maintenance job deleted")
}
return client.IgnoreNotFound(err)
}
minutes, err := strconv.Atoi(decidim.Annotations[maintenanceAnnotation])
if err != nil {
return fmt.Errorf("%s should be a number of minutes: %w", maintenanceAnnotation, err)
}
err = lshr.CreateOrPatch(ctx, r, &job, func() error {
if job.CreationTimestamp.IsZero() {
log.Info("Creating maintenance job")
}
lshr.ApplyLabels(decidim, &job, &lshr.LabelOpts{
Component: "maintenance",
Version: decidim.GetVersion(),
})
container := corev1.Container{
Image: decidim.Spec.Image,
ImagePullPolicy: corev1.PullAlways,
Name: "main",
Command: []string{"sh", "-c"},
Args: []string{fmt.Sprintf("echo waiting %dm && sleep %dm", minutes, minutes)},
Env: r.getEnvVars(decidim, resources),
EnvFrom: append(decidim.Spec.EnvFrom, corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.secret.Name,
},
},
}),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("240m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("6Gi"),
},
},
}
job.Spec.Template.Spec.Containers = []corev1.Container{container}
job.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyOnFailure
gracePeriod := int64(0)
job.Spec.Template.Spec.TerminationGracePeriodSeconds = &gracePeriod
return controllerutil.SetControllerReference(decidim, &job, r.Scheme())
})
if err != nil {
return err
}
if !job.Status.CompletionTime.IsZero() {
log.Info("Maintenance job is complete, marking it for deletion")
delete(decidim.Annotations, maintenanceAnnotation)
return lshr.Patch(ctx, r, patcher, decidim, lshr.PatchOpts{DisableReadyCondition: true})
}
return nil
}

View file

@ -0,0 +1,98 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
const MEMCACHED_PORT = 11211
func (r *DecidimReconciler) reconcileMemcachedDeployment(ctx context.Context, decidim *appsv1alpha1.Decidim) error {
var deployment appsv1.Deployment
lshr.SetResourceNamespacedName(decidim, &deployment, "memcached")
return lshr.CreateOrPatch(ctx, r, &deployment, func() error {
lshr.ApplyLabels(decidim, &deployment, &lshr.LabelOpts{
Component: "memcached",
Version: decidim.GetVersion(),
})
deployment.Spec.Selector = &metav1.LabelSelector{
MatchLabels: lshr.ExtractLabelSelector(&deployment),
}
deployment.Spec.Template.ObjectMeta.Labels = deployment.Labels
replicas := int32(1)
deployment.Spec.Replicas = &replicas
deployment.Spec.Template.Spec.Containers = []corev1.Container{
{
Image: "docker.io/memcached:1.6",
Name: "memcached",
Ports: []corev1.ContainerPort{
{
ContainerPort: MEMCACHED_PORT,
Protocol: corev1.ProtocolTCP,
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("5m"),
corev1.ResourceMemory: resource.MustParse("32Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("200m"),
corev1.ResourceMemory: resource.MustParse("1Gi"),
},
},
},
}
return controllerutil.SetControllerReference(decidim, &deployment, r.Scheme())
})
}
func (r *DecidimReconciler) reconcileMemcachedService(ctx context.Context, decidim *appsv1alpha1.Decidim) error {
var svc corev1.Service
lshr.SetResourceNamespacedName(decidim, &svc, "memcached")
return lshr.CreateOrPatch(ctx, r, &svc, func() error {
lshr.ApplyLabels(decidim, &svc, &lshr.LabelOpts{
Component: "memcached",
Version: decidim.GetVersion(),
})
svc.Spec.Ports = []corev1.ServicePort{
{
Port: MEMCACHED_PORT,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromInt(MEMCACHED_PORT),
},
}
svc.Spec.Type = corev1.ServiceTypeClusterIP
svc.Spec.Selector = lshr.ExtractLabelSelector(&svc)
return controllerutil.SetControllerReference(decidim, &svc, r.Scheme())
})
}

View file

@ -0,0 +1,45 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
policyv1 "k8s.io/api/policy/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcilePodDisruptionBudget(ctx context.Context, decidim *appsv1alpha1.Decidim) error {
var pdb policyv1.PodDisruptionBudget
lshr.SetResourceNamespacedName(decidim, &pdb)
return lshr.CreateOrPatch(ctx, r, &pdb, func() error {
lshr.ApplyLabels(decidim, &pdb, nil)
pdb.Spec.Selector = &v1.LabelSelector{
MatchLabels: lshr.GetLabelSelector(decidim, &lshr.LabelOpts{
Component: "app",
}),
}
min := intstr.FromInt(1)
pdb.Spec.MinAvailable = &min
return controllerutil.SetControllerReference(decidim, &pdb, r.Scheme())
})
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
corev1alpha1 "libre.sh/api/core/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcilePostgres(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) error {
var postgres corev1alpha1.Postgres
resources.postgres = &postgres
lshr.SetResourceNamespacedName(decidim, &postgres)
return lshr.CreateOrPatch(ctx, r, &postgres, func() error {
lshr.ApplyLabels(decidim, &postgres, nil)
postgres.Spec.Database = "decidim"
return controllerutil.SetControllerReference(decidim, &postgres, r.Scheme())
})
}

View file

@ -0,0 +1,40 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshcore "libre.sh/api/core/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileRedis(ctx context.Context, decidim *appsv1alpha1.Decidim, resources *decidimResources) error {
var redis lshcore.Redis
resources.redis = &redis
lshr.SetResourceNamespacedName(decidim, &redis)
return lshr.CreateOrPatch(ctx, r, &redis, func() error {
redis.Spec.Suspend = decidim.GetSuspend()
redis.Spec.Persistence = lshcore.RedisPersistence{
Enabled: true,
}
return controllerutil.SetControllerReference(decidim, &redis, r.Scheme())
})
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 apps
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshr "libre.sh/pkg/controller-runtime"
)
func (r *DecidimReconciler) reconcileService(ctx context.Context, decidim *appsv1alpha1.Decidim) (*corev1.Service, error) {
var svc corev1.Service
lshr.SetResourceNamespacedName(decidim, &svc)
err := lshr.CreateOrPatch(ctx, r, &svc, func() error {
lshr.ApplyLabels(decidim, &svc, &lshr.LabelOpts{
Component: "app",
Version: decidim.GetVersion(),
})
targetPort := intstr.IntOrString{
StrVal: "http",
Type: intstr.String,
}
svc.Spec.Ports = []corev1.ServicePort{
{
Name: "http",
Port: 80,
Protocol: corev1.ProtocolTCP,
TargetPort: targetPort,
},
}
svc.Spec.Type = corev1.ServiceTypeClusterIP
svc.Spec.Selector = lshr.ExtractLabelSelector(&svc)
return controllerutil.SetControllerReference(decidim, &svc, r.Scheme())
})
return &svc, err
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 decidim
import (
"context"
"fmt"
"net/http"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/log"
)
func HealthCheck(ctx context.Context, svc *corev1.Service) bool {
log := log.FromContext(ctx)
url := fmt.Sprintf("http://%s.%s.svc.cluster.local/health_check", svc.Name, svc.Namespace)
resp, err := http.Get(url)
if err == nil && resp.StatusCode == http.StatusOK {
log.Info("Health check is ok")
return true
}
log.Info("Waiting for health check to complete")
return false
}

168
internal/decidim/pg.go Normal file
View file

@ -0,0 +1,168 @@
/*
Copyright 2023 IndieHosters.
Licensed under the EUPL, Version 1.2 or later (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
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 decidim
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "time/tzdata"
"github.com/lib/pq"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
appsv1alpha1 "libre.sh/api/apps/v1alpha1"
lshcore "libre.sh/api/core/v1alpha1"
lshmeta "libre.sh/api/meta/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
func OpenPostgreSQL(ctx context.Context, c client.Client, postgres *lshcore.Postgres) (*sql.DB, error) {
var secret corev1.Secret
err := c.Get(ctx, types.NamespacedName{
Namespace: postgres.Namespace,
Name: postgres.SecretName(),
}, &secret)
if err != nil {
return nil, err
}
return sql.Open("postgres", string(secret.Data[lshmeta.SecretURLKey]))
}
func SyncOrganization(ctx context.Context, decidim *appsv1alpha1.Decidim, db *sql.DB) error {
rows, err := db.Query(`SELECT count(*) from decidim_organizations WHERE id=$1`, decidim.Spec.Organization.ID)
if err != nil {
return err
}
defer rows.Close()
var count int
if rows.Next() {
err = rows.Scan(&count)
if err != nil {
return err
}
}
log.FromContext(ctx).Info("sql count", "value", count)
kv := getRowKV(decidim)
var args []interface{}
var query string
if count == 0 {
query, args = getInsertQuery(decidim, kv)
} else {
query, args = getUpdateQuery(decidim, kv)
}
_, err = db.Exec(query, args...)
return err
}
func getTZNow(decidim *appsv1alpha1.Decidim) time.Time {
tz, err := time.LoadLocation(decidim.Spec.TimeZone)
if err != nil {
panic(err)
}
return time.Now().In(tz)
}
func getRowKV(decidim *appsv1alpha1.Decidim) map[string]interface{} {
return map[string]interface{}{
"name": decidim.Name,
"host": decidim.Spec.Host,
"default_locale": decidim.Spec.Locale.Default,
"available_locales": pq.Array(decidim.Spec.Locale.Available),
"updated_at": getTZNow(decidim),
// "description": nil,
// "logo": nil,
// "twitter_handler": nil,
// "show_statistics": true,
// "favicon": nil,
// "instagram_handler": nil,
// "facebook_handler": nil,
// "youtube_handler": nil,
// "github_handler": nil,
// "official_img_header": nil,
// "official_img_footer": nil,
// "official_url": nil,
"reference_prefix": "/",
"secondary_hosts": pq.Array(decidim.Spec.AdditionalHosts),
"available_authorizations": pq.Array(decidim.Spec.AvailableAuthorizations),
"file_upload_settings": decidim.Spec.FileUploadSettings.Raw,
// "header_snippets": nil,
// "cta_button_text": nil,
// "cta_button_path": nil,
// "enable_omnipresent_banner": false,
// "omnipresent_banner_title": nil,
// "omnipresent_banner_short_description": nil,
// "omnipresent_banner_url": nil,
// "highlighted_content_banner_enabled": false,
// "highlighted_content_banner_title": nil,
// "highlighted_content_banner_short_description": nil,
// "highlighted_content_banner_action_title": nil,
// "highlighted_content_banner_action_subtitle": nil,
// "highlighted_content_banner_action_url": nil,
// "highlighted_content_banner_image": nil,
// "tos_version": nil,
// "badges_enabled": nil,
// "send_welcome_notification": nil,
// "welcome_notification_subject": nil,
// "welcome_notification_body": nil,
"users_registration_mode": decidim.Spec.UsersRegistrationMode,
// "id_documents_methods": nil,
// "id_documents_explanation_text": nil,
// "user_groups_enabled": nil,
// "smtp_settings": nil,
// "colors": nil,
"force_users_to_authenticate_before_access_organization": decidim.Spec.ForceUsersToAuthenticateBeforeAccessOrganization,
// "omniauth_settings": nil,
// "rich_text_editor_in_public_views": nil,
// "admin_terms_of_use_body": nil,
"time_zone": decidim.Spec.TimeZone,
// "deepl_api_key": nil,
}
}
func getInsertQuery(decidim *appsv1alpha1.Decidim, kv map[string]interface{}) (string, []interface{}) {
kv["id"] = decidim.Spec.Organization.ID
kv["created_at"] = getTZNow(decidim)
columns := []string{}
values := []string{}
args := []interface{}{}
i := 1
for k, v := range kv {
columns = append(columns, k)
args = append(args, v)
values = append(values, fmt.Sprintf("$%d", i))
i++
}
return fmt.Sprintf("INSERT INTO decidim_organizations (%s) VALUES (%s)", strings.Join(columns, ","), strings.Join(values, ",")), args
}
func getUpdateQuery(decidim *appsv1alpha1.Decidim, kv map[string]interface{}) (string, []interface{}) {
args := []interface{}{}
sets := []string{}
i := 1
for k, v := range kv {
set := fmt.Sprintf("%s=$%d", k, i)
sets = append(sets, set)
args = append(args, v)
i++
}
args = append(args, decidim.Spec.Organization.ID)
return fmt.Sprintf("UPDATE decidim_organizations SET %s WHERE id=$%d", strings.Join(sets, ","), i), args
}