├── .dockerignore ├── .drone.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md └── stale.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── build ├── .drone.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bin │ └── po-diff.sh ├── images │ └── build │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── build-scripts │ │ └── install-packages.sh │ │ └── root │ │ ├── build │ │ ├── rsyncd.sh │ │ └── run.sh │ │ └── usr │ │ └── local │ │ └── bin │ │ ├── setup-credentials-helper.sh │ │ └── xvfb-chrome └── makelib │ ├── cache.mk │ ├── common.mk │ ├── gcp.mk │ ├── gettext.mk │ ├── git-publish.mk │ ├── golang.mk │ ├── helm.mk │ ├── image.mk │ ├── k8s-tools.mk │ ├── kubebuilder-v1.mk │ ├── kubebuilder-v2.mk │ ├── kubebuilder-v3.mk │ ├── nodejs.mk │ ├── php.mk │ ├── protobuf.mk │ ├── react.mk │ ├── utils.mk │ └── wordpress.mk ├── cmd └── wordpress-operator │ └── main.go ├── config ├── crd │ └── bases │ │ └── wordpress.presslabs.org_wordpresses.yaml ├── default │ ├── kustomization.yaml │ └── manager_image_patch.yaml ├── kustomization.yaml ├── manager │ └── manager.yaml ├── patches │ ├── volume.yaml │ └── x-kubernetes-list-map-keys.yaml ├── rbac │ └── role.yaml ├── samples │ └── wordpress_v1alpha1_wordpress.yaml └── webhook │ └── manifests.yaml ├── deploy └── charts │ └── wordpress-operator │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── crds │ └── wordpress.presslabs.org_wordpresses.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── deployment.yaml │ ├── service.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── go.mod ├── go.sum ├── hack ├── Dockerfile.skaffold ├── boilerplate.go.txt ├── license-check └── skaffold-values.yaml ├── images └── wordpress-operator │ ├── Dockerfile │ └── Makefile ├── pkg ├── apis │ ├── addtoscheme_wordpress_v1alpha1.go │ ├── apis.go │ └── wordpress │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── v1alpha1_suite_test.go │ │ ├── wordpress_types.go │ │ ├── wordpress_types_test.go │ │ ├── zz_generated.deepcopy.go │ │ └── zz_generated.defaults.go ├── cmd │ └── options │ │ └── options.go ├── controller │ ├── add_wordpress.go │ ├── add_wp_cron.go │ ├── controller.go │ ├── wordpress │ │ ├── internal │ │ │ └── sync │ │ │ │ ├── code_pvc.go │ │ │ │ ├── common.go │ │ │ │ ├── deploy.go │ │ │ │ ├── ingress.go │ │ │ │ ├── ingress_test.go │ │ │ │ ├── media_pvc.go │ │ │ │ ├── secret.go │ │ │ │ ├── service.go │ │ │ │ ├── sync_suite_test.go │ │ │ │ └── upgrade.go │ │ ├── wordpress_controller.go │ │ ├── wordpress_controller_suite_test.go │ │ └── wordpress_controller_test.go │ └── wp-cron │ │ └── wpcron_controller.go ├── internal │ └── wordpress │ │ ├── defaults.go │ │ ├── pod_template.go │ │ ├── pod_template_suite_test.go │ │ ├── pod_template_test.go │ │ └── wordpress.go └── webhook │ └── doc.go └── skaffold.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | hack/* 3 | bin/* 4 | 5 | .cache/ 6 | .work/ 7 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | name: default 4 | 5 | clone: 6 | disable: true 7 | 8 | workspace: 9 | base: /workspace 10 | path: src/github.com/bitpoke/wordpress-operator 11 | 12 | steps: 13 | - name: git 14 | pull: default 15 | image: plugins/git 16 | settings: 17 | depth: 0 18 | tags: true 19 | 20 | - name: install dependencies 21 | pull: always 22 | image: docker.io/bitpoke/build:v0.8.3 23 | commands: 24 | - make -j4 build.tools 25 | 26 | - name: verify generated code 27 | image: docker.io/bitpoke/build:v0.8.3 28 | commands: 29 | - make generate 30 | - git diff --exit-code 31 | 32 | - name: lint 33 | image: docker.io/bitpoke/build:v0.8.3 34 | commands: 35 | - make -j4 lint 36 | 37 | - name: test 38 | image: docker.io/bitpoke/build:v0.8.3 39 | commands: 40 | - make test 41 | 42 | - name: build 43 | image: docker.io/bitpoke/build:v0.8.3 44 | commands: 45 | - make -j4 build 46 | 47 | services: 48 | - name: docker 49 | image: docker:20.10.8-dind-rootless 50 | environment: 51 | DOCKER_TLS_CERTDIR: "" 52 | 53 | trigger: 54 | ref: 55 | - refs/pull/** 56 | - refs/heads/master 57 | - refs/heads/release-* 58 | # CI related changes SHOULD be prefixed with drone- 59 | - refs/heads/drone-* 60 | 61 | --- 62 | kind: pipeline 63 | name: publish 64 | 65 | trigger: 66 | ref: 67 | - refs/tags/** 68 | clone: 69 | disable: true 70 | 71 | workspace: 72 | base: /workspace 73 | path: src/github.com/bitpoke/wordpress-operator 74 | 75 | steps: 76 | - name: git 77 | image: plugins/git 78 | settings: 79 | depth: 0 80 | tags: true 81 | 82 | - name: build 83 | image: docker.io/bitpoke/build:v0.8.3 84 | commands: 85 | - make -j4 build 86 | 87 | - name: publish 88 | image: docker.io/bitpoke/build:v0.8.3 89 | environment: 90 | DOCKER_REGISTRY: docker.io/bitpoke 91 | DOCKER_USERNAME: bitpokebot 92 | DOCKER_PASSWORD: 93 | from_secret: DOCKER_PASSWORD 94 | SSH_KEY: 95 | from_secret: SSH_KEY 96 | # make build system happy by setting a branch name on tags 97 | BRANCH_NAME: release-${DRONE_TAG} 98 | commands: 99 | - /usr/local/bin/setup-credentials-helper.sh 100 | - git config --global user.email "bot@bitpoke.io" 101 | - git config --global user.name "Bitpoke Bot" 102 | - git config --global push.default current 103 | - ssh-keyscan -H github.com > /etc/ssh/ssh_known_hosts 104 | - make publish 105 | 106 | services: 107 | - name: docker 108 | image: docker:20.10.8-dind-rootless 109 | environment: 110 | DOCKER_TLS_CERTDIR: "" 111 | 112 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug or a defect 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What happened**: 11 | 12 | **What you expected to happen**: 13 | 14 | **How to reproduce it (as minimally and precisely as possible)**: 15 | 16 | **Anything else?**: 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Request a new feature or an enhancement 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 15 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - security 8 | - lifecycle/frozen 9 | # Label to use when marking an issue as stale 10 | staleLabel: lifecycle/stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | 17 | You can add lifecycle/frozen label to indicate that this issue or PR should 18 | not be auto-closed due to inactivity. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: > 21 | This issue has been closed due to lack of activity. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | .DS_Store 17 | .idea 18 | bin/* 19 | webroot/* 20 | 21 | .work 22 | .cache 23 | _output 24 | 25 | build 26 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | # TODO: re-enable tests linting and make them pass 3 | tests: false 4 | skip-files: 5 | - zz_generated 6 | 7 | linters-settings: 8 | dupl: 9 | threshold: 400 10 | gocyclo: 11 | min-complexity: 15 12 | cyclop: 13 | max-complexity: 15 14 | govet: 15 | check-shadowing: true 16 | lll: 17 | line-length: 170 18 | tab-width: 4 19 | 20 | linters: 21 | presets: 22 | - bugs 23 | - unused 24 | - format 25 | - style 26 | - complexity 27 | - performance 28 | 29 | disable: 30 | - gochecknoglobals 31 | - gochecknoinits 32 | - wrapcheck 33 | - exhaustivestruct 34 | # TODO: fix those linters (they were added for 1.42.1 upgrade) 35 | - gci 36 | 37 | issues: 38 | max-same-issues: 0 39 | exclude-use-default: false 40 | exclude: 41 | # gosec G104, about unhandled errors. We do that with errcheck already 42 | - "G104: Errors unhandled" 43 | - "mnd: " 44 | - "Line contains TODO/BUG/FIXME" 45 | exclude-rules: 46 | - linters: 47 | - godox 48 | text: "TODO:" 49 | - linters: 50 | # Ignore package comments (ST1000) since most of the time are irrelevant 51 | - stylecheck 52 | text: "ST1000" 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project 5 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | ### Changed 10 | ### Removed 11 | ### Fixed 12 | 13 | ## [0.12.2] - 2023-05-23 14 | ### Changed 15 | * Minimum required Kubernetes version is 1.21 16 | * Bump https://github.com/bitpoke/build to 0.8.3 17 | * Allow the operator to be namespace scoped (`SCOPED_NAMESPACE` environment variable) 18 | * Set resources for `init-wp` initContainer 19 | 20 | ## [0.12.1] - 2021-12-22 21 | ### Changed 22 | * Bump https://github.com/bitpoke/build to 0.7.1 23 | ### Fixed 24 | * Fix the app version in the published Helm charts 25 | 26 | ## [0.12.0] - 2021-12-22 27 | ### Added 28 | ### Changed 29 | * Minimum required Kubernetes version is 1.19 30 | * Use `networking.k8s.io/v1` for `Ingress` resources 31 | * Run WordPress Operator as non-root user 32 | * Bump https://github.com/bitpoke/build to 0.7.0 33 | ### Removed 34 | ### Fixed 35 | 36 | ## [0.11.1] - 2021-11-22 37 | ### Changed 38 | * Change the default image to `docker.io/bitpoke/wordpress-runtime:5.8.2` 39 | 40 | ## [0.11.0] - 2021-11-15 41 | ### Changed 42 | * Use [Bitpoke Build](https://github.com/bitpoke/build) for building the 43 | project 44 | ### Removed 45 | * Drop support for Helm v2 46 | ### Fixed 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Project Setup 2 | PROJECT_NAME := wordpress-operator 3 | PROJECT_REPO := github.com/bitpoke/$(PROJECT_NAME) 4 | 5 | PLATFORMS = linux_amd64 darwin_amd64 6 | 7 | include build/makelib/common.mk 8 | 9 | GO111MODULE=on 10 | GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/wordpress-operator 11 | GO_SUPPORTED_VERSIONS = 1.17 12 | GOFMT_VERSION = 1.17 13 | GOLANGCI_LINT_VERSION = 1.42.1 14 | GO_LDFLAGS += -X github.com/bitpoke/wordpress-operator/pkg/version.buildDate=$(BUILD_DATE) \ 15 | -X github.com/bitpoke/wordpress-operator/pkg/version.gitVersion=$(VERSION) \ 16 | -X github.com/bitpoke/wordpress-operator/pkg/version.gitCommit=$(GIT_COMMIT) \ 17 | -X github.com/bitpoke/wordpress-operator/pkg/version.gitTreeState=$(GIT_TREE_STATE) 18 | include build/makelib/golang.mk 19 | 20 | DOCKER_REGISTRY ?= docker.io/bitpoke 21 | IMAGES ?= wordpress-operator 22 | include build/makelib/image.mk 23 | 24 | KUBEBUILDER_ASSETS_VERSION := 1.21.2 25 | GEN_CRD_OPTIONS := crd:crdVersions=v1,preserveUnknownFields=false 26 | include build/makelib/kubebuilder-v3.mk 27 | 28 | # fix for https://github.com/kubernetes-sigs/controller-tools/issues/476 29 | .PHONY: .kubebuilder.fix-preserve-unknown-fields 30 | .kubebuilder.fix-preserve-unknown-fields: 31 | @for crd in $(wildcard $(CRD_DIR)/*.yaml) ; do \ 32 | $(YQ) e '.spec.preserveUnknownFields=false' -i "$${crd}" ;\ 33 | done 34 | .kubebuilder.manifests.done: .kubebuilder.fix-preserve-unknown-fields 35 | 36 | include build/makelib/helm.mk 37 | 38 | .PHONY: .kubebuilder.update.chart 39 | .kubebuilder.update.chart: kubebuilder.manifests $(YQ) 40 | @$(INFO) updating helm RBAC and CRDs from kubebuilder manifests 41 | @rm -rf $(HELM_CHARTS_DIR)/wordpress-operator/crds 42 | @mkdir -p $(HELM_CHARTS_DIR)/wordpress-operator/crds 43 | @set -e; \ 44 | for crd in $(wildcard $(CRD_DIR)/*.yaml) ; do \ 45 | cp $${crd} $(HELM_CHARTS_DIR)/wordpress-operator/crds/ ; \ 46 | $(YQ) e '.metadata.labels["app.kubernetes.io/name"]="wordpress-operator"' -i $(HELM_CHARTS_DIR)/wordpress-operator/crds/$$(basename $${crd}) ; \ 47 | $(YQ) e 'del(.metadata.creationTimestamp)' -i $(HELM_CHARTS_DIR)/wordpress-operator/crds/$$(basename $${crd}) ; \ 48 | $(YQ) e 'del(.status)' -i $(HELM_CHARTS_DIR)/wordpress-operator/crds/$$(basename $${crd}) ; \ 49 | done 50 | @echo '{{- if .Values.rbac.create }}' > $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 51 | @echo 'apiVersion: rbac.authorization.k8s.io/v1' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 52 | @echo 'kind: ClusterRole' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 53 | @echo 'metadata:' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 54 | @echo ' name: {{ include "wordpress-operator.fullname" . }}' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 55 | @echo ' labels:' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 56 | @echo ' {{- include "wordpress-operator.labels" . | nindent 4 }}' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 57 | @echo 'rules:' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 58 | @yq e -P '.rules' config/rbac/role.yaml >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 59 | @echo '{{- end }}' >> $(HELM_CHARTS_DIR)/wordpress-operator/templates/clusterrole.yaml 60 | @$(OK) updating helm RBAC and CRDs from kubebuilder manifests 61 | .generate.run: .kubebuilder.update.chart 62 | 63 | .PHONY: .helm.publish 64 | .helm.publish: 65 | @$(INFO) publishing helm charts 66 | @rm -rf $(WORK_DIR)/charts 67 | @git clone -q git@github.com:bitpoke/helm-charts.git $(WORK_DIR)/charts 68 | @cp $(HELM_OUTPUT_DIR)/*.tgz $(WORK_DIR)/charts/docs/ 69 | @git -C $(WORK_DIR)/charts add $(WORK_DIR)/charts/docs/*.tgz 70 | @git -C $(WORK_DIR)/charts commit -q -m "Added $(call list-join,$(COMMA)$(SPACE),$(foreach c,$(HELM_CHARTS),$(c)-v$(HELM_CHART_VERSION)))" 71 | @git -C $(WORK_DIR)/charts push -q 72 | @$(OK) publishing helm charts 73 | .publish.run: .helm.publish 74 | 75 | .PHONY: .helm.package.prepare.wordpress-operator 76 | .helm.package.prepare.wordpress-operator: $(YQ) 77 | @$(INFO) prepare wordpress-operator chart $(HELM_CHART_VERSION) 78 | @$(YQ) e '.image.repository="$(DOCKER_REGISTRY)/wordpress-operator"' -i $(HELM_CHARTS_WORK_DIR)/wordpress-operator/values.yaml 79 | @$(SED) 's/:latest/:$(IMAGE_TAG)/g' $(HELM_CHARTS_WORK_DIR)/wordpress-operator/Chart.yaml 80 | @$(OK) prepare wordpress-operator chart $(HELM_CHART_VERSION) 81 | .helm.package.run.wordpress-operator: .helm.package.prepare.wordpress-operator 82 | 83 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "1" 2 | domain: presslabs.org 3 | repo: github.com/bitpoke/wordpress-operator 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wordpress-operator [![Build Status](https://ci.bitpoke.io/api/badges/bitpoke/wordpress-operator/status.svg)](https://ci.bitpoke.io/bitpoke/wordpress-operator) 2 | === 3 | Bitpoke WordPress operator enables managing multiple WordPress installments at scale. 4 | 5 | ## Goals and status 6 | 7 | The main goals of the operator are: 8 | 9 | 1. Easily deploy scalable WordPress sites on top of kubernetes 10 | 2. Allow best practices for en masse upgrades (canary, slow rollout, etc.) 11 | 3. Friendly to devops (monitoring, availability, scalability and backup stories solved) 12 | 13 | The project is actively developed and maintained and has reached stable beta state. Check [here](https://github.com/bitpoke/wordpress-operator/releases) the project releases. 14 | 15 | The minimum supported Kubernetes version is 1.19. 16 | 17 | ## Components 18 | 19 | 1. WordPress operator - this project 20 | 2. WordPress runtime - container image supporting the project goals (https://github.com/bitpoke/stack-runtimes/tree/master/wordpress) 21 | 22 | ## Deploy 23 | 24 | ### Install CRDs 25 | 26 | #### This step is optional. By default helm will install CRDs. 27 | 28 | Install kustomize. New to kustomize? Check https://kustomize.io/ 29 | Install kubectl. For more details, see: https://kubernetes.io/docs/tasks/tools/install-kubectl/ 30 | 31 | To install CRDs use the following command: 32 | 33 | ```shell 34 | kubectl apply -f https://raw.githubusercontent.com/bitpoke/wordpress-operator/master/config/crd/bases/wordpress.presslabs.org_wordpresses.yaml 35 | ``` 36 | 37 | ### Install controller 38 | 39 | Install helm. New to helm? Check https://github.com/helm/helm#install 40 | 41 | To deploy this controller, use the provided helm chart, by running: 42 | 43 | ```shell 44 | helm repo add bitpoke https://helm-charts.bitpoke.io 45 | helm install wordpress-operator bitpoke/wordpress-operator 46 | ``` 47 | 48 | ## Deploying a WordPress Site 49 | 50 | ```yaml 51 | apiVersion: wordpress.presslabs.org/v1alpha1 52 | kind: Wordpress 53 | metadata: 54 | name: mysite 55 | spec: 56 | replicas: 3 57 | domains: 58 | - example.com 59 | # image: docker.io/bitpoke/wordpress-runtime 60 | # tag: latest 61 | code: # where to find the code 62 | # contentSubpath: wp-content/ 63 | # by default, code get's an empty dir. Can be one of the following: 64 | git: 65 | repository: https://github.com/example.com 66 | # reference: master 67 | # env: 68 | # - name: SSH_RSA_PRIVATE_KEY 69 | # valueFrom: 70 | # secretKeyRef: 71 | # name: mysite 72 | # key: id_rsa 73 | 74 | # persistentVolumeClaim: {} 75 | # hostPath: {} 76 | # emptyDir: {} (default) 77 | 78 | media: # where to find the media files 79 | # by default, code get's an empty dir. Can be one of the following: 80 | gcs: # store files using Google Cloud Storage 81 | bucket: calins-wordpress-runtime-playground 82 | prefix: mysite/ 83 | env: 84 | - name: GOOGLE_CREDENTIALS 85 | valueFrom: 86 | secretKeyRef: 87 | name: mysite 88 | key: google_application_credentials.json 89 | - name: GOOGLE_PROJECT_ID 90 | value: development 91 | # persistentVolumeClaim: {} 92 | # hostPath: {} 93 | # emptyDir: {} 94 | bootstrap: # wordpress install config 95 | env: 96 | - name: WORDPRESS_BOOTSTRAP_USER 97 | valueFrom: 98 | secretKeyRef: 99 | name: mysite 100 | key: USER 101 | - name: WORDPRESS_BOOTSTRAP_PASSWORD 102 | valueFrom: 103 | secretKeyRef: 104 | name: mysite 105 | key: PASSWORD 106 | - name: WORDPRESS_BOOTSTRAP_EMAIL 107 | valueFrom: 108 | secretKeyRef: 109 | name: mysite 110 | key: EMAIL 111 | - name: WORDPRESS_BOOTSTRAP_TITLE 112 | valueFrom: 113 | secretKeyRef: 114 | name: mysite 115 | key: TITLE 116 | # extra volumes for the WordPress container 117 | volumes: [] 118 | # extra volume mounts for the WordPress container 119 | volumeMounts: [] 120 | # extra env variables for the WordPress container 121 | env: 122 | - name: DB_HOST 123 | value: mysite-mysql 124 | - name: DB_USER 125 | valueFrom: 126 | secretKeyRef: mysite-mysql 127 | key: USER 128 | - name: DB_PASSWORD 129 | valueFrom: 130 | secretKeyRef: mysite-mysql 131 | key: PASSWORD 132 | - name: DB_NAME 133 | valueFrom: 134 | secretKeyRef: mysite-mysql 135 | key: DATABASE 136 | envFrom: [] 137 | 138 | # secret containg HTTPS certificate 139 | tlsSecretRef: mysite-tls 140 | # extra ingress annotations 141 | ingressAnnotations: {} 142 | ``` 143 | 144 | ## License 145 | 146 | This project is licensed under Apache 2.0 license. Read the [LICENSE](LICENSE) file in the 147 | top distribution directory, for the full license text. 148 | -------------------------------------------------------------------------------- /build/.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | clone: 5 | disable: true 6 | 7 | workspace: 8 | base: /workspace 9 | path: src/github.com/bitpoke/build 10 | 11 | steps: 12 | - name: clone 13 | image: plugins/git 14 | settings: 15 | depth: 0 16 | tags: true 17 | 18 | - name: install dependencies 19 | image: docker.io/bitpoke/build:v0.8.0 20 | commands: 21 | - make -j4 build.tools 22 | 23 | - name: build 24 | pull: true 25 | image: docker.io/bitpoke/build:v0.8.0 26 | commands: 27 | - dockerize -wait http://docker:2375/_ping -timeout 30s 28 | - make V=1 build 29 | 30 | - name: publish 31 | image: docker.io/bitpoke/build:v0.8.0 32 | commands: 33 | - /usr/local/bin/setup-credentials-helper.sh 34 | - make publish 35 | environment: 36 | DOCKER_USERNAME: bitpokebot 37 | DOCKER_PASSWORD: 38 | from_secret: DOCKER_PASSWORD 39 | when: 40 | ref: 41 | - refs/heads/master-* 42 | - refs/heads/release-* 43 | 44 | services: 45 | - name: docker 46 | image: docker:20.10.8-dind-rootless 47 | environment: 48 | DOCKER_TLS_CERTDIR: "" 49 | 50 | trigger: 51 | ref: 52 | - refs/pull/** 53 | - refs/heads/** 54 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | _output 2 | .cache 3 | .work 4 | /bin/ 5 | -------------------------------------------------------------------------------- /build/Makefile: -------------------------------------------------------------------------------- 1 | # Project Setup 2 | PROJECT_NAME := presslabs-build 3 | PROJECT_REPO := github.com/presslabs/build 4 | 5 | PLATFORMS = linux_amd64 6 | 7 | # this is required, since by default, the makelib files are under a ./build path prefix, but here, 8 | # they are under root 9 | ROOT_DIR := $(abspath $(shell cd ./ && pwd -P)) 10 | 11 | include makelib/common.mk 12 | 13 | IMAGES ?= build 14 | DOCKER_REGISTRY ?= docker.io/bitpoke 15 | 16 | include makelib/image.mk 17 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | # build 2 | bitpoke GNU make based build system 3 | 4 | ## Goals 5 | 6 | 1. Allow building locally the same way the project is build on CI 7 | 2. Provide a sane test, build, publish flow 8 | 3. Provide stable toolchain for building (eg. pinned tool versions) 9 | 4. Enables caching for speeding up builds. 10 | 11 | ## Quickstart 12 | 13 | ```sh 14 | git subtree add -P build https://github.com/bitpoke/build.git 15 | 16 | cat < Makefile 17 | # Project Setup 18 | PROJECT_NAME := mysql-operator 19 | PROJECT_REPO := github.com/bitpoke/mysql-operator 20 | 21 | include build/makelib/common.mk 22 | ``` 23 | 24 | ## Usage 25 | 26 | ``` 27 | Usage: make [make-options] [options] 28 | 29 | Common Targets: 30 | build Build source code and other artifacts for host platform. 31 | build.all Build source code and other artifacts for all platforms. 32 | build.tools Install the required build tools. 33 | clean Remove all files created during the build. 34 | distclean Remove all files created during the build including cached tools. 35 | generate Run code generation tools. 36 | fmt Run code auto-formatting tools. 37 | lint Run lint and code analysis tools. 38 | test Runs unit tests. 39 | e2e Runs end-to-end integration tests. 40 | translate Collect translation strings and post-process the .pot/.po files. 41 | help Show this help info. 42 | ``` 43 | 44 | ## Acknowledgement 45 | 46 | This work is based on https://github.com/upbound/build. 47 | -------------------------------------------------------------------------------- /build/bin/po-diff.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Pressinfra SRL 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | export ROOT_DIR=$(dirname "${BASH_SOURCE}")/../.. 22 | 23 | # Install tools we need, but only from vendor/... 24 | cd "${ROOT_DIR}" 25 | 26 | diff -u \ 27 | <(grep -E '^msgid' "${1}" | sort | sed 's/msgid[[:space:]]*//g') \ 28 | <(grep -E '^msgid' "${2}" | sort | sed 's/msgid[[:space:]]*//g') 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /build/images/build/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM google/cloud-sdk:431.0.0 16 | 17 | ARG ARCH 18 | 19 | ENV DEBIAN_FRONTEND noninteractive 20 | 21 | COPY build-scripts /usr/local/build-scripts 22 | 23 | # ------------------------------------------------------------------------------------------------ 24 | # git config 25 | RUN set -ex \ 26 | && git config --global user.email "bot@bitpoke.cloud" \ 27 | && git config --global user.name "Bitpoke Bot" \ 28 | && git config --global diff.tar-filelist.binary true \ 29 | && git config --global diff.tar-filelist.textconv 'tar -tvf' \ 30 | && git config --global diff.tar.binary true \ 31 | && git config --global diff.tar.textconv 'tar -xvOf' 32 | 33 | # ------------------------------------------------------------------------------------------------ 34 | # install build and release tools 35 | RUN /usr/local/build-scripts/install-packages.sh \ 36 | apt-transport-https \ 37 | gettext \ 38 | jq \ 39 | lsb-release \ 40 | make \ 41 | rsync \ 42 | runit \ 43 | sudo \ 44 | wget \ 45 | zip 46 | 47 | # ------------------------------------------------------------------------------------------------ 48 | # Go support 49 | RUN GO_VERSION=1.17.13 && \ 50 | GO_HASH=4cdd2bc664724dc7db94ad51b503512c5ae7220951cac568120f64f8e94399fc && \ 51 | curl -fsSL https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz -o golang.tar.gz && \ 52 | echo "${GO_HASH} golang.tar.gz" | sha256sum -c - && \ 53 | tar -C /usr/local -xzf golang.tar.gz && \ 54 | rm golang.tar.gz 55 | ENV GOPATH /workspace 56 | ENV PATH /workspace/bin:/usr/local/go/bin:$PATH 57 | 58 | # precompile the go standard library for all supported platforms and configurations 59 | # the install suffixes match those in golang.mk so please keep them in sync 60 | RUN platforms="darwin_amd64 windows_amd64 linux_amd64 linux_arm64" && \ 61 | for p in $platforms; do CGO_ENABLED=0 GOOS=${p%_*} GOARCH=${p##*_} GOARM=7 go install -installsuffix static -a std; done 62 | 63 | # ------------------------------------------------------------------------------------------------ 64 | # Node JS and chrome support 65 | RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - && \ 66 | curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ 67 | echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \ 68 | /usr/local/build-scripts/install-packages.sh \ 69 | nodejs \ 70 | google-chrome-stable \ 71 | xvfb && \ 72 | rm -f /etc/apt/sources.list.d/google.list && \ 73 | ln -fs /usr/local/bin/xvfb-chrome /usr/bin/google-chrome && \ 74 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 75 | ENV CHROME_BIN /usr/bin/google-chrome 76 | 77 | # ------------------------------------------------------------------------------------------------ 78 | # Yarn 79 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - && \ 80 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list && \ 81 | /usr/local/build-scripts/install-packages.sh \ 82 | yarn && \ 83 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 84 | 85 | # ------------------------------------------------------------------------------------------------ 86 | # PHP 87 | RUN sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \ 88 | && wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg \ 89 | && /usr/local/build-scripts/install-packages.sh \ 90 | php7.4-bcmath \ 91 | php7.4-curl \ 92 | php7.4-cli \ 93 | php7.4-fpm \ 94 | php7.4-gd \ 95 | php7.4-mbstring \ 96 | php7.4-mysql \ 97 | php7.4-opcache \ 98 | php7.4-tidy \ 99 | php7.4-xml \ 100 | php7.4-xmlrpc \ 101 | php7.4-xsl \ 102 | php7.4-zip \ 103 | php7.4-apcu \ 104 | php7.4-apcu-bc \ 105 | php7.4-geoip \ 106 | php7.4-imagick \ 107 | php7.4-memcached \ 108 | php7.4-redis \ 109 | php7.4-yaml \ 110 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ 111 | && COMPOSER_VERSION=2.2.21 \ 112 | && COMPOSER_HASH=5211584ad39af26704da9f6209bc5d8104a2d576e80ce9c7ed8368ddd779d0af \ 113 | && curl -fsSL -o /usr/local/bin/composer https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar \ 114 | && echo "${COMPOSER_HASH} /usr/local/bin/composer" | sha256sum -c - \ 115 | && chmod +x /usr/local/bin/composer 116 | 117 | # ------------------------------------------------------------------------------------------------ 118 | # docker-compose and docker-buildx 119 | 120 | COPY --from=docker/buildx-bin:0.10.4 /buildx /usr/libexec/docker/cli-plugins/docker-buildx 121 | RUN set -ex \ 122 | && export DOCKER_COMPOSE_VERSION="2.18.1" \ 123 | && curl -sL -o /usr/libexec/docker/cli-plugins/docker-compose "https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64" \ 124 | && chmod 0755 /usr/libexec/docker/cli-plugins/docker-compose 125 | 126 | # ------------------------------------------------------------------------------------------------ 127 | # rclone 128 | RUN set -ex \ 129 | && export RCLONE_VERSION=1.62.2 \ 130 | && curl -sL -o rclone-v${RCLONE_VERSION}-linux-amd64.deb https://github.com/rclone/rclone/releases/download/v${RCLONE_VERSION}/rclone-v${RCLONE_VERSION}-linux-amd64.deb \ 131 | && dpkg -i rclone-v${RCLONE_VERSION}-linux-amd64.deb \ 132 | && rm rclone-v${RCLONE_VERSION}-linux-amd64.deb \ 133 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 134 | 135 | # ------------------------------------------------------------------------------------------------ 136 | # dockerize 137 | RUN set -ex \ 138 | && export DOCKERIZE_VERSION="2.2.0" \ 139 | && curl -sL -o dockerize.tar.gz "https://github.com/bitpoke/dockerize/releases/download/v${DOCKERIZE_VERSION}/dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz" \ 140 | && tar -C /usr/local/bin -xzvf dockerize.tar.gz \ 141 | && rm dockerize.tar.gz \ 142 | && chmod 0755 /usr/local/bin/dockerize \ 143 | && chown root:root /usr/local/bin/dockerize 144 | 145 | # ------------------------------------------------------------------------------------------------ 146 | # sops 147 | RUN set -ex \ 148 | && export SOPS_VERSION="3.7.3" \ 149 | && curl -sL -o /usr/local/bin/sops "https://github.com/mozilla/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux" \ 150 | && chmod 0755 /usr/local/bin/sops \ 151 | && chown root:root /usr/local/bin/sops 152 | 153 | # ------------------------------------------------------------------------------------------------ 154 | # helm 155 | RUN set -ex \ 156 | && export HELM_VERSION="3.12.0" \ 157 | && curl -sL -o helm.tar.gz "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" \ 158 | && tar -C /usr/local/bin -xzvf helm.tar.gz --strip-components 1 linux-amd64/helm \ 159 | && rm helm.tar.gz \ 160 | && chmod 0755 /usr/local/bin/helm \ 161 | && chown root:root /usr/local/bin/helm 162 | 163 | # ------------------------------------------------------------------------------------------------ 164 | # helm secrets plugin 165 | RUN set -ex \ 166 | && export HELM_SECRETS_VERSION="3.15.0" \ 167 | && helm plugin install https://github.com/jkroepke/helm-secrets --version ${HELM_SECRETS_VERSION} 168 | 169 | # ------------------------------------------------------------------------------------------------ 170 | # kustomize 171 | RUN set -ex \ 172 | && export KUSTOMIZE_VERSION="4.5.7" \ 173 | && curl -sL -o kustomize.tar.gz "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" \ 174 | && tar -C /usr/local/bin -xzvf kustomize.tar.gz \ 175 | && rm kustomize.tar.gz \ 176 | && chmod 0755 /usr/local/bin/kustomize \ 177 | && chown root:root /usr/local/bin/kustomize 178 | 179 | # ------------------------------------------------------------------------------------------------ 180 | # hugo 181 | RUN set -ex \ 182 | && export HUGO_VERSION="0.111.3" \ 183 | && curl -sLo- "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz" | tar -zx -C /usr/local/bin hugo \ 184 | && pip3 install pygments \ 185 | && npm install -g postcss postcss-cli 186 | 187 | # ------------------------------------------------------------------------------------------------ 188 | # firebase-cli 189 | RUN set -ex \ 190 | && npm install -g firebase-tools 191 | 192 | # ------------------------------------------------------------------------------------------------ 193 | # Run tini as PID 1 and avoid signal handling issues 194 | RUN set -ex \ 195 | && export TINI_VERSION=v0.19.0 \ 196 | && curl -sL -o /tini https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${ARCH} \ 197 | && chmod +x /tini 198 | 199 | COPY root / 200 | 201 | ENTRYPOINT [ "/tini", "-g", "--", "/build/run.sh" ] 202 | -------------------------------------------------------------------------------- /build/images/build/Makefile: -------------------------------------------------------------------------------- 1 | PLATFORMS := linux_amd64 2 | include ../../makelib/common.mk 3 | 4 | # this is required, since by default, the makelib files are under a ./build path prefix, but here, 5 | # they are under root 6 | ROOT_DIR := $(abspath $(shell cd ./../.. && pwd -P)) 7 | 8 | IMAGE = $(BUILD_REGISTRY)/build-$(ARCH) 9 | CACHE_IMAGES = $(IMAGE) 10 | include ../../makelib/image.mk 11 | 12 | img.build: 13 | @$(INFO) docker build $(IMAGE) $(IMAGE_PLATFORM) 14 | @cp -La . $(IMAGE_TEMP_DIR) 15 | @mkdir -p $(IMAGE_TEMP_DIR)/rootfs 16 | @docker buildx build $(BUILD_ARGS) \ 17 | --platform $(IMAGE_PLATFORM) \ 18 | -t $(IMAGE) \ 19 | --build-arg ARCH=$(ARCH) \ 20 | $(IMAGE_TEMP_DIR) 21 | @$(OK) docker build $(IMAGE) 22 | -------------------------------------------------------------------------------- /build/images/build/build-scripts/install-packages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Pressinfra 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | apt-get update 22 | apt-get install -yy -q --no-install-recommends "${@}" 23 | apt-get clean 24 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 25 | -------------------------------------------------------------------------------- /build/images/build/root/build/rsyncd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Copyright 2016 The Upbound Authors. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | VOLUME=${VOLUME:-/volume} 18 | ALLOW=${ALLOW:-192.168.0.0/16 172.16.0.0/12 10.0.0.0/8} 19 | OWNER=${OWNER:-nobody} 20 | GROUP=${GROUP:-nogroup} 21 | 22 | if [[ "${GROUP}" != "nogroup" && "${GROUP}" != "root" ]]; then 23 | groupadd -g ${GROUP} rsync 24 | fi 25 | 26 | if [[ "${OWNER}" != "nobody" && "${OWNER}" != "root" ]]; then 27 | groupadd -u ${OWNER} -G rsync rsync 28 | fi 29 | 30 | chown "${OWNER}:${GROUP}" "${VOLUME}" 31 | 32 | [ -f /etc/rsyncd.conf ] || cat < /etc/rsyncd.conf 33 | uid = ${OWNER} 34 | gid = ${GROUP} 35 | use chroot = yes 36 | log file = /dev/stdout 37 | reverse lookup = no 38 | [volume] 39 | hosts deny = * 40 | hosts allow = ${ALLOW} 41 | read only = false 42 | path = ${VOLUME} 43 | comment = volume 44 | EOF 45 | 46 | for dir in ${MKDIRS}; do 47 | mkdir -p ${dir} 48 | chown "${OWNER}:${GROUP}" ${dir} 49 | done 50 | 51 | exec /usr/bin/rsync --no-detach --daemon --config /etc/rsyncd.conf "$@" 52 | -------------------------------------------------------------------------------- /build/images/build/root/build/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Copyright 2016 The Upbound Authors. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | ARGS="$@" 18 | if [ $# -eq 0 ]; then 19 | ARGS=/bin/bash 20 | fi 21 | 22 | BUILDER_USER=${BUILDER_USER:-upbound} 23 | BUILDER_GROUP=${BUILDER_GROUP:-upbound} 24 | BUILDER_UID=${BUILDER_UID:-1000} 25 | BUILDER_GID=${BUILDER_GID:-1000} 26 | 27 | groupadd -o -g $BUILDER_GID $BUILDER_GROUP 2> /dev/null 28 | useradd -o -m -g $BUILDER_GID -u $BUILDER_UID $BUILDER_USER 2> /dev/null 29 | echo "$BUILDER_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 30 | export HOME=/home/${BUILDER_USER} 31 | echo "127.0.0.1 $(cat /etc/hostname)" >> /etc/hosts 32 | [[ -S /var/run/docker.sock ]] && chmod 666 /var/run/docker.sock 33 | chown -R $BUILDER_UID:$BUILDER_GID $HOME 34 | exec chpst -u :$BUILDER_UID:$BUILDER_GID ${ARGS} 35 | -------------------------------------------------------------------------------- /build/images/build/root/usr/local/bin/setup-credentials-helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | : "${GOOGLE_CREDENTIALS:="$(cat "$PLUGIN_GOOGLE_CREDENTIALS_FILE" 2>/dev/null)"}" 3 | : "${GOOGLE_CLOUD_PROJECT:="$PLUGIN_GOOGLE_CLOUD_PROJECT"}" 4 | : "${GOOGLE_CLOUD_CLUSTER:="$PLUGIN_GOOGLE_CLOUD_CLUSTER"}" 5 | : "${GOOGLE_CLOUD_ZONE:="$PLUGIN_GOOGLE_CLOUD_ZONE"}" 6 | : "${SSH_KEY:="$PLUGIN_SSH_KEY"}" 7 | : "${DOCKER_USERNAME:="$PLUGIN_DOCKER_USERNAME"}" 8 | : "${DOCKER_PASSWORD:="$PLUGIN_DOCKER_PASSWORD"}" 9 | : "${DOCKER_REGISTRY:="${PLUGIN_DOCKER_REGISTRY:-docker.io}"}" 10 | 11 | DOCKER_REGISTRY_HOST="$(echo "${DOCKER_REGISTRY}" | awk 'BEGIN{ FS="/" }{print $1}')" 12 | 13 | export PATH="$CI_WORKSPACE/bin:$PATH" 14 | 15 | require_param() { 16 | declare name="$1" 17 | local env_name 18 | env_name="$(echo "$name" | tr /a-z/ /A-Z/)" 19 | if [ -z "${!env_name}" ] ; then 20 | echo "You must define \"$name\" parameter or define $env_name environment variable" >&2 21 | exit 2 22 | fi 23 | } 24 | 25 | require_google_credentials() { 26 | if [ -z "$GOOGLE_CREDENTIALS" ] ; then 27 | echo "You must define \"google_credentials_file\" parameter or define GOOGLE_CREDENTIALS environment variable" >&2 28 | exit 2 29 | fi 30 | } 31 | 32 | run() { 33 | echo "+" "$@" 34 | "$@" 35 | } 36 | 37 | if [ -n "$DOCKER_PASSWORD" ] ; then 38 | require_param DOCKER_USERNAME 39 | echo "+ docker login $DOCKER_REGISTRY_HOST -u $DOCKER_USERNAME" 40 | echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin 41 | fi 42 | 43 | if [ -n "$GOOGLE_CREDENTIALS" ] ; then 44 | echo "$GOOGLE_CREDENTIALS" > /run/google-credentials.json 45 | run gcloud auth activate-service-account --quiet --key-file=/run/google-credentials.json 46 | run gcloud auth configure-docker --quiet 47 | fi 48 | 49 | if [ -n "$GOOGLE_CLOUD_PROJECT" ] ; then 50 | run gcloud config set project "$GOOGLE_CLOUD_PROJECT" 51 | fi 52 | 53 | if [ -n "$GOOGLE_CLOUD_CLUSTER" ] ; then 54 | require_google_credentials 55 | require_param "google_cloud_project" 56 | require_param "google_cloud_zone" 57 | 58 | run gcloud container clusters get-credentials "$GOOGLE_CLOUD_CLUSTER" --project "$GOOGLE_CLOUD_PROJECT" --zone "$GOOGLE_CLOUD_ZONE" 59 | # Display kubernetees versions (usefull for debugging) 60 | run kubectl version 61 | fi 62 | 63 | if [ -n "$SSH_KEY" ] ; then 64 | require_param "home" 65 | test -d "$HOME/.ssh" || mkdir -p "$HOME/.ssh" 66 | echo "$SSH_KEY" > "$HOME/.ssh/id_rsa" 67 | chmod 0400 "$HOME/.ssh/id_rsa" 68 | echo "Installed ssh key into $HOME/.ssh/id_rsa" 69 | run ssh-keygen -y -f "$HOME/.ssh/id_rsa" 70 | fi 71 | 72 | if [[ -n "${GIT_USER}" && -n "${GIT_PASSWORD}" ]] ; then 73 | git config --global user.email "${GIT_EMAIL:-bot@bitpoke.cloud}" 74 | git config --global user.name "$GIT_USER" 75 | 76 | cat <> ~/.netrc 77 | machine ${GIT_HOST:-github.com} 78 | login ${GIT_USER} 79 | password ${GIT_PASSWORD} 80 | EOF 81 | fi 82 | -------------------------------------------------------------------------------- /build/images/build/root/usr/local/bin/xvfb-chrome: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2016 The Upbound Authors. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | _kill_procs() { 18 | kill -TERM $chrome 19 | wait $chrome 20 | } 21 | 22 | # Setup a trap to catch SIGTERM and relay it to child processes 23 | trap _kill_procs SIGTERM 24 | 25 | # Start Chrome inside xvfb 26 | xvfb-run -a -s "-screen 0 1920x1080x24 -nolisten tcp" /opt/google/chrome/chrome --no-sandbox $@ & 27 | chrome=$! 28 | 29 | wait $chrome 30 | -------------------------------------------------------------------------------- /build/makelib/cache.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra SRL. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __CACHE_MAKEFILE__ 16 | __CACHE_MAKEFILE__ := included 17 | 18 | RCLONE_BIN ?= RCLONE_VERSION=true rclone 19 | RCLONE_ARGS ?= -q --config /dev/null 20 | 21 | ifeq ($(CACHE_BACKEND),) 22 | $(error You must define CACHE_BACKEND before adding cache support. See format at https://rclone.org/docs/#backend-path-to-dir) 23 | endif 24 | 25 | CACHE_COMPRESSION ?= gzip 26 | 27 | ifneq ($(DRONE_PULL_REQUEST),) 28 | CACHE_NAME ?= $(PROJECT_NAME)-pr$(DRONE_PULL_REQUEST)-cache 29 | else ifneq ($(DRONE_TAG),) 30 | CACHE_NAME ?= $(PROJECT_NAME)-$(DRONE_TAG)-cache 31 | else 32 | CACHE_NAME ?= $(PROJECT_NAME)-$(BRANCH_NAME)-cache 33 | endif 34 | 35 | 36 | RCLONE := $(RCLONE_BIN) $(RCLONE_ARGS) 37 | 38 | ifeq ($(CACHE_COMPRESSION),gzip) 39 | TAR_COMPRESS_ARGS += -z 40 | CACHE_EXTENSION_SUFFIX := .gz 41 | endif 42 | 43 | CACHE_FILE := $(CACHE_NAME).tar$(CACHE_EXTENSION_SUFFIX) 44 | 45 | .PHONY: cache.store cache.restore 46 | 47 | cache.store: 48 | @$(INFO) storing cache $(CACHE_FILE) into $(CACHE_BACKEND) 49 | @$(RCLONE) mkdir $(CACHE_BACKEND) || $(FAIL) 50 | @tar -C $(CACHE_DIR) $(TAR_COMPRESS_ARGS) -cf - ./ | $(RCLONE) rcat $(CACHE_BACKEND)/$(CACHE_FILE) || $(FAIL) 51 | @$(OK) cache store 52 | 53 | cache.restore: |$(CACHE_DIR) 54 | @$(INFO) restoring cache from $(CACHE_BACKEND)/$(CACHE_FILE) 55 | @$(RCLONE) cat $(CACHE_BACKEND)/$(CACHE_FILE) | tar -C $(CACHE_DIR) $(TAR_COMPRESS_ARGS) -x \ 56 | && $(OK) cache restore \ 57 | || $(WARN) cache restore failed 58 | 59 | endif # __CACHE_MAKEFILE__ 60 | -------------------------------------------------------------------------------- /build/makelib/gcp.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra SRL. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __GOOGLE_CLOUD_MAKEFILE__ 16 | __GOOGLE_CLOUD_MAKEFILE__ := included 17 | 18 | ifeq ($(origin GOOGLE_CLOUD_PROJECT),undefined) 19 | ifneq ($(GCLOUD_PROJECT),) 20 | GOOGLE_CLOUD_PROJECT := $(GCLOUD_PROJECT) 21 | else 22 | GOOGLE_CLOUD_PROJECT := $(shell gcloud config get-value project) 23 | endif 24 | endif 25 | 26 | ifeq ($(GOOGLE_CLOUD_PROJECT),) 27 | $(error Could not determine current Google Cloud Project. Set the GOOGLE_CLOUD_PROJECT environment variable or set with `gcloud config`) 28 | else 29 | export GOOGLE_CLOUD_PROJECT 30 | endif 31 | 32 | endif # __GOOGLE_CLOUD_MAKEFILE__ 33 | -------------------------------------------------------------------------------- /build/makelib/gettext.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __GETTEXT_MAKEFILE__ 16 | __GETTEXT_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Options 20 | 21 | # ==================================================================================== 22 | # Translations 23 | 24 | # The list of languages to generate translations for 25 | LANGUAGES ?= 26 | 27 | LOCALES_DIR ?= $(ROOT_DIR)/locales 28 | $(LOCALES_DIR): 29 | @mkdir -p $(LOCALES_DIR) 30 | 31 | ifeq ($(LANGUAGES),) 32 | $(error You must specify the LANGUAGES variable in order to handle translations) 33 | endif 34 | 35 | ifeq ($(HOSTOS),darwin) 36 | MSGFMT = /usr/local/opt/gettext/bin/msgfmt 37 | MSGMERGE = /usr/local/opt/gettext/bin/msgmerge 38 | else 39 | MSGFMT = msgfmt 40 | MSGMERGE = msgmerge 41 | endif 42 | 43 | PO_FILES := $(shell find $(LOCALES_DIR) -name '*.po') 44 | POT_FILES := $(shell find $(LOCALES_DIR) -mindepth 1 -maxdepth 1 -name '*.pot') 45 | 46 | # lint the code 47 | $(eval $(call common.target,translations)) 48 | 49 | gettext.lint: 50 | @$(INFO) msgfmt check 51 | $(foreach p,$(PO_FILES),@$(MSGFMT) -c $(p) || $(FAIL) ${\n}) 52 | @$(OK) msgfmt check 53 | 54 | .gettext.merge: 55 | @$(INFO) msgmerge 56 | $(foreach l,$(LANGUAGES),@mkdir -p $(LOCALES_DIR)/$(l) || $(FAIL) ${\n}) 57 | $(foreach pot,$(POT_FILES),$(foreach l,$(LANGUAGES), \ 58 | @touch $(LOCALES_DIR)/$(l)/$(basename $(notdir $(pot))).po || $(FAIL) ${\n} \ 59 | @$(MSGMERGE) -q --no-wrap --sort-output --no-fuzzy-matching --lang=$(l) -U "$(LOCALES_DIR)/$(l)/$(basename $(notdir $(pot))).po" "$(pot)" || $(FAIL) ${\n} \ 60 | )) 61 | @find $(LOCALES_DIR) -name '*.po~' -delete 62 | @find $(LOCALES_DIR) -name '*.pot~' -delete 63 | @$(OK) msgmerge 64 | 65 | .gettext.build: 66 | @$(INFO) copying translations 67 | @rm -rf $(OUTPUT_DIR)/locales 68 | @cp -a $(LOCALES_DIR) $(OUTPUT_DIR)/locales 69 | @$(OK) copying translations 70 | 71 | .PHONY: gettext.lint .gettext.build .gettext.merge 72 | 73 | # ==================================================================================== 74 | # Common Targets 75 | .lint.run: gettext.lint 76 | 77 | .translations.run: .gettext.merge 78 | 79 | .build.code: .gettext.build 80 | 81 | endif # __GETTEXT_MAKEFILE__ 82 | -------------------------------------------------------------------------------- /build/makelib/git-publish.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __GIT_PUBLISH_MAKEFILE__ 16 | __GIT_PUBLISH_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Options 20 | 21 | PUBLISH_BRANCH ?= master 22 | PUBLISH_PREFIX ?= / 23 | PUBLISH_TAGS ?= true 24 | 25 | ifeq ($(PUBLISH_DIRS),) 26 | PUBLISH_DIR ?= $(CURDIR:$(abspath $(ROOT_DIR))/%=%) 27 | endif 28 | 29 | PUBLISH_WORK_BRANCH := build/split-$(COMMIT_HASH)/$(PUBLISH_DIR) 30 | PUBLISH_WORK_DIR := $(WORK_DIR)/git-publish/$(PUBLISH_DIR) 31 | PUBLISH_GIT := git -C $(PUBLISH_WORK_DIR) 32 | 33 | GIT_MERGE_ARGS ?= --ff-only 34 | GIT_SUBTREE_MERGE_ARGS ?= --squash 35 | ifeq ($(PUBLISH_TAGS),true) 36 | GIT_PUSH_ARGS := --follow-tags 37 | endif 38 | 39 | PUBLISH_DIRS ?= $(PUBLISH_DIR) 40 | 41 | # ==================================================================================== 42 | # git publish targets 43 | 44 | git.urlize = $(patsubst %,https://%,$(patsubst %.git,%,$(patsubst https://%,%,$(patsubst git@github.com:%,https://github.com/%,$(1))))) 45 | git.workbranch = build/split-$(COMMIT_HASH)/$(1) 46 | 47 | # 1 publish directory 48 | define git.publish 49 | 50 | $(ROOT_DIR)/.git/refs/heads/$(call git.workbranch,$(1)): 51 | @$(INFO) git subtree split $(1) 52 | @cd $(ROOT_DIR) && git subtree split -q -P $(1) -b $(call git.workbranch,$(1)) $(COMMIT_HASH) 53 | @$(OK) git subtree split $(1) 54 | .PHONY: .git.build.artifacts.$(1) 55 | .git.build.artifacts.$(1): $(ROOT_DIR)/.git/refs/heads/$(call git.workbranch,$(1)) 56 | .git.build.artifacts: .git.build.artifacts.$(1) 57 | 58 | .PHONY: .git.clean.$(1) 59 | .git.clean.$(1): 60 | @cd $(ROOT_DIR) && git branch -D $(call git.workbranch,$(1)) || true 61 | .git.clean: .git.clean.$(1) 62 | 63 | .PHONY: .do.git.publish.$(1) 64 | .do.git.publish.$(1): |$(ROOT_DIR)/.git/refs/heads/$(call git.workbranch,$(1)) 65 | @$(MAKE) -C $(1) .git.publish 66 | 67 | endef 68 | 69 | ifeq ($(filter-out $(PUBLISH_DIR),$(PUBLISH_DIRS)),) 70 | .git.publish: |$(ROOT_DIR)/.git/refs/heads/$(PUBLISH_WORK_BRANCH) 71 | @$(INFO) Publishing $(1) to $(PUBLISH_REPO)@$(PUBLISH_BRANCH) under $(PUBLISH_PREFIX) 72 | @rm -rf $(PUBLISH_WORK_DIR) && mkdir -p $(PUBLISH_WORK_DIR) 73 | @$(PUBLISH_GIT) init -q 74 | @$(PUBLISH_GIT) remote add origin $(PUBLISH_REPO) 75 | @$(PUBLISH_GIT) remote add upstream $(ROOT_DIR)/.git 76 | @$(PUBLISH_GIT) fetch -q upstream +refs/heads/$(PUBLISH_WORK_BRANCH): 77 | @$(PUBLISH_GIT) checkout -q -b $(PUBLISH_BRANCH) 78 | @set -e; cd $(PUBLISH_WORK_DIR); if git ls-remote --heads origin | grep -q refs/heads/$(PUBLISH_BRANCH); then \ 79 | $(PUBLISH_GIT) fetch -q origin +refs/heads/$(PUBLISH_BRANCH): ;\ 80 | $(PUBLISH_GIT) reset -q --hard origin/$(PUBLISH_BRANCH) ;\ 81 | $(PUBLISH_GIT) branch -q -u origin/$(PUBLISH_BRANCH) ;\ 82 | fi 83 | ifeq ($(PUBLISH_PREFIX),/) 84 | @set -e; \ 85 | $(PUBLISH_GIT) merge -q $(GIT_MERGE_ARGS) \ 86 | -m "Merge '$(PUBLISH_DIR)' from $(patsubst https://github.com/%,%,$(call git.urlize,$(REMOTE_URL)))@$(COMMIT_HASH)" \ 87 | upstream/$(PUBLISH_WORK_BRANCH) ;\ 88 | if [ "$(PUBLISH_TAGS)" == "true" ] ; then \ 89 | for t in $(TAGS) ; do \ 90 | $(PUBLISH_GIT) tag -a -m "$$t" $$t ;\ 91 | done ;\ 92 | fi 93 | else 94 | @set -e; \ 95 | if [ -d "$(PUBLISH_WORK_DIR)/$(PUBLISH_PREFIX)" ] ; then \ 96 | $(PUBLISH_GIT) subtree -q merge -P $(PUBLISH_PREFIX) $(GIT_SUBTREE_MERGE_ARGS) \ 97 | -m "Merge '$(PUBLISH_DIR)' from $(patsubst https://github.com/%,%,$(call git.urlize,$(REMOTE_URL)))@$(COMMIT_HASH)" \ 98 | upstream/$(PUBLISH_WORK_BRANCH) ;\ 99 | else \ 100 | $(PUBLISH_GIT) subtree add -q -P $(PUBLISH_PREFIX) $(GIT_SUBTREE_MERGE_ARGS) \ 101 | -m "Add '$(PUBLISH_DIR)' from $(patsubst https://github.com/%,%,$(call git.urlize,$(REMOTE_URL)))@$(COMMIT_HASH)" \ 102 | $(ROOT_DIR)/.git $(PUBLISH_WORK_BRANCH) ;\ 103 | fi 104 | endif 105 | @$(PUBLISH_GIT) push -u origin $(GIT_PUSH_ARGS) $(PUBLISH_BRANCH) 106 | @$(OK) Published $(1) to $(PUBLISH_REPO)@$(PUBLISH_BRANCH) 107 | else 108 | .git.publish: $(foreach d,$(PUBLISH_DIRS),.do.git.publish.$(d)) 109 | endif 110 | 111 | $(foreach d,$(PUBLISH_DIRS), $(eval $(call git.publish,$(d)))) 112 | 113 | .PHONY: .git.clean .git.build.artifacts .git.publish 114 | 115 | # ==================================================================================== 116 | # Common Targets 117 | 118 | # if PUBLISH_DIRS is defined the invoke publish for each dir 119 | ifneq ($(filter-out $(PUBLISH_DIR),$(PUBLISH_DIRS)),) 120 | 121 | .publish.init: .git.build.artifacts 122 | clean: .git.clean 123 | 124 | # only publish for master and release branches 125 | # also, if publishing for tags is enabled, 126 | # publish if the current commit is a tag 127 | ifneq ($(filter master release-%,$(BRANCH_NAME)),) 128 | .publish.run: $(addprefix .do.git.publish.,$(PUBLISH_DIRS)) 129 | else ifeq ($(PUBLISH_TAGS),true) 130 | ifneq ($(TAGS),) 131 | .publish.run: $(addprefix .do.git.publish.,$(PUBLISH_DIRS)) 132 | endif 133 | endif 134 | 135 | else # assume this .mk file is being included for a single dir 136 | 137 | ifeq ($(PUBLISH_REPO),) 138 | $(error You must specify the PUBLISH_REPO variable in order to handle git publishing) 139 | endif 140 | 141 | .publish.init: .git.build.artifacts 142 | clean: .git.clean 143 | 144 | endif # PUBLISH_DIRS 145 | 146 | 147 | endif # __GIT_PUBLISH_MAKEFILE__ 148 | -------------------------------------------------------------------------------- /build/makelib/helm.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __HELM_MAKEFILE__ 16 | __HELM_MAKEFILE__ := included 17 | 18 | include $(COMMON_SELF_DIR)/k8s-tools.mk 19 | 20 | # the charts directory 21 | HELM_CHARTS_DIR ?= deploy/charts 22 | 23 | HELM_CHARTS ?= $(patsubst $(HELM_CHARTS_DIR)/%,%,$(shell find $(HELM_CHARTS_DIR) -mindepth 1 -maxdepth 1 -type d)) 24 | 25 | # the base url where helm charts are published 26 | # ifeq ($(HELM_BASE_URL),) 27 | # $(error the variable HELM_BASE_URL must be set prior to including helm.mk) 28 | # endif 29 | 30 | # the charts output directory 31 | HELM_OUTPUT_DIR ?= $(OUTPUT_DIR)/charts 32 | 33 | # the helm index file 34 | HELM_INDEX := $(HELM_OUTPUT_DIR)/index.yaml 35 | 36 | # helm home 37 | HELM_HOME := $(abspath $(WORK_DIR)/helm) 38 | HELM_CHARTS_WORK_DIR := $(abspath $(WORK_DIR)/charts) 39 | export HELM_HOME 40 | 41 | # remove the leading `v` for helm chart versions 42 | HELM_CHART_VERSION := $(VERSION:v%=%) 43 | HELM_APP_VERSION ?= $(VERSION) 44 | 45 | # ==================================================================================== 46 | # Tools install targets 47 | 48 | HELM_VERSION := 3.6.3 49 | HELM_DOWNLOAD_URL := https://get.helm.sh/helm-v$(HELM_VERSION)-$(HOSTOS)-$(HOSTARCH).tar.gz 50 | $(eval $(call tool.download.tar.gz,helm,$(HELM_VERSION),$(HELM_DOWNLOAD_URL))) 51 | 52 | # ==================================================================================== 53 | # Helm Targets 54 | 55 | $(HELM_HOME): $(HELM) 56 | @mkdir -p $(HELM_HOME) 57 | 58 | $(HELM_OUTPUT_DIR): 59 | @mkdir -p $(HELM_OUTPUT_DIR) 60 | 61 | $(HELM_CHARTS_WORK_DIR): 62 | @mkdir -p $(HELM_CHARTS_WORK_DIR) 63 | 64 | define helm.chart 65 | 66 | .helm.package.init.$(1): $(HELM_CHARTS_WORK_DIR) 67 | @rm -rf $(HELM_CHARTS_WORK_DIR)/$(1) 68 | @cp -a $(abspath $(HELM_CHARTS_DIR)/$(1)) $(HELM_CHARTS_WORK_DIR)/$(1) 69 | .helm.package.run.$(1): $(HELM_OUTPUT_DIR) $(HELM_HOME) 70 | @$(INFO) helm package $(1) $(HELM_CHART_VERSION) 71 | @$(HELM) package --version $(HELM_CHART_VERSION) --app-version $(HELM_APP_VERSION) -d $(HELM_OUTPUT_DIR) $(HELM_CHARTS_WORK_DIR)/$(1) 72 | @$(OK) helm package $(1) $(HELM_CHART_VERSION) 73 | .helm.package.done.$(1): ; @: 74 | .helm.package.$(1): 75 | @$(MAKE) .helm.package.init.$(1) 76 | @$(MAKE) .helm.package.run.$(1) 77 | @$(MAKE) .helm.package.done.$(1) 78 | 79 | .PHONY: .helm.package.init.$(1) .helm.package.run.$(1) .helm.package.done.$(1) .helm.package.$(1) 80 | 81 | $(HELM_OUTPUT_DIR)/$(1)-$(HELM_CHART_VERSION).tgz: $(HELM_HOME) $(HELM_OUTPUT_DIR) $(shell find $(HELM_CHARTS_DIR)/$(1) -type f) 82 | 83 | .PHONY: .helm.lint.$(1) 84 | .helm.lint.$(1): $(HELM_HOME) 85 | @$(INFO) helm lint $(1) 86 | @rm -rf $(abspath $(HELM_CHARTS_DIR)/$(1)/charts) 87 | @$(HELM) dependency build $(abspath $(HELM_CHARTS_DIR)/$(1)) 88 | @$(HELM) lint $(abspath $(HELM_CHARTS_DIR)/$(1)) $(HELM_CHART_LINT_ARGS_$(1)) --strict || $$(FAIL) 89 | @$(OK) helm lint $(1) 90 | 91 | helm.lint: .helm.lint.$(1) 92 | 93 | .PHONY: .helm.dep.$(1) 94 | .helm.dep.$(1): $(HELM_HOME) 95 | @$(INFO) helm dep $(1) $(HELM_CHART_VERSION) 96 | @$(HELM) dependency update $(abspath $(HELM_CHARTS_DIR)/$(1)) 97 | @$(OK) helm dep $(1) $(HELM_CHART_VERSION) 98 | 99 | helm.dep: .helm.dep.$(1) 100 | 101 | $(HELM_INDEX): .helm.package.$(1) 102 | endef 103 | $(foreach p,$(HELM_CHARTS),$(eval $(call helm.chart,$(p)))) 104 | 105 | $(HELM_INDEX): $(HELM_HOME) $(HELM_OUTPUT_DIR) 106 | @$(INFO) helm index 107 | @$(HELM) repo index $(HELM_OUTPUT_DIR) 108 | @$(OK) helm index 109 | 110 | helm.build: $(HELM_INDEX) 111 | 112 | .helm.clean: 113 | @rm -fr $(HELM_OUTPUT_DIR) 114 | 115 | .PHONY: helm.lint helm.build helm.dep .helm.clean 116 | 117 | # ==================================================================================== 118 | # Common Targets 119 | 120 | .build.check: helm.lint 121 | .build.artifacts: helm.build 122 | .lint.run: helm.lint 123 | clean: .helm.clean 124 | 125 | # ==================================================================================== 126 | # Special Targets 127 | 128 | define HELM_HELPTEXT 129 | Helm Targets: 130 | helm.dep Upgrade charts dependencies 131 | 132 | endef 133 | export HELM_HELPTEXT 134 | 135 | .PHONY: .helm.help 136 | .helm.help: 137 | @echo "$$HELM_HELPTEXT" 138 | 139 | .help: .helm.help 140 | 141 | endif # __HELM_MAKEFILE__ 142 | -------------------------------------------------------------------------------- /build/makelib/k8s-tools.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __K8S_TOOLS_MAKEFILE__ 16 | __K8S_TOOLS_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # tools 20 | 21 | # kubectl download and install 22 | KUBECTL_VERSION ?= 1.19.13 23 | KUBECTL_DOWNLOAD_URL ?= https://storage.googleapis.com/kubernetes-release/release/v$(KUBECTL_VERSION)/bin/$(HOSTOS)/$(HOSTARCH)/kubectl 24 | $(eval $(call tool.download,kubectl,$(KUBECTL_VERSION),$(KUBECTL_DOWNLOAD_URL))) 25 | 26 | # kind download and install 27 | KIND_VERSION ?= 0.11.1 28 | KIND_DOWNLOAD_URL ?= https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-$(HOSTOS)-$(HOSTARCH) 29 | $(eval $(call tool.download,kind,$(KIND_VERSION),$(KIND_DOWNLOAD_URL))) 30 | 31 | # kind download and install 32 | KUSTOMIZE_VERSION ?= 4.2.0 33 | KUSTOMIZE_DOWNLOAD_URL ?=https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_$(HOST_PLATFORM).tar.gz 34 | $(eval $(call tool.download.tar.gz,kustomize,$(KUSTOMIZE_VERSION),$(KUSTOMIZE_DOWNLOAD_URL),kustomize,0)) 35 | 36 | endif # __K8S_TOOLS_MAKEFILE__ 37 | 38 | -------------------------------------------------------------------------------- /build/makelib/kubebuilder-v1.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __KUBEBUILDERV1_MAKEFILE__ 16 | __KUBEBUILDERV1_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Options 20 | 21 | KUBEBUILDER_VERSION ?= 1.0.8 22 | KUBEBUILDER := $(TOOLS_HOST_DIR)/kubebuilder-$(KUBEBUILDER_VERSION) 23 | 24 | CRD_DIR ?= config/crds 25 | API_DIR ?= pkg/apis 26 | 27 | # these are use by the kubebuilder test harness 28 | 29 | TEST_ASSET_KUBE_APISERVER := $(KUBEBUILDER)/kube-apiserver 30 | TEST_ASSET_ETCD := $(KUBEBUILDER)/etcd 31 | TEST_ASSET_KUBECTL := $(KUBEBUILDER)/kubectl 32 | export TEST_ASSET_KUBE_APISERVER TEST_ASSET_ETCD TEST_ASSET_KUBECTL 33 | 34 | # ==================================================================================== 35 | # Setup environment 36 | 37 | include $(COMMON_SELF_DIR)/golang.mk 38 | 39 | # ==================================================================================== 40 | # tools 41 | 42 | # kubebuilder download and install 43 | $(KUBEBUILDER): 44 | @echo ${TIME} ${BLUE}[TOOL]${CNone} installing kubebuilder $(KUBEBUILDER_VERSION) 45 | @mkdir -p $(TOOLS_HOST_DIR)/tmp || $(FAIL) 46 | @curl -fsSL https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VERSION)/kubebuilder_$(KUBEBUILDER_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH).tar.gz | tar -xz -C $(TOOLS_HOST_DIR)/tmp || $(FAIL) 47 | @mv $(TOOLS_HOST_DIR)/tmp/kubebuilder_$(KUBEBUILDER_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH)/bin $(KUBEBUILDER) || $(FAIL) 48 | @rm -fr $(TOOLS_HOST_DIR)/tmp 49 | @$(OK) installing kubebuilder $(KUBEBUILDER_VERSION) 50 | 51 | $(eval $(call tool.go.vendor.install,controller-gen,sigs.k8s.io/controller-tools/cmd/controller-gen)) 52 | 53 | # ==================================================================================== 54 | # Kubebuilder Targets 55 | 56 | $(eval $(call common.target,kubebuilder.manifests)) 57 | 58 | # Generate manifests e.g. CRD, RBAC etc. 59 | .do.kubebuilder.manifests: $(CONTROLLER_GEN) 60 | @$(INFO) Generating Kubebuilder manifests 61 | @# first delete the CRD_DIR, to remove the CRDs of types that no longer exist 62 | @rm -rf $(CRD_DIR) 63 | @$(CONTROLLER_GEN) all 64 | @$(CONTROLLER_GEN) webhook 65 | @$(OK) Generating Kubebuilder manifests 66 | 67 | .PHONY: .do.kubebuilder.manifests 68 | .kubebuilder.manifests.run: .do.kubebuilder.manifests 69 | 70 | # ==================================================================================== 71 | # Common Targets 72 | 73 | build.tools: $(KUBEBUILDER) 74 | .test.init: $(KUBEBUILDER) 75 | go.test.unit: $(KUBEBUILDER) 76 | 77 | # ==================================================================================== 78 | # Special Targets 79 | 80 | define KUBEBULDERV1_HELPTEXT 81 | Kubebuilder Targets: 82 | kubebuilder.manifests Generates Kubernetes custom resources manifests (e.g. CRDs RBACs, ...) 83 | 84 | endef 85 | export KUBEBULDERV1_HELPTEXT 86 | 87 | .kubebuilder.help: 88 | @echo "$$KUBEBULDERV1_HELPTEXT" 89 | 90 | .help: .kubebuilder.help 91 | .generate.run: kubebuilder.manifests 92 | 93 | .PHONY: .kubebuilder.help kubebuilder.manifests 94 | 95 | endif # __KUBEBUILDERV1_MAKEFILE__ 96 | -------------------------------------------------------------------------------- /build/makelib/kubebuilder-v2.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __KUBEBUILDERV2_MAKEFILE__ 16 | __KUBEBUILDERV2_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Options 20 | 21 | KUBEBUILDER_VERSION ?= 2.3.2 22 | KUBEBUILDER := $(TOOLS_HOST_DIR)/kubebuilder-$(KUBEBUILDER_VERSION) 23 | 24 | CRD_DIR ?= config/crds 25 | API_DIR ?= pkg/apis 26 | RBAC_DIR ?= config/rbac 27 | 28 | BOILERPLATE_FILE ?= ./hack/boilerplate.go.txt 29 | 30 | GEN_CRD_OPTIONS ?= crd:trivialVersions=true 31 | GEN_RBAC_OPTIONS ?= rbac:roleName=manager-role 32 | GEN_WEBHOOK_OPTIONS ?= 33 | GEN_OBJECT_OPTIONS ?= object:headerFile=$(BOILERPLATE_FILE) 34 | GEN_OUTPUTS_OPTIONS ?= output:crd:artifacts:config=$(CRD_DIR) output:rbac:artifacts:config=$(RBAC_DIR) 35 | 36 | # these are use by the kubebuilder test harness 37 | 38 | TEST_ASSET_KUBE_APISERVER := $(KUBEBUILDER)/kube-apiserver 39 | TEST_ASSET_ETCD := $(KUBEBUILDER)/etcd 40 | TEST_ASSET_KUBECTL := $(KUBEBUILDER)/kubectl 41 | export TEST_ASSET_KUBE_APISERVER TEST_ASSET_ETCD TEST_ASSET_KUBECTL 42 | 43 | # ==================================================================================== 44 | # Setup environment 45 | 46 | include $(COMMON_SELF_DIR)/golang.mk 47 | 48 | # ==================================================================================== 49 | # tools 50 | 51 | # kubebuilder download and install 52 | $(KUBEBUILDER): 53 | @echo ${TIME} ${BLUE}[TOOL]${CNone} installing kubebuilder $(KUBEBUILDER_VERSION) 54 | @mkdir -p $(TOOLS_HOST_DIR)/tmp || $(FAIL) 55 | @curl -fsSL https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VERSION)/kubebuilder_$(KUBEBUILDER_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH).tar.gz | tar -xz -C $(TOOLS_HOST_DIR)/tmp || $(FAIL) 56 | @mv $(TOOLS_HOST_DIR)/tmp/kubebuilder_$(KUBEBUILDER_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH)/bin $(KUBEBUILDER) || $(FAIL) 57 | @rm -fr $(TOOLS_HOST_DIR)/tmp 58 | @$(OK) installing kubebuilder $(KUBEBUILDER_VERSION) 59 | 60 | CONTROLLER_GEN_VERSION ?= 0.6.1 61 | CONTROLLER_GEN_URL ?= sigs.k8s.io/controller-tools/cmd/controller-gen 62 | $(eval $(call tool.go.install,controller-gen,v$(CONTROLLER_GEN_VERSION),$(CONTROLLER_GEN_URL))) 63 | 64 | # ==================================================================================== 65 | # Kubebuilder Targets 66 | 67 | $(eval $(call common.target,kubebuilder.manifests)) 68 | 69 | # Generate manifests e.g. CRD, RBAC etc. 70 | .do.kubebuilder.manifests: $(CONTROLLER_GEN) 71 | @$(INFO) Generating Kubebuilder manifests 72 | @# first delete the CRD_DIR, to remove the CRDs of types that no longer exist 73 | 74 | @$(CONTROLLER_GEN) paths="$(call list-join,;,$(foreach p,$(GO_SUBDIRS),./$(p)/... ))" $(GEN_CRD_OPTIONS) $(GEN_RBAC_OPTIONS) $(GEN_WEBHOOK_OPTIONS) $(GEN_OBJECT_OPTIONS) $(GEN_OUTPUTS_OPTIONS) 75 | 76 | @$(OK) Generating Kubebuilder manifests 77 | 78 | .PHONY: .do.kubebuilder.manifests 79 | .kubebuilder.manifests.run: .do.kubebuilder.manifests 80 | 81 | # ==================================================================================== 82 | # Common Targets 83 | 84 | build.tools: $(KUBEBUILDER) 85 | .test.init: $(KUBEBUILDER) 86 | go.test.unit: $(KUBEBUILDER) 87 | 88 | # ==================================================================================== 89 | # Special Targets 90 | 91 | define KUBEBULDERV2_HELPTEXT 92 | Kubebuilder Targets: 93 | kubebuilder.manifests Generates Kubernetes custom resources manifests (e.g. CRDs RBACs, ...) 94 | 95 | endef 96 | export KUBEBULDERV2_HELPTEXT 97 | 98 | .kubebuilder.help: 99 | @echo "$$KUBEBULDERV2_HELPTEXT" 100 | 101 | .help: .kubebuilder.help 102 | go.generate: kubebuilder.manifests 103 | 104 | .PHONY: .kubebuilder.help kubebuilder.manifests 105 | 106 | endif # __KUBEBUILDERV2_MAKEFILE__ 107 | -------------------------------------------------------------------------------- /build/makelib/kubebuilder-v3.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __KUBEBUILDERV3_MAKEFILE__ 16 | __KUBEBUILDERV3_MAKEFILE__ := included 17 | 18 | include $(COMMON_SELF_DIR)/golang.mk 19 | 20 | # ==================================================================================== 21 | # Options 22 | 23 | CRD_DIR ?= config/crd/bases 24 | RBAC_DIR ?= config/rbac 25 | 26 | BOILERPLATE_FILE ?= hack/boilerplate.go.txt 27 | 28 | CONTROLLER_GEN_CRD_OPTIONS ?= crd output:crd:artifacts:config=$(CRD_DIR) 29 | CONTROLLER_GEN_RBAC_OPTIONS ?= rbac:roleName=manager-role output:rbac:artifacts:config=$(RBAC_DIR) 30 | CONTROLLER_GEN_WEBHOOK_OPTIONS ?= webhook 31 | CONTROLLER_GEN_OBJECT_OPTIONS ?= object:headerFile=$(BOILERPLATE_FILE) 32 | CONTROLLER_GEN_PATHS ?= $(foreach t,$(GO_SUBDIRS),paths=./$(t)/...) 33 | 34 | KUBEBUILDER_ASSETS_VERSION ?= 1.19.2 35 | KUBEBUILDER_ASSETS = $(CACHE_DIR)/kubebuilder/k8s/$(KUBEBUILDER_ASSETS_VERSION)-$(HOSTOS)-$(HOSTARCH) 36 | export KUBEBUILDER_ASSETS 37 | 38 | # ==================================================================================== 39 | # tools 40 | 41 | # setup-envtest download and install 42 | SETUP_ENVTEST_VERSION ?= 0.0.0-20211206022232-3ffc700bc2a3 43 | SETUP_ENVTEST_DOWNLOAD_URL ?= sigs.k8s.io/controller-runtime/tools/setup-envtest 44 | $(eval $(call tool.go.install,setup-envtest,v$(SETUP_ENVTEST_VERSION),$(SETUP_ENVTEST_DOWNLOAD_URL))) 45 | 46 | # kubebuilder download and install 47 | KUBEBUILDER_VERSION ?= 3.2.0 48 | KUBEBUILDER_DOWNLOAD_URL ?= https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VERSION)/kubebuilder_$(HOST_PLATFORM) 49 | $(eval $(call tool.download,kubebuilder,$(KUBEBUILDER_VERSION),$(KUBEBUILDER_DOWNLOAD_URL))) 50 | 51 | # controller-gen download and install 52 | CONTROLLER_GEN_VERSION ?= 0.7.0 53 | CONTROLLER_GEN_DOWNLOAD_URL ?= sigs.k8s.io/controller-tools/cmd/controller-gen 54 | $(eval $(call tool.go.install,controller-gen,v$(CONTROLLER_GEN_VERSION),$(CONTROLLER_GEN_DOWNLOAD_URL))) 55 | 56 | build.tools: |$(KUBEBUILDER_ASSETS) 57 | $(KUBEBUILDER_ASSETS): $(SETUP_ENVTEST) 58 | @echo ${TIME} ${BLUE}[TOOL]${CNone} installing kubebuilder assets for Kubernetes $(KUBEBUILDER_ASSETS_VERSION) 59 | @$(SETUP_ENVTEST) --bin-dir=$(CACHE_DIR)/kubebuilder --os=$(HOSTOS) --arch=$(HOSTARCH) use $(KUBEBUILDER_ASSETS_VERSION) 60 | @$(OK) installing kubebuilder assets for Kubernetes $(KUBEBUILDER_ASSETS_VERSION) 61 | 62 | # ==================================================================================== 63 | # Kubebuilder Targets 64 | 65 | $(eval $(call common.target,kubebuilder.manifests)) 66 | # Generate manifests e.g. CRD, RBAC etc. 67 | .do.kubebuilder.manifests: $(CONTROLLER_GEN) 68 | @$(INFO) Generating Kubernetes \(CRDs, RBAC, WebhookConfig, etc.\) manifests 69 | @$(CONTROLLER_GEN) \ 70 | $(CONTROLLER_GEN_CRD_OPTIONS) \ 71 | $(CONTROLLER_GEN_RBAC_OPTIONS) \ 72 | $(CONTROLLER_CONTROLLER_GEN_WEBHOOK_OPTIONS) \ 73 | $(CONTROLLER_GEN_PATHS) 74 | @$(OK) Generating Kubernetes \(CRDs, RBAC, WebhookConfig, etc.\) manifests 75 | .PHONY: .do.kubebuilder.manifests 76 | .kubebuilder.manifests.run: .do.kubebuilder.manifests 77 | 78 | $(eval $(call common.target,kubebuilder.code)) 79 | # Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 80 | .do.kubebuilder.code: $(CONTROLLER_GEN) 81 | @$(INFO) Generating DeepCopy, DeepCopyInto, and DeepCopyObject code 82 | @$(CONTROLLER_GEN) \ 83 | $(CONTROLLER_GEN_OBJECT_OPTIONS) \ 84 | $(CONTROLLER_GEN_PATHS) 85 | @$(OK) Generating DeepCopy, DeepCopyInto, and DeepCopyObject code 86 | .PHONY: .do.kubebuilder.code 87 | .kubebuilder.code.run: .do.kubebuilder.code 88 | 89 | # ==================================================================================== 90 | # Common Targets 91 | 92 | .test.init: |$(KUBEBUILDER_ASSETS) 93 | go.test.unit: |$(KUBEBUILDER_ASSETS) 94 | go.generate: kubebuilder.code 95 | .generate.init: .kubebuilder.manifests.init 96 | .generate.run: .kubebuilder.manifests.run 97 | .generate.done: .kubebuilder.manifests.done 98 | 99 | # ==================================================================================== 100 | # Special Targets 101 | 102 | define KUBEBULDERV3_HELPTEXT 103 | Kubebuilder Targets: 104 | kubebuilder.manifests Generates Kubernetes custom resources manifests (e.g. CRDs RBACs, ...) 105 | kubebuilder.code Generates DeepCopy, DeepCopyInto, and DeepCopyObject code 106 | 107 | endef 108 | export KUBEBULDERV3_HELPTEXT 109 | 110 | .kubebuilder.help: 111 | @echo "$$KUBEBULDERV3_HELPTEXT" 112 | 113 | .help: .kubebuilder.help 114 | .PHONY: .kubebuilder.help 115 | 116 | endif # __KUBEBUILDERV3_MAKEFILE__ 117 | -------------------------------------------------------------------------------- /build/makelib/nodejs.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # Copyright 2019 The Pressinfra Authors. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ifndef __NODEJS_MAKEFILE__ 17 | __NODEJS_MAKEFILE__ := included 18 | 19 | # ==================================================================================== 20 | # Options 21 | 22 | # supported node versions 23 | NODE_SUPPORTED_VERSIONS ?= 10|12 24 | NODE := node 25 | 26 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 27 | 28 | # The location of node application within this git repo. 29 | NODE_ROOT_DIR ?= $(SELF_DIR)/../.. 30 | 31 | # The location of node application source code, relative to the NODE_ROOT_DIR 32 | NODE_SRC_DIR ?= src 33 | 34 | NODE_ENV ?= production 35 | export NODE_ENV 36 | 37 | YARN := yarn 38 | YARN_MODULE_DIR := $(NODE_ROOT_DIR)/node_modules 39 | YARN_BIN_DIR := $(abspath $(NODE_ROOT_DIR)/node_modules/.bin) 40 | YARN_PACKAGE_FILE := $(NODE_ROOT_DIR)/package.json 41 | YARN_PACKAGE_LOCK_FILE := $(NODE_ROOT_DIR)/yarn.lock 42 | 43 | NODE_SRCS ?= $(abspath $(YARN_PACKAGE_FILE)) $(abspath $(YARN_PACKAGE_LOCK_FILE)) $(shell find $(abspath $(NODE_ROOT_DIR)/$(NODE_SRC_DIR)) -type f | grep -v '__tests__') 44 | 45 | YARN_CACHE_FOLDER ?= $(CACHE_DIR)/yarn 46 | export YARN_CACHE_FOLDER 47 | 48 | YARN_OUTDIR ?= $(OUTPUT_DIR)/yarn 49 | export YARN_OUTDIR 50 | 51 | EXTEND_ESLINT ?= true 52 | export EXTEND_ESLINT 53 | 54 | ESLINT_OUTPUT_DIR := $(OUTPUT_DIR)/lint/eslint 55 | 56 | # ==================================================================================== 57 | # NodeJS Tools Targets 58 | 59 | ESLINT := $(YARN_BIN_DIR)/eslint 60 | $(ESLINT): |yarn.install 61 | build.tools: $(ESLINT) 62 | 63 | # ==================================================================================== 64 | # YARN Targets 65 | 66 | .PHONY: .yarn.init 67 | .yarn.init: 68 | @if ! `$(NODE) --version | grep -q -E '^v($(NODE_SUPPORTED_VERSIONS))\.'`; then \ 69 | $(ERR) unsupported node version. Please install one of the following supported version: '$(NODE_SUPPORTED_VERSIONS)' ;\ 70 | exit 1 ;\ 71 | fi 72 | 73 | # some node packages like node-sass require platform/arch specific install. we need 74 | # to run yarn for each platform. As a result we track a stamp file per host 75 | YARN_INSTALL_STAMP := $(YARN_MODULE_DIR)/.yarn.install.$(HOST_PLATFORM).stamp 76 | 77 | # only run "yarn" if the package.json has changed 78 | $(YARN_INSTALL_STAMP): $(YARN_PACKAGE_FILE) $(YARN_PACKAGE_LOCK_FILE) 79 | @echo ${TIME} $(BLUE)[TOOL]$(CNone) yarn install 80 | @cd $(NODE_ROOT_DIR); $(YARN) --silent --frozen-lockfile --non-interactive --production=false || $(FAIL) 81 | @touch $(YARN_INSTALL_STAMP) 82 | @$(OK) yarn install 83 | 84 | yarn.install: .yarn.init $(YARN_INSTALL_STAMP) 85 | 86 | .yarn.clean: 87 | @rm -rf $(YARN_MODULE_DIR) 88 | 89 | .PHONY: yarn.install .yarn.clean 90 | 91 | # ==================================================================================== 92 | # NodeJS Targets 93 | 94 | $(ESLINT_OUTPUT_DIR)/stylecheck.xml: $(ESLINT) $(NODE_SRCS) 95 | @$(INFO) eslint 96 | @mkdir -p $(ESLINT_OUTPUT_DIR) 97 | @cd $(NODE_ROOT_DIR); $(ESLINT) '$(NODE_SRC_DIR)/**/*.{ts,tsx}' --color 98 | @touch $@ 99 | @$(OK) eslint 100 | 101 | js.lint: $(ESLINT_OUTPUT_DIR)/stylecheck.xml 102 | 103 | js.lint.fix: 104 | @$(INFO) eslint fix 105 | @cd $(NODE_ROOT_DIR); $(ESLINT) '$(NODE_SRC_DIR)/**/*.{ts,tsx}' --color 106 | @$(OK) eslint fix 107 | 108 | # common target for building a node js project 109 | $(eval $(call common.target,js.build)) 110 | 111 | # common target for testing a node js project 112 | $(eval $(call common.target,js.test)) 113 | 114 | .PHONY: js.lint js.lint.fix 115 | 116 | # ==================================================================================== 117 | # Common Targets 118 | 119 | .build.init: .yarn.init .js.build.init 120 | .build.check: js.lint 121 | .build.code: .js.build.run 122 | .build.done: .js.build.done 123 | 124 | .test.init: .js.test.init 125 | .test.run: .js.test.run 126 | .test.done: .js.test.done 127 | 128 | clean: .yarn.clean 129 | 130 | .lint.run: js.lint 131 | .fmt.run: js.lint.fix 132 | 133 | # ==================================================================================== 134 | # Special Targets 135 | 136 | define NODEJS_HELPTEXT 137 | nodejs Targets: 138 | yarn.install Installs dependencies in a make friendly manner. 139 | 140 | endef 141 | export NODEJS_HELPTEXT 142 | 143 | .PHONY: .js.help 144 | .js.help: 145 | @echo "$$NODEJS_HELPTEXT" 146 | 147 | .help: .js.help 148 | 149 | endif # __NODEJS_MAKEFILE__ 150 | -------------------------------------------------------------------------------- /build/makelib/php.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __PHP_MAKEFILE__ 16 | __PHP_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Options 20 | 21 | # supported php versions 22 | PHP_SUPPORTED_VERSIONS ?= 7.3|7.4 23 | PHP := php 24 | 25 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 26 | 27 | # The location of php application within this git repo. 28 | PHP_ROOT_DIR ?= $(SELF_DIR)/../.. 29 | 30 | # The location of php application source code, relative to the PHP_ROOT_DIR 31 | PHP_SRC_DIR ?= src 32 | 33 | COMPOSER_VERSION ?= 1.10.5 34 | COMPOSER_DOWNLOAD_URL ?= https://getcomposer.org/download/$(COMPOSER_VERSION)/composer.phar 35 | $(eval $(call tool.download,composer,$(COMPOSER_VERSION),$(COMPOSER_DOWNLOAD_URL))) 36 | 37 | COMPOSER_INSTALL_ARGS ?= --prefer-dist --classmap-authoritative 38 | COMPOSER_VENDOR_DIR := $(PHP_ROOT_DIR)/vendor 39 | COMPOSER_BIN_DIR := $(abspath $(PHP_ROOT_DIR)/vendor/bin) 40 | COMPOSER_JSON_FILE := $(PHP_ROOT_DIR)/composer.json 41 | COMPOSER_LOCK_FILE := $(PHP_ROOT_DIR)/composer.lock 42 | 43 | COMPOSER_CACHE_DIR := $(CACHE_DIR)/composer 44 | export COMPOSER_CACHE_DIR 45 | 46 | # ==================================================================================== 47 | # PHP Tools Targets 48 | 49 | PHPUNIT := $(COMPOSER_BIN_DIR)/phpunit 50 | $(PHPUNIT): |composer.install 51 | build.tools: $(PHPUNIT) 52 | 53 | PHPCS := $(COMPOSER_BIN_DIR)/phpcs 54 | $(PHPCS): |composer.install 55 | build.tools: $(PHPCS) 56 | 57 | PHPCBF := $(COMPOSER_BIN_DIR)/phpcbf 58 | $(PHPCBF): |composer.install 59 | build.tools: $(PHPCBF) 60 | 61 | # ==================================================================================== 62 | # Composer targets 63 | 64 | .PHONY: .composer.init 65 | .composer.init: 66 | @if ! `$(PHP) --version | grep -q -E '^PHP ($(PHP_SUPPORTED_VERSIONS))\.'`; then \ 67 | $(ERR) unsupported PHP version. Please install one of the following supported version: '$(PHP_SUPPORTED_VERSIONS)' ;\ 68 | exit 1 ;\ 69 | fi 70 | $(COMPOSER): .composer.init 71 | 72 | COMPOSER_INSTALL_STAMP := $(COMPOSER_VENDOR_DIR)/.composer.install.stamp 73 | 74 | # only run "composer" if the composer.json has changed 75 | $(COMPOSER_INSTALL_STAMP): $(COMPOSER) $(COMPOSER_JSON_FILE) $(COMPOSER_LOCK_FILE) 76 | @echo ${TIME} $(BLUE)[TOOL]$(CNone) composer install 77 | @cd $(PHP_ROOT_DIR); $(COMPOSER) install --no-interaction || $(FAIL) 78 | @touch $(COMPOSER_INSTALL_STAMP) 79 | @$(OK) composer install 80 | 81 | composer.install: $(COMPOSER_INSTALL_STAMP) 82 | 83 | composer.update: $(COMPOSER) 84 | @echo ${TIME} $(BLUE)[TOOL]$(CNone) composer update 85 | @cd $(PHP_ROOT_DIR); $(COMPOSER) update || $(FAIL) 86 | @touch $(COMPOSER_INSTALL_STAMP) 87 | @$(OK) composer install 88 | 89 | 90 | .composer.clean: 91 | @rm -rf $(COMPOSER_VENDOR_DIR) 92 | 93 | .PHONY: composer.install composer.update .composer.clean 94 | 95 | # ==================================================================================== 96 | # PHP Targets 97 | 98 | php.lint: 99 | @$(INFO) phpcs $(PHP_SRC_DIR) 100 | @cd $(PHP_ROOT_DIR); $(PHPCS) $(PHP_SRC_DIR) 101 | @$(OK) phpcs $(PHP_SRC_DIR) 102 | 103 | php.lint.fix: 104 | @$(INFO) phpcbf $(PHP_SRC_DIR) 105 | @cd $(PHP_ROOT_DIR); $(PHPCBF) $(PHP_SRC_DIR) 106 | @$(OK) phpcbf $(PHP_SRC_DIR) 107 | .PHONY: php.lint php.lint.fix 108 | 109 | # common target for building a php project 110 | $(eval $(call common.target,php.build)) 111 | 112 | # common target for testing a php project 113 | $(eval $(call common.target,php.test)) 114 | 115 | .PHONY: .do.php.test 116 | .php.test.run: .do.php.test 117 | .do.php.test: $(PHPUNIT) 118 | @$(INFO) phpunit 119 | @$(PHPUNIT) $(PHPUNIT_ARGS) 120 | @$(OK) phpunit 121 | 122 | 123 | # ==================================================================================== 124 | # Common Targets 125 | 126 | .build.init: .composer.init 127 | .build.check: php.lint 128 | .build.code: .php.build.run 129 | .build.done: .php.build.done 130 | 131 | .test.init: .php.test.init 132 | .test.run: .php.test.run 133 | .test.done: .php.test.done 134 | 135 | clean: .composer.clean 136 | 137 | .lint.run: php.lint 138 | .fmt.run: php.lint.fix 139 | 140 | # ==================================================================================== 141 | # Special Targets 142 | 143 | define PHP_HELPTEXT 144 | PHP Targets: 145 | composer.install Installs dependencies in a make friendly manner. 146 | composer.update Updates dependencies in a make friendly manner. 147 | 148 | endef 149 | export PHP_HELPTEXT 150 | 151 | .PHONY: .php.help 152 | .php.help: 153 | @echo "$$PHP_HELPTEXT" 154 | 155 | .help: .php.help 156 | 157 | endif # __PHP_MAKEFILE__ 158 | -------------------------------------------------------------------------------- /build/makelib/protobuf.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The Upbound Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __PROTOBUF_MAKEFILE__ 16 | __PROTOBUF_MAKEFILE__ := included 17 | 18 | # ==================================================================================== 19 | # Setup protobuf environment 20 | 21 | PROTOBUF_DIR ?= proto 22 | PROTOBUF_FILES ?= $(sort $(shell find $(PROTOBUF_DIR) -name "*.proto")) 23 | 24 | PROTOC_VERSION ?= 3.10.1 25 | 26 | # ==================================================================================== 27 | # Tools install targets 28 | 29 | PROTOTOOL_VERSION ?= 1.9.0 30 | PROTOTOOL_CACHE_PATH := $(TOOLS_HOST_DIR)/prototool 31 | export PROTOTOOL_CACHE_PATH 32 | 33 | PROTOTOOL_DOWNLOAD_URL ?= https://github.com/uber/prototool/releases/download/v$(PROTOTOOL_VERSION)/prototool-$(HOSTOS)-x86_64 34 | $(eval $(call tool.download,prototool,$(PROTOTOOL_VERSION),$(PROTOTOOL_DOWNLOAD_URL))) 35 | 36 | # ==================================================================================== 37 | # Protobuf Targets 38 | 39 | build.tools: .pb.prototool.cache.update 40 | .pb.prototool.cache.update: $(PROTOTOOL_CACHE_PATH)/.update 41 | $(PROTOTOOL_CACHE_PATH)/.update: $(PROTOBUF_DIR)/prototool.yaml |$(PROTOTOOL) 42 | @echo ${TIME} $(BLUE)[TOOL]$(CNone) updating prototool cache 43 | @$(PROTOTOOL) cache update $(PROTOBUF_DIR) 44 | @touch $@ 45 | @$(OK) updating prototool cache 46 | 47 | .pb.init: .pb.prototool.cache.update 48 | 49 | pb.lint: $(PROTOTOOL) 50 | @$(INFO) prototool lint 51 | @$(PROTOTOOL) lint $(PROTOBUF_DIR) || $(FAIL) 52 | @$(OK) prototool lint 53 | 54 | pb.fmt.verify: $(PROTOTOOL) 55 | @$(INFO) prototool format verify 56 | @$(PROTOTOOL) format -l $(PROTOBUF_DIR) || $(FAIL) 57 | @$(OK) prototool format verify 58 | 59 | pb.fmt: $(PROTOTOOL) 60 | @$(INFO) prototool format 61 | @$(PROTOTOOL) format -w $(PROTOBUF_DIR) || $(FAIL) 62 | @$(OK) prototool format 63 | 64 | # expose as common target so that we can hook in other generators 65 | # eg. https://github.com/dcodeIO/protobuf.js 66 | $(eval $(call common.target,pb.generate)) 67 | 68 | .pb.prototool.generate: 69 | @$(INFO) prototool generate 70 | @$(PROTOTOOL) generate $(PROTOBUF_DIR) 71 | @$(OK) prototool generate 72 | 73 | .pb.generate.init: .pb.init 74 | .pb.generate.run: .pb.prototool.generate 75 | 76 | .PHONY: .go.init go.lint go.fmt go.generate .pb.clean .pb.distclean 77 | .PHONY: .pb.prototool.cache.update .pb.prototool.generate 78 | 79 | # ==================================================================================== 80 | # Common Targets 81 | 82 | .lint.init: .pb.init 83 | .lint.run: pb.fmt.verify pb.lint 84 | 85 | .fmt.run: pb.fmt 86 | 87 | .generate.init: .pb.init 88 | .generate.run: pb.generate 89 | 90 | # ==================================================================================== 91 | # Special Targets 92 | 93 | define PROTOBUF_HELPTEXT 94 | Protobuf Targets: 95 | pb.generate Generate code from protobuf files in $(PROTOBUF_DIR) 96 | 97 | endef 98 | export PROTOBUF_HELPTEXT 99 | 100 | .PHONY: .go.help 101 | .pb.help: 102 | @echo "$$PROTOBUF_HELPTEXT" 103 | 104 | .help: .pb.help 105 | 106 | 107 | # # we use a consistent version of gofmt even while running different go compilers. 108 | # # see https://github.com/golang/go/issues/26397 for more details 109 | # PROTOC_VERSION ?= 3.10.1 110 | # PROTOC_DOWNLOAD_URL ?= https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(HOSTOS)-$(HOSTARCH).zip 111 | # $(eval $(call tool.download.zip,protoc,$(PROTOC_VERSION),$(PROTOC_DOWNLOAD_URL),bin/protoc)) 112 | 113 | endif # __PROTOBUF_MAKEFILE__ 114 | -------------------------------------------------------------------------------- /build/makelib/react.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __REACT_MAKEFILE__ 16 | __REACT_MAKEFILE__ := included 17 | 18 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 19 | include $(SELF_DIR)/nodejs.mk 20 | 21 | # ==================================================================================== 22 | # Options 23 | 24 | REACT_OUTPUT_DIR ?= $(OUTPUT_DIR)/react 25 | 26 | REACT_LOCALE_PREFIX ?= messages 27 | 28 | # ==================================================================================== 29 | # React app Targets 30 | 31 | REACT := $(YARN_BIN_DIR)/react-scripts --max_old_space_size=4096 32 | $(REACT): yarn.install 33 | build.tools: $(REACT) 34 | 35 | $(REACT_OUTPUT_DIR)/index.html: $(REACT) $(NODE_SRCS) 36 | @$(INFO) react-scripts build 37 | @cd $(NODE_ROOT_DIR); $(REACT) build 38 | @mkdir -p $(REACT_OUTPUT_DIR) 39 | @rm -rf $(REACT_OUTPUT_DIR) 40 | @mv $(NODE_ROOT_DIR)/build $(REACT_OUTPUT_DIR) 41 | @$(OK) react-scripts build 42 | react.build: $(REACT_OUTPUT_DIR)/index.html 43 | 44 | react.test: $(REACT) 45 | @$(INFO) react-scripts test 46 | @cd $(NODE_ROOT_DIR); TZ='UTC' $(REACT) test --env=jsdom --verbose --colors 47 | @$(OK) react-scripts test 48 | 49 | react.run: $(REACT) 50 | @cd $(NODE_ROOT_DIR); NODE_ENV=development BROWSER=none $(REACT) start 51 | 52 | .react.clean: 53 | @rm -rf $(REACT_OUTPUT_DIR) 54 | 55 | .PHONY: react.build react.test .react.clean 56 | 57 | ifneq ($(LANGUAGES),) 58 | I18NEXT_CONV := $(YARN_BIN_DIR)/i18next-conv 59 | REACT_GETTEXT_PARSER := $(YARN_BIN_DIR)/react-gettext-parser 60 | 61 | $(I18NEXT_CONV): yarn.install 62 | $(REACT_GETTEXT_PARSER): yarn.install 63 | build.tools: $(REACT_GETTEXT_PARSER) $(I18NEXT_CONV) 64 | 65 | .PHONY: react.collect-translations 66 | react.collect-translations: $(REACT_GETTEXT_PARSER) |$(WORK_DIR) 67 | @$(INFO) react-gettext-parser collect translations 68 | @cd $(NODE_ROOT_DIR); $(REACT_GETTEXT_PARSER) --config .gettextparser --no-wrap --output $(abspath $(WORK_DIR))/$(REACT_LOCALE_PREFIX).pot '$(NODE_SRC_DIR)/**/*.{js,ts,tsx}' 69 | # Update the .pot file only if there are changes to actual messages. We need this because the collector always updates 70 | # the POT-Creation-Date 71 | # 72 | @$(MAKELIB_BIN_DIR)/po-diff.sh $(LOCALES_DIR)/$(REACT_LOCALE_PREFIX).pot $(WORK_DIR)/$(REACT_LOCALE_PREFIX).pot || \ 73 | mv $(WORK_DIR)/$(REACT_LOCALE_PREFIX).pot $(LOCALES_DIR)/$(REACT_LOCALE_PREFIX).pot 74 | @rm -f $(WORK_DIR)/$(REACT_LOCALE_PREFIX).pot 75 | # 76 | @$(OK) react-gettext-parser collect translations 77 | 78 | react.convert-translations: $(I18NEXT_CONV) 79 | @$(INFO) i18next convert translations to json 80 | $(foreach l,$(LANGUAGES),@$(I18NEXT_CONV) --language $(l) --skipUntranslated \ 81 | --source $(LOCALES_DIR)/$(l)/$(REACT_LOCALE_PREFIX).po \ 82 | --target $(NODE_ROOT_DIR)/$(NODE_SRC_DIR)/locales/$(l).json \ 83 | > /dev/null || $(FAIL) ${\n}\ 84 | ) 85 | @$(OK) i18next convert translations to json 86 | 87 | .translations.init: react.collect-translations 88 | .translations.done: react.convert-translations 89 | endif 90 | 91 | # ==================================================================================== 92 | # Common Targets 93 | 94 | .js.build.run: react.build 95 | .js.test.run: react.test 96 | clean: .react.clean 97 | 98 | # ==================================================================================== 99 | # Special Targets 100 | 101 | define REACT_HELPTEXT 102 | React Targets: 103 | react.run Run the react application for development. 104 | 105 | endef 106 | export REACT_HELPTEXT 107 | 108 | .PHONY: .react.help 109 | .react.help: 110 | @echo "$$REACT_HELPTEXT" 111 | 112 | .help: .react.help 113 | 114 | endif # __REACT_MAKEFILE__ 115 | -------------------------------------------------------------------------------- /build/makelib/utils.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Pressinfra SRL. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __UTILS_MAKEFILE__ 16 | __UTILS_MAKEFILE__ := included 17 | 18 | COMMA := , 19 | # define literal space character in a way that work with both GNU make 3 and 4 20 | SPACE := $(subst ,, ) 21 | 22 | # define a newline 23 | define \n 24 | 25 | 26 | endef 27 | 28 | # ==================================================================================== 29 | # Colors 30 | 31 | BLACK := $(shell printf "\033[30m") 32 | BLACK_BOLD := $(shell printf "\033[30;1m") 33 | RED := $(shell printf "\033[31m") 34 | RED_BOLD := $(shell printf "\033[31;1m") 35 | GREEN := $(shell printf "\033[32m") 36 | GREEN_BOLD := $(shell printf "\033[32;1m") 37 | YELLOW := $(shell printf "\033[33m") 38 | YELLOW_BOLD := $(shell printf "\033[33;1m") 39 | BLUE := $(shell printf "\033[34m") 40 | BLUE_BOLD := $(shell printf "\033[34;1m") 41 | MAGENTA := $(shell printf "\033[35m") 42 | MAGENTA_BOLD := $(shell printf "\033[35;1m") 43 | CYAN := $(shell printf "\033[36m") 44 | CYAN_BOLD := $(shell printf "\033[36;1m") 45 | WHITE := $(shell printf "\033[37m") 46 | WHITE_BOLD := $(shell printf "\033[37;1m") 47 | CNone := $(shell printf "\033[0m") 48 | 49 | # ==================================================================================== 50 | # Logger 51 | 52 | TIME_LONG = `date +%Y-%m-%d' '%H:%M:%S` 53 | TIME_SHORT = `date +%H:%M:%S` 54 | TIME = $(TIME_SHORT) 55 | 56 | INFO = echo ${TIME} ${BLUE}[ .. ]${CNone} 57 | WARN = echo ${TIME} ${YELLOW}[WARN]${CNone} 58 | ERR = echo ${TIME} ${RED}[FAIL]${CNone} 59 | OK = echo ${TIME} ${GREEN}[ OK ]${CNone} 60 | FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false) 61 | 62 | 63 | # ==================================================================================== 64 | # Utility functions 65 | lower = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) 66 | upper = $(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,$(subst z,Z,$1)))))))))))))))))))))))))) 67 | list-join = $(subst $(SPACE),$(1),$(strip $(2))) 68 | 69 | # ==================================================================================== 70 | # Tools macros 71 | # 72 | # Theses macros are used to install tools in an idempotent, cache friendly way. 73 | 74 | define tool 75 | $(subst -,_,$(call upper,$(1))) := $$(TOOLS_BIN_DIR)/$(1) 76 | 77 | build.tools: $$(TOOLS_BIN_DIR)/$(1) 78 | $$(TOOLS_BIN_DIR)/$(1): $$(TOOLS_HOST_DIR)/$(1)-v$(2) |$$(TOOLS_BIN_DIR) 79 | @ln -sf $$< $$@ 80 | endef 81 | 82 | # Creates a target for downloading a tool from a given url 83 | # 1 tool, 2 version, 3 download url 84 | define tool.download 85 | $(call tool,$(1),$(2)) 86 | 87 | $$(TOOLS_HOST_DIR)/$(1)-v$(2): |$$(TOOLS_HOST_DIR) 88 | @echo ${TIME} ${BLUE}[TOOL]${CNone} installing $(1) version $(2) from $(3) 89 | @curl -fsSLo $$@ $(3) || $$(FAIL) 90 | @chmod +x $$@ 91 | @$$(OK) installing $(1) version $(2) from $(3) 92 | endef # tool.download 93 | 94 | # Creates a target for downloading and unarchiving a tool from a given url 95 | # 1 tool, 2 version, 3 download url, 4 tool path within archive, 5 tar strip components 96 | define tool.download.tar.gz 97 | $(call tool,$(1),$(2)) 98 | 99 | ifeq ($(4),) 100 | $(1)_TOOL_ARCHIVE_PATH = $(1) 101 | else 102 | $(1)_TOOL_ARCHIVE_PATH = $(4) 103 | endif 104 | 105 | 106 | $$(TOOLS_HOST_DIR)/$(1)-v$(2): |$$(TOOLS_HOST_DIR) 107 | @echo ${TIME} ${BLUE}[TOOL]${CNone} installing $(1) version $(2) from $(3) 108 | @mkdir -p $$(TOOLS_HOST_DIR)/tmp-$(1)-v$(2) || $$(FAIL) 109 | ifeq ($(5),) 110 | @curl -fsSL $(3) | tar -xz --strip-components=1 -C $$(TOOLS_HOST_DIR)/tmp-$(1)-v$(2) || $$(FAIL) 111 | else 112 | @curl -fsSL $(3) | tar -xz --strip-components=$(5) -C $$(TOOLS_HOST_DIR)/tmp-$(1)-v$(2) || $$(FAIL) 113 | endif 114 | @mv $$(TOOLS_HOST_DIR)/tmp-$(1)-v$(2)/$$($(1)_TOOL_ARCHIVE_PATH) $$@ || $(FAIL) 115 | @chmod +x $$@ 116 | @rm -rf $$(TOOLS_HOST_DIR)/tmp-$(1)-v$(2) 117 | @$$(OK) installing $(1) version $(2) from $(3) 118 | endef # tool.download.tar.gz 119 | 120 | 121 | endif # __UTILS_MAKEFILE__ 122 | -------------------------------------------------------------------------------- /build/makelib/wordpress.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Pressinfra Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef __WORDPRESS_MAKEFILE__ 16 | __WORDPRESS_MAKEFILE__ := included 17 | 18 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 19 | include $(SELF_DIR)/php.mk 20 | 21 | # ==================================================================================== 22 | # Options 23 | 24 | WP_VERSION ?= master 25 | 26 | WP_OUTPUT_DIR := $(OUTPUT_DIR)/wordpress-$(WP_VERSION) 27 | 28 | ifeq ($(WP_VERSION),master) 29 | WP_DOWNLOAD_URL ?= https://github.com/WordPress/wordpress-develop/archive/$(WP_VERSION).tar.gz 30 | else 31 | WP_DOWNLOAD_URL ?= https://wordpress.org/wordpress-$(WP_VERSION).tar.gz 32 | endif 33 | WP_ARCHIVE := $(CACHE_DIR)/wordpress-$(WP_VERSION).tar.gz 34 | 35 | 36 | WP_TESTS_VERSION ?= $(WP_VERSION) 37 | WP_TESTS_DIR ?= $(WORK_DIR)/wordpress-develop-$(WP_VERSION) 38 | WP_TESTS_CONFIG ?= $(abspath $(PHP_ROOT_DIR))/tests/wp-tests-config.php 39 | WP_TESTS_DOWNLOAD_URL ?= https://github.com/WordPress/wordpress-develop/archive/$(WP_VERSION).tar.gz 40 | WP_TESTS_ARCHIVE := $(CACHE_DIR)/wordpress-develop-$(WP_VERSION).tar.gz 41 | 42 | # ===================================================================================== 43 | # WordPress Targets 44 | 45 | $(WP_TESTS_ARCHIVE): 46 | @$(INFO) fetching $(notdir $@) from $(WP_TESTS_DOWNLOAD_URL) 47 | @curl -sLo "$@" "$(WP_TESTS_DOWNLOAD_URL)" || $(FAIL) 48 | @$(OK) fetching $(notdir $@) from $(WP_TESTS_DOWNLOAD_URL) 49 | 50 | $(WP_TESTS_DIR)/src/wp-includes/version.php: $(WP_TESTS_ARCHIVE) 51 | @$(INFO) unpacking $< 52 | @rm -rf $(WP_TESTS_DIR) && mkdir -p $(WP_TESTS_DIR) 53 | @tar -zxf $< -C $(WP_TESTS_DIR) --strip-components 1 54 | @cp tests/wp-tests-config.php $(WP_TESTS_DIR) 55 | @mkdir -p $(WP_TESTS_DIR)/src/wp-content/uploads 56 | @test -f $@ && touch $@ || $(FAIL) 57 | @$(OK) unpacking $< 58 | 59 | $(WP_TESTS_DIR)/wp-tests-config.php: $(WP_TESTS_CONFIG) $(WP_TESTS_DIR)/src/wp-includes/version.php 60 | @cp $(WP_TESTS_CONFIG) $@ 61 | 62 | # add WP_TESTS_DIR env var for running tests 63 | .do.php.test: PHPUNIT:=WP_TESTS_DIR=$(WP_TESTS_DIR) $(PHPUNIT) 64 | 65 | $(WP_ARCHIVE): 66 | @$(INFO) fetching $(notdir $@) from $(WP_DOWNLOAD_URL) 67 | @curl -sLo "$@" "$(WP_DOWNLOAD_URL)" || $(FAIL) 68 | @$(OK) fetching $(notdir $@) from $(WP_DOWNLOAD_URL) 69 | 70 | $(WP_OUTPUT_DIR)/wp-includes/version.php: $(WP_ARCHIVE) 71 | @$(INFO) unpacking $< 72 | @rm -rf $(WP_OUTPUT_DIR) && mkdir -p $(WP_OUTPUT_DIR) 73 | @tar -zxf $< -C $(WP_OUTPUT_DIR) --strip-components 1 74 | @test -f $@ && touch $@ || $(FAIL) 75 | @$(OK) unpacking $< 76 | 77 | $(eval $(call common.target,wordpress.build)) 78 | .wordpress.build.init: $(WP_OUTPUT_DIR)/wp-includes/version.php 79 | 80 | # ==================================================================================== 81 | # Common Targets 82 | 83 | .php.test.init: $(WP_TESTS_DIR)/wp-tests-config.php 84 | .build.artifacts: wordpress.build 85 | 86 | endif # __WORDPRESS_MAKEFILE__ 87 | -------------------------------------------------------------------------------- /cmd/wordpress-operator/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | 22 | logf "github.com/presslabs/controller-util/log" 23 | flag "github.com/spf13/pflag" 24 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 25 | "k8s.io/klog/v2/klogr" 26 | "sigs.k8s.io/controller-runtime/pkg/client/config" 27 | "sigs.k8s.io/controller-runtime/pkg/healthz" 28 | "sigs.k8s.io/controller-runtime/pkg/manager" 29 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 30 | 31 | "github.com/bitpoke/wordpress-operator/pkg/apis" 32 | "github.com/bitpoke/wordpress-operator/pkg/cmd/options" 33 | "github.com/bitpoke/wordpress-operator/pkg/controller" 34 | ) 35 | 36 | const genericErrorExitCode = 1 37 | 38 | var setupLog = logf.Log.WithName("wordpress-operator") 39 | 40 | // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete 41 | 42 | func main() { 43 | options.AddToFlagSet(flag.CommandLine) 44 | flag.Parse() 45 | 46 | logf.SetLogger(klogr.New()) 47 | setupLog.Info("Starting wordpress-operator...") 48 | 49 | // Get a config to talk to the apiserver 50 | cfg, err := config.GetConfig() 51 | if err != nil { 52 | setupLog.Error(err, "unable to get configuration") 53 | os.Exit(genericErrorExitCode) 54 | } 55 | 56 | opt := manager.Options{ 57 | LeaderElection: options.LeaderElection, 58 | LeaderElectionID: options.LeaderElectionID, 59 | LeaderElectionNamespace: options.LeaderElectionNamespace, 60 | LeaderElectionResourceLock: "leases", 61 | MetricsBindAddress: options.MetricsBindAddress, 62 | HealthProbeBindAddress: options.HealthProbeBindAddress, 63 | } 64 | 65 | if options.WatchNamespace != "" { 66 | opt.Namespace = options.WatchNamespace 67 | } 68 | 69 | // Create a new Cmd to provide shared dependencies and start components 70 | mgr, err := manager.New(cfg, opt) 71 | if err != nil { 72 | setupLog.Error(err, "unable to create a new manager") 73 | os.Exit(genericErrorExitCode) 74 | } 75 | 76 | // Setup Scheme for all resources 77 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 78 | setupLog.Error(err, "unable to register types to scheme") 79 | os.Exit(genericErrorExitCode) 80 | } 81 | 82 | // Setup all Controllers 83 | if err := controller.AddToManager(mgr); err != nil { 84 | setupLog.Error(err, "unable to setup controllers") 85 | os.Exit(genericErrorExitCode) 86 | } 87 | 88 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 89 | setupLog.Error(err, "unable to set up health check") 90 | os.Exit(1) 91 | } 92 | 93 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 94 | setupLog.Error(err, "unable to set up ready check") 95 | os.Exit(1) 96 | } 97 | 98 | // Start the Cmd 99 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 100 | setupLog.Error(err, "unable to start the manager") 101 | os.Exit(genericErrorExitCode) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | kind: Kustomization 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | namespace: wordpress-operator-system 5 | 6 | # Value of this field is prepended to the 7 | # names of all resources, e.g. a deployment named 8 | # "wordpress" becomes "alices-wordpress". 9 | # Note that it should also match with the prefix (text before '-') of the namespace 10 | # field above. 11 | namePrefix: wordpress-operator- 12 | 13 | # Labels to add to all resources and selectors. 14 | #commonLabels: 15 | # someName: someValue 16 | 17 | # Each entry in this list must resolve to an existing 18 | # resource definition in YAML. These are the resource 19 | # files that kustomize reads, modifies and emits as a 20 | # YAML string, with resources separated by document 21 | # markers ("---"). 22 | resources: 23 | - ../rbac/*.yaml 24 | - ../manager/*.yaml 25 | - ../rbac/rbac_role.yaml 26 | - ../rbac/rbac_role_binding.yaml 27 | 28 | patchesStrategicMerge: 29 | - manager_image_patch.yaml 30 | -------------------------------------------------------------------------------- /config/default/manager_image_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | # Change the value of image field below to your controller image URL 11 | - image: IMAGE_URL 12 | name: manager 13 | -------------------------------------------------------------------------------- /config/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - crds/wordpress.presslabs.org_wordpresses.yaml 3 | 4 | 5 | patchesJson6902: 6 | # https://github.com/kubernetes/kubernetes/issues/84880 7 | - target: 8 | group: apiextensions.k8s.io 9 | version: v1 10 | kind: CustomResourceDefinition 11 | name: wordpresses.wordpress.presslabs.org 12 | path: patches/volume.yaml 13 | 14 | # https://github.com/kubernetes-sigs/controller-tools/issues/444 15 | - target: 16 | group: apiextensions.k8s.io 17 | version: v1 18 | kind: CustomResourceDefinition 19 | name: wordpresses.wordpress.presslabs.org 20 | path: patches/x-kubernetes-list-map-keys.yaml 21 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | controller-tools.k8s.io: "1.0" 6 | name: system 7 | --- 8 | apiVersion: v1 9 | kind: Service 10 | metadata: 11 | name: controller-manager-service 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | controller-tools.k8s.io: "1.0" 16 | spec: 17 | selector: 18 | control-plane: controller-manager 19 | controller-tools.k8s.io: "1.0" 20 | ports: 21 | - port: 443 22 | --- 23 | apiVersion: apps/v1 24 | kind: StatefulSet 25 | metadata: 26 | name: controller-manager 27 | namespace: system 28 | labels: 29 | control-plane: controller-manager 30 | controller-tools.k8s.io: "1.0" 31 | spec: 32 | selector: 33 | matchLabels: 34 | control-plane: controller-manager 35 | controller-tools.k8s.io: "1.0" 36 | serviceName: controller-manager-service 37 | template: 38 | metadata: 39 | labels: 40 | control-plane: controller-manager 41 | controller-tools.k8s.io: "1.0" 42 | spec: 43 | containers: 44 | - command: 45 | - /root/manager 46 | image: controller:latest 47 | name: manager 48 | resources: 49 | limits: 50 | cpu: 100m 51 | memory: 30Mi 52 | requests: 53 | cpu: 100m 54 | memory: 20Mi 55 | terminationGracePeriodSeconds: 10 56 | -------------------------------------------------------------------------------- /config/patches/volume.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/volumes/items 3 | value: 4 | type: object 5 | -------------------------------------------------------------------------------- /config/patches/x-kubernetes-list-map-keys.yaml: -------------------------------------------------------------------------------- 1 | - op: remove 2 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/sidecars/items/properties/ports 3 | 4 | - op: remove 5 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/initContainers/items/properties/ports 6 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - apps 11 | resources: 12 | - deployments 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - batch 23 | resources: 24 | - cronjobs 25 | - jobs 26 | verbs: 27 | - create 28 | - delete 29 | - get 30 | - list 31 | - patch 32 | - update 33 | - watch 34 | - apiGroups: 35 | - coordination.k8s.io 36 | resources: 37 | - leases 38 | verbs: 39 | - create 40 | - delete 41 | - get 42 | - list 43 | - patch 44 | - update 45 | - watch 46 | - apiGroups: 47 | - "" 48 | resources: 49 | - events 50 | - persistentvolumeclaims 51 | - secrets 52 | - services 53 | verbs: 54 | - create 55 | - delete 56 | - get 57 | - list 58 | - patch 59 | - update 60 | - watch 61 | - apiGroups: 62 | - networking.k8s.io 63 | resources: 64 | - ingresses 65 | verbs: 66 | - create 67 | - delete 68 | - get 69 | - list 70 | - patch 71 | - update 72 | - watch 73 | - apiGroups: 74 | - wordpress.presslabs.org 75 | resources: 76 | - wordpresses 77 | - wordpresses/status 78 | verbs: 79 | - create 80 | - delete 81 | - get 82 | - list 83 | - patch 84 | - update 85 | - watch 86 | -------------------------------------------------------------------------------- /config/samples/wordpress_v1alpha1_wordpress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: mysite-db 6 | type: Opaque 7 | data: 8 | ROOT_PASSWORD: bm90LXNvLXNlY3VyZQ== 9 | USER: d29yZHByZXNz 10 | PASSWORD: d3AtaW5zZWN1cmU= 11 | DATABASE: d29yZHByZXNz 12 | --- 13 | apiVersion: v1 14 | kind: Secret 15 | metadata: 16 | name: mysite-salt 17 | type: Opaque 18 | data: 19 | AUTH_KEY: RU5UOEBLUENENlNKJzkpaE1TKSx4V0tQSShvYENDbW5ZVkZoQEt9TE5TLzIwOjhPZGY5JSc2YTReY3p4a1g0LQ== 20 | SECURE_AUTH_KEY: MXJRLlZZQUtdcyk7KX5bKnskeixBe20uN3ZeXzFCby5TbU1Jdkh1VSFGSn10bjNwUWYmUmBdcylbMk9Tajp2LQ== 21 | LOGGED_IN_KEY: Sm4xdmVGSV0zP0pmPVUsN3llX1lacChvP2M3YWtlYiVEX3JZbk4jKEo+QGVAL1ZUdHd5aVlKbEBWaT1zcj11PQ== 22 | NONCE_KEY: Jm9eTn1oNTRKLn5pOTlbWHpqQ3tNJG53YVgneEFGPW1PPiYwdSRDVTFPLlB1RVc1NVZtcmRPfWMoYG43QmNNdg== 23 | AUTH_SALT: RnorP35BZFB3d3IwfVNsWnYsL0szZiE3Xj0tV2NQY20wc1IxZFpob2dfNnFqXn1tLjpDUmQzXV5XZCh3W1ApPw== 24 | SECURE_AUTH_SALT: QllpZVlrfkExRzFnWlpMUDI/aXJieyVifW0yP2BUP3Z0ZHwwJCFNS0klbEUsNGBUbWhjeE4sNVNge2Mzbzt5IQ== 25 | LOGGED_IN_SALT: QHNeZSJaSTU0PnNWKS9QOjxmWConIVRhMmwuWFpHO2QxV0xxQzBdY0NeTjFpNiU+PCEnYEA1ZT5KSy5oanl4Ug== 26 | NONCE_SALT: OCclcHg7UTIzZXdkVi9PJCsoVWZTWkUuTUwnZ2N3IzFPfGpZRWBAaD5VMTtbdnIzWHtSdnt4ZUVWcGtETnhzUQ== 27 | --- 28 | apiVersion: mysql.presslabs.org/v1alpha1 29 | kind: MysqlCluster 30 | metadata: 31 | name: mysite 32 | spec: 33 | replicas: 1 34 | secretName: mysite-db 35 | --- 36 | apiVersion: wordpress.presslabs.org/v1alpha1 37 | kind: Wordpress 38 | metadata: 39 | name: mysite 40 | spec: 41 | replicas: 1 42 | image: quay.io/presslabs/wordpress-runtime:4.9.8-php71 43 | secretRef: mysite 44 | tlsSecretRef: mysite-tls 45 | envFrom: 46 | - prefix: "WORDPRESS_" 47 | secretRef: 48 | name: mysite-salt 49 | env: 50 | - name: DB_HOST 51 | value: mysite-mysql-master 52 | - name: DB_USER 53 | valueFrom: 54 | secretKeyRef: 55 | name: mysite-db 56 | key: USER 57 | - name: DB_PASSWORD 58 | valueFrom: 59 | secretKeyRef: 60 | name: mysite-db 61 | key: PASSWORD 62 | - name: DB_NAME 63 | valueFrom: 64 | secretKeyRef: 65 | name: mysite-db 66 | key: DATABASE 67 | domains: 68 | - calin.localtest.me 69 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitpoke/wordpress-operator/9b763fd90a3995cfcbc9d1df83c9f964a1e3c703/config/webhook/manifests.yaml -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: wordpress-operator 3 | description: Bitpoke WordPress Operator Helm Chart 4 | appVersion: latest 5 | kubeVersion: ">= 1.19.0-0" 6 | keywords: 7 | - wordpress 8 | - bitpoke 9 | - woocommerce 10 | - php 11 | - blog 12 | - http 13 | - web 14 | - application 15 | version: 0.0.0 16 | home: https://www.bitpoke.io/ 17 | sources: 18 | - https://github.com/bitpoke/wordpress-operator.git 19 | annotations: 20 | category: CMS 21 | artifacthub.io/license: "Apache-2.0" 22 | artifacthub.io/operator: "true" 23 | artifacthub.io/operatorCapabilities: "full lifecycle" 24 | artifacthub.io/images: | 25 | - name: wordpress-operator 26 | image: docker.io/bitpoke/wordpress-operator:latest 27 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/README.md: -------------------------------------------------------------------------------- 1 | # Bitpoke WordPress Operator 2 | 3 | This is the helm chart for [wordpress-operator](https://github.com/bitpoke/wordpress-operator). 4 | 5 | ## TL;DR 6 | ```sh 7 | helm repo add bitpoke https://helm-charts.bitpoke.io 8 | helm install wordpress-operator bitpoke/wordpress-operator 9 | ``` 10 | 11 | ## Configuration 12 | The following table contains the configuration parameters for wordpress-operator and default values. 13 | 14 | | Parameter | Description | Default value | 15 | | --- | --- | --- | 16 | | `replicas` | Replicas for controller | `1` | 17 | | `image.repository` | Controller image repository | `docker.io/bitpoke/wordpress-operator` | 18 | | `image.pullPolicy` | Controller image pull policy | `IfNotPresent` | 19 | | `image.tag ` | Controller image tag | `latest` | 20 | | `imagePullSecrets` | Controller image pull secret | | 21 | | `podAnnotations` | Extra pod annotations | `{}` | 22 | | `podSecurityContext` | The pod security context. `65532` is the UID/GID for the nonroot user in the official images | `{runAsNonRoot: true, runAsUser: 65532, runAsGroup: 65532, fsGroup: 65532}` | 23 | | `securityContext` | Security context for the WordPress Operator container | `{}` | 24 | | `resources` | Controller container resources limits and requests | `{}` | 25 | | `nodeSelector` | Controller pod nodeSelector | `{}` | 26 | | `tolerations` | Controller pod tolerations | `{}` | 27 | | `affinity` | Controller pod node affinity | `{}` | 28 | | `extraArgs` | Args that are passed to controller, check controller command line flags | `[]` | 29 | | `extraEnv` | Extra environment vars that are passed to controller, check controller command line flags | `{}` | 30 | | `rbac.create` | Whether or not to create rbac service account, role and roleBinding | `true` | 31 | | `serviceAccount.create` | Specifies whether a service account should be created | `true` | 32 | | `serviceAccount.annotations` | Annotations to add to the service account | `{}` | 33 | | `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | `empty` | 34 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | WordPress Operator installed in {{ .Release.Namespace }} 2 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "wordpress-operator.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "wordpress-operator.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "wordpress-operator.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "wordpress-operator.labels" -}} 37 | helm.sh/chart: {{ include "wordpress-operator.chart" . }} 38 | {{ include "wordpress-operator.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "wordpress-operator.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "wordpress-operator.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "wordpress-operator.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "wordpress-operator.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ include "wordpress-operator.fullname" . }} 6 | labels: 7 | {{- include "wordpress-operator.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: 10 | - apps 11 | resources: 12 | - deployments 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - batch 23 | resources: 24 | - cronjobs 25 | - jobs 26 | verbs: 27 | - create 28 | - delete 29 | - get 30 | - list 31 | - patch 32 | - update 33 | - watch 34 | - apiGroups: 35 | - coordination.k8s.io 36 | resources: 37 | - leases 38 | verbs: 39 | - create 40 | - delete 41 | - get 42 | - list 43 | - patch 44 | - update 45 | - watch 46 | - apiGroups: 47 | - "" 48 | resources: 49 | - events 50 | - persistentvolumeclaims 51 | - secrets 52 | - services 53 | verbs: 54 | - create 55 | - delete 56 | - get 57 | - list 58 | - patch 59 | - update 60 | - watch 61 | - apiGroups: 62 | - networking.k8s.io 63 | resources: 64 | - ingresses 65 | verbs: 66 | - create 67 | - delete 68 | - get 69 | - list 70 | - patch 71 | - update 72 | - watch 73 | - apiGroups: 74 | - wordpress.presslabs.org 75 | resources: 76 | - wordpresses 77 | - wordpresses/status 78 | verbs: 79 | - create 80 | - delete 81 | - get 82 | - list 83 | - patch 84 | - update 85 | - watch 86 | {{- end }} 87 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | kind: ClusterRoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: {{ template "wordpress-operator.fullname" . }} 6 | labels: 7 | {{- include "wordpress-operator.labels" . | nindent 4 }} 8 | subjects: 9 | - kind: ServiceAccount 10 | name: {{ template "wordpress-operator.serviceAccountName" . }} 11 | namespace: {{ .Release.Namespace }} 12 | roleRef: 13 | kind: ClusterRole 14 | name: {{ template "wordpress-operator.fullname" . }} 15 | apiGroup: rbac.authorization.k8s.io 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "wordpress-operator.fullname" . }} 5 | labels: 6 | {{- include "wordpress-operator.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "wordpress-operator.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | {{- with .Values.podAnnotations }} 15 | annotations: 16 | {{- toYaml . | nindent 8 }} 17 | {{- end }} 18 | labels: 19 | {{- include "wordpress-operator.selectorLabels" . | nindent 8 }} 20 | spec: 21 | {{- with .Values.imagePullSecrets }} 22 | imagePullSecrets: 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | serviceAccountName: {{ include "wordpress-operator.serviceAccountName" . }} 26 | securityContext: 27 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 28 | containers: 29 | - name: {{ .Chart.Name }} 30 | securityContext: 31 | {{- toYaml .Values.securityContext | nindent 12 }} 32 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 33 | imagePullPolicy: {{ .Values.image.pullPolicy }} 34 | {{- if .Values.extraEnv }} 35 | env: 36 | {{- toYaml .Values.extraEnv | nindent 12 }} 37 | {{- end }} 38 | {{- if .Values.extraArgs }} 39 | args: 40 | {{- toYaml .Values.extraArgs | nindent 12 }} 41 | {{- end }} 42 | ports: 43 | - name: health 44 | containerPort: 8081 45 | protocol: TCP 46 | - name: prometheus 47 | containerPort: 8080 48 | protocol: TCP 49 | livenessProbe: 50 | httpGet: 51 | path: /healthz 52 | port: health 53 | readinessProbe: 54 | httpGet: 55 | path: /readyz 56 | port: health 57 | resources: 58 | {{- toYaml .Values.resources | nindent 12 }} 59 | {{- with .Values.nodeSelector }} 60 | nodeSelector: 61 | {{- toYaml . | nindent 8 }} 62 | {{- end }} 63 | {{- with .Values.affinity }} 64 | affinity: 65 | {{- toYaml . | nindent 8 }} 66 | {{- end }} 67 | {{- with .Values.tolerations }} 68 | tolerations: 69 | {{- toYaml . | nindent 8 }} 70 | {{- end }} 71 | 72 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "wordpress-operator.fullname" . }} 5 | labels: 6 | {{- include "wordpress-operator.labels" . | nindent 4 }} 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - port: 9125 11 | targetPort: prometheus 12 | protocol: TCP 13 | name: prometheus 14 | selector: 15 | {{- include "wordpress-operator.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "wordpress-operator.serviceAccountName" . }} 6 | labels: 7 | {{- include "wordpress-operator.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /deploy/charts/wordpress-operator/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for wordpress-operator. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: docker.io/bitpoke/wordpress-operator 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | rbac: 18 | # Specifies whether RBAC resources should be created 19 | create: true 20 | 21 | serviceAccount: 22 | # Specifies whether a service account should be created 23 | create: true 24 | # Annotations to add to the service account 25 | annotations: {} 26 | # The name of the service account to use. 27 | # If not set and create is true, a name is generated using the fullname template 28 | name: "" 29 | 30 | podAnnotations: {} 31 | 32 | podSecurityContext: 33 | runAsNonRoot: true 34 | # 65532 is the UID for nonroot user from distroless image 35 | runAsUser: 65532 36 | runAsGroup: 65532 37 | fsGroup: 65532 38 | 39 | securityContext: {} 40 | # capabilities: 41 | # drop: 42 | # - ALL 43 | # readOnlyRootFilesystem: true 44 | # runAsNonRoot: true 45 | # runAsUser: 1000 46 | 47 | extraArgs: [] 48 | # --leader-elect=false 49 | 50 | resources: {} 51 | # We usually recommend not to specify default resources and to leave this as a conscious 52 | # choice for the user. This also increases chances charts run on environments with little 53 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 54 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 55 | # limits: 56 | # cpu: 100m 57 | # memory: 128Mi 58 | # requests: 59 | # cpu: 100m 60 | # memory: 128Mi 61 | 62 | nodeSelector: {} 63 | 64 | tolerations: [] 65 | 66 | affinity: {} 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bitpoke/wordpress-operator 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/appscode/mergo v0.3.6 7 | github.com/cooleo/slugify v0.0.0-20161029032441-81db6b52442d 8 | github.com/go-logr/logr v0.4.0 9 | github.com/onsi/ginkgo v1.16.4 10 | github.com/onsi/gomega v1.15.0 11 | github.com/presslabs/controller-util v0.3.0 12 | github.com/spf13/pflag v1.0.5 13 | golang.org/x/net v0.8.0 14 | 15 | // kubernetes 16 | k8s.io/api v0.21.4 17 | k8s.io/apimachinery v0.21.4 18 | k8s.io/client-go v0.21.4 19 | k8s.io/klog/v2 v2.10.0 20 | sigs.k8s.io/controller-runtime v0.9.7 21 | ) 22 | 23 | require ( 24 | cloud.google.com/go v0.54.0 // indirect 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/blendle/zapdriver v1.3.1 // indirect 27 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/evanphx/json-patch v4.11.0+incompatible // indirect 30 | github.com/fsnotify/fsnotify v1.4.9 // indirect 31 | github.com/go-logr/zapr v0.4.0 // indirect 32 | github.com/go-test/deep v1.0.7 // indirect 33 | github.com/gogo/protobuf v1.3.2 // indirect 34 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 35 | github.com/golang/protobuf v1.5.2 // indirect 36 | github.com/google/go-cmp v0.5.5 // indirect 37 | github.com/google/gofuzz v1.1.0 // indirect 38 | github.com/google/uuid v1.1.2 // indirect 39 | github.com/googleapis/gnostic v0.5.5 // indirect 40 | github.com/hashicorp/golang-lru v0.5.4 // indirect 41 | github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 // indirect 42 | github.com/imdario/mergo v0.3.12 // indirect 43 | github.com/json-iterator/go v1.1.11 // indirect 44 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.1 // indirect 47 | github.com/nxadm/tail v1.4.8 // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/prometheus/client_golang v1.11.1 // indirect 50 | github.com/prometheus/client_model v0.2.0 // indirect 51 | github.com/prometheus/common v0.26.0 // indirect 52 | github.com/prometheus/procfs v0.6.0 // indirect 53 | go.uber.org/atomic v1.7.0 // indirect 54 | go.uber.org/multierr v1.6.0 // indirect 55 | go.uber.org/zap v1.19.0 // indirect 56 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect 57 | golang.org/x/sys v0.6.0 // indirect 58 | golang.org/x/term v0.6.0 // indirect 59 | golang.org/x/text v0.8.0 // indirect 60 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 61 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 62 | google.golang.org/appengine v1.6.7 // indirect 63 | google.golang.org/protobuf v1.26.0 // indirect 64 | gopkg.in/inf.v0 v0.9.1 // indirect 65 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.0 // indirect 68 | k8s.io/apiextensions-apiserver v0.21.4 // indirect 69 | k8s.io/component-base v0.21.4 // indirect 70 | k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect 71 | k8s.io/utils v0.0.0-20210802155522-efc7438f0176 // indirect 72 | sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect 73 | sigs.k8s.io/yaml v1.2.0 // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /hack/Dockerfile.skaffold: -------------------------------------------------------------------------------- 1 | # Build the dashboard binary 2 | FROM golang:1.16 as builder 3 | 4 | ARG BUILD_DATE="" 5 | ARG GIT_COMMIT="unavailable" 6 | ARG GIT_TREE_STATE="clean" 7 | 8 | ENV BUILD_DATE=$BUILD_DATE 9 | ENV GIT_COMMIT=$GIT_COMMIT 10 | ENV GIT_TREE_STATE=$GIT_TREE_STATE 11 | 12 | # Copy in the go src 13 | WORKDIR /app 14 | COPY pkg/ pkg/ 15 | COPY cmd/ cmd/ 16 | COPY go.mod go.mod 17 | COPY go.sum go.sum 18 | 19 | # Build 20 | RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ 21 | -a -o wp-operator ./cmd/wordpress-operator/main.go 22 | 23 | # Copy the operator binary into a thin image 24 | FROM scratch 25 | COPY --from=builder /app/wp-operator /wp-operator 26 | 27 | ENTRYPOINT ["/wp-operator"] 28 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/license-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | MISSING=($(grep -s -rL --include "*.go" "Licensed under the Apache License, Version 2.0" pkg cmd)) 3 | 4 | if [[ -n "${MISSING[0]}" ]]; then 5 | echo "Files missing license header:" 6 | printf "\t%s\n" "${MISSING[@]}" 7 | exit 1 8 | fi 9 | exit 0 10 | -------------------------------------------------------------------------------- /hack/skaffold-values.yaml: -------------------------------------------------------------------------------- 1 | 2 | extraEnv: 3 | DEV: "true" 4 | -------------------------------------------------------------------------------- /images/wordpress-operator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copy the wordpress-operator binary into a thin image 2 | # The image is pinned to the nonroot tag 3 | FROM gcr.io/distroless/static-debian10@sha256:50115802102da4a7dbc74f5399028347682361ebf0792b7a11b088e648e69ac2 4 | 5 | COPY rootfs / 6 | ENTRYPOINT ["/wordpress-operator"] 7 | CMD ["help"] 8 | -------------------------------------------------------------------------------- /images/wordpress-operator/Makefile: -------------------------------------------------------------------------------- 1 | PLATFORMS := linux_amd64 2 | include ../../build/makelib/common.mk 3 | 4 | IMAGE = $(BUILD_REGISTRY)/wordpress-operator-$(ARCH) 5 | CACHE_IMAGES = $(IMAGE) 6 | include ../../build/makelib/image.mk 7 | 8 | img.build: 9 | @$(INFO) docker build $(IMAGE) $(PLATFORM) 10 | @cp -La . $(IMAGE_TEMP_DIR) 11 | @mkdir -p $(IMAGE_TEMP_DIR)/rootfs 12 | @cp $(OUTPUT_DIR)/bin/linux_$(ARCH)/wordpress-operator $(IMAGE_TEMP_DIR)/rootfs/wordpress-operator 13 | @docker build $(BUILD_ARGS) \ 14 | --build-arg ARCH=$(ARCH) \ 15 | --build-arg TINI_VERSION=$(TINI_VERSION) \ 16 | -t $(IMAGE) \ 17 | $(IMAGE_TEMP_DIR) 18 | @$(OK) docker build $(IMAGE) 19 | -------------------------------------------------------------------------------- /pkg/apis/addtoscheme_wordpress_v1alpha1.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 21 | ) 22 | 23 | func init() { 24 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back. 25 | AddToSchemes = append(AddToSchemes, v1alpha1.AddToScheme, v1alpha1.RegisterDefaults) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/apis/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Generate deepcopy for apis 18 | 19 | // Package apis contains Kubernetes API groups. 20 | package apis 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // AddToSchemes may be used to add all resources defined in the project to a Scheme. 27 | var AddToSchemes runtime.SchemeBuilder 28 | 29 | // AddToScheme adds all Resources to the Scheme. 30 | func AddToScheme(s *runtime.Scheme) error { 31 | return AddToSchemes.AddToScheme(s) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/apis/wordpress/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the wordpress v1alpha1 API group 18 | // 19 | // +kubebuilder:object:generate:=true 20 | // +groupName=wordpress.presslabs.org 21 | // 22 | package v1alpha1 23 | -------------------------------------------------------------------------------- /pkg/apis/wordpress/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // NOTE: Boilerplate only. Ignore this file. 18 | 19 | package v1alpha1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // SchemeGroupVersion is group version used to register these objects. 28 | SchemeGroupVersion = schema.GroupVersion{Group: "wordpress.presslabs.org", Version: "v1alpha1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 31 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /pkg/apis/wordpress/v1alpha1/v1alpha1_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1_test 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "k8s.io/client-go/rest" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | 31 | . "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 32 | ) 33 | 34 | var t *envtest.Environment 35 | var cfg *rest.Config 36 | var c client.Client 37 | 38 | func TestV1alpha1(t *testing.T) { 39 | RegisterFailHandler(Fail) 40 | RunSpecsWithDefaultAndCustomReporters(t, "API v1 Suite", []Reporter{printer.NewlineReporter{}}) 41 | } 42 | 43 | var _ = BeforeSuite(func() { 44 | var err error 45 | 46 | t = &envtest.Environment{ 47 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")}, 48 | } 49 | 50 | err = SchemeBuilder.AddToScheme(scheme.Scheme) 51 | Expect(err).NotTo(HaveOccurred()) 52 | 53 | cfg, err = t.Start() 54 | Expect(err).NotTo(HaveOccurred()) 55 | 56 | c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 57 | Expect(err).NotTo(HaveOccurred()) 58 | }) 59 | 60 | var _ = AfterSuite(func() { 61 | Expect(t.Stop()).To(Succeed()) 62 | }) 63 | -------------------------------------------------------------------------------- /pkg/apis/wordpress/v1alpha1/wordpress_types_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1_test 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | 23 | "golang.org/x/net/context" 24 | // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/types" 26 | 27 | . "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 28 | ) 29 | 30 | var _ = Describe("Wordpress CRUD", func() { 31 | var created *Wordpress 32 | var key types.NamespacedName 33 | 34 | BeforeEach(func() { 35 | key = types.NamespacedName{Name: "foo", Namespace: "default"} 36 | 37 | created = &Wordpress{} 38 | created.Name = key.Name 39 | created.Namespace = key.Namespace 40 | created.Spec.Domains = []Domain{"example.com"} 41 | }) 42 | 43 | AfterEach(func() { 44 | // nolint: errcheck 45 | c.Delete(context.TODO(), created) 46 | }) 47 | 48 | Describe("when sending a storage request", func() { 49 | Context("for a valid config", func() { 50 | It("should provide CRUD access to the object", func() { 51 | fetched := &Wordpress{} 52 | 53 | By("returning success from the create request") 54 | Expect(c.Create(context.TODO(), created)).Should(Succeed()) 55 | 56 | By("returning the same object as created") 57 | Expect(c.Get(context.TODO(), key, fetched)).Should(Succeed()) 58 | Expect(fetched).To(Equal(created)) 59 | 60 | By("allowing label updates") 61 | updated := fetched.DeepCopy() 62 | updated.Labels = map[string]string{"hello": "world"} 63 | Expect(c.Update(context.TODO(), updated)).Should(Succeed()) 64 | Expect(c.Get(context.TODO(), key, fetched)).Should(Succeed()) 65 | Expect(fetched).To(Equal(updated)) 66 | 67 | By("deleting an fetched object") 68 | Expect(c.Delete(context.TODO(), fetched)).Should(Succeed()) 69 | Expect(c.Get(context.TODO(), key, fetched)).To(HaveOccurred()) 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /pkg/apis/wordpress/v1alpha1/zz_generated.defaults.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2020 Pressinfra SRL. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by main. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // RegisterDefaults adds defaulters functions to the given scheme. 28 | // Public to allow building arbitrary schemes. 29 | // All generated defaulters are covering - they call all nested defaulters. 30 | func RegisterDefaults(scheme *runtime.Scheme) error { 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cmd/options/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "io/ioutil" 21 | "os" 22 | "strings" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | 26 | "github.com/spf13/pflag" 27 | ) 28 | 29 | var ( 30 | // GitCloneImage is the image used by the init container that clones the code. 31 | GitCloneImage = "docker.io/library/buildpack-deps:stretch-scm" 32 | 33 | // WordpressRuntimeImage is the base image used to run your code. 34 | WordpressRuntimeImage = "docker.io/bitpoke/wordpress-runtime:5.8.2" 35 | 36 | // IngressClass is the default ingress class used used for creating WordPress ingresses. 37 | IngressClass = "" 38 | 39 | // LeaderElection determines whether or not to use leader election when starting the manager. 40 | LeaderElection = false 41 | 42 | // LeaderElectionNamespace determines the namespace in which the leader election resource will be created. 43 | LeaderElectionNamespace = namespace() 44 | 45 | // LeaderElectionID determines the name of the resource that leader election will use for holding the leader lock. 46 | LeaderElectionID = "q7e8p7jr.wordpress-operator.bitpoke.io" 47 | 48 | // MetricsBindAddress is the TCP address that the controller should bind to for serving prometheus metrics. 49 | // It can be set to "0" to disable the metrics serving. 50 | MetricsBindAddress = ":8080" 51 | 52 | // HealthProbeBindAddress is the TCP address that the controller should bind to for serving health probes. 53 | HealthProbeBindAddress = ":8081" 54 | 55 | // WatchNamespace sets the Namespace field, which restricts the manager's cache to watch objects in the desired namespace. 56 | WatchNamespace = os.Getenv("WATCH_NAMESPACE") 57 | ) 58 | 59 | func namespace() string { 60 | if ns := os.Getenv("KUBE_NAMESPACE"); ns != "" { 61 | return ns 62 | } 63 | 64 | if ns := os.Getenv("MY_POD_NAMESPACE"); ns != "" { 65 | return ns 66 | } 67 | 68 | if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { 69 | if ns := strings.TrimSpace(string(data)); len(ns) > 0 { 70 | return ns 71 | } 72 | } 73 | 74 | return corev1.NamespaceDefault 75 | } 76 | 77 | // AddToFlagSet set command line arguments. 78 | func AddToFlagSet(flag *pflag.FlagSet) { 79 | flag.StringVar(&GitCloneImage, "git-clone-image", GitCloneImage, "The image used when cloning code from git.") 80 | flag.StringVar(&WordpressRuntimeImage, "wordpress-runtime-image", WordpressRuntimeImage, "The base image used for Wordpress.") 81 | flag.StringVar(&IngressClass, "ingress-class", IngressClass, "The default ingress class for WordPress sites.") 82 | flag.BoolVar(&LeaderElection, "leader-election", LeaderElection, "Enables or disables controller leader election.") 83 | flag.StringVar(&LeaderElectionNamespace, "leader-election-namespace", LeaderElectionNamespace, "The namespace in which the leader election resource will be created.") 84 | flag.StringVar(&LeaderElectionID, "leader-election-id", LeaderElectionID, "The name of the resource that leader election will use for holding the leader lock.") 85 | flag.StringVar(&MetricsBindAddress, "metrics-addr", MetricsBindAddress, "The TCP address that the controller should bind to for serving prometheus metrics."+ 86 | " It can be set to \"0\" to disable the metrics serving.") 87 | flag.StringVar(&HealthProbeBindAddress, "healthz-addr", HealthProbeBindAddress, "The TCP address that the controller should bind to for serving health probes.") 88 | } 89 | -------------------------------------------------------------------------------- /pkg/controller/add_wordpress.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "github.com/bitpoke/wordpress-operator/pkg/controller/wordpress" 21 | ) 22 | 23 | func init() { 24 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 25 | AddToManagerFuncs = append(AddToManagerFuncs, wordpress.Add) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/controller/add_wp_cron.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | wpcron "github.com/bitpoke/wordpress-operator/pkg/controller/wp-cron" 21 | ) 22 | 23 | func init() { 24 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 25 | AddToManagerFuncs = append(AddToManagerFuncs, wpcron.Add) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "sigs.k8s.io/controller-runtime/pkg/manager" 21 | ) 22 | 23 | // AddToManagerFuncs is a list of functions to add all Controllers to the Manager. 24 | var AddToManagerFuncs []func(manager.Manager) error 25 | 26 | // AddToManager adds all Controllers to the Manager. 27 | func AddToManager(m manager.Manager) error { 28 | for _, f := range AddToManagerFuncs { 29 | if err := f(m); err != nil { 30 | return err 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/code_pvc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "errors" 21 | "reflect" 22 | 23 | "github.com/presslabs/controller-util/syncer" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 30 | ) 31 | 32 | var errCodeVolumeClaimNotDefined = errors.New(".spec.code.persistentVolumeClaim is not defined") 33 | 34 | // NewCodePVCSyncer returns a new sync.Interface for reconciling codePVC. 35 | func NewCodePVCSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 36 | objLabels := wp.ComponentLabels(wordpress.WordpressCodePVC) 37 | 38 | obj := &corev1.PersistentVolumeClaim{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | Name: wp.ComponentName(wordpress.WordpressCodePVC), 41 | Namespace: wp.Namespace, 42 | }, 43 | } 44 | 45 | return syncer.NewObjectSyncer("CodePVC", wp.Unwrap(), obj, c, func() error { 46 | obj.Labels = labels.Merge(labels.Merge(wp.Spec.CodeVolumeSpec.Labels, objLabels), controllerLabels) 47 | 48 | if wp.Spec.CodeVolumeSpec == nil || wp.Spec.CodeVolumeSpec.PersistentVolumeClaim == nil { 49 | return errCodeVolumeClaimNotDefined 50 | } 51 | 52 | if len(wp.Spec.CodeVolumeSpec.Annotations) > 0 { 53 | obj.Annotations = labels.Merge(obj.Annotations, wp.Spec.CodeVolumeSpec.Annotations) 54 | } 55 | 56 | // PVC spec is immutable 57 | if !reflect.DeepEqual(obj.Spec, corev1.PersistentVolumeClaimSpec{}) { 58 | return nil 59 | } 60 | 61 | obj.Spec = *wp.Spec.CodeVolumeSpec.PersistentVolumeClaim 62 | 63 | return nil 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | var controllerLabels = map[string]string{ 20 | "app.kubernetes.io/managed-by": "wordpress-operator.presslabs.org", 21 | } 22 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/deploy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "errors" 21 | "reflect" 22 | 23 | appsv1 "k8s.io/api/apps/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | "github.com/appscode/mergo" 30 | 31 | "github.com/presslabs/controller-util/mergo/transformers" 32 | "github.com/presslabs/controller-util/syncer" 33 | 34 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 35 | ) 36 | 37 | var errImmutableDeploymentSelector = errors.New("deployment selector is immutable") 38 | 39 | // NewDeploymentSyncer returns a new sync.Interface for reconciling web Deployment. 40 | func NewDeploymentSyncer(wp *wordpress.Wordpress, secret *corev1.Secret, c client.Client) syncer.Interface { 41 | objLabels := wp.ComponentLabels(wordpress.WordpressDeployment) 42 | 43 | obj := &appsv1.Deployment{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: wp.ComponentName(wordpress.WordpressDeployment), 46 | Namespace: wp.Namespace, 47 | }, 48 | } 49 | 50 | return syncer.NewObjectSyncer("Deployment", wp.Unwrap(), obj, c, func() error { 51 | obj.Labels = labels.Merge(labels.Merge(obj.Labels, objLabels), controllerLabels) 52 | 53 | template := wp.WebPodTemplateSpec() 54 | 55 | if len(template.Annotations) == 0 { 56 | template.Annotations = make(map[string]string) 57 | } 58 | template.Annotations["wordpress.presslabs.org/secretVersion"] = secret.ResourceVersion 59 | 60 | obj.Spec.Template.ObjectMeta = template.ObjectMeta 61 | 62 | selector := metav1.SetAsLabelSelector(wp.WebPodLabels()) 63 | if !reflect.DeepEqual(selector, obj.Spec.Selector) { 64 | if obj.ObjectMeta.CreationTimestamp.IsZero() { 65 | obj.Spec.Selector = selector 66 | } else { 67 | return errImmutableDeploymentSelector 68 | } 69 | } 70 | 71 | err := mergo.Merge(&obj.Spec.Template.Spec, template.Spec, mergo.WithTransformers(transformers.PodSpec)) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | obj.Spec.Template.Spec.NodeSelector = wp.Spec.NodeSelector 77 | obj.Spec.Template.Spec.Tolerations = wp.Spec.Tolerations 78 | 79 | if wp.Spec.Replicas != nil { 80 | obj.Spec.Replicas = wp.Spec.Replicas 81 | } 82 | 83 | if wp.Spec.DeploymentStrategy != nil { 84 | obj.Spec.Strategy = *wp.Spec.DeploymentStrategy 85 | } 86 | 87 | return nil 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/ingress.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | netv1 "k8s.io/api/networking/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | "github.com/presslabs/controller-util/syncer" 26 | 27 | "github.com/bitpoke/wordpress-operator/pkg/cmd/options" 28 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 29 | ) 30 | 31 | const ingressClassAnnotationKey = "kubernetes.io/ingress.class" 32 | 33 | func upsertPath(rules []netv1.IngressRule, domain, path string, bk netv1.IngressBackend) []netv1.IngressRule { 34 | var rule *netv1.IngressRule 35 | 36 | for i := range rules { 37 | if rules[i].Host == domain { 38 | rule = &rules[i] 39 | 40 | break 41 | } 42 | } 43 | 44 | if rule == nil { 45 | rules = append(rules, netv1.IngressRule{Host: domain}) 46 | rule = &rules[len(rules)-1] 47 | } 48 | 49 | if rule.HTTP == nil { 50 | rule.HTTP = &netv1.HTTPIngressRuleValue{} 51 | } 52 | 53 | var httpPath *netv1.HTTPIngressPath 54 | 55 | for i := range rule.HTTP.Paths { 56 | if rule.HTTP.Paths[i].Path == path { 57 | httpPath = &rule.HTTP.Paths[i] 58 | 59 | break 60 | } 61 | } 62 | 63 | if httpPath == nil { 64 | rule.HTTP.Paths = append(rule.HTTP.Paths, netv1.HTTPIngressPath{Path: path}) 65 | httpPath = &rule.HTTP.Paths[len(rule.HTTP.Paths)-1] 66 | } 67 | 68 | pathType := netv1.PathTypePrefix 69 | httpPath.PathType = &pathType 70 | httpPath.Backend = bk 71 | 72 | return rules 73 | } 74 | 75 | // NewIngressSyncer returns a new sync.Interface for reconciling web Ingress. 76 | func NewIngressSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 77 | objLabels := wp.ComponentLabels(wordpress.WordpressIngress) 78 | 79 | obj := &netv1.Ingress{ 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Name: wp.ComponentName(wordpress.WordpressIngress), 82 | Namespace: wp.Namespace, 83 | }, 84 | } 85 | 86 | bk := netv1.IngressBackend{ 87 | Service: &netv1.IngressServiceBackend{ 88 | Name: wp.ComponentName(wordpress.WordpressService), 89 | Port: netv1.ServiceBackendPort{Name: "http"}, 90 | }, 91 | } 92 | 93 | return syncer.NewObjectSyncer("Ingress", wp.Unwrap(), obj, c, func() error { 94 | obj.Labels = labels.Merge(labels.Merge(obj.Labels, objLabels), controllerLabels) 95 | 96 | if len(obj.ObjectMeta.Annotations) == 0 { 97 | obj.ObjectMeta.Annotations = make(map[string]string) 98 | } 99 | 100 | for k, v := range wp.Spec.IngressAnnotations { 101 | obj.ObjectMeta.Annotations[k] = v 102 | } 103 | delete(obj.ObjectMeta.Annotations, ingressClassAnnotationKey) 104 | 105 | if options.IngressClass != "" { 106 | obj.Spec.IngressClassName = &options.IngressClass 107 | } else { 108 | obj.Spec.IngressClassName = nil 109 | } 110 | 111 | rules := []netv1.IngressRule{} 112 | for _, route := range wp.Spec.Routes { 113 | path := route.Path 114 | if path == "" { 115 | path = "/" 116 | } 117 | rules = upsertPath(rules, route.Domain, path, bk) 118 | } 119 | 120 | obj.Spec.Rules = rules 121 | 122 | if len(wp.Spec.TLSSecretRef) > 0 { 123 | tls := netv1.IngressTLS{ 124 | SecretName: string(wp.Spec.TLSSecretRef), 125 | } 126 | for _, route := range wp.Spec.Routes { 127 | tls.Hosts = append(tls.Hosts, route.Domain) 128 | } 129 | obj.Spec.TLS = []netv1.IngressTLS{tls} 130 | } else { 131 | obj.Spec.TLS = nil 132 | } 133 | 134 | return nil 135 | }) 136 | } 137 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/ingress_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | 23 | netv1 "k8s.io/api/networking/v1" 24 | ) 25 | 26 | var _ = Describe("The upsertPath function", func() { 27 | var ( 28 | rules []netv1.IngressRule 29 | bk netv1.IngressBackend 30 | pathTypePrefix netv1.PathType 31 | ) 32 | 33 | BeforeEach(func() { 34 | pathTypePrefix = netv1.PathTypePrefix 35 | rules = []netv1.IngressRule{ 36 | { 37 | Host: "bitpoke.io", 38 | IngressRuleValue: netv1.IngressRuleValue{ 39 | HTTP: &netv1.HTTPIngressRuleValue{ 40 | Paths: []netv1.HTTPIngressPath{ 41 | {Path: "/", PathType: &pathTypePrefix}, 42 | }, 43 | }, 44 | }, 45 | }, 46 | {Host: "blog.bitpoke.io"}, 47 | } 48 | bk = netv1.IngressBackend{} 49 | }) 50 | 51 | When("upserting a new host", func() { 52 | It("should create a new rule", func() { 53 | Expect(upsertPath(rules, "docs.bitpoke.io", "/", bk)).To(Equal( 54 | []netv1.IngressRule{ 55 | { 56 | Host: "bitpoke.io", 57 | IngressRuleValue: netv1.IngressRuleValue{ 58 | HTTP: &netv1.HTTPIngressRuleValue{ 59 | Paths: []netv1.HTTPIngressPath{ 60 | {Path: "/", PathType: &pathTypePrefix}, 61 | }, 62 | }, 63 | }, 64 | }, 65 | {Host: "blog.bitpoke.io"}, 66 | { 67 | Host: "docs.bitpoke.io", 68 | IngressRuleValue: netv1.IngressRuleValue{ 69 | HTTP: &netv1.HTTPIngressRuleValue{ 70 | Paths: []netv1.HTTPIngressPath{ 71 | {Path: "/", PathType: &pathTypePrefix, Backend: bk}, 72 | }, 73 | }, 74 | }, 75 | }, 76 | })) 77 | }) 78 | }) 79 | 80 | When("upserting an existing host with a new path", func() { 81 | It("should add the path to the existing rule", func() { 82 | Expect(upsertPath(rules, "bitpoke.io", "/blog", bk)).To(Equal( 83 | []netv1.IngressRule{ 84 | { 85 | Host: "bitpoke.io", 86 | IngressRuleValue: netv1.IngressRuleValue{ 87 | HTTP: &netv1.HTTPIngressRuleValue{ 88 | Paths: []netv1.HTTPIngressPath{ 89 | {Path: "/", PathType: &pathTypePrefix}, 90 | {Path: "/blog", PathType: &pathTypePrefix, Backend: bk}, 91 | }, 92 | }, 93 | }, 94 | }, 95 | {Host: "blog.bitpoke.io"}, 96 | })) 97 | }) 98 | }) 99 | 100 | When("upserting an existing host with an existing path", func() { 101 | It("should not change anything", func() { 102 | Expect(upsertPath(rules, "bitpoke.io", "/", bk)).To(Equal( 103 | []netv1.IngressRule{ 104 | { 105 | Host: "bitpoke.io", 106 | IngressRuleValue: netv1.IngressRuleValue{ 107 | HTTP: &netv1.HTTPIngressRuleValue{ 108 | Paths: []netv1.HTTPIngressPath{ 109 | {Path: "/", PathType: &pathTypePrefix, Backend: bk}, 110 | }, 111 | }, 112 | }, 113 | }, 114 | {Host: "blog.bitpoke.io"}, 115 | })) 116 | 117 | }) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/media_pvc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "errors" 21 | "reflect" 22 | 23 | "github.com/presslabs/controller-util/syncer" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 30 | ) 31 | 32 | var errMediaVolumeClaimNotDefined = errors.New(".spec.media.persistentVolumeClaim is not defined") 33 | 34 | // NewMediaPVCSyncer returns a new sync.Interface for reconciling media PVC. 35 | func NewMediaPVCSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 36 | objLabels := wp.ComponentLabels(wordpress.WordpressMediaPVC) 37 | 38 | obj := &corev1.PersistentVolumeClaim{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | Name: wp.ComponentName(wordpress.WordpressMediaPVC), 41 | Namespace: wp.Namespace, 42 | }, 43 | } 44 | 45 | return syncer.NewObjectSyncer("MediaPVC", wp.Unwrap(), obj, c, func() error { 46 | obj.Labels = labels.Merge(labels.Merge(wp.Spec.MediaVolumeSpec.Labels, objLabels), controllerLabels) 47 | 48 | if len(wp.Spec.MediaVolumeSpec.Annotations) > 0 { 49 | obj.Annotations = labels.Merge(obj.Annotations, wp.Spec.MediaVolumeSpec.Annotations) 50 | } 51 | 52 | if wp.Spec.MediaVolumeSpec == nil || wp.Spec.MediaVolumeSpec.PersistentVolumeClaim == nil { 53 | return errMediaVolumeClaimNotDefined 54 | } 55 | 56 | // PVC spec is immutable 57 | if !reflect.DeepEqual(obj.Spec, corev1.PersistentVolumeClaimSpec{}) { 58 | return nil 59 | } 60 | 61 | obj.Spec = *wp.Spec.MediaVolumeSpec.PersistentVolumeClaim 62 | 63 | return nil 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/secret.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | "github.com/presslabs/controller-util/rand" 26 | "github.com/presslabs/controller-util/syncer" 27 | 28 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 29 | ) 30 | 31 | var generatedSalts = map[string]int{ 32 | "AUTH_KEY": 64, 33 | "SECURE_AUTH_KEY": 64, 34 | "LOGGED_IN_KEY": 64, 35 | "NONCE_KEY": 64, 36 | "AUTH_SALT": 64, 37 | "SECURE_AUTH_SALT": 64, 38 | "LOGGED_IN_SALT": 64, 39 | "NONCE_SALT": 64, 40 | } 41 | 42 | // NewSecretSyncer returns a new sync.Interface for reconciling wordpress secret. 43 | func NewSecretSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 44 | objLabels := wp.ComponentLabels(wordpress.WordpressSecret) 45 | 46 | obj := &corev1.Secret{ 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Name: wp.ComponentName(wordpress.WordpressSecret), 49 | Namespace: wp.Namespace, 50 | }, 51 | } 52 | 53 | return syncer.NewObjectSyncer("Secret", wp.Unwrap(), obj, c, func() error { 54 | obj.Labels = labels.Merge(labels.Merge(obj.Labels, objLabels), controllerLabels) 55 | 56 | if len(obj.Data) == 0 { 57 | obj.Data = make(map[string][]byte) 58 | } 59 | 60 | for name, size := range generatedSalts { 61 | if len(obj.Data[name]) == 0 { 62 | random, err := rand.ASCIIString(size) 63 | if err != nil { 64 | return err 65 | } 66 | obj.Data[name] = []byte(random) 67 | } 68 | } 69 | 70 | return nil 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "errors" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/apimachinery/pkg/util/intstr" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | "github.com/presslabs/controller-util/syncer" 29 | 30 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 31 | ) 32 | 33 | var errImmutableServiceSelector = errors.New("service selector is immutable") 34 | 35 | // NewServiceSyncer returns a new sync.Interface for reconciling web Service. 36 | func NewServiceSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 37 | objLabels := wp.ComponentLabels(wordpress.WordpressDeployment) 38 | 39 | obj := &corev1.Service{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Name: wp.Name, 42 | Namespace: wp.Namespace, 43 | }, 44 | } 45 | 46 | return syncer.NewObjectSyncer("Service", wp.Unwrap(), obj, c, func() error { 47 | obj.Labels = labels.Merge(labels.Merge(obj.Labels, objLabels), controllerLabels) 48 | 49 | selector := wp.WebPodLabels() 50 | if !labels.Equals(selector, obj.Spec.Selector) { 51 | if obj.ObjectMeta.CreationTimestamp.IsZero() { 52 | obj.Spec.Selector = selector 53 | } else { 54 | return errImmutableServiceSelector 55 | } 56 | } 57 | 58 | if len(obj.Spec.Ports) != 2 { 59 | obj.Spec.Ports = make([]corev1.ServicePort, 2) 60 | } 61 | 62 | obj.Spec.Ports[0].Name = "http" 63 | obj.Spec.Ports[0].Port = int32(80) 64 | obj.Spec.Ports[0].TargetPort = intstr.FromInt(wordpress.InternalHTTPPort) 65 | 66 | obj.Spec.Ports[1].Name = "prometheus" 67 | obj.Spec.Ports[1].Port = int32(wordpress.MetricsExporterPort) 68 | obj.Spec.Ports[1].TargetPort = intstr.FromInt(wordpress.MetricsExporterPort) 69 | 70 | return nil 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/sync_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | logf "github.com/presslabs/controller-util/log" 26 | "k8s.io/klog/v2" 27 | "k8s.io/klog/v2/klogr" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | ) 30 | 31 | func TestPodTemplate(t *testing.T) { 32 | klog.SetOutput(GinkgoWriter) 33 | logf.SetLogger(klogr.New()) 34 | 35 | RegisterFailHandler(Fail) 36 | RunSpecsWithDefaultAndCustomReporters(t, "Wordpress Sync Test Suite", []Reporter{printer.NewlineReporter{}}) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/internal/sync/upgrade.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | batchv1 "k8s.io/api/batch/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | "github.com/appscode/mergo" 26 | 27 | "github.com/presslabs/controller-util/mergo/transformers" 28 | "github.com/presslabs/controller-util/syncer" 29 | 30 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 31 | ) 32 | 33 | // NewDBUpgradeJobSyncer returns a new sync.Interface for reconciling database upgrade Job. 34 | func NewDBUpgradeJobSyncer(wp *wordpress.Wordpress, c client.Client) syncer.Interface { 35 | objLabels := wp.ComponentLabels(wordpress.WordpressDBUpgrade) 36 | 37 | obj := &batchv1.Job{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: wp.ComponentName(wordpress.WordpressDBUpgrade), 40 | Namespace: wp.Namespace, 41 | }, 42 | } 43 | 44 | var ( 45 | backoffLimit int32 46 | activeDeadlineSeconds int64 = 10 47 | ) 48 | 49 | return syncer.NewObjectSyncer("DBUpgradeJob", wp.Unwrap(), obj, c, func() error { 50 | obj.Labels = labels.Merge(labels.Merge(obj.Labels, objLabels), controllerLabels) 51 | 52 | if !obj.CreationTimestamp.IsZero() { 53 | // TODO (calind): handle the case that the existing job is failed 54 | return nil 55 | } 56 | 57 | obj.Spec.BackoffLimit = &backoffLimit 58 | obj.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds 59 | 60 | cmd := []string{"/bin/sh", "-c", "wp core update-db --network || wp core update-db && wp cache flush"} 61 | template := wp.JobPodTemplateSpec(cmd...) 62 | 63 | obj.Spec.Template.ObjectMeta = template.ObjectMeta 64 | 65 | err := mergo.Merge(&obj.Spec.Template.Spec, template.Spec, mergo.WithTransformers(transformers.PodSpec)) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/wordpress_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/presslabs/controller-util/syncer" 23 | appsv1 "k8s.io/api/apps/v1" 24 | batchv1 "k8s.io/api/batch/v1" 25 | corev1 "k8s.io/api/core/v1" 26 | netv1 "k8s.io/api/networking/v1" 27 | "k8s.io/apimachinery/pkg/api/errors" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | "k8s.io/apimachinery/pkg/types" 31 | "k8s.io/client-go/tools/record" 32 | "sigs.k8s.io/controller-runtime/pkg/client" 33 | "sigs.k8s.io/controller-runtime/pkg/controller" 34 | "sigs.k8s.io/controller-runtime/pkg/handler" 35 | "sigs.k8s.io/controller-runtime/pkg/manager" 36 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 37 | "sigs.k8s.io/controller-runtime/pkg/source" 38 | 39 | wordpressv1alpha1 "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 40 | "github.com/bitpoke/wordpress-operator/pkg/controller/wordpress/internal/sync" 41 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 42 | ) 43 | 44 | const controllerName = "wordpress-controller" 45 | 46 | // Add creates a new Wordpress Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller 47 | // and Start it when the Manager is Started. 48 | func Add(mgr manager.Manager) error { 49 | return add(mgr, newReconciler(mgr)) 50 | } 51 | 52 | // newReconciler returns a new reconcile.Reconciler. 53 | func newReconciler(mgr manager.Manager) reconcile.Reconciler { 54 | return &ReconcileWordpress{Client: mgr.GetClient(), scheme: mgr.GetScheme(), recorder: mgr.GetEventRecorderFor(controllerName)} 55 | } 56 | 57 | // add adds a new Controller to mgr with r as the reconcile.Reconciler. 58 | func add(mgr manager.Manager, r reconcile.Reconciler) error { 59 | // Create a new controller 60 | c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r}) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // Watch for changes to Wordpress 66 | err = c.Watch(&source.Kind{Type: &wordpressv1alpha1.Wordpress{}}, &handler.EnqueueRequestForObject{}) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | subresources := []client.Object{ 72 | &appsv1.Deployment{}, 73 | &corev1.PersistentVolumeClaim{}, 74 | &corev1.Service{}, 75 | &corev1.Secret{}, 76 | &netv1.Ingress{}, 77 | } 78 | 79 | for _, subresource := range subresources { 80 | err = c.Watch(&source.Kind{Type: subresource}, &handler.EnqueueRequestForOwner{ 81 | IsController: true, 82 | OwnerType: &wordpressv1alpha1.Wordpress{}, 83 | }) 84 | if err != nil { 85 | return err 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | var _ reconcile.Reconciler = &ReconcileWordpress{} 93 | 94 | // ReconcileWordpress reconciles a Wordpress object. 95 | type ReconcileWordpress struct { 96 | client.Client 97 | scheme *runtime.Scheme 98 | recorder record.EventRecorder 99 | } 100 | 101 | // Automatically generate RBAC rules to allow the Controller to read and write Deployments 102 | // +kubebuilder:rbac:groups=core,resources=secrets;services;persistentvolumeclaims;events,verbs=get;list;watch;create;update;patch;delete 103 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 104 | // +kubebuilder:rbac:groups=batch,resources=jobs;cronjobs,verbs=get;list;watch;create;update;patch;delete 105 | // +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete 106 | // +kubebuilder:rbac:groups=wordpress.presslabs.org,resources=wordpresses;wordpresses/status,verbs=get;list;watch;create;update;patch;delete 107 | 108 | // Reconcile reads that state of the cluster for a Wordpress object and makes changes based on the state read 109 | // and what is in the Wordpress.Spec. 110 | func (r *ReconcileWordpress) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 111 | // Fetch the Wordpress instance 112 | wp := wordpress.New(&wordpressv1alpha1.Wordpress{}) 113 | 114 | err := r.Get(ctx, request.NamespacedName, wp.Unwrap()) 115 | if err != nil { 116 | return reconcile.Result{}, ignoreNotFound(err) 117 | } 118 | 119 | if updated, needsMigration := r.maybeMigrate(wp.Unwrap()); needsMigration { 120 | err = r.Update(ctx, updated) 121 | 122 | return reconcile.Result{}, err 123 | } 124 | 125 | r.scheme.Default(wp.Unwrap()) 126 | wp.SetDefaults() 127 | 128 | secretSyncer := sync.NewSecretSyncer(wp, r.Client) 129 | deploySyncer := sync.NewDeploymentSyncer(wp, secretSyncer.Object().(*corev1.Secret), r.Client) 130 | syncers := []syncer.Interface{ 131 | secretSyncer, 132 | deploySyncer, 133 | sync.NewServiceSyncer(wp, r.Client), 134 | sync.NewIngressSyncer(wp, r.Client), 135 | // sync.NewDBUpgradeJobSyncer(wp, r.Client), 136 | } 137 | 138 | if wp.Spec.CodeVolumeSpec != nil && wp.Spec.CodeVolumeSpec.PersistentVolumeClaim != nil { 139 | syncers = append(syncers, sync.NewCodePVCSyncer(wp, r.Client)) 140 | } 141 | 142 | if wp.Spec.MediaVolumeSpec != nil && wp.Spec.MediaVolumeSpec.PersistentVolumeClaim != nil { 143 | syncers = append(syncers, sync.NewMediaPVCSyncer(wp, r.Client)) 144 | } 145 | 146 | if err = r.sync(ctx, syncers); err != nil { 147 | return reconcile.Result{}, err 148 | } 149 | 150 | oldStatus := wp.Status.DeepCopy() 151 | wp.Status.Replicas = deploySyncer.Object().(*appsv1.Deployment).Status.Replicas 152 | 153 | if oldStatus.Replicas != wp.Status.Replicas { 154 | if errUp := r.Status().Update(ctx, wp.Unwrap()); errUp != nil { 155 | return reconcile.Result{}, errUp 156 | } 157 | } 158 | 159 | // remove old cron job if exists 160 | if err = r.cleanupCronJob(ctx, wp); err != nil { 161 | return reconcile.Result{}, err 162 | } 163 | 164 | return reconcile.Result{}, nil 165 | } 166 | 167 | func ignoreNotFound(err error) error { 168 | if errors.IsNotFound(err) { 169 | return nil 170 | } 171 | 172 | return err 173 | } 174 | 175 | func (r *ReconcileWordpress) sync(ctx context.Context, syncers []syncer.Interface) error { 176 | for _, s := range syncers { 177 | if err := syncer.Sync(ctx, s, r.recorder); err != nil { 178 | return err 179 | } 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func (r *ReconcileWordpress) maybeMigrate(wp *wordpressv1alpha1.Wordpress) (*wordpressv1alpha1.Wordpress, bool) { 186 | var needsMigration bool 187 | 188 | out := wp.DeepCopy() 189 | 190 | if len(out.Spec.Routes) == 0 { 191 | for i := range out.Spec.Domains { 192 | out.Spec.Routes = append(out.Spec.Routes, wordpressv1alpha1.RouteSpec{ 193 | Domain: string(out.Spec.Domains[i]), 194 | Path: "/", 195 | }) 196 | needsMigration = true 197 | } 198 | } 199 | 200 | out.Spec.Domains = nil 201 | 202 | return out, needsMigration 203 | } 204 | 205 | func (r *ReconcileWordpress) cleanupCronJob(ctx context.Context, wp *wordpress.Wordpress) error { 206 | cronKey := types.NamespacedName{ 207 | Name: wp.ComponentName(wordpress.WordpressCron), 208 | Namespace: wp.Namespace, 209 | } 210 | 211 | cronJob := &batchv1.CronJob{} 212 | 213 | if err := r.Get(ctx, cronKey, cronJob); err != nil { 214 | return ignoreNotFound(err) 215 | } 216 | 217 | if !isOwnedBy(cronJob.OwnerReferences, wp) { 218 | return nil 219 | } 220 | 221 | return r.Delete(ctx, cronJob) 222 | } 223 | 224 | func isOwnedBy(refs []metav1.OwnerReference, owner *wordpress.Wordpress) bool { 225 | for _, ref := range refs { 226 | if (ref.Kind == "Wordpress" || ref.Kind == "wordpress") && ref.Name == owner.Name { 227 | return true 228 | } 229 | } 230 | 231 | return false 232 | } 233 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/wordpress_controller_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "context" 21 | "path/filepath" 22 | "testing" 23 | 24 | . "github.com/onsi/ginkgo" 25 | . "github.com/onsi/gomega" 26 | 27 | logf "github.com/presslabs/controller-util/log" 28 | "k8s.io/client-go/kubernetes/scheme" 29 | "k8s.io/client-go/rest" 30 | "k8s.io/klog/v2" 31 | "k8s.io/klog/v2/klogr" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 34 | "sigs.k8s.io/controller-runtime/pkg/manager" 35 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 36 | 37 | "github.com/bitpoke/wordpress-operator/pkg/apis" 38 | ) 39 | 40 | var cfg *rest.Config 41 | var t *envtest.Environment 42 | 43 | func TestWordpressController(t *testing.T) { 44 | klog.SetOutput(GinkgoWriter) 45 | logf.SetLogger(klogr.New()) 46 | 47 | RegisterFailHandler(Fail) 48 | RunSpecsWithDefaultAndCustomReporters(t, "Wordpress Controller Suite", []Reporter{printer.NewlineReporter{}}) 49 | } 50 | 51 | var _ = BeforeSuite(func() { 52 | var err error 53 | 54 | t = &envtest.Environment{ 55 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, 56 | } 57 | 58 | Expect(apis.AddToScheme(scheme.Scheme)).To(Succeed()) 59 | 60 | cfg, err = t.Start() 61 | Expect(err).To(BeNil()) 62 | }) 63 | 64 | var _ = AfterSuite(func() { 65 | // nolint: errcheck 66 | t.Stop() 67 | }) 68 | 69 | // SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and 70 | // writes the request to requests after Reconcile is finished. 71 | func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) { 72 | requests := make(chan reconcile.Request) 73 | fn := reconcile.Func(func(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { 74 | result, err := inner.Reconcile(ctx, req) 75 | requests <- req 76 | return result, err 77 | }) 78 | return fn, requests 79 | } 80 | 81 | // StartTestManager adds recFn 82 | func StartTestManager(mgr manager.Manager) context.CancelFunc { 83 | ctx, stop := context.WithCancel(context.Background()) 84 | // defer cancel() 85 | 86 | go func() { 87 | defer GinkgoRecover() 88 | Expect(mgr.Start(ctx)).To(Succeed()) 89 | }() 90 | return stop 91 | } 92 | -------------------------------------------------------------------------------- /pkg/controller/wordpress/wordpress_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "math/rand" 23 | "time" 24 | 25 | . "github.com/onsi/ginkgo" 26 | . "github.com/onsi/ginkgo/extensions/table" 27 | . "github.com/onsi/gomega" 28 | 29 | appsv1 "k8s.io/api/apps/v1" 30 | corev1 "k8s.io/api/core/v1" 31 | netv1 "k8s.io/api/networking/v1" 32 | "k8s.io/apimachinery/pkg/api/resource" 33 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 | "k8s.io/apimachinery/pkg/types" 35 | "sigs.k8s.io/controller-runtime/pkg/client" 36 | "sigs.k8s.io/controller-runtime/pkg/manager" 37 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 38 | 39 | wordpressv1alpha1 "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 40 | ) 41 | 42 | const timeout = time.Second * 5 43 | 44 | var _ = Describe("Wordpress controller", func() { 45 | var ( 46 | // channel for incoming reconcile requests 47 | requests chan reconcile.Request 48 | // stop channel for controller manager 49 | stop context.CancelFunc 50 | // controller k8s client 51 | c client.Client 52 | ) 53 | 54 | BeforeEach(func() { 55 | var recFn reconcile.Reconciler 56 | 57 | mgr, err := manager.New(cfg, manager.Options{ 58 | MetricsBindAddress: "0", 59 | }) 60 | Expect(err).NotTo(HaveOccurred()) 61 | c = mgr.GetClient() 62 | 63 | recFn, requests = SetupTestReconcile(newReconciler(mgr)) 64 | Expect(add(mgr, recFn)).To(Succeed()) 65 | 66 | stop = StartTestManager(mgr) 67 | }) 68 | 69 | AfterEach(func() { 70 | stop() 71 | }) 72 | 73 | When("creating a new Wordpress resource", func() { 74 | var ( 75 | expectedRequest reconcile.Request 76 | wp *wordpressv1alpha1.Wordpress 77 | ) 78 | 79 | entries := []TableEntry{ 80 | Entry("reconciles the wp secret", "%s-wp", &corev1.Secret{}), 81 | Entry("reconciles the deployment", "%s", &appsv1.Deployment{}), 82 | Entry("reconciles the service", "%s", &corev1.Service{}), 83 | Entry("reconciles the ingress", "%s", &netv1.Ingress{}), 84 | Entry("reconciles the code pvc", "%s-code", &corev1.PersistentVolumeClaim{}), 85 | Entry("reconciles the media pvc", "%s-media", &corev1.PersistentVolumeClaim{}), 86 | } 87 | 88 | BeforeEach(func() { 89 | r := rand.Int31() 90 | name := fmt.Sprintf("wp-%d", r) 91 | routes := []wordpressv1alpha1.RouteSpec{ 92 | { 93 | Domain: fmt.Sprintf("%s.example.com", name), 94 | }, 95 | } 96 | expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: name, Namespace: "default"}} 97 | 98 | wp = &wordpressv1alpha1.Wordpress{ 99 | ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"}, 100 | Spec: wordpressv1alpha1.WordpressSpec{ 101 | Routes: routes, 102 | CodeVolumeSpec: &wordpressv1alpha1.CodeVolumeSpec{}, 103 | MediaVolumeSpec: &wordpressv1alpha1.MediaVolumeSpec{}, 104 | }, 105 | } 106 | 107 | wp.Spec.CodeVolumeSpec.PersistentVolumeClaim = &corev1.PersistentVolumeClaimSpec{ 108 | AccessModes: []corev1.PersistentVolumeAccessMode{ 109 | corev1.ReadWriteOnce, 110 | }, 111 | Resources: corev1.ResourceRequirements{ 112 | Requests: corev1.ResourceList{ 113 | corev1.ResourceStorage: resource.MustParse("1Gi"), 114 | }, 115 | }, 116 | } 117 | 118 | wp.Spec.MediaVolumeSpec.PersistentVolumeClaim = &corev1.PersistentVolumeClaimSpec{ 119 | AccessModes: []corev1.PersistentVolumeAccessMode{ 120 | corev1.ReadWriteOnce, 121 | }, 122 | Resources: corev1.ResourceRequirements{ 123 | Requests: corev1.ResourceList{ 124 | corev1.ResourceStorage: resource.MustParse("1Gi"), 125 | }, 126 | }, 127 | } 128 | 129 | Expect(c.Create(context.TODO(), wp)).To(Succeed()) 130 | 131 | // Wait for initial reconciliation 132 | Eventually(requests, timeout).Should(Receive(Equal(expectedRequest))) 133 | // Wait for a second reconciliation triggered by components being created 134 | Eventually(requests, timeout).Should(Receive(Equal(expectedRequest))) 135 | // We need to make sure that the controller does not create infinite loops 136 | Consistently(requests).ShouldNot(Receive(Equal(expectedRequest))) 137 | }) 138 | 139 | // nolint: errcheck 140 | AfterEach(func() { 141 | Expect(c.Delete(context.TODO(), wp)).To(Succeed()) 142 | 143 | // GC created objects 144 | for _, e := range entries { 145 | obj := e.Parameters[1].(client.Object) 146 | nameFmt := e.Parameters[0].(string) 147 | mo := obj.(metav1.Object) 148 | mo.SetName(fmt.Sprintf(nameFmt, wp.Name)) 149 | mo.SetNamespace(wp.Namespace) 150 | c.Delete(context.TODO(), obj) 151 | } 152 | }) 153 | 154 | DescribeTable("the reconciler", func(nameFmt string, obj client.Object) { 155 | key := types.NamespacedName{ 156 | Name: fmt.Sprintf(nameFmt, wp.Name), 157 | Namespace: wp.Namespace, 158 | } 159 | Eventually(func() error { return c.Get(context.TODO(), key, obj) }, timeout).Should(Succeed()) 160 | 161 | // Delete the resource and expect Reconcile to be called 162 | Expect(c.Delete(context.TODO(), obj)).To(Succeed()) 163 | Eventually(requests, timeout).Should(Receive(Equal(expectedRequest))) 164 | Eventually(func() error { return c.Get(context.TODO(), key, obj) }, timeout).Should(Succeed()) 165 | }, entries...) 166 | 167 | It("allows specifying deployment strategy", func() { 168 | key := types.NamespacedName{ 169 | Name: wp.Name, 170 | Namespace: wp.Namespace, 171 | } 172 | deploy := &appsv1.Deployment{} 173 | Eventually(func() error { return c.Get(context.TODO(), key, deploy) }, timeout).Should(Succeed()) 174 | Expect(deploy.Spec.Strategy.Type).To(Equal(appsv1.RollingUpdateDeploymentStrategyType)) 175 | 176 | wp.Spec.DeploymentStrategy = &appsv1.DeploymentStrategy{ 177 | Type: appsv1.RecreateDeploymentStrategyType, 178 | } 179 | Expect(c.Update(context.TODO(), wp)).To(Succeed()) 180 | Eventually(requests, timeout).Should(Receive(Equal(expectedRequest))) 181 | 182 | Expect(c.Get(context.TODO(), key, deploy)).To(Succeed()) 183 | Expect(deploy.Spec.Strategy.Type).To(Equal(appsv1.RecreateDeploymentStrategyType)) 184 | }) 185 | }) 186 | }) 187 | -------------------------------------------------------------------------------- /pkg/controller/wp-cron/wpcron_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wpcron 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "net/http" 24 | "net/url" 25 | "time" 26 | 27 | "github.com/go-logr/logr" 28 | corev1 "k8s.io/api/core/v1" 29 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/client-go/tools/record" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/controller" 35 | "sigs.k8s.io/controller-runtime/pkg/handler" 36 | logf "sigs.k8s.io/controller-runtime/pkg/log" 37 | "sigs.k8s.io/controller-runtime/pkg/manager" 38 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 39 | "sigs.k8s.io/controller-runtime/pkg/source" 40 | 41 | wordpressv1alpha1 "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 42 | "github.com/bitpoke/wordpress-operator/pkg/internal/wordpress" 43 | ) 44 | 45 | const ( 46 | controllerName = "wp-cron-controller" 47 | cronTriggerInterval = 30 * time.Second 48 | cronTriggerTimeout = 30 * time.Second 49 | ) 50 | 51 | var errHTTP = errors.New("HTTP error") 52 | 53 | // Add creates a new Wordpress Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller 54 | // and Start it when the Manager is Started. 55 | func Add(mgr manager.Manager) error { 56 | return add(mgr, newReconciler(mgr)) 57 | } 58 | 59 | // newReconciler returns a new reconcile.Reconciler. 60 | func newReconciler(mgr manager.Manager) reconcile.Reconciler { 61 | return &ReconcileWordpress{ 62 | Client: mgr.GetClient(), 63 | Log: logf.Log.WithName(controllerName).WithValues("controller", controllerName), 64 | scheme: mgr.GetScheme(), 65 | recorder: mgr.GetEventRecorderFor(controllerName), 66 | } 67 | } 68 | 69 | // add adds a new Controller to mgr with r as the reconcile.Reconciler. 70 | func add(mgr manager.Manager, r reconcile.Reconciler) error { 71 | // Create a new controller 72 | c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r, MaxConcurrentReconciles: 32}) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | // Watch for changes to Wordpress 78 | err = c.Watch(&source.Kind{Type: &wordpressv1alpha1.Wordpress{}}, &handler.EnqueueRequestForObject{}) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | return nil 84 | } 85 | 86 | var _ reconcile.Reconciler = &ReconcileWordpress{} 87 | 88 | // ReconcileWordpress reconciles a Wordpress object. 89 | type ReconcileWordpress struct { 90 | client.Client 91 | Log logr.Logger 92 | scheme *runtime.Scheme 93 | recorder record.EventRecorder 94 | } 95 | 96 | // Reconcile reads that state of the cluster for a Wordpress object and makes changes based on the state read 97 | // and what is in the Wordpress.Spec. 98 | // 99 | // Automatically generate RBAC rules to allow the Controller to read and write Deployments. 100 | // +kubebuilder:rbac:groups=wordpress.presslabs.org,resources=wordpresses;wordpresses/status,verbs=get;list;watch;create;update;patch;delete 101 | func (r *ReconcileWordpress) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 102 | // Fetch the Wordpress instance 103 | wp := wordpress.New(&wordpressv1alpha1.Wordpress{}) 104 | 105 | err := r.Get(ctx, request.NamespacedName, wp.Unwrap()) 106 | if err != nil { 107 | return reconcile.Result{}, ignoreNotFound(err) 108 | } 109 | 110 | r.scheme.Default(wp.Unwrap()) 111 | wp.SetDefaults() 112 | 113 | log := r.Log.WithValues("key", request.NamespacedName) 114 | 115 | requeue := reconcile.Result{ 116 | Requeue: true, 117 | RequeueAfter: cronTriggerInterval, 118 | } 119 | 120 | svcHostname := fmt.Sprintf("%s.%s.svc", wp.Name, wp.Namespace) 121 | u := wp.SiteURL("wp-cron.php") + "?doing_wp_cron" 122 | 123 | _u, err := url.Parse(u) 124 | if err != nil { 125 | log.Error(err, "error parsing url", "url", u) 126 | 127 | return requeue, nil 128 | } 129 | 130 | _u.Scheme = "http" 131 | _u.Host = svcHostname 132 | 133 | ctxWithTimeout, cancel := context.WithTimeout(context.Background(), cronTriggerTimeout) 134 | defer cancel() 135 | 136 | err = r.pingURL(ctxWithTimeout, _u.String(), wp.MainDomain()) 137 | if err != nil { 138 | log.Error(err, "error while triggering wp-cron") 139 | } 140 | 141 | err = r.updateWPCronStatus(ctx, wp, err) 142 | if err != nil { 143 | log.Error(err, "error updating wordpress wp-cron status") 144 | } 145 | 146 | return requeue, nil 147 | } 148 | 149 | func maybeUpdateWPCronCondition(cond wordpressv1alpha1.WordpressCondition, err error) (wordpressv1alpha1.WordpressCondition, bool) { 150 | needsUpdate := false 151 | 152 | // We've got an error, but WPCronTriggering is true/unknown/empty 153 | if err != nil && (cond.Status == corev1.ConditionTrue || cond.Status == corev1.ConditionUnknown || cond.Status == "") { 154 | needsUpdate = true 155 | } 156 | 157 | // The error message has changed 158 | if err != nil && cond.Message != err.Error() { 159 | needsUpdate = true 160 | } 161 | 162 | // WPCronTriggering is resuming normal operation 163 | if err == nil && (cond.Status == corev1.ConditionFalse || cond.Status == corev1.ConditionUnknown || cond.Status == "") { 164 | needsUpdate = true 165 | } 166 | 167 | if needsUpdate { 168 | now := metav1.Now() 169 | cond.LastUpdateTime = now 170 | cond.LastTransitionTime = now 171 | 172 | if err == nil { 173 | cond.Status = corev1.ConditionTrue 174 | cond.Reason = wordpressv1alpha1.WPCronTriggeringReason 175 | cond.Message = "wp-cron is triggering" 176 | } else { 177 | cond.Status = corev1.ConditionFalse 178 | cond.Reason = wordpressv1alpha1.WPCronTriggerErrorReason 179 | cond.Message = err.Error() 180 | } 181 | } 182 | 183 | return cond, needsUpdate 184 | } 185 | 186 | func (r *ReconcileWordpress) updateWPCronStatus(ctx context.Context, wp *wordpress.Wordpress, e error) error { 187 | var needsUpdate bool 188 | 189 | idx := -1 190 | 191 | for i := range wp.Status.Conditions { 192 | if wp.Status.Conditions[i].Type == wordpressv1alpha1.WPCronTriggeringCondition { 193 | idx = i 194 | } 195 | } 196 | 197 | if idx == -1 { 198 | wp.Status.Conditions = append(wp.Status.Conditions, wordpressv1alpha1.WordpressCondition{ 199 | Type: wordpressv1alpha1.WPCronTriggeringCondition, 200 | }) 201 | idx = len(wp.Status.Conditions) - 1 202 | } 203 | 204 | wp.Status.Conditions[idx], needsUpdate = maybeUpdateWPCronCondition(wp.Status.Conditions[idx], e) 205 | 206 | if needsUpdate { 207 | err := r.Client.Status().Update(ctx, wp.Unwrap()) 208 | if err != nil { 209 | return err 210 | } 211 | } 212 | 213 | return nil 214 | } 215 | 216 | func (r *ReconcileWordpress) pingURL(ctx context.Context, url, hostOverride string) error { 217 | client := &http.Client{} 218 | 219 | req, err := http.NewRequestWithContext(ctx, "GET", url, nil) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | req.Host = hostOverride 225 | 226 | resp, err := client.Do(req) 227 | if err != nil { 228 | return err 229 | } 230 | 231 | defer func() { 232 | err := resp.Body.Close() 233 | if err != nil { 234 | r.Log.Error(err, "unexpected error while closing HTTP response body") 235 | } 236 | }() 237 | 238 | if resp.StatusCode != 200 { 239 | return fmt.Errorf("%w: %v, %v", errHTTP, resp.StatusCode, http.StatusText(resp.StatusCode)) 240 | } 241 | 242 | return nil 243 | } 244 | 245 | func ignoreNotFound(err error) error { 246 | if k8serrors.IsNotFound(err) { 247 | return nil 248 | } 249 | 250 | return err 251 | } 252 | -------------------------------------------------------------------------------- /pkg/internal/wordpress/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "path" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | "k8s.io/apimachinery/pkg/api/resource" 24 | 25 | "github.com/bitpoke/wordpress-operator/pkg/cmd/options" 26 | ) 27 | 28 | const ( 29 | codeSrcMountPath = "/var/run/presslabs.org/code/src" 30 | 31 | defaultCodeMountPath = "/app/web/wp-content" 32 | defaultRepoCodeSubPath = "wp-content" 33 | 34 | configMountPath = "/app/config" 35 | defaultRepoConfigSubPath = "config" 36 | 37 | mediaSubPath = "uploads" 38 | defaultMediaMountPath = defaultCodeMountPath + "/" + mediaSubPath 39 | 40 | knativeVarLogVolume = "knative-var-log" 41 | knativeVarLogMountPath = "/var/log" 42 | 43 | knativeInternalVolume = "knative-internal" 44 | knativeInternalMountPath = "/var/knative-internal" 45 | ) 46 | 47 | var varLogSizeLimit = resource.MustParse("1Gi") 48 | 49 | // SetDefaults sets Wordpress field defaults. 50 | func (wp *Wordpress) SetDefaults() { 51 | if len(wp.Spec.Image) == 0 { 52 | wp.Spec.Image = options.WordpressRuntimeImage 53 | } 54 | 55 | if len(wp.Spec.ImagePullPolicy) == 0 { 56 | wp.Spec.ImagePullPolicy = corev1.PullAlways 57 | } 58 | 59 | if wp.Spec.CodeVolumeSpec != nil && wp.Spec.CodeVolumeSpec.MountPath == "" { 60 | wp.Spec.CodeVolumeSpec.MountPath = defaultCodeMountPath 61 | } 62 | 63 | if wp.Spec.CodeVolumeSpec != nil && wp.Spec.CodeVolumeSpec.ContentSubPath == "" { 64 | wp.Spec.CodeVolumeSpec.ContentSubPath = defaultRepoCodeSubPath 65 | } 66 | 67 | if wp.Spec.CodeVolumeSpec != nil && wp.Spec.CodeVolumeSpec.ConfigSubPath == "" { 68 | wp.Spec.CodeVolumeSpec.ConfigSubPath = defaultRepoConfigSubPath 69 | } 70 | 71 | if wp.Spec.MediaVolumeSpec != nil && wp.Spec.MediaVolumeSpec.MountPath == "" { 72 | if wp.Spec.CodeVolumeSpec != nil { 73 | wp.Spec.MediaVolumeSpec.MountPath = path.Join(wp.Spec.CodeVolumeSpec.MountPath, mediaSubPath) 74 | } else { 75 | wp.Spec.MediaVolumeSpec.MountPath = defaultMediaMountPath 76 | } 77 | } 78 | 79 | if wp.Spec.WordpressPathPrefix == "" { 80 | wp.Spec.WordpressPathPrefix = "/wp" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/internal/wordpress/pod_template_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | logf "github.com/presslabs/controller-util/log" 26 | "k8s.io/klog/v2" 27 | "k8s.io/klog/v2/klogr" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | ) 30 | 31 | func TestPodTemplate(t *testing.T) { 32 | klog.SetOutput(GinkgoWriter) 33 | logf.SetLogger(klogr.New()) 34 | 35 | RegisterFailHandler(Fail) 36 | RunSpecsWithDefaultAndCustomReporters(t, "Pod Template Suite", []Reporter{printer.NewlineReporter{}}) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/internal/wordpress/pod_template_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "fmt" 21 | "math/rand" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/ginkgo/extensions/table" 25 | . "github.com/onsi/gomega" 26 | 27 | corev1 "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/apimachinery/pkg/util/intstr" 30 | 31 | wordpressv1alpha1 "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 32 | "github.com/bitpoke/wordpress-operator/pkg/cmd/options" 33 | ) 34 | 35 | var _ = Describe("Web pod spec", func() { 36 | var ( 37 | wp *Wordpress 38 | ) 39 | 40 | BeforeEach(func() { 41 | name := fmt.Sprintf("cluster-%d", rand.Int31()) 42 | ns := "default" 43 | 44 | wp = New(&wordpressv1alpha1.Wordpress{ 45 | ObjectMeta: metav1.ObjectMeta{ 46 | Name: name, 47 | Namespace: ns, 48 | Labels: map[string]string{"app.kubernetes.io/part-of": "test"}, 49 | }, 50 | Spec: wordpressv1alpha1.WordpressSpec{ 51 | Routes: []wordpressv1alpha1.RouteSpec{ 52 | { 53 | Domain: "test.com", 54 | }, 55 | }, 56 | }, 57 | }) 58 | wp.SetDefaults() 59 | }) 60 | 61 | DescribeTable("Shouldn't generate any new init containers if git is not configured", 62 | func(f func() func() corev1.PodTemplateSpec) { 63 | // we need this hack to allow wp to be initialized 64 | podSpec := f() 65 | Expect(podSpec().Spec.InitContainers).To(HaveLen(0)) 66 | }, 67 | Entry("for web pod", func() func() corev1.PodTemplateSpec { return wp.WebPodTemplateSpec }), 68 | Entry("for job pod", func() func() corev1.PodTemplateSpec { 69 | return func() corev1.PodTemplateSpec { return wp.JobPodTemplateSpec() } 70 | }), 71 | ) 72 | 73 | DescribeTable("Should generate a git container when git is configured", 74 | func(f func() (func() corev1.PodTemplateSpec, *Wordpress)) { 75 | // we need this hack to allow wp to be initialized with our custom values 76 | podSpec, w := f() 77 | 78 | w.Spec.CodeVolumeSpec = &wordpressv1alpha1.CodeVolumeSpec{ 79 | GitDir: &wordpressv1alpha1.GitVolumeSource{}, 80 | } 81 | containers := podSpec().Spec.InitContainers 82 | 83 | Expect(containers).To(HaveLen(2)) 84 | Expect(containers[1].Name).To(Equal("git")) 85 | Expect(containers[1].Image).To(Equal(options.GitCloneImage)) 86 | }, 87 | Entry("for web pod", func() (func() corev1.PodTemplateSpec, *Wordpress) { 88 | return wp.WebPodTemplateSpec, wp 89 | }), 90 | Entry("for job pod", func() (func() corev1.PodTemplateSpec, *Wordpress) { 91 | return func() corev1.PodTemplateSpec { return wp.JobPodTemplateSpec("test") }, wp 92 | }), 93 | ) 94 | 95 | DescribeTable("Should generate an init contaniner, used to install Wordpress", 96 | func(f func() (func() corev1.PodTemplateSpec, *Wordpress)) { 97 | // we need this hack to allow wp to be initialized with our custom values 98 | podSpec, w := f() 99 | 100 | w.Spec.WordpressBootstrapSpec = &wordpressv1alpha1.WordpressBootstrapSpec{} 101 | containers := podSpec().Spec.InitContainers 102 | 103 | Expect(containers).To(HaveLen(1)) 104 | Expect(containers[0].Name).To(Equal("install-wp")) 105 | Expect(containers[0].Image).To(Equal(w.Spec.Image)) 106 | }, 107 | Entry("for web pod", func() (func() corev1.PodTemplateSpec, *Wordpress) { 108 | return wp.WebPodTemplateSpec, wp 109 | }), 110 | Entry("for job pod", func() (func() corev1.PodTemplateSpec, *Wordpress) { 111 | return func() corev1.PodTemplateSpec { return wp.JobPodTemplateSpec("test") }, wp 112 | }), 113 | ) 114 | 115 | It("should generate a valid STACK_ROUTES", func() { 116 | spec := wp.WebPodTemplateSpec() 117 | e, found := lookupEnvVar("STACK_ROUTES", spec.Spec.Containers[0].Env) 118 | Expect(found).To(BeTrue()) 119 | Expect(e.Value).To(Equal("test.com")) 120 | }) 121 | 122 | It("should generate a valid STACK_ROUTES when routes list is empty", func() { 123 | wp.Spec.Routes = []wordpressv1alpha1.RouteSpec{} 124 | spec := wp.WebPodTemplateSpec() 125 | e, found := lookupEnvVar("STACK_ROUTES", spec.Spec.Containers[0].Env) 126 | Expect(found).To(BeTrue()) 127 | Expect(e.Value).To(Equal(fmt.Sprintf("%s.default.svc", wp.Name))) 128 | }) 129 | 130 | It("should generate a valid STACK_ROUTES keeping routes order", func() { 131 | wp.Spec.Routes = []wordpressv1alpha1.RouteSpec{ 132 | { 133 | Domain: "test.com", 134 | }, 135 | { 136 | Domain: "test.org", 137 | }, 138 | } 139 | spec := wp.WebPodTemplateSpec() 140 | e, found := lookupEnvVar("STACK_ROUTES", spec.Spec.Containers[0].Env) 141 | Expect(found).To(BeTrue()) 142 | Expect(e.Value).To(Equal("test.com,test.org")) 143 | }) 144 | 145 | It("should generate a valid STACK_ROUTES with paths w/o trailing slash", func() { 146 | wp.Spec.Routes = []wordpressv1alpha1.RouteSpec{ 147 | { 148 | Domain: "test.com", 149 | Path: "/", 150 | }, 151 | { 152 | Domain: "test.org", 153 | Path: "/abc", 154 | }, 155 | { 156 | Domain: "test.net", 157 | Path: "/xyz/", 158 | }, 159 | } 160 | spec := wp.WebPodTemplateSpec() 161 | e, found := lookupEnvVar("STACK_ROUTES", spec.Spec.Containers[0].Env) 162 | Expect(found).To(BeTrue()) 163 | Expect(e.Value).To(Equal("test.com,test.org/abc,test.net/xyz")) 164 | }) 165 | 166 | It("should give me the default domain", func() { 167 | Expect(wp.MainDomain()).To(Equal("test.com")) 168 | 169 | wp.Spec.Routes = []wordpressv1alpha1.RouteSpec{} 170 | Expect(wp.MainDomain()).To(Equal(fmt.Sprintf("%s.default.svc", wp.Name))) 171 | }) 172 | 173 | It("should give me right home URL, without trailing slash", func() { 174 | // WP_HOME and WP_SITEURL should not contain a trailing slash, 175 | // as per: https://wordpress.org/support/article/changing-the-site-url/ 176 | 177 | Expect(wp.HomeURL()).To(Equal("http://test.com")) 178 | }) 179 | 180 | It("should give me right home URL for subpath", func() { 181 | Expect(wp.HomeURL("foo")).To(Equal("http://test.com/foo")) 182 | Expect(wp.HomeURL("foo", "bar")).To(Equal("http://test.com/foo/bar")) 183 | Expect(wp.HomeURL("foo/bar")).To(Equal("http://test.com/foo/bar")) 184 | 185 | wp.Spec.Routes[0].Path = "/subpath" 186 | Expect(wp.HomeURL()).To(Equal("http://test.com/subpath")) 187 | }) 188 | 189 | It("should give me the default readiness probe", func() { 190 | spec := wp.WebPodTemplateSpec() 191 | 192 | Expect(*spec.Spec.Containers[0].ReadinessProbe).To(Equal(corev1.Probe{ 193 | Handler: corev1.Handler{ 194 | HTTPGet: &corev1.HTTPGetAction{ 195 | Path: "/", 196 | Port: intstr.FromInt(InternalHTTPPort), 197 | HTTPHeaders: []corev1.HTTPHeader{ 198 | { 199 | Name: "Host", 200 | Value: wp.MainDomain(), 201 | }, 202 | }, 203 | }, 204 | }, 205 | FailureThreshold: 3, 206 | InitialDelaySeconds: 10, 207 | PeriodSeconds: 5, 208 | SuccessThreshold: 1, 209 | TimeoutSeconds: 30, 210 | })) 211 | }) 212 | 213 | It("should give me the custom readiness probe specified in the Wordpress resource", func() { 214 | probe := corev1.Probe{ 215 | Handler: corev1.Handler{ 216 | HTTPGet: &corev1.HTTPGetAction{ 217 | Path: "/custom", 218 | Port: intstr.FromInt(InternalHTTPPort), 219 | }, 220 | }, 221 | } 222 | 223 | wp.Spec.ReadinessProbe = &probe 224 | spec := wp.WebPodTemplateSpec() 225 | 226 | Expect(*spec.Spec.Containers[0].ReadinessProbe).To(Equal(probe)) 227 | }) 228 | 229 | It("should give me the default liveness probe", func() { 230 | spec := wp.WebPodTemplateSpec() 231 | 232 | Expect(*spec.Spec.Containers[0].LivenessProbe).To(Equal(corev1.Probe{ 233 | Handler: corev1.Handler{ 234 | HTTPGet: &corev1.HTTPGetAction{ 235 | Path: "/-/php-ping", 236 | Port: intstr.FromInt(InternalHTTPPort), 237 | }, 238 | }, 239 | FailureThreshold: 3, 240 | InitialDelaySeconds: 10, 241 | PeriodSeconds: 5, 242 | SuccessThreshold: 1, 243 | TimeoutSeconds: 30, 244 | })) 245 | }) 246 | 247 | It("should give me the custom liveness probe specified in the Wordpress resource", func() { 248 | probe := corev1.Probe{ 249 | Handler: corev1.Handler{ 250 | HTTPGet: &corev1.HTTPGetAction{ 251 | Path: "/custom", 252 | Port: intstr.FromInt(InternalHTTPPort), 253 | }, 254 | }, 255 | } 256 | 257 | wp.Spec.LivenessProbe = &probe 258 | spec := wp.WebPodTemplateSpec() 259 | 260 | Expect(*spec.Spec.Containers[0].LivenessProbe).To(Equal(probe)) 261 | }) 262 | 263 | }) 264 | 265 | // nolint: unparam 266 | func lookupEnvVar(name string, env []corev1.EnvVar) (corev1.EnvVar, bool) { 267 | for _, e := range env { 268 | if e.Name == name { 269 | return e, true 270 | } 271 | } 272 | return corev1.EnvVar{}, false 273 | } 274 | -------------------------------------------------------------------------------- /pkg/internal/wordpress/wordpress.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package wordpress 18 | 19 | import ( 20 | "fmt" 21 | "path" 22 | 23 | "github.com/cooleo/slugify" 24 | "k8s.io/apimachinery/pkg/labels" 25 | 26 | wordpressv1alpha1 "github.com/bitpoke/wordpress-operator/pkg/apis/wordpress/v1alpha1" 27 | ) 28 | 29 | // Wordpress embeds wordpressv1alpha1.Wordpress and adds utility functions. 30 | type Wordpress struct { 31 | *wordpressv1alpha1.Wordpress 32 | } 33 | 34 | type component struct { 35 | name string // eg. web, database, cache 36 | objNameFmt string 37 | objName string 38 | } 39 | 40 | var ( 41 | // WordpressSecret component. 42 | WordpressSecret = component{name: "web", objNameFmt: "%s-wp"} 43 | // WordpressDeployment component. 44 | WordpressDeployment = component{name: "web", objNameFmt: "%s"} 45 | // WordpressCron component. 46 | WordpressCron = component{name: "cron", objNameFmt: "%s-wp-cron"} 47 | // WordpressDBUpgrade component. 48 | WordpressDBUpgrade = component{name: "upgrade", objNameFmt: "%s-upgrade"} 49 | // WordpressService component. 50 | WordpressService = component{name: "web", objNameFmt: "%s"} 51 | // WordpressIngress component. 52 | WordpressIngress = component{name: "web", objNameFmt: "%s"} 53 | // WordpressCodePVC component. 54 | WordpressCodePVC = component{name: "code", objNameFmt: "%s-code"} 55 | // WordpressMediaPVC component. 56 | WordpressMediaPVC = component{name: "media", objNameFmt: "%s-media"} 57 | ) 58 | 59 | // New wraps a wordpressv1alpha1.Wordpress into a Wordpress object. 60 | func New(obj *wordpressv1alpha1.Wordpress) *Wordpress { 61 | return &Wordpress{obj} 62 | } 63 | 64 | // Unwrap returns the wrapped wordpressv1alpha1.Wordpress object. 65 | func (wp *Wordpress) Unwrap() *wordpressv1alpha1.Wordpress { 66 | return wp.Wordpress 67 | } 68 | 69 | // Labels returns default label set for wordpressv1alpha1.Wordpress. 70 | func (wp *Wordpress) Labels() labels.Set { 71 | partOf := "wordpress" 72 | if wp.ObjectMeta.Labels != nil && len(wp.ObjectMeta.Labels["app.kubernetes.io/part-of"]) > 0 { 73 | partOf = wp.ObjectMeta.Labels["app.kubernetes.io/part-of"] 74 | } 75 | 76 | labels := labels.Set{ 77 | "app.kubernetes.io/name": "wordpress", 78 | "app.kubernetes.io/part-of": partOf, 79 | "app.kubernetes.io/instance": wp.ObjectMeta.Name, 80 | } 81 | 82 | return labels 83 | } 84 | 85 | // ComponentLabels returns labels for a label set for a wordpressv1alpha1.Wordpress component. 86 | func (wp *Wordpress) ComponentLabels(component component) labels.Set { 87 | l := wp.Labels() 88 | l["app.kubernetes.io/component"] = component.name 89 | 90 | if component == WordpressDBUpgrade { 91 | l["wordpress.presslabs.org/upgrade-for"] = wp.ImageVersion() 92 | } 93 | 94 | return l 95 | } 96 | 97 | // ComponentName returns the object name for a component. 98 | func (wp *Wordpress) ComponentName(component component) string { 99 | name := component.objName 100 | if len(component.objNameFmt) > 0 { 101 | name = fmt.Sprintf(component.objNameFmt, wp.ObjectMeta.Name) 102 | } 103 | 104 | if component == WordpressDBUpgrade { 105 | name = fmt.Sprintf("%s-for-%s", name, wp.ImageVersion()) 106 | } 107 | 108 | return name 109 | } 110 | 111 | // ImageVersion returns the version from the image in a format suitable 112 | // for kubernetes object names and labels. 113 | func (wp *Wordpress) ImageVersion() string { 114 | return slugify.Slugify(wp.Spec.Image) 115 | } 116 | 117 | // WebPodLabels return labels to apply to web pods. 118 | func (wp *Wordpress) WebPodLabels() labels.Set { 119 | l := wp.Labels() 120 | l["app.kubernetes.io/component"] = "web" 121 | 122 | return l 123 | } 124 | 125 | // JobPodLabels return labels to apply to cli job pods. 126 | func (wp *Wordpress) JobPodLabels() labels.Set { 127 | l := wp.Labels() 128 | l["app.kubernetes.io/component"] = "wp-cli" 129 | 130 | return l 131 | } 132 | 133 | // MainDomain returns the site main domain or a local domain ..svc.cluster.local. 134 | func (wp *Wordpress) MainDomain() string { 135 | if len(wp.Spec.Routes) > 0 { 136 | return wp.Spec.Routes[0].Domain 137 | } 138 | 139 | // return the local cluster name that points to wordpress service 140 | return fmt.Sprintf("%s.%s.svc", wp.ComponentName(WordpressService), wp.Namespace) 141 | } 142 | 143 | // HomeURL returns the WP_HOMEURL (e.g. http://example.com/) 144 | func (wp *Wordpress) HomeURL(subPaths ...string) string { 145 | scheme := "http" 146 | if len(wp.Spec.TLSSecretRef) > 0 { 147 | scheme = "https" 148 | } 149 | 150 | paths := []string{"/"} 151 | if len(wp.Spec.Routes) > 0 { 152 | paths = append(paths, wp.Spec.Routes[0].Path) 153 | } 154 | 155 | paths = append(paths, subPaths...) 156 | 157 | p := path.Join(paths...) 158 | if p == "/" { 159 | p = "" 160 | } 161 | 162 | return fmt.Sprintf("%s://%s%s", scheme, wp.MainDomain(), p) 163 | } 164 | 165 | // SiteURL returns the WP_SITEURL (e.g. http://example.com/wp) 166 | func (wp *Wordpress) SiteURL(subPaths ...string) string { 167 | p := []string{wp.Spec.WordpressPathPrefix} 168 | p = append(p, subPaths...) 169 | 170 | return wp.HomeURL(p...) 171 | } 172 | -------------------------------------------------------------------------------- /pkg/webhook/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Pressinfra SRL. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package webhook is an empty package to conform with kubebuilder-v1 folder structure. 18 | package webhook 19 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v1beta11 2 | kind: Config 3 | build: 4 | artifacts: 5 | - image: quay.io/presslabs/wordpress-operator 6 | docker: 7 | dockerfile: hack/Dockerfile.skaffold 8 | deploy: 9 | helm: 10 | releases: 11 | - name: wp-operator 12 | chartPath: deploy/charts/wordpress-operator 13 | setValues: 14 | imagePullPolicy: Always 15 | valuesFiles: 16 | - hack/skaffold-values.yaml 17 | values: 18 | image: quay.io/presslabs/wordpress-operator 19 | 20 | --------------------------------------------------------------------------------