├── .circleci └── config.yml ├── .codeclimate.yml ├── .dockerignore ├── .github ├── CODEOWNERS └── workflows │ ├── fast-forward-branch.yaml │ ├── lint.yml │ └── tagRelease.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── PROJECT ├── README.md ├── apis ├── apps │ ├── common.go │ └── v1alpha1 │ │ ├── apimanager_types.go │ │ ├── apimanager_types_test.go │ │ ├── apimanagerbackup_types.go │ │ ├── apimanagerrestore_types.go │ │ ├── groupversion_info.go │ │ └── zz_generated.deepcopy.go └── capabilities │ ├── v1alpha1 │ ├── groupversion_info.go │ ├── tenant_types.go │ └── zz_generated.deepcopy.go │ └── v1beta1 │ ├── activedoc_types.go │ ├── application_types.go │ ├── applicationauth_types.go │ ├── backend_types.go │ ├── custompolicydefinition_types.go │ ├── developeraccount_types.go │ ├── developeruser_types.go │ ├── groupversion_info.go │ ├── openapi_types.go │ ├── product_types.go │ ├── product_types_test.go │ ├── proxyconfigpromote_types.go │ └── zz_generated.deepcopy.go ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── 3scale-operator.clusterserviceversion.yaml │ ├── apps.3scale.net_apimanagerbackups.yaml │ ├── apps.3scale.net_apimanagerrestores.yaml │ ├── apps.3scale.net_apimanagers.yaml │ ├── capabilities.3scale.net_activedocs.yaml │ ├── capabilities.3scale.net_applicationauths.yaml │ ├── capabilities.3scale.net_applications.yaml │ ├── capabilities.3scale.net_backends.yaml │ ├── capabilities.3scale.net_custompolicydefinitions.yaml │ ├── capabilities.3scale.net_developeraccounts.yaml │ ├── capabilities.3scale.net_developerusers.yaml │ ├── capabilities.3scale.net_openapis.yaml │ ├── capabilities.3scale.net_products.yaml │ ├── capabilities.3scale.net_proxyconfigpromotes.yaml │ ├── capabilities.3scale.net_tenants.yaml │ ├── threescale-operator-controller-manager-metrics-monitor_monitoring.coreos.com_v1_servicemonitor.yaml │ └── threescale-operator-controller-manager-metrics-service_v1_service.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── apps.3scale.net_apimanagerbackups.yaml │ │ ├── apps.3scale.net_apimanagerrestores.yaml │ │ ├── apps.3scale.net_apimanagers.yaml │ │ ├── capabilities.3scale.net_activedocs.yaml │ │ ├── capabilities.3scale.net_applicationauths.yaml │ │ ├── capabilities.3scale.net_applications.yaml │ │ ├── capabilities.3scale.net_backends.yaml │ │ ├── capabilities.3scale.net_custompolicydefinitions.yaml │ │ ├── capabilities.3scale.net_developeraccounts.yaml │ │ ├── capabilities.3scale.net_developerusers.yaml │ │ ├── capabilities.3scale.net_openapis.yaml │ │ ├── capabilities.3scale.net_products.yaml │ │ ├── capabilities.3scale.net_proxyconfigpromotes.yaml │ │ └── capabilities.3scale.net_tenants.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── activedocopenapiref_openapi_validation_in_activedocs.yaml │ │ ├── cainjection_in_activedocs.yaml │ │ ├── cainjection_in_apimanagerbackups.yaml │ │ ├── cainjection_in_apimanagerrestores.yaml │ │ ├── cainjection_in_apimanagers.yaml │ │ ├── cainjection_in_applicationauths.yaml │ │ ├── cainjection_in_applications.yaml │ │ ├── cainjection_in_backends.yaml │ │ ├── cainjection_in_custompolicydefinitions.yaml │ │ ├── cainjection_in_developeraccounts.yaml │ │ ├── cainjection_in_developerusers.yaml │ │ ├── cainjection_in_openapis.yaml │ │ ├── cainjection_in_products.yaml │ │ ├── cainjection_in_proxyconfigpromotes.yaml │ │ ├── cainjection_in_tenants.yaml │ │ ├── openapiref_openapi_validation_in_openapis.yaml │ │ ├── product_authentication_openapi_validation_in_products.yaml │ │ ├── product_deployment_openapi_validation_in_products.yaml │ │ ├── webhook_in_activedocs.yaml │ │ ├── webhook_in_apimanagerbackups.yaml │ │ ├── webhook_in_apimanagerrestores.yaml │ │ ├── webhook_in_apimanagers.yaml │ │ ├── webhook_in_applicationauths.yaml │ │ ├── webhook_in_applications.yaml │ │ ├── webhook_in_backends.yaml │ │ ├── webhook_in_custompolicydefinitions.yaml │ │ ├── webhook_in_developeraccounts.yaml │ │ ├── webhook_in_developerusers.yaml │ │ ├── webhook_in_openapis.yaml │ │ ├── webhook_in_products.yaml │ │ ├── webhook_in_proxyconfigpromotes.yaml │ │ └── webhook_in_tenants.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── dev-databases │ ├── backend-redis │ │ ├── deployment.yaml │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ ├── system-mysql │ │ ├── deployment.yaml │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ ├── system-postgresql │ │ ├── deployment.yaml │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ └── system-redis │ │ ├── deployment.yaml │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── secret.yaml │ │ └── service.yaml ├── jaeger │ ├── deployment.yaml │ ├── kustomization.yaml │ └── service.yaml ├── manager │ ├── kustomization.yaml │ ├── manager.yaml │ ├── metrics_service.yaml │ └── service_account.yaml ├── manifests │ ├── bases │ │ └── 3scale-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── activedoc_editor_role.yaml │ ├── activedoc_viewer_role.yaml │ ├── apimanager_editor_role.yaml │ ├── apimanager_viewer_role.yaml │ ├── apimanagerbackup_editor_role.yaml │ ├── apimanagerbackup_viewer_role.yaml │ ├── apimanagerrestore_editor_role.yaml │ ├── apimanagerrestore_viewer_role.yaml │ ├── application_editor_role.yaml │ ├── application_viewer_role.yaml │ ├── applicationauth_editor_role.yaml │ ├── applicationauth_viewer_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── backend_editor_role.yaml │ ├── backend_viewer_role.yaml │ ├── clusterrole_binding.yaml │ ├── custompolicydefinition_editor_role.yaml │ ├── custompolicydefinition_viewer_role.yaml │ ├── developeraccount_editor_role.yaml │ ├── developeraccount_viewer_role.yaml │ ├── developeruser_editor_role.yaml │ ├── developeruser_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── openapi_editor_role.yaml │ ├── openapi_viewer_role.yaml │ ├── product_editor_role.yaml │ ├── product_viewer_role.yaml │ ├── proxyconfigpromote_editor_role.yaml │ ├── proxyconfigpromote_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── tenant_editor_role.yaml │ └── tenant_viewer_role.yaml ├── requirements │ └── operator-requirements.yaml ├── samples │ ├── apps_v1alpha1_apimanager_simple.yaml │ ├── apps_v1alpha1_apimanagerbackup.yaml │ ├── apps_v1alpha1_apimanagerrestore.yaml │ ├── capabilities_v1alpha1_tenant.yaml │ ├── capabilities_v1beta1_activedoc_url.yaml │ ├── capabilities_v1beta1_application.yaml │ ├── capabilities_v1beta1_applicationauth.yaml │ ├── capabilities_v1beta1_backend.yaml │ ├── capabilities_v1beta1_custompolicydefinition.yaml │ ├── capabilities_v1beta1_developeraccount.yaml │ ├── capabilities_v1beta1_developeruser_admin.yaml │ ├── capabilities_v1beta1_openapi_url.yaml │ ├── capabilities_v1beta1_product.yaml │ ├── capabilities_v1beta1_proxyconfigpromote.yaml │ └── kustomization.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── controllers ├── apps │ ├── apimanager_controller.go │ ├── apimanager_controller_test.go │ ├── apimanager_status_reconciler.go │ ├── apimanager_suite_test.go │ ├── apimanagerbackup_controller.go │ ├── apimanagerbackup_logic_reconciler.go │ ├── apimanagerrestore_controller.go │ ├── apimanagerrestore_logic_reconciler.go │ ├── config_map_requirements_to_apimanager_event_mapper.go │ ├── secret_to_apimanager_event_mapper.go │ └── webconsole_controller.go ├── capabilities │ ├── activedoc_controller.go │ ├── activedoc_status_reconciler.go │ ├── activedoc_threescale_reconciler.go │ ├── application_controller.go │ ├── application_controller_test.go │ ├── application_plan_reconciler.go │ ├── application_plans.go │ ├── application_status_reconciler.go │ ├── application_status_reconciler_test.go │ ├── application_threescale_reconciler.go │ ├── applicationauth_controller.go │ ├── applicationauth_controller_test.go │ ├── applicationauth_status_reconciler.go │ ├── applicationauth_status_reconciler_test.go │ ├── applications.go │ ├── applications_test.go │ ├── backend_controller.go │ ├── backend_status_reconciler.go │ ├── backend_threescale_reconciler.go │ ├── backend_usages.go │ ├── custompolicydefinition_controller.go │ ├── custompolicydefinition_status_reconciler.go │ ├── custompolicydefinition_threescale_reconciler.go │ ├── developeraccount_controller.go │ ├── developeraccount_status_reconciler.go │ ├── developeraccount_threescale_reconciler.go │ ├── developeruser_controller.go │ ├── developeruser_status_reconciler.go │ ├── developeruser_threescale_reconciler.go │ ├── mapping_rules.go │ ├── methods.go │ ├── metrics.go │ ├── openapi_backend_reconciler.go │ ├── openapi_controller.go │ ├── openapi_controller_test.go │ ├── openapi_product_reconciler.go │ ├── openapi_product_reconciler_test.go │ ├── openapi_status_reconciler.go │ ├── product.go │ ├── product_controller.go │ ├── product_oidc.go │ ├── product_policies.go │ ├── product_policies_test.go │ ├── product_status_reconciler.go │ ├── product_threescale_reconciler.go │ ├── proxy.go │ ├── proxyconfigpromote_controller.go │ ├── proxyconfigpromote_controller_test.go │ ├── proxyconfigpromote_status_reconciler.go │ ├── proxyconfigpromote_status_reconciler_test.go │ ├── secret_to_openapi_event_mapper.go │ ├── tenant_controller.go │ ├── tenant_status_reconciler.go │ └── tenant_threescale_reconciler.go └── subscription │ ├── csvlocator │ └── csvlocator.go │ └── subscription_controller.go ├── doc ├── 3scale-diagram.dia ├── 3scale-diagram.png ├── activedoc-reference.md ├── adding-apicast-custom-environments.md ├── adding-custom-policies.md ├── apicast-enabling-tls-at-pod-level.md ├── apimanager-reference.md ├── apimanagerbackup-reference.md ├── apimanagerrestore-reference.md ├── application-reference.md ├── applicationauth-reference.md ├── backend-reference.md ├── capabilities-diagram.dia ├── capabilities-diagram.png ├── community-release-build.md ├── cr_samples │ ├── activedoc │ │ └── capabilities_v1beta1_activedoc_secret.yaml │ ├── apimanager │ │ ├── apps_v1alpha1_apimanager_apicast_custom_environment.yaml │ │ ├── apps_v1alpha1_apimanager_apicast_custom_policy.yaml │ │ ├── apps_v1alpha1_apimanager_devlatest.yaml │ │ ├── apps_v1alpha1_apimanager_external_components.yaml │ │ ├── apps_v1alpha1_apimanager_monitoring.yaml │ │ ├── apps_v1alpha1_apimanager_pdb.yaml │ │ └── apps_v1alpha1_apimanager_s3.yaml │ ├── developeruser │ │ └── capabilities_v1beta1_developeruser_member.yaml │ ├── openapi │ │ └── capabilities_v1beta1_openapi_secret.yaml │ └── product │ │ ├── capabilities_v1beta1_product_apicast_hosted.yaml │ │ ├── capabilities_v1beta1_product_apicast_selfmanaged.yaml │ │ ├── capabilities_v1beta1_product_gateway_response.yaml │ │ ├── capabilities_v1beta1_product_mappingrules.yaml │ │ ├── capabilities_v1beta1_product_oidc.yaml │ │ ├── capabilities_v1beta1_product_plan_limits.yaml │ │ ├── capabilities_v1beta1_product_plan_pricingrules.yaml │ │ └── capabilities_v1beta1_product_policies.yaml ├── custompolicydefinition-reference.md ├── dependency_decisions.yml ├── developeraccount-reference.md ├── developeruser-reference.md ├── development.md ├── gateway-instrumentation.md ├── monitoring-stack-deployment │ ├── 3scale-scrape-configs.yaml │ ├── README.md │ ├── datasource-v4.yaml │ ├── datasource-v5.yaml │ ├── grafana-v4.yaml │ ├── grafana-v5.yaml │ └── prometheus.yaml ├── openapi-3scale-extensions.md ├── openapi-reference.md ├── openapi-user-guide.md ├── operator-application-capabilities.md ├── operator-backup-and-restore.md ├── operator-monitoring-resources.md ├── operator-user-guide.md ├── product-reference.md ├── prometheusrules │ ├── README.md │ ├── apicast.yaml │ ├── backend-listener.yaml │ ├── backend-worker.yaml │ ├── system-app.yaml │ ├── system-sidekiq.yaml │ ├── threescale-kube-state-metrics-pre49.yaml │ ├── threescale-kube-state-metrics.yaml │ ├── zync-que.yaml │ └── zync.yaml ├── proxyConfigPromote-reference.md ├── quickstart-guide.md ├── redis-sentinel-tls.md └── tenant-reference.md ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── licenses.xml ├── main.go ├── make └── jaeger.mk ├── pkg ├── 3scale │ └── amp │ │ ├── .gitignore │ │ ├── cmd │ │ ├── prometheusrules.go │ │ └── root.go │ │ ├── component │ │ ├── ampimages.go │ │ ├── ampimages_options.go │ │ ├── apicast.go │ │ ├── apicast_monitoring.go │ │ ├── apicast_options.go │ │ ├── backend.go │ │ ├── backend_monitoring.go │ │ ├── backend_options.go │ │ ├── deployment_annotations.go │ │ ├── deployments_lister.go │ │ ├── generic_monitoring.go │ │ ├── hash_secret.go │ │ ├── highavailability.go │ │ ├── highavailability_options.go │ │ ├── hpa.go │ │ ├── images.go │ │ ├── memcached.go │ │ ├── memcached_options.go │ │ ├── pod_disruption_budget.go │ │ ├── system.go │ │ ├── system_monitoring.go │ │ ├── system_options.go │ │ ├── system_searchd.go │ │ ├── system_searchd_options.go │ │ ├── zync.go │ │ ├── zync_monitoring.go │ │ └── zync_options.go │ │ ├── main.go │ │ ├── operator │ │ ├── ampimages_options_provider.go │ │ ├── ampimages_options_provider_test.go │ │ ├── ampimages_reconciler.go │ │ ├── ampimages_reconciler_test.go │ │ ├── apicast_options_provider.go │ │ ├── apicast_options_provider_test.go │ │ ├── apicast_reconciler.go │ │ ├── apicast_reconciler_test.go │ │ ├── apimanager_utils.go │ │ ├── backend_options_provider.go │ │ ├── backend_options_provider_test.go │ │ ├── backend_reconciler.go │ │ ├── backend_reconciler_test.go │ │ ├── base_apimanager_logic_reconciler.go │ │ ├── base_apimanager_logic_reconciler_test.go │ │ ├── dependency_reconciler.go │ │ ├── generic_monitoring_reconciler.go │ │ ├── helper_test.go │ │ ├── highavailability_options_provider.go │ │ ├── highavailability_options_provider_test.go │ │ ├── highavailability_reconciler.go │ │ ├── highavailability_reconciler_test.go │ │ ├── images.go │ │ ├── images_test.go │ │ ├── memcached_options_provider.go │ │ ├── memcached_options_provider_test.go │ │ ├── memcached_reconciler.go │ │ ├── memcached_reconciler_test.go │ │ ├── system_options_provider.go │ │ ├── system_options_provider_test.go │ │ ├── system_reconciler.go │ │ ├── system_reconciler_test.go │ │ ├── system_searchd_options_provider.go │ │ ├── system_searchd_options_provider_test.go │ │ ├── system_searchd_reconciler.go │ │ ├── system_searchd_reconciler_test.go │ │ ├── zync_options_provider.go │ │ ├── zync_options_provider_test.go │ │ ├── zync_reconciler.go │ │ └── zync_reconciler_test.go │ │ └── prometheusrules │ │ ├── apicast_rules.go │ │ ├── backend_listener_rules.go │ │ ├── backend_worker_rules.go │ │ ├── factory.go │ │ ├── kube_state_metrics_rules.go │ │ ├── system_app_rules.go │ │ ├── system_sidekiq_rules.go │ │ ├── zync_que_rules.go │ │ └── zync_rules.go ├── apispkg │ ├── common │ │ ├── status_conditions.go │ │ ├── version_compare.go │ │ └── version_compare_test.go │ └── helper │ │ └── email.go ├── assets │ ├── assets.go │ ├── assets │ │ └── monitoring │ │ │ ├── apicast-grafana-dashboard-1.json.tpl │ │ │ ├── apicast-grafana-dashboard-2.json.tpl │ │ │ ├── backend-grafana-dashboard-1.json.tpl │ │ │ ├── kubernetes-resources-by-namespace-grafana-dashboard-1.json.tpl │ │ │ ├── kubernetes-resources-by-pod-grafana-dashboard-1.json.tpl │ │ │ ├── system-grafana-dashboard-1.json.tpl │ │ │ └── zync-grafana-dashboard-1.json.tpl │ └── bindata.go ├── backup │ ├── apimanager_backup.go │ ├── apimanager_backup_options.go │ ├── apimanager_backup_options_provider.go │ └── apimanager_backup_pvc_options.go ├── controller │ └── helper │ │ ├── application_entity.go │ │ ├── application_plan_entity.go │ │ ├── application_plan_entity_test.go │ │ ├── backend_list.go │ │ ├── backend_list_test.go │ │ ├── backendapi_entity.go │ │ ├── backendapi_entity_test.go │ │ ├── backendapi_remote_index.go │ │ ├── backendapi_remote_index_test.go │ │ ├── developeruser.go │ │ ├── developeruser_test.go │ │ ├── helper_test.go │ │ ├── lookup_provider_account.go │ │ ├── lookup_provider_account_test.go │ │ ├── product_entity.go │ │ ├── product_entity_test.go │ │ ├── product_list.go │ │ ├── product_list_test.go │ │ ├── shared.go │ │ ├── tenant.go │ │ ├── threescale_api.go │ │ ├── threescale_api_test.go │ │ ├── url.go │ │ └── url_test.go ├── crypto │ └── rand │ │ └── rand.go ├── handlers │ ├── apimanager_managed_routes_event_mapper.go │ └── apimanager_managed_routes_event_mapper_test.go ├── helper │ ├── annotations.go │ ├── boolean_utils.go │ ├── cache.go │ ├── container_port_utils.go │ ├── deployment.go │ ├── envvarutils.go │ ├── envvarutils_test.go │ ├── errors.go │ ├── helper.go │ ├── job.go │ ├── k8s.go │ ├── kubernetes_types.go │ ├── labels.go │ ├── loghttp.go │ ├── maputils.go │ ├── object_meta_merger.go │ ├── openapi_utils.go │ ├── openshift.go │ ├── podHelper.go │ ├── pointer_utils.go │ ├── reconcile_error_handler.go │ ├── redis_database_version.go │ ├── redis_database_version_helper.go │ ├── redis_database_version_test.go │ ├── requirements_common_helper.go │ ├── requirements_common_helper_test.go │ ├── route.go │ ├── runtime_object.go │ ├── secretutils.go │ ├── secretutils_test.go │ ├── slice_string_utils.go │ ├── slice_string_utils_test.go │ ├── system_database_helper.go │ ├── system_database_versions.go │ ├── task_runner.go │ ├── volume_utils.go │ ├── volumemount_utils.go │ └── webconsole.go ├── reconcilers │ ├── base_reconciler.go │ ├── base_reconciler_test.go │ ├── configmap.go │ ├── configmap_test.go │ ├── deployment.go │ ├── deployment_test.go │ ├── grafanadashboards.go │ ├── grafanadashboards_test.go │ ├── hpa.go │ ├── hpa_test.go │ ├── pod_disruption_budget.go │ ├── pod_disruption_budget_test.go │ ├── pod_monitors.go │ ├── pod_monitors_test.go │ ├── prometheus_rules.go │ ├── prometheus_rules_test.go │ ├── role.go │ ├── role_test.go │ ├── secret.go │ ├── secret_test.go │ ├── service.go │ ├── serviceaccount.go │ └── serviceaccount_test.go └── restore │ ├── apimanager_restore.go │ ├── apimanager_restore_options.go │ ├── apimanager_restore_options_provider.go │ ├── apimanager_restore_pvc_options.go │ └── runtime_apimanager_restore_info.go ├── test ├── crds │ └── crd_validation_test.go ├── manifests-version │ └── deployment_version_test.go └── unitcontrollers │ └── apimanager_controller_test.go └── version └── version.go /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | # ... CONFIG CONTENT ... 3 | golint: 4 | enabled: true 5 | # ... CONFIG CONTENT ... 6 | checks: 7 | GoLint/Naming/Initialisms: 8 | enabled: false 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global owners 2 | * @3scale/3scale-operators-devs 3 | -------------------------------------------------------------------------------- /.github/workflows/fast-forward-branch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Fast-forward between branches 3 | "on": 4 | workflow_dispatch: 5 | inputs: 6 | ref: 7 | description: 'The branch name or commit to fast-forward from' 8 | default: 'master' 9 | type: string 10 | to_branch: 11 | description: 'The branch name to fast-forward to' 12 | default: 'managed-services' 13 | type: string 14 | jobs: 15 | fast-forward: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.inputs.ref }} 21 | fetch-depth: 0 22 | - run: | 23 | git fetch origin ${{ github.event.inputs.ref }}:${{ github.event.inputs.to_branch }} 24 | git push origin ${{ github.event.inputs.to_branch }} 25 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | # pull-requests: read 13 | 14 | jobs: 15 | lint: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | - name: Setup go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version-file: go.mod 25 | - name: Run lint 26 | run: make lint 27 | -------------------------------------------------------------------------------- /.github/workflows/tagRelease.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow that is manually triggered to create a tag release in the repo in which it is present. 2 | # It can be manually triggered by those with permissions, or triggered from the main action which can trigger tag releases for all repos at once. 3 | 4 | name: tagRelease 5 | 6 | on: 7 | workflow_dispatch: 8 | # Inputs the workflow accepts. 9 | inputs: 10 | tag: 11 | # Friendly description to be shown in the UI instead of 'tag' 12 | description: 'Tag to be created along with release' 13 | # Input has to be provided for the workflow to run 14 | required: true 15 | git_ref: 16 | description: 'Git reference to create tag from, can be a commit hash or branch' 17 | required: true 18 | 19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 20 | jobs: 21 | createRelease: 22 | runs-on: ubuntu-latest 23 | name: Create Release 24 | steps: 25 | - uses: softprops/action-gh-release@v1 #This action will create the release with the params specified. 26 | with: 27 | tag_name: ${{ inputs.tag }} 28 | target_commitish: ${{ inputs.git_ref }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #vendor dir 2 | /vendor/ 3 | # Temporary Build Files 4 | build/_output 5 | build/_test 6 | ### IntelliJ ### 7 | .idea 8 | # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 9 | ### Emacs ### 10 | # -*- mode: gitignore; -*- 11 | *~ 12 | \#*\# 13 | /.emacs.desktop 14 | /.emacs.desktop.lock 15 | *.elc 16 | auto-save-list 17 | tramp 18 | .\#* 19 | # Org-mode 20 | .org-id-locations 21 | *_archive 22 | # flymake-mode 23 | *_flymake.* 24 | # eshell files 25 | /eshell/history 26 | /eshell/lastdir 27 | # elpa packages 28 | /elpa/ 29 | # reftex files 30 | *.rel 31 | # AUCTeX auto folder 32 | /auto/ 33 | # cask packages 34 | .cask/ 35 | dist/ 36 | # Flycheck 37 | flycheck_*.el 38 | # server auth directory 39 | /server/ 40 | # projectiles files 41 | .projectile 42 | projectile-bookmarks.eld 43 | # directory configuration 44 | .dir-locals.el 45 | # saveplace 46 | places 47 | # url cache 48 | url/cache/ 49 | # cedet 50 | ede-projects.el 51 | # smex 52 | smex-items 53 | # company-statistics 54 | company-statistics-cache.el 55 | # anaconda-mode 56 | anaconda-mode/ 57 | ### Go ### 58 | # Binaries for programs and plugins 59 | *.exe 60 | *.exe~ 61 | *.dll 62 | *.so 63 | *.dylib 64 | bin 65 | # Test binary, build with 'go test -c' 66 | *.test 67 | # Output of the go coverage tool, specifically when used with LiteIDE 68 | *.out 69 | ### Vim ### 70 | # swap 71 | .sw[a-p] 72 | .*.sw[a-p] 73 | # session 74 | Session.vim 75 | # temporary 76 | .netrwhist 77 | # auto-generated tag files 78 | tags 79 | ### VisualStudioCode ### 80 | .vscode/* 81 | .history 82 | # End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 83 | # Coverage output 84 | _output 85 | 86 | # 87 | 88 | # Kubernetes Generated files - skip generated files, except for vendored files 89 | 90 | !vendor/**/zz_generated.* 91 | 92 | # editor and IDE paraphernalia 93 | *.swp 94 | *.swo 95 | *~ 96 | 97 | # controller-runtime testenv binaries 98 | testbin/* 99 | 100 | 3scale-operator 101 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | modules-download-mode: readonly 4 | 5 | formatters: 6 | enable: 7 | - gofmt 8 | - goimports 9 | exclusions: 10 | generated: disable 11 | 12 | linters: 13 | enable: 14 | - asasalint 15 | - asciicheck 16 | - bidichk 17 | - bodyclose 18 | - copyloopvar 19 | - decorder 20 | - dogsled 21 | - durationcheck 22 | - errchkjson 23 | - exptostd 24 | - fatcontext 25 | - ginkgolinter 26 | - gocheckcompilerdirectives 27 | - gochecksumtype 28 | - goprintffuncname 29 | - grouper 30 | - iface 31 | - inamedparam 32 | - interfacebloat 33 | - makezero 34 | - mirror 35 | - misspell 36 | - nilerr 37 | - nilnesserr 38 | - nolintlint 39 | - nosprintfhostport 40 | - prealloc 41 | - reassign 42 | - revive 43 | - staticcheck 44 | - unconvert 45 | - unparam 46 | - wastedassign 47 | - whitespace 48 | settings: 49 | staticcheck: 50 | checks: 51 | - all 52 | - -QF1003 # https://staticcheck.dev/docs/checks/#QF1003 Convert if/else-if chain to tagged switch 53 | - -ST1003 # https://staticcheck.dev/docs/checks/#ST1003 Poorly chosen identifier. 54 | - -QF1008 # https://staticcheck.dev/docs/checks/#QF1008 Omit embedded fields from selector expression. 55 | nolintlint: 56 | require-specific: true 57 | revive: 58 | rules: 59 | - name: unused-parameter 60 | disabled: true 61 | misspell: 62 | ignore-rules: 63 | - searchd 64 | exclusions: 65 | generated: strict 66 | presets: 67 | - comments 68 | - std-error-handling 69 | rules: 70 | - linters: 71 | - recvcheck 72 | path: pkg/k8s.io/ 73 | - path: (.+)_test.go 74 | text: ' always receives ' 75 | linters: 76 | - unparam 77 | 78 | issues: 79 | max-issues-per-linter: 0 80 | max-same-issues: 0 81 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | 3 | FROM mirror.gcr.io/library/golang:1.22.8 as builder 4 | 5 | WORKDIR /workspace 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | # cache deps before building and copying source so that we don't need to re-download as much 10 | # and so that source changes don't invalidate our downloaded layer 11 | RUN go mod download 12 | 13 | # Copy the go source 14 | COPY main.go main.go 15 | COPY apis/ apis/ 16 | COPY controllers/ controllers/ 17 | 18 | COPY pkg/ pkg/ 19 | COPY version/ version/ 20 | 21 | # Build 22 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=`go env GOARCH` go build -a -o manager main.go 23 | 24 | # Use distroless as minimal base image to package the manager binary 25 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 26 | FROM gcr.io/distroless/static:nonroot 27 | WORKDIR / 28 | COPY --from=builder /workspace/manager . 29 | USER nonroot:nonroot 30 | 31 | ENTRYPOINT ["/manager"] 32 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Red Hat 3scale Operator 2 | Copyright (c) 2010-2016 3scale, Inc 3 | Copyright (c) 2016-2019 Red Hat, Inc. 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 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: 3scale.net 2 | layout: go.kubebuilder.io/v2 3 | multigroup: true 4 | projectName: 3scale-operator 5 | repo: github.com/3scale/3scale-operator 6 | resources: 7 | - group: apps 8 | kind: APIManager 9 | version: v1alpha1 10 | - group: apps 11 | kind: APIManagerBackup 12 | version: v1alpha1 13 | - group: apps 14 | kind: APIManagerRestore 15 | version: v1alpha1 16 | - group: capabilities 17 | kind: Tenant 18 | version: v1alpha1 19 | - group: capabilities 20 | kind: Backend 21 | version: v1beta1 22 | - group: capabilities 23 | kind: Product 24 | version: v1beta1 25 | - group: capabilities 26 | kind: OpenAPI 27 | version: v1beta1 28 | - group: capabilities 29 | kind: ActiveDoc 30 | version: v1beta1 31 | - group: capabilities 32 | kind: CustomPolicyDefinition 33 | version: v1beta1 34 | - group: capabilities 35 | kind: DeveloperAccount 36 | version: v1beta1 37 | - group: capabilities 38 | kind: DeveloperUser 39 | version: v1beta1 40 | - group: capabilities 41 | kind: ProxyConfigPromote 42 | version: v1beta1 43 | - group: capabilities 44 | kind: Application 45 | version: v1beta1 46 | - group: capabilities 47 | kind: ApplicationAuth 48 | version: v1beta1 49 | version: 3-alpha 50 | plugins: 51 | go.sdk.operatorframework.io/v2-alpha: {} -------------------------------------------------------------------------------- /apis/apps/common.go: -------------------------------------------------------------------------------- 1 | package apps 2 | 3 | const ( 4 | AwsAccessKeyID = "AWS_ACCESS_KEY_ID" 5 | AwsSecretAccessKey = "AWS_SECRET_ACCESS_KEY" 6 | AwsBucket = "AWS_BUCKET" 7 | AwsRegion = "AWS_REGION" 8 | AwsProtocol = "AWS_PROTOCOL" 9 | AwsHostname = "AWS_HOSTNAME" 10 | AwsPathStyle = "AWS_PATH_STYLE" 11 | AwsRoleArn = "AWS_ROLE_ARN" 12 | AwsWebIdentityTokenFile = "AWS_WEB_IDENTITY_TOKEN_FILE" 13 | APIcastDefaultTracingLibrary = "jaeger" 14 | APIManagerKind = "APIManager" 15 | ) 16 | -------------------------------------------------------------------------------- /apis/apps/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat. 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 apps v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=apps.3scale.net 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "apps.3scale.net", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/capabilities/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat. 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 capabilities v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=capabilities.3scale.net 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | const ( 28 | TenantKind = "Tenant" 29 | ) 30 | 31 | var ( 32 | // GroupVersion is group version used to register these objects 33 | GroupVersion = schema.GroupVersion{Group: "capabilities.3scale.net", Version: "v1alpha1"} 34 | 35 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 36 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 37 | 38 | // AddToScheme adds the types in this group-version to the given scheme. 39 | AddToScheme = SchemeBuilder.AddToScheme 40 | ) 41 | -------------------------------------------------------------------------------- /apis/capabilities/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat. 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 v1beta1 contains API Schema definitions for the capabilities v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=capabilities.3scale.net 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "capabilities.3scale.net", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 4 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 5 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 6 | LABEL operators.operatorframework.io.bundle.package.v1=3scale-operator 7 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 8 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.2.0 9 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 10 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v2 11 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 12 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 13 | COPY bundle/manifests /manifests/ 14 | COPY bundle/metadata /metadata/ 15 | COPY bundle/tests/scorecard /tests/scorecard/ 16 | -------------------------------------------------------------------------------- /bundle/manifests/threescale-operator-controller-manager-metrics-monitor_monitoring.coreos.com_v1_servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: ServiceMonitor 3 | metadata: 4 | labels: 5 | app: 3scale-api-management 6 | control-plane: controller-manager 7 | name: threescale-operator-controller-manager-metrics-monitor 8 | spec: 9 | endpoints: 10 | - path: /metrics 11 | port: metrics 12 | selector: 13 | matchLabels: 14 | app: 3scale-api-management 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /bundle/manifests/threescale-operator-controller-manager-metrics-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | control-plane: controller-manager 8 | name: threescale-operator-controller-manager-metrics-service 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8080 13 | targetPort: metrics 14 | selector: 15 | app: 3scale-api-management 16 | control-plane: controller-manager 17 | status: 18 | loadBalancer: {} 19 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | operators.operatorframework.io.bundle.channels.v1: alpha 3 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 4 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: 3scale-operator 7 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.2.0 8 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 9 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v2 10 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 11 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 12 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:v1.1.0 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | - entrypoint: 16 | - scorecard-test 17 | - olm-bundle-validation 18 | image: quay.io/operator-framework/scorecard-test:v1.1.0 19 | labels: 20 | suite: olm 21 | test: olm-bundle-validation-test 22 | - entrypoint: 23 | - scorecard-test 24 | - olm-crds-have-validation 25 | image: quay.io/operator-framework/scorecard-test:v1.1.0 26 | labels: 27 | suite: olm 28 | test: olm-crds-have-validation-test 29 | - entrypoint: 30 | - scorecard-test 31 | - olm-status-descriptors 32 | image: quay.io/operator-framework/scorecard-test:v1.1.0 33 | labels: 34 | suite: olm 35 | test: olm-status-descriptors-test 36 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for 4 | # breaking changes 5 | apiVersion: cert-manager.io/v1alpha2 6 | kind: Issuer 7 | metadata: 8 | name: selfsigned-issuer 9 | namespace: system 10 | spec: 11 | selfSigned: {} 12 | --- 13 | apiVersion: cert-manager.io/v1alpha2 14 | kind: Certificate 15 | metadata: 16 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 17 | namespace: system 18 | spec: 19 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 20 | dnsNames: 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 22 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 27 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/activedocopenapiref_openapi_validation_in_activedocs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds `oneOf` OpenAPI 2 | # validation for the activeDocOpenAPIRef attribute 3 | # to the ActiveDoc CRD due to at the moment 4 | # of writing this (2020-11-02) kubebuilder 5 | # does not support `oneOf` statement 6 | # OpenAPI validation 7 | - op: add 8 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/activeDocOpenAPIRef/oneOf 9 | value: 10 | - required: ["secretRef"] 11 | - required: ["url"] 12 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_activedocs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: activedocs.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_apimanagerbackups.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: apimanagerbackups.apps.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_apimanagerrestores.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: apimanagerrestores.apps.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_apimanagers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: apimanagers.apps.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_applicationauths.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: applicationauths.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_applications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: applications.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_backends.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: backends.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_custompolicydefinitions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: custompolicydefinitions.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_developeraccounts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: developeraccounts.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_developerusers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: developerusers.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_openapis.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: openapis.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_products.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: products.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_proxyconfigpromotes.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: proxyconfigpromotes.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_tenants.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: tenants.capabilities.3scale.net 9 | -------------------------------------------------------------------------------- /config/crd/patches/openapiref_openapi_validation_in_openapis.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds `oneOf` OpenAPI 2 | # validation for the openapiRef attribute 3 | # to the OpenAPI CRD due to at the moment 4 | # of writing this (2020-11-02) kubebuilder 5 | # does not support `oneOf` statement 6 | # OpenAPI validation 7 | - op: add 8 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/openapiRef/oneOf 9 | value: 10 | - required: ["secretRef"] 11 | - required: ["url"] 12 | -------------------------------------------------------------------------------- /config/crd/patches/product_authentication_openapi_validation_in_products.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds `oneOf` OpenAPI 2 | # validation for the authentication attribute 3 | # to the Product CRD due to at the moment 4 | # of writing this (2020-11-02) kubebuilder 5 | # does not support `oneOf` statement 6 | # OpenAPI validation 7 | - op: add 8 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/deployment/properties/apicastHosted/properties/authentication/oneOf 9 | value: 10 | - required: ["userkey"] 11 | - required: ["appKeyAppID"] 12 | - required: ["oidc"] 13 | - op: add 14 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/deployment/properties/apicastSelfManaged/properties/authentication/oneOf 15 | value: 16 | - required: ["userkey"] 17 | - required: ["appKeyAppID"] 18 | - required: ["oidc"] 19 | -------------------------------------------------------------------------------- /config/crd/patches/product_deployment_openapi_validation_in_products.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds `oneOf` OpenAPI 2 | # validation for the deployment attribute 3 | # to the Product CRD due to at the moment 4 | # of writing this (2020-11-02) kubebuilder 5 | # does not support `oneOf` statement 6 | # OpenAPI validation 7 | - op: add 8 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/deployment/oneOf 9 | value: 10 | - required: ["apicastHosted"] 11 | - required: ["apicastSelfManaged"] 12 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_activedocs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: activedocs.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_apimanagerbackups.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: apimanagerbackups.apps.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_apimanagerrestores.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: apimanagerrestores.apps.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_apimanagers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: apimanagers.apps.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_applicationauths.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: applicationauths.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_applications.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: applications.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_backends.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: backends.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_custompolicydefinitions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: custompolicydefinitions.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_developeraccounts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: developeraccounts.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_developerusers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: developerusers.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_openapis.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: openapis.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_products.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: products.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_proxyconfigpromotes.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: proxyconfigpromotes.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhook: 11 | webhookClientConfig: 12 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 13 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 14 | caBundle: Cg== 15 | service: 16 | namespace: system 17 | name: webhook-service 18 | path: /convert 19 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_tenants.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: tenants.capabilities.3scale.net 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager-v2 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch exposes metrics endpoint in plain HTTP 8080 port 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager-v2 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: manager 13 | args: 14 | - "--metrics-addr=0.0.0.0:8080" 15 | - "--enable-leader-election" 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager-v2 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/dev-databases/backend-redis/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend-redis 5 | labels: 6 | app: backend-redis 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: backend-redis 12 | template: 13 | metadata: 14 | labels: 15 | app: backend-redis 16 | spec: 17 | containers: 18 | - name: redis 19 | image: redis 20 | ports: 21 | - containerPort: 6379 22 | resources: 23 | requests: 24 | memory: "64Mi" 25 | cpu: "250m" 26 | limits: 27 | memory: "128Mi" 28 | cpu: "500m" 29 | livenessProbe: 30 | tcpSocket: 31 | port: 6379 32 | initialDelaySeconds: 5 33 | periodSeconds: 10 34 | readinessProbe: 35 | tcpSocket: 36 | port: 6379 37 | initialDelaySeconds: 5 38 | periodSeconds: 10 39 | volumeMounts: 40 | - mountPath: /data 41 | name: redis-storage 42 | volumes: 43 | - name: redis-storage 44 | persistentVolumeClaim: 45 | claimName: backend-redis-pvc 46 | -------------------------------------------------------------------------------- /config/dev-databases/backend-redis/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - pvc.yaml 5 | - secret-processed.yaml 6 | 7 | images: 8 | - name: redis 9 | newName: quay.io/fedora/redis-7 -------------------------------------------------------------------------------- /config/dev-databases/backend-redis/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: backend-redis-pvc 5 | labels: 6 | app: backend-redis 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 1Gi -------------------------------------------------------------------------------- /config/dev-databases/backend-redis/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: backend-redis 5 | stringData: 6 | REDIS_QUEUES_SENTINEL_HOSTS: "" 7 | REDIS_QUEUES_SENTINEL_ROLE: "" 8 | REDIS_QUEUES_URL: redis://backend-redis.$(NAMESPACE).svc.cluster.local:6379/1 9 | REDIS_STORAGE_SENTINEL_HOSTS: "" 10 | REDIS_STORAGE_SENTINEL_ROLE: "" 11 | REDIS_STORAGE_URL: redis://backend-redis.$(NAMESPACE).svc.cluster.local:6379/2 12 | type: Opaque -------------------------------------------------------------------------------- /config/dev-databases/backend-redis/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: backend-redis 5 | labels: 6 | app: backend-redis 7 | spec: 8 | ports: 9 | - port: 6379 10 | targetPort: 6379 11 | selector: 12 | app: backend-redis 13 | type: ClusterIP -------------------------------------------------------------------------------- /config/dev-databases/system-mysql/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: system-mysql 5 | labels: 6 | app: mysql 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mysql 12 | template: 13 | metadata: 14 | labels: 15 | app: mysql 16 | spec: 17 | containers: 18 | - name: mysql 19 | image: mysql 20 | ports: 21 | - containerPort: 3306 22 | env: 23 | - name: MYSQL_ROOT_PASSWORD 24 | valueFrom: 25 | secretKeyRef: 26 | name: system-database 27 | key: DB_ROOT_PASSWORD 28 | - name: MYSQL_USER 29 | valueFrom: 30 | secretKeyRef: 31 | name: system-database 32 | key: DB_USER 33 | - name: MYSQL_PASSWORD 34 | valueFrom: 35 | secretKeyRef: 36 | name: system-database 37 | key: DB_PASSWORD 38 | - name: MYSQL_DATABASE 39 | value: dev 40 | - name: MYSQL_DEFAULT_AUTHENTICATION_PLUGIN 41 | value: mysql_native_password 42 | volumeMounts: 43 | - name: mysql-storage 44 | mountPath: /var/lib/mysql/data 45 | livenessProbe: 46 | tcpSocket: 47 | port: 3306 48 | initialDelaySeconds: 30 49 | periodSeconds: 10 50 | readinessProbe: 51 | tcpSocket: 52 | port: 3306 53 | initialDelaySeconds: 10 54 | periodSeconds: 5 55 | volumes: 56 | - name: mysql-storage 57 | persistentVolumeClaim: 58 | claimName: system-mysql-pvc 59 | -------------------------------------------------------------------------------- /config/dev-databases/system-mysql/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - pvc.yaml 5 | - secret-processed.yaml 6 | 7 | images: 8 | - name: mysql 9 | newName: quay.io/sclorg/mysql-80-c8s 10 | -------------------------------------------------------------------------------- /config/dev-databases/system-mysql/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: system-mysql-pvc 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 1Gi -------------------------------------------------------------------------------- /config/dev-databases/system-mysql/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: system-database 5 | stringData: 6 | DB_USER: mysql 7 | DB_PASSWORD: password 8 | DB_ROOT_PASSWORD: rootpassword 9 | URL: mysql2://root:rootpassword@system-mysql.$(NAMESPACE).svc.cluster.local/dev 10 | type: Opaque -------------------------------------------------------------------------------- /config/dev-databases/system-mysql/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: system-mysql 5 | labels: 6 | app: mysql 7 | spec: 8 | ports: 9 | - port: 3306 10 | targetPort: 3306 11 | selector: 12 | app: mysql 13 | type: ClusterIP -------------------------------------------------------------------------------- /config/dev-databases/system-postgresql/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: system-postgresql 5 | labels: 6 | app: system-postgresql 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: system-postgresql 12 | template: 13 | metadata: 14 | labels: 15 | app: system-postgresql 16 | spec: 17 | containers: 18 | - name: postgresql 19 | image: postgres 20 | ports: 21 | - containerPort: 5432 22 | env: 23 | - name: POSTGRESQL_USER 24 | valueFrom: 25 | secretKeyRef: 26 | name: system-database 27 | key: DB_USER 28 | - name: POSTGRESQL_PASSWORD 29 | valueFrom: 30 | secretKeyRef: 31 | name: system-database 32 | key: DB_PASSWORD 33 | - name: POSTGRESQL_DATABASE 34 | value: "dev" 35 | - name: PGDATA 36 | value: /var/lib/postgresql/data/pgdata 37 | volumeMounts: 38 | - name: postgresql-storage 39 | mountPath: /var/lib/postgresql/data 40 | livenessProbe: 41 | tcpSocket: 42 | port: 5432 43 | initialDelaySeconds: 15 44 | periodSeconds: 20 45 | readinessProbe: 46 | tcpSocket: 47 | port: 5432 48 | initialDelaySeconds: 5 49 | periodSeconds: 10 50 | volumes: 51 | - name: postgresql-storage 52 | persistentVolumeClaim: 53 | claimName: system-postgresql-pvc -------------------------------------------------------------------------------- /config/dev-databases/system-postgresql/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - pvc.yaml 5 | - secret-processed.yaml 6 | 7 | images: 8 | - name: postgres 9 | newName: quay.io/sclorg/postgresql-15-c8s -------------------------------------------------------------------------------- /config/dev-databases/system-postgresql/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: system-postgresql-pvc 5 | labels: 6 | app: system-postgresql 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 1Gi -------------------------------------------------------------------------------- /config/dev-databases/system-postgresql/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: system-database 5 | stringData: 6 | DB_USER: myuser 7 | DB_PASSWORD: password 8 | URL: postgresql://myuser:password@system-postgresql.$(NAMESPACE).svc.cluster.local/dev 9 | type: Opaque -------------------------------------------------------------------------------- /config/dev-databases/system-postgresql/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: system-postgresql 5 | labels: 6 | app: system-postgresql 7 | spec: 8 | ports: 9 | - port: 5432 10 | targetPort: 5432 11 | selector: 12 | app: system-postgresql 13 | type: ClusterIP -------------------------------------------------------------------------------- /config/dev-databases/system-redis/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: system-redis 5 | labels: 6 | app: system-redis 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: system-redis 12 | template: 13 | metadata: 14 | labels: 15 | app: system-redis 16 | spec: 17 | containers: 18 | - name: redis 19 | image: redis 20 | ports: 21 | - containerPort: 6379 22 | resources: 23 | requests: 24 | memory: "64Mi" 25 | cpu: "250m" 26 | limits: 27 | memory: "128Mi" 28 | cpu: "500m" 29 | livenessProbe: 30 | tcpSocket: 31 | port: 6379 32 | initialDelaySeconds: 5 33 | periodSeconds: 10 34 | readinessProbe: 35 | tcpSocket: 36 | port: 6379 37 | initialDelaySeconds: 5 38 | periodSeconds: 10 39 | volumeMounts: 40 | - mountPath: /data 41 | name: redis-storage 42 | volumes: 43 | - name: redis-storage 44 | persistentVolumeClaim: 45 | claimName: system-redis-pvc -------------------------------------------------------------------------------- /config/dev-databases/system-redis/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml 4 | - pvc.yaml 5 | - secret-processed.yaml 6 | 7 | images: 8 | - name: redis 9 | newName: quay.io/fedora/redis-7 -------------------------------------------------------------------------------- /config/dev-databases/system-redis/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: system-redis-pvc 5 | labels: 6 | app: system-redis 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 1Gi -------------------------------------------------------------------------------- /config/dev-databases/system-redis/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: system-redis 5 | stringData: 6 | SENTINEL_HOSTS: "" 7 | SENTINEL_ROLE: "" 8 | URL: redis://system-redis.$(NAMESPACE).svc.cluster.local:6379/1 9 | type: Opaque -------------------------------------------------------------------------------- /config/dev-databases/system-redis/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: system-redis 5 | labels: 6 | app: system-redis 7 | spec: 8 | ports: 9 | - port: 6379 10 | targetPort: 6379 11 | selector: 12 | app: system-redis 13 | type: ClusterIP -------------------------------------------------------------------------------- /config/jaeger/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: jaeger 6 | labels: 7 | app: jaeger 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: jaeger 13 | template: 14 | metadata: 15 | labels: 16 | app: jaeger 17 | spec: 18 | containers: 19 | - name: jaeger 20 | image: jaegertracing/all-in-one:latest 21 | env: 22 | - name: JAEGER_DISABLED 23 | value: "false" 24 | - name: COLLECTOR_OTLP_ENABLED 25 | value: "true" 26 | imagePullPolicy: Always 27 | ports: 28 | - containerPort: 16686 29 | - containerPort: 4317 -------------------------------------------------------------------------------- /config/jaeger/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - deployment.yaml 6 | - service.yaml -------------------------------------------------------------------------------- /config/jaeger/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: jaeger 6 | labels: 7 | app: jaeger 8 | spec: 9 | ports: 10 | - port: 16686 11 | name: http 12 | - port: 4317 13 | name: internal 14 | selector: 15 | app: jaeger -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | - metrics_service.yaml 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | images: 7 | - name: controller 8 | newName: quay.io/3scale/3scale-operator 9 | newTag: master 10 | -------------------------------------------------------------------------------- /config/manager/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | name: controller-manager-metrics-service 8 | namespace: system 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8080 13 | targetPort: metrics 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/manager/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: 3scale-operator 5 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../default 3 | - ../samples 4 | - ../scorecard 5 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | --- 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: metrics 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | app: 3scale-api-management 18 | -------------------------------------------------------------------------------- /config/rbac/activedoc_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit activedocs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: activedoc-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - activedocs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - activedocs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/activedoc_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view activedocs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: activedoc-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - activedocs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - activedocs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/apimanager_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit apimanagers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanager-editor-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - apps.3scale.net 21 | resources: 22 | - apimanagers/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/apimanager_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view apimanagers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanager-viewer-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - apps.3scale.net 17 | resources: 18 | - apimanagers/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/apimanagerbackup_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit apimanagerbackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanagerbackup-editor-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagerbackups 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - apps.3scale.net 21 | resources: 22 | - apimanagerbackups/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/apimanagerbackup_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view apimanagerbackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanagerbackup-viewer-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagerbackups 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - apps.3scale.net 17 | resources: 18 | - apimanagerbackups/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/apimanagerrestore_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit apimanagerrestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanagerrestore-editor-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagerrestores 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - apps.3scale.net 21 | resources: 22 | - apimanagerrestores/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/apimanagerrestore_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view apimanagerrestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: apimanagerrestore-viewer-role 6 | rules: 7 | - apiGroups: 8 | - apps.3scale.net 9 | resources: 10 | - apimanagerrestores 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - apps.3scale.net 17 | resources: 18 | - apimanagerrestores/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/application_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit applications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: application-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - applications 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - applications/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/application_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view applications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: application-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - applications 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - applications/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/applicationauth_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit applicationauths. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: applicationauth-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - applicationauths 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - applicationauths/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/applicationauth_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view applicationauths. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: applicationauth-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - applicationauths 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - applicationauths/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: 3scale-operator 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/backend_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit backends. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: backend-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - backends 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - backends/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/backend_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view backends. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: backend-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - backends 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - backends/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/clusterrole_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-clusterrolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: 3scale-operator 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/custompolicydefinition_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit custompolicydefinitions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: custompolicydefinition-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - custompolicydefinitions 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - custompolicydefinitions/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/custompolicydefinition_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view custompolicydefinitions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: custompolicydefinition-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - custompolicydefinitions 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - custompolicydefinitions/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/developeraccount_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit developeraccounts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: developeraccount-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - developeraccounts 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - developeraccounts/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/developeraccount_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view developeraccounts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: developeraccount-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - developeraccounts 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - developeraccounts/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/developeruser_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit developerusers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: developeruser-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - developerusers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - developerusers/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/developeruser_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view developerusers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: developeruser-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - developerusers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - developerusers/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - clusterrole_binding.yaml 5 | - leader_election_role.yaml 6 | - leader_election_role_binding.yaml 7 | # Comment the following 4 lines if you want to disable 8 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 9 | # which protects your /metrics endpoint. 10 | #- auth_proxy_service.yaml 11 | #- auth_proxy_role.yaml 12 | #- auth_proxy_role_binding.yaml 13 | #- auth_proxy_client_clusterrole.yaml 14 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | - coordination.k8s.io 10 | resources: 11 | - configmaps 12 | - leases 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - create 18 | - update 19 | - patch 20 | - delete 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - configmaps/status 25 | verbs: 26 | - get 27 | - update 28 | - patch 29 | - apiGroups: 30 | - "" 31 | resources: 32 | - events 33 | verbs: 34 | - create 35 | - patch 36 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: 3scale-operator 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/openapi_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit openapis. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: openapi-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - openapis 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - openapis/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/openapi_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view openapis. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: openapi-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - openapis 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - openapis/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/product_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit products. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: product-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - products 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - products/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/product_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view products. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: product-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - products 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - products/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/proxyconfigpromote_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit proxyconfigpromotes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: proxyconfigpromote-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - proxyconfigpromotes 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - proxyconfigpromotes/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/proxyconfigpromote_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view proxyconfigpromotes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: proxyconfigpromote-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - proxyconfigpromotes 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - proxyconfigpromotes/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: 3scale-operator 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/tenant_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit tenants. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: tenant-editor-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - tenants 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - capabilities.3scale.net 21 | resources: 22 | - tenants/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/tenant_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view tenants. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: tenant-viewer-role 6 | rules: 7 | - apiGroups: 8 | - capabilities.3scale.net 9 | resources: 10 | - tenants 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - capabilities.3scale.net 17 | resources: 18 | - tenants/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/requirements/operator-requirements.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: template.openshift.io/v1 2 | kind: Template 3 | metadata: 4 | name: operator-requirements 5 | objects: 6 | - apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: 3scale-api-management-operator-requirements 10 | data: 11 | rht_threescale_version_requirements: ${THREESCALE_VERSION} 12 | rht_mysql_requirements: 8.0.0 13 | rht_postgres_requirements: 15.0.0 14 | rht_system_redis_requirements: 7.0.0 15 | rht_backend_redis_requirements: 7.0.0 16 | parameters: 17 | - name: THREESCALE_VERSION -------------------------------------------------------------------------------- /config/samples/apps_v1alpha1_apimanager_simple.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-sample 5 | spec: 6 | wildcardDomain: example.com 7 | status: 8 | deployments: 9 | ready: 10 | - "deployment x ready" 11 | -------------------------------------------------------------------------------- /config/samples/apps_v1alpha1_apimanagerbackup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManagerBackup 3 | metadata: 4 | name: apimanagerbackup-sample 5 | spec: 6 | backupDestination: 7 | persistentVolumeClaim: 8 | volumeName: "mypreexistingvolume" 9 | status: {} 10 | -------------------------------------------------------------------------------- /config/samples/apps_v1alpha1_apimanagerrestore.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManagerRestore 3 | metadata: 4 | name: apimanagerrestore-sample 5 | spec: 6 | restoreSource: 7 | persistentVolumeClaim: 8 | claimSource: 9 | claimName: "mybackeduppvc" 10 | status: {} 11 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1alpha1_tenant.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1alpha1 2 | kind: Tenant 3 | metadata: 4 | name: tenant-sample 5 | spec: 6 | username: admin 7 | systemMasterUrl: https://master.example.com 8 | email: admin@example.com 9 | organizationName: Example.com 10 | masterCredentialsRef: 11 | name: system-seed 12 | passwordCredentialsRef: 13 | name: ecorp-admin-secret 14 | tenantSecretRef: 15 | name: ecorp-tenant-secret 16 | namespace: operator-test 17 | status: 18 | adminId: 1 19 | tenantId: 2 20 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_activedoc_url.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: ActiveDoc 3 | metadata: 4 | name: activedoc-from-url 5 | spec: 6 | name: "Operated ActiveDoc From URL" 7 | activeDocOpenAPIRef: 8 | url: "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.json" 9 | status: {} 10 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Application 3 | metadata: 4 | name: application-sample 5 | spec: 6 | accountCR: 7 | name: "developeraccount-sample" 8 | applicationPlanName: "plan01" 9 | productCR: 10 | name: "product-sample" 11 | name: "testApp" 12 | description: "testing application " 13 | suspend: false 14 | status: {} 15 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_applicationauth.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: ApplicationAuth 3 | metadata: 4 | name: applicationauth-sample 5 | spec: 6 | applicationCRName: application-cr-name 7 | generateSecret: true 8 | authSecretRef: 9 | name: auth-secret-reference 10 | status: {} 11 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_backend.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Backend 3 | metadata: 4 | name: backend1-sample 5 | spec: 6 | name: "Operated Backend 1" 7 | systemName: "backend1" 8 | privateBaseURL: "https://api.example.com" 9 | status: {} 10 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_custompolicydefinition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: CustomPolicyDefinition 3 | metadata: 4 | name: custompolicydefinition-sample 5 | spec: 6 | name: "MyCustomPolicy" 7 | version: "0.0.1" 8 | schema: 9 | name: "MyCustomPolicy" 10 | version: "0.0.1" 11 | summary: "some summary" 12 | $schema: "http://json-schema.org/draft-07/schema#" 13 | configuration: 14 | type: "object" 15 | properties: 16 | someAttr: 17 | description: "Some attribute" 18 | type: "integer" 19 | status: {} 20 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_developeraccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: DeveloperAccount 3 | metadata: 4 | name: developeraccount-simple-sample 5 | spec: 6 | orgName: Ecorp 7 | status: {} 8 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_developeruser_admin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: DeveloperUser 3 | metadata: 4 | name: developeruser-admin-sample 5 | spec: 6 | username: myusername2 7 | email: myusername2@example.com 8 | passwordCredentialsRef: 9 | name: mysecret 10 | role: admin 11 | developerAccountRef: 12 | name: developeraccount1 13 | status: {} 14 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_openapi_url.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: OpenAPI 3 | metadata: 4 | name: openapi-from-url 5 | spec: 6 | openapiRef: 7 | url: "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml" 8 | status: {} 9 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_product.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-sample 5 | spec: 6 | name: "OperatedProduct 1" 7 | status: {} 8 | -------------------------------------------------------------------------------- /config/samples/capabilities_v1beta1_proxyconfigpromote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: ProxyConfigPromote 3 | metadata: 4 | name: proxyconfigpromote-sample 5 | spec: 6 | productCRName: product1-sample 7 | production: true 8 | status: {} 9 | 10 | 11 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - apps_v1alpha1_apimanager_simple.yaml 4 | - apps_v1alpha1_apimanagerbackup.yaml 5 | - apps_v1alpha1_apimanagerrestore.yaml 6 | - capabilities_v1alpha1_tenant.yaml 7 | - capabilities_v1beta1_backend.yaml 8 | - capabilities_v1beta1_product.yaml 9 | - capabilities_v1beta1_openapi_url.yaml 10 | - capabilities_v1beta1_activedoc_url.yaml 11 | - capabilities_v1beta1_developeraccount.yaml 12 | - capabilities_v1beta1_developeruser_admin.yaml 13 | - capabilities_v1beta1_custompolicydefinition.yaml 14 | - capabilities_v1beta1_proxyconfigpromote.yaml 15 | - capabilities_v1beta1_application.yaml 16 | - capabilities_v1beta1_applicationauth.yaml 17 | # +kubebuilder:scaffold:manifestskustomizesamples 18 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | # +kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.1.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.1.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.1.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | # - op: add 22 | # path: /stages/0/tests/- 23 | # value: 24 | # entrypoint: 25 | # - scorecard-test 26 | # - olm-crds-have-resources 27 | # image: quay.io/operator-framework/scorecard-test:v1.1.0 28 | # labels: 29 | # suite: olm 30 | # test: olm-crds-have-resources-test 31 | # - op: add 32 | # path: /stages/0/tests/- 33 | # value: 34 | # entrypoint: 35 | # - scorecard-test 36 | # - olm-spec-descriptors 37 | # image: quay.io/operator-framework/scorecard-test:v1.1.0 38 | # labels: 39 | # suite: olm 40 | # test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.1.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /controllers/apps/config_map_requirements_to_apimanager_event_mapper.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 7 | "github.com/3scale/3scale-operator/pkg/helper" 8 | "github.com/go-logr/logr" 9 | "k8s.io/apimachinery/pkg/types" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 12 | ) 13 | 14 | type ConfigMapToApimanagerEventMapper struct { 15 | Context context.Context 16 | K8sClient client.Client 17 | Logger logr.Logger 18 | Namespace string 19 | } 20 | 21 | func (s *ConfigMapToApimanagerEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { 22 | objectName := obj.GetName() 23 | if objectName != helper.OperatorRequirementsConfigMapName { 24 | return nil 25 | } 26 | 27 | apimanagerList := &appsv1alpha1.APIManagerList{} 28 | 29 | // filter by Secret UID 30 | opts := []client.ListOption{} 31 | 32 | // Support namespace scope or cluster scoped 33 | if s.Namespace != "" { 34 | opts = append(opts, client.InNamespace(s.Namespace)) 35 | } 36 | 37 | err := s.K8sClient.List(ctx, apimanagerList, opts...) 38 | if err != nil { 39 | s.Logger.Error(err, "reading apimanager list") 40 | return nil 41 | } 42 | 43 | requests := []reconcile.Request{} 44 | for idx := range apimanagerList.Items { 45 | requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ 46 | Name: apimanagerList.Items[idx].GetName(), 47 | Namespace: apimanagerList.Items[idx].GetNamespace(), 48 | }}) 49 | } 50 | 51 | return requests 52 | } 53 | -------------------------------------------------------------------------------- /controllers/apps/secret_to_apimanager_event_mapper.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 8 | "github.com/go-logr/logr" 9 | "k8s.io/apimachinery/pkg/types" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 12 | ) 13 | 14 | const ( 15 | APImanagerSecretLabelPrefix = "secret.apimanager.apps.3scale.net/" 16 | ) 17 | 18 | // SecretToApimanagerEventMapper is an EventHandler that maps secret object to apimanager CR's 19 | type SecretToApimanagerEventMapper struct { 20 | Context context.Context 21 | K8sClient client.Client 22 | Logger logr.Logger 23 | Namespace string 24 | } 25 | 26 | func apimanagerSecretLabelKey(uid string) string { 27 | return fmt.Sprintf("%s%s", APImanagerSecretLabelPrefix, uid) 28 | } 29 | 30 | func (s *SecretToApimanagerEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { 31 | apimanagerList := &appsv1alpha1.APIManagerList{} 32 | 33 | // filter by Secret UID 34 | opts := []client.ListOption{client.HasLabels{apimanagerSecretLabelKey(string(obj.GetUID()))}} 35 | 36 | // Support namespace scope or cluster scoped 37 | if s.Namespace != "" { 38 | opts = append(opts, client.InNamespace(s.Namespace)) 39 | } 40 | 41 | err := s.K8sClient.List(ctx, apimanagerList, opts...) 42 | if err != nil { 43 | s.Logger.Error(err, "reading apimanager list") 44 | return nil 45 | } 46 | 47 | s.Logger.V(1).Info("Processing object", "key", client.ObjectKeyFromObject(obj), "accepted", len(apimanagerList.Items) > 0) 48 | 49 | requests := []reconcile.Request{} 50 | for idx := range apimanagerList.Items { 51 | requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ 52 | Name: apimanagerList.Items[idx].GetName(), 53 | Namespace: apimanagerList.Items[idx].GetNamespace(), 54 | }}) 55 | } 56 | 57 | return requests 58 | } 59 | -------------------------------------------------------------------------------- /controllers/capabilities/product.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/3scale/3scale-operator/pkg/helper" 7 | threescaleapi "github.com/3scale/3scale-porta-go-client/client" 8 | ) 9 | 10 | func (t *ProductThreescaleReconciler) syncProduct(_ interface{}) error { 11 | params := threescaleapi.Params{} 12 | 13 | if t.productEntity.Name() != t.resource.Spec.Name { 14 | params["name"] = t.resource.Spec.Name 15 | } 16 | 17 | if t.productEntity.Description() != t.resource.Spec.Description { 18 | params["description"] = t.resource.Spec.Description 19 | } 20 | 21 | specDeploymentOption := t.resource.Spec.DeploymentOption() 22 | if specDeploymentOption != nil { 23 | if t.productEntity.DeploymentOption() != *specDeploymentOption { 24 | params["deployment_option"] = *specDeploymentOption 25 | } 26 | } // only update deployment_option when set in the CR 27 | 28 | specAuthMode := t.resource.Spec.AuthenticationMode() 29 | if specAuthMode != nil { 30 | if t.productEntity.BackendVersion() != *specAuthMode { 31 | params["backend_version"] = *specAuthMode 32 | } 33 | } // only update backend_version when set in the CR 34 | 35 | if !helper.ManagedByOperatorAnnotationExists(t.productEntity.Annotations()) { 36 | for k, v := range helper.ManagedByOperatorAnnotation() { 37 | params[k] = v 38 | } 39 | } 40 | 41 | if len(params) > 0 { 42 | err := t.productEntity.Update(params) 43 | if err != nil { 44 | return fmt.Errorf("error sync product [%s;%d]: %w", t.resource.Spec.SystemName, t.productEntity.ID(), err) 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /controllers/capabilities/product_oidc.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (t *ProductThreescaleReconciler) syncOIDCConfiguration(_ interface{}) error { 8 | desiredSpec := t.resource.Spec.OIDCSpec() 9 | if desiredSpec == nil || desiredSpec.AuthenticationFlow == nil { 10 | return nil 11 | } 12 | 13 | existing, err := t.productEntity.OIDCConfiguration() 14 | if err != nil { 15 | return fmt.Errorf("error sync product [%s] oidc configuration: %w", t.resource.Spec.SystemName, err) 16 | } 17 | 18 | newOIDCConf := *existing 19 | 20 | updated := false 21 | 22 | if newOIDCConf.Element.StandardFlowEnabled != desiredSpec.AuthenticationFlow.StandardFlowEnabled { 23 | newOIDCConf.Element.StandardFlowEnabled = desiredSpec.AuthenticationFlow.StandardFlowEnabled 24 | updated = true 25 | } 26 | 27 | if newOIDCConf.Element.ImplicitFlowEnabled != desiredSpec.AuthenticationFlow.ImplicitFlowEnabled { 28 | newOIDCConf.Element.ImplicitFlowEnabled = desiredSpec.AuthenticationFlow.ImplicitFlowEnabled 29 | updated = true 30 | } 31 | 32 | if newOIDCConf.Element.ServiceAccountsEnabled != desiredSpec.AuthenticationFlow.ServiceAccountsEnabled { 33 | newOIDCConf.Element.ServiceAccountsEnabled = desiredSpec.AuthenticationFlow.ServiceAccountsEnabled 34 | updated = true 35 | } 36 | 37 | if newOIDCConf.Element.DirectAccessGrantsEnabled != desiredSpec.AuthenticationFlow.DirectAccessGrantsEnabled { 38 | newOIDCConf.Element.DirectAccessGrantsEnabled = desiredSpec.AuthenticationFlow.DirectAccessGrantsEnabled 39 | updated = true 40 | } 41 | 42 | if updated { 43 | err := t.productEntity.UpdateOIDCConfiguration(&newOIDCConf) 44 | if err != nil { 45 | return fmt.Errorf("error sync product [%s] oidc configuration: %w", t.resource.Spec.SystemName, err) 46 | } 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /controllers/capabilities/secret_to_openapi_event_mapper.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "k8s.io/apimachinery/pkg/types" 7 | "sigs.k8s.io/controller-runtime/pkg/client" 8 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 9 | 10 | capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" 11 | "github.com/go-logr/logr" 12 | ) 13 | 14 | // SecretToOpenAPIEventMapper is an EventHandler that maps an OAS source secret to it's corresponding OpenAPI CR 15 | type SecretToOpenAPIEventMapper struct { 16 | Context context.Context 17 | K8sClient client.Client 18 | Logger logr.Logger 19 | } 20 | 21 | func (s *SecretToOpenAPIEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { 22 | openAPIList := &capabilitiesv1beta1.OpenAPIList{} 23 | 24 | // Filter by Secret UID 25 | opts := []client.ListOption{ 26 | client.MatchingLabels{ 27 | openAPISecretRefLabelKey: string(obj.GetUID()), 28 | }, 29 | } 30 | 31 | err := s.K8sClient.List(ctx, openAPIList, opts...) 32 | if err != nil { 33 | s.Logger.Error(err, "failed to list OpenAPI resources") 34 | return nil 35 | } 36 | 37 | s.Logger.V(1).Info("Processing object", "key", client.ObjectKeyFromObject(obj), "accepted", len(openAPIList.Items) > 0) 38 | 39 | requests := []reconcile.Request{} 40 | for idx := range openAPIList.Items { 41 | requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ 42 | Name: openAPIList.Items[idx].GetName(), 43 | Namespace: openAPIList.Items[idx].GetNamespace(), 44 | }}) 45 | } 46 | 47 | return requests 48 | } 49 | -------------------------------------------------------------------------------- /doc/3scale-diagram.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3scale/3scale-operator/a3d4b4323e8e29d622d691a2f1fe4e4236f82d32/doc/3scale-diagram.dia -------------------------------------------------------------------------------- /doc/3scale-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3scale/3scale-operator/a3d4b4323e8e29d622d691a2f1fe4e4236f82d32/doc/3scale-diagram.png -------------------------------------------------------------------------------- /doc/capabilities-diagram.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3scale/3scale-operator/a3d4b4323e8e29d622d691a2f1fe4e4236f82d32/doc/capabilities-diagram.dia -------------------------------------------------------------------------------- /doc/capabilities-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3scale/3scale-operator/a3d4b4323e8e29d622d691a2f1fe4e4236f82d32/doc/capabilities-diagram.png -------------------------------------------------------------------------------- /doc/cr_samples/activedoc/capabilities_v1beta1_activedoc_secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: ActiveDoc 3 | metadata: 4 | name: activedoc-secret 5 | spec: 6 | name: "Operated ActiveDoc From secret" 7 | activeDocOpenAPIRef: 8 | secretRef: 9 | name: myactivedoc 10 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_apicast_custom_environment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-apicast-custom-environment 5 | spec: 6 | wildcardDomain: 7 | apicast: 8 | productionSpec: 9 | customEnvironments: 10 | - secretRef: 11 | name: env1 12 | - secretRef: 13 | name: env2 14 | stagingSpec: 15 | customEnvironments: 16 | - secretRef: 17 | name: env1 18 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_apicast_custom_policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-apicast-custom-environment 5 | spec: 6 | wildcardDomain: 7 | apicast: 8 | productionSpec: 9 | customPolicies: 10 | - name: Example 11 | version: "0.1" 12 | secretRef: 13 | name: cp-1 14 | stagingSpec: 15 | customPolicies: 16 | - name: Example 17 | version: "0.1" 18 | secretRef: 19 | name: cp-1 20 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_devlatest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-dev-latest-sample 5 | spec: 6 | wildcardDomain: example.com 7 | apicast: 8 | image: "quay.io/3scale/apicast:latest" 9 | backend: 10 | image: "quay.io/3scale/apisonator:latest" 11 | system: 12 | image: "quay.io/3scale/porta:latest" 13 | zync: 14 | image: "quay.io/3scale/zync:latest" 15 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_external_components.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: example-apimanager-external-components-sample 5 | spec: 6 | wildcardDomain: example.com 7 | # External databases mode expects to have pre-created secrets with the desired database URL. 8 | # See reference documentation. 9 | externalComponents: 10 | backend: 11 | redis: true 12 | system: 13 | database: true 14 | redis: true 15 | zync: 16 | database: true 17 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_monitoring.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-monitoring-sample 5 | spec: 6 | wildcardDomain: example.com 7 | monitoring: 8 | enabled: true 9 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_pdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-pdb-sample 5 | spec: 6 | wildcardDomain: example.com 7 | podDisruptionBudget: 8 | enabled: true 9 | -------------------------------------------------------------------------------- /doc/cr_samples/apimanager/apps_v1alpha1_apimanager_s3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.3scale.net/v1alpha1 2 | kind: APIManager 3 | metadata: 4 | name: apimanager-s3-sample 5 | spec: 6 | wildcardDomain: 7 | system: 8 | fileStorage: 9 | simpleStorageService: 10 | configurationSecretRef: 11 | name: 12 | -------------------------------------------------------------------------------- /doc/cr_samples/developeruser/capabilities_v1beta1_developeruser_member.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: DeveloperUser 3 | metadata: 4 | name: developeruser-member-sample 5 | spec: 6 | username: myusername1 7 | email: myusername1@example.com 8 | passwordCredentialsRef: 9 | name: mysecret 10 | developerAccountRef: 11 | name: mydeveloperaccount 12 | -------------------------------------------------------------------------------- /doc/cr_samples/openapi/capabilities_v1beta1_openapi_secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: OpenAPI 3 | metadata: 4 | name: openapi-from-secret 5 | spec: 6 | openapiRef: 7 | secretRef: 8 | name: openapi-secret-name 9 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_apicast_hosted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-apicast-hosted-sample 5 | spec: 6 | name: "OperatedProduct 1" 7 | deployment: 8 | apicastHosted: {} 9 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_apicast_selfmanaged.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-apicast-selfmanaged-sample 5 | spec: 6 | name: "OperatedProduct 1" 7 | deployment: 8 | apicastSelfManaged: 9 | stagingPublicBaseURL: "https://staging.api.example.com" 10 | productionPublicBaseURL: "https://production.api.example.com" 11 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_gateway_response.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-gateway-response 5 | spec: 6 | name: "OperatedProduct 1" 7 | deployment: 8 | apicastHosted: 9 | authentication: 10 | userkey: 11 | gatewayResponse: 12 | errorStatusAuthFailed: 500 13 | errorHeadersAuthFailed: "text/plain; charset=mycharset" 14 | errorAuthFailed: "My custom reponse body" 15 | errorStatusAuthMissing: 500 16 | errorHeadersAuthMissing: "text/plain; charset=mycharset" 17 | errorAuthMissing: "My custom reponse body" 18 | errorStatusNoMatch: 501 19 | errorHeadersNoMatch: "text/plain; charset=mycharset" 20 | errorNoMatch: "My custom reponse body" 21 | errorStatusLimitsExceeded: 502 22 | errorHeadersLimitsExceeded: "text/plain; charset=mycharset" 23 | errorLimitsExceeded: "My custom reponse body" 24 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_mappingrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-mappingrules-sample 5 | spec: 6 | name: "OperatedProduct 1" 7 | metrics: 8 | hits: 9 | description: Number of API hits 10 | friendlyName: Hits 11 | unit: "hit" 12 | methods: 13 | method01: 14 | friendlyName: Method01 15 | mappingRules: 16 | - httpMethod: GET 17 | pattern: "/pets" 18 | increment: 1 19 | metricMethodRef: hits 20 | - httpMethod: GET 21 | pattern: "/cars" 22 | increment: 1 23 | metricMethodRef: method01 24 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_oidc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-oidc 5 | spec: 6 | name: "OperatedProduct 1" 7 | deployment: 8 | apicastHosted: 9 | authentication: 10 | oidc: 11 | issuerType: "keycloak" 12 | issuerEndpoint: "https://myclientid:myclientsecret@mykeycloack.example.com/auth/realms/myrealm" 13 | authenticationFlow: 14 | standardFlowEnabled: false 15 | implicitFlowEnabled: true 16 | serviceAccountsEnabled: true 17 | directAccessGrantsEnabled: true 18 | jwtClaimWithClientID: "azp" 19 | jwtClaimWithClientIDType: "plain" 20 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_plan_limits.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-plan-limits-sample 5 | spec: 6 | name: "OperatedProduct 1" 7 | metrics: 8 | hits: 9 | description: Number of API hits 10 | friendlyName: Hits 11 | unit: "hit" 12 | applicationPlans: 13 | plan01: 14 | name: "My Plan 01" 15 | limits: 16 | - period: month 17 | value: 300 18 | metricMethodRef: 19 | systemName: hits 20 | backend: backendA 21 | - period: week 22 | value: 100 23 | metricMethodRef: 24 | systemName: hits 25 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_plan_pricingrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-plan-pricingrules 5 | spec: 6 | name: "OperatedProduct 1" 7 | metrics: 8 | hits: 9 | description: Number of API hits 10 | friendlyName: Hits 11 | unit: "hit" 12 | applicationPlans: 13 | plan01: 14 | name: "My Plan 01" 15 | pricingRules: 16 | - from: 1 17 | to: 100 18 | pricePerUnit: "15.45" 19 | metricMethodRef: 20 | systemName: hits 21 | - from: 1 22 | to: 300 23 | pricePerUnit: "15.45" 24 | metricMethodRef: 25 | systemName: hits 26 | backend: backendA 27 | -------------------------------------------------------------------------------- /doc/cr_samples/product/capabilities_v1beta1_product_policies.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: capabilities.3scale.net/v1beta1 2 | kind: Product 3 | metadata: 4 | name: product1-policies 5 | spec: 6 | name: "OperatedProduct 1" 7 | policies: 8 | - configuration: 9 | http_proxy: http://example.com 10 | https_proxy: https://example.com 11 | enabled: true 12 | name: camel 13 | version: builtin 14 | - configuration: {} 15 | enabled: true 16 | name: apicast 17 | version: builtin 18 | -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/3scale-scrape-configs.yaml: -------------------------------------------------------------------------------- 1 | - job_name: openshift-monitoring-federation 2 | honor_labels: true 3 | static_configs: 4 | - targets: 5 | - 'prometheus-k8s.openshift-monitoring.svc:9091' 6 | scrape_interval: 30s 7 | metrics_path: /federate 8 | params: 9 | match[]: 10 | - '{endpoint="https-metrics"}' 11 | - '{service="kube-state-metrics"}' 12 | - '{service="node-exporter"}' 13 | - '{__name__=~"namespace_pod_name_container_name:.*"}' 14 | - '{__name__=~"node_namespace_pod_container:.*"}' 15 | - '{__name__=~"node:.*"}' 16 | - '{__name__=~"instance:.*"}' 17 | - '{__name__=~"container_memory_.*"}' 18 | - '{__name__=~":node_memory_.*"}' 19 | scheme: https 20 | tls_config: 21 | insecure_skip_verify: true 22 | bearer_token: "" 23 | metric_relabel_configs: 24 | - action: labeldrop 25 | regex: prometheus_replica 26 | -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/datasource-v4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: integreatly.org/v1alpha1 2 | kind: GrafanaDataSource 3 | metadata: 4 | name: prometheus 5 | spec: 6 | name: middleware 7 | datasources: 8 | - name: Prometheus 9 | type: prometheus 10 | access: proxy 11 | url: http://prometheus-operated:9090 12 | isDefault: true 13 | version: 1 14 | editable: true 15 | jsonData: 16 | timeInterval: "5s" 17 | -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/datasource-v5.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: prometheus 5 | spec: 6 | name: middleware 7 | instanceSelector: 8 | matchLabels: 9 | apim-management: grafana 10 | datasource: 11 | name: Prometheus 12 | type: prometheus 13 | access: proxy 14 | url: http://prometheus-operated:9090 15 | isDefault: true 16 | jsonData: 17 | timeInterval: "5s" -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/grafana-v4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: integreatly.org/v1alpha1 2 | kind: Grafana 3 | metadata: 4 | name: example-grafana 5 | spec: 6 | config: 7 | log: 8 | mode: "console" 9 | level: "debug" 10 | security: 11 | admin_password: "1234" 12 | admin_user: "admin" 13 | auth: 14 | disable_login_form: False 15 | disable_signout_menu: True 16 | auth.basic: 17 | enabled: true 18 | auth.anonymous: 19 | enabled: True 20 | dashboardLabelSelector: 21 | - matchExpressions: 22 | - key: monitoring-key 23 | operator: In 24 | values: 25 | - middleware 26 | ingress: 27 | enabled: true 28 | -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/grafana-v5.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: example-grafana 5 | labels: 6 | apim-management: grafana 7 | spec: 8 | config: 9 | log: 10 | mode: "console" 11 | level: "debug" 12 | security: 13 | admin_password: "1234" 14 | admin_user: "admin" 15 | auth: 16 | disable_login_form: "false" 17 | disable_signout_menu: "true" 18 | auth.basic: 19 | enabled: "true" 20 | auth.anonymous: 21 | enabled: "true" 22 | ingress: 23 | enabled: true -------------------------------------------------------------------------------- /doc/monitoring-stack-deployment/prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: Prometheus 3 | metadata: 4 | labels: 5 | prometheus: k8s 6 | name: example 7 | spec: 8 | externalUrl: https://prometheus.namespace_name.apps.DOMAIN 9 | podMonitorSelector: {} 10 | replicas: 1 11 | ruleSelector: {} 12 | securityContext: {} 13 | serviceAccountName: prometheus-k8s 14 | serviceMonitorSelector: {} 15 | additionalScrapeConfigs: 16 | key: 3scale-scrape-configs.yaml 17 | name: additional-scrape-configs 18 | -------------------------------------------------------------------------------- /doc/prometheusrules/README.md: -------------------------------------------------------------------------------- 1 | ## 3scale PrometheusRules 2 | 3 | ### Index 4 | 5 | * [Apicast](apicast.yaml) 6 | * [Backend Listener](backend-listener.yaml) 7 | * [Backend Worker](backend-worker.yaml) 8 | * [System App](system-app.yaml) 9 | * [System Sidekiq](system-sidekiq.yaml) 10 | * [3scale Kube State Metrics](threescale-kube-state-metrics.yaml) 11 | * [3scale Kube State Metrics (Openshift <4.9)](threescale-kube-state-metrics-pre49.yaml) 12 | * [Zync](zync.yaml) 13 | * [Zync QUE](zync-que.yaml) 14 | 15 | ### Namespaced prometheus rules 16 | 17 | Published prometheus rules are namespaced with the generic `__NAMESPACE__` token. 18 | The namespacing avoids conflicts when multiple 3scale instances are deployed in a cluster. 19 | 20 | Before deploying the prometheus rules, make sure you modify the prometheus rules resources with 21 | your desired namespace. It can be easily done, for instance, for the apicast prometheus rules: 22 | 23 | ```bash 24 | sed -i 's/__NAMESPACE__/mynamespace/g' apicast.yaml 25 | ``` 26 | 27 | Optionally, you can generate the prometheus rules with your own namespace name. 28 | Read the section about [Building 3scale prometheus rules](/doc/development.md#building-3scale-prometheus-rules) 29 | to follow the steps and know about required development tools needed to be installed. 30 | 31 | ### Tune the prometheus rules based on your infraestructure 32 | 33 | If you decided to not have the 3scale operator deploy the prometheus rules for you, 34 | it is clear that you want to tune them to your own needs. Before deploying the published prometheus rules, 35 | make sure you pay attention to: 36 | 37 | * Rule expression conditions and thresholds 38 | * Duration of the rule (the `for` fieldp) 39 | * Severity of the rule 40 | 41 | -------------------------------------------------------------------------------- /doc/prometheusrules/backend-listener.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PrometheusRule 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | prometheus: application-monitoring 8 | role: alert-rules 9 | threescale_component: backend 10 | name: backend-listener 11 | spec: 12 | groups: 13 | - name: __NAMESPACE__/backend-listener.rules 14 | rules: 15 | - alert: ThreescaleBackendListener5XXRequestsHigh 16 | annotations: 17 | description: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 18 | 5000 HTTP 5xx requests in the last 5 minutes 19 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/backend_listener_5xx_requests_high.adoc 20 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 5000 21 | HTTP 5xx requests in the last 5 minutes 22 | expr: sum(rate(apisonator_listener_response_codes{job=~"backend.*",namespace="__NAMESPACE__",resp_code="5xx"}[5m])) 23 | by (namespace,job,resp_code) > 5000 24 | for: 5m 25 | labels: 26 | severity: critical 27 | - alert: ThreescaleBackendListenerJobDown 28 | annotations: 29 | description: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 30 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/prometheus_job_down.adoc 31 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 32 | expr: up{job=~".*backend-listener.*",namespace="__NAMESPACE__"} == 0 33 | for: 1m 34 | labels: 35 | severity: critical 36 | -------------------------------------------------------------------------------- /doc/prometheusrules/backend-worker.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PrometheusRule 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | prometheus: application-monitoring 8 | role: alert-rules 9 | threescale_component: backend 10 | name: backend-worker 11 | spec: 12 | groups: 13 | - name: __NAMESPACE__/backend-worker.rules 14 | rules: 15 | - alert: ThreescaleBackendWorkerJobsCountRunningHigh 16 | annotations: 17 | description: '{{$labels.container_name}} replica controller on {{$labels.namespace}} 18 | project: Has more than 1000 jobs processed in the last 5 minutes' 19 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/backend_worker_jobs_count_running_high.adoc 20 | summary: '{{$labels.container_name}} replica controller on {{$labels.namespace}}: 21 | Has more than 10000 jobs processed in the last 5 minutes' 22 | expr: sum(avg_over_time(apisonator_worker_job_count{job=~"backend.*",namespace="__NAMESPACE__"} 23 | [5m])) by (namespace,job) > 10000 24 | for: 5m 25 | labels: 26 | severity: critical 27 | - alert: ThreescaleBackendWorkerJobDown 28 | annotations: 29 | description: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 30 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/prometheus_job_down.adoc 31 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 32 | expr: up{job=~".*backend-worker.*",namespace="__NAMESPACE__"} == 0 33 | for: 1m 34 | labels: 35 | severity: critical 36 | -------------------------------------------------------------------------------- /doc/prometheusrules/system-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PrometheusRule 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | prometheus: application-monitoring 8 | role: alert-rules 9 | threescale_component: system 10 | name: system-app 11 | spec: 12 | groups: 13 | - name: __NAMESPACE__/system-app.rules 14 | rules: 15 | - alert: ThreescaleSystemApp5XXRequestsHigh 16 | annotations: 17 | description: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 18 | 50 HTTP 5xx requests in the last minute 19 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/system_app_5xx_requests_high.adoc 20 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 50 21 | HTTP 5xx requests in the last minute 22 | expr: sum(rate(rails_requests_total{namespace="__NAMESPACE__",pod=~"system-app-[a-z0-9]+-[a-z0-9]+",status=~"5[0-9]*"}[1m])) 23 | by (namespace,job) > 50 24 | for: 1m 25 | labels: 26 | severity: warning 27 | - alert: ThreescaleSystemAppJobDown 28 | annotations: 29 | description: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 30 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/prometheus_job_down.adoc 31 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 32 | expr: up{job=~".*system-app.*",namespace="__NAMESPACE__"} == 0 33 | for: 1m 34 | labels: 35 | severity: critical 36 | -------------------------------------------------------------------------------- /doc/prometheusrules/system-sidekiq.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PrometheusRule 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | prometheus: application-monitoring 8 | role: alert-rules 9 | threescale_component: system 10 | name: system-sidekiq 11 | spec: 12 | groups: 13 | - name: __NAMESPACE__/system-sidekiq.rules 14 | rules: 15 | - alert: ThreescaleSystemSidekiqJobDown 16 | annotations: 17 | description: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 18 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/prometheus_job_down.adoc 19 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 20 | expr: up{job=~".*system-sidekiq.*",namespace="__NAMESPACE__"} == 0 21 | for: 1m 22 | labels: 23 | severity: critical 24 | -------------------------------------------------------------------------------- /doc/prometheusrules/zync.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PrometheusRule 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: 3scale-api-management 7 | prometheus: application-monitoring 8 | role: alert-rules 9 | threescale_component: zync 10 | name: zync 11 | spec: 12 | groups: 13 | - name: __NAMESPACE__/zync.rules 14 | rules: 15 | - alert: ThreescaleZyncJobDown 16 | annotations: 17 | description: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 18 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/prometheus_job_down.adoc 19 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} is DOWN 20 | expr: up{job=~".*/zync",namespace="__NAMESPACE__"} == 0 21 | for: 1m 22 | labels: 23 | severity: critical 24 | - alert: ThreescaleZync5XXRequestsHigh 25 | annotations: 26 | description: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 27 | 50 HTTP 5xx requests in the last minute 28 | sop_url: https://github.com/3scale/3scale-Operations/blob/master/sops/alerts/zync_5xx_requests_high.adoc 29 | summary: Job {{ $labels.job }} on {{ $labels.namespace }} has more than 50 30 | HTTP 5xx requests in the last minute 31 | expr: sum(rate(rails_requests_total{namespace="__NAMESPACE__",pod=~"zync-[a-z0-9]+-[a-z0-9]+",status=~"5[0-9]*"}[1m])) 32 | by (namespace,job) > 50 33 | for: 1m 34 | labels: 35 | severity: warning 36 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat. 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 | */ -------------------------------------------------------------------------------- /make/jaeger.mk: -------------------------------------------------------------------------------- 1 | ##@ Jaeger 2 | 3 | jaeger-deploy: 4 | $(KUSTOMIZE) build config/jaeger | $(KUBECTL) apply -f - 5 | 6 | apicast-opentelemetry-deploy: 7 | $(KUSTOMIZE) build examples/opentelemetry | $(KUBECTL) apply -f - -------------------------------------------------------------------------------- /pkg/3scale/amp/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | /vendor 3 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/ampimages.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | type AmpImages struct { 9 | Options *AmpImagesOptions 10 | } 11 | 12 | func NewAmpImages(options *AmpImagesOptions) *AmpImages { 13 | return &AmpImages{Options: options} 14 | } 15 | 16 | func (ampImages *AmpImages) DeploymentsServiceAccount() *v1.ServiceAccount { 17 | return &v1.ServiceAccount{ 18 | TypeMeta: metav1.TypeMeta{ 19 | Kind: "ServiceAccount", 20 | APIVersion: "v1", 21 | }, 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: "amp", 24 | }, 25 | ImagePullSecrets: ampImages.Options.ImagePullSecrets, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/ampimages_options.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | v1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | // AmpImagesOptions container object with all required to create components 9 | type AmpImagesOptions struct { 10 | AppLabel string `validate:"required"` 11 | AmpRelease string `validate:"required"` 12 | ApicastImage string `validate:"required"` 13 | BackendImage string `validate:"required"` 14 | SystemImage string `validate:"required"` 15 | ZyncImage string `validate:"required"` 16 | ZyncDatabasePostgreSQLImage string `validate:"required"` 17 | SystemMemcachedImage string `validate:"required"` 18 | SystemSearchdImage string `validate:"required"` 19 | ImagePullSecrets []v1.LocalObjectReference `validate:"required"` 20 | } 21 | 22 | func NewAmpImagesOptions() *AmpImagesOptions { 23 | return &AmpImagesOptions{} 24 | } 25 | 26 | func (a *AmpImagesOptions) Validate() error { 27 | validate := validator.New() 28 | return validate.Struct(a) 29 | } 30 | 31 | func AmpImagesDefaultImagePullSecrets() []v1.LocalObjectReference { 32 | return []v1.LocalObjectReference{} 33 | } 34 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/deployments_lister.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | type SystemDatabaseType string 4 | 5 | const ( 6 | SystemDatabaseTypeExternal SystemDatabaseType = "external" 7 | ) 8 | 9 | type DeploymentsLister struct { 10 | ExternalZyncDatabase bool 11 | IsZyncEnabled bool 12 | } 13 | 14 | func (d *DeploymentsLister) DeploymentNames() []string { 15 | var deployments []string 16 | deployments = append(deployments, 17 | ApicastStagingName, 18 | ApicastProductionName, 19 | BackendListenerName, 20 | BackendWorkerName, 21 | BackendCronName, 22 | SystemMemcachedDeploymentName, 23 | SystemAppDeploymentName, 24 | SystemSidekiqName, 25 | SystemSearchdDeploymentName, 26 | ) 27 | 28 | if d.IsZyncEnabled { 29 | deployments = append(deployments, ZyncName) 30 | deployments = append(deployments, ZyncQueDeploymentName) 31 | } 32 | 33 | if !d.ExternalZyncDatabase && d.IsZyncEnabled { 34 | deployments = append(deployments, ZyncDatabaseDeploymentName) 35 | } 36 | 37 | return deployments 38 | } 39 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/highavailability_options.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import "github.com/go-playground/validator/v10" 4 | 5 | type HighAvailabilityOptions struct { 6 | BackendRedisQueuesEndpoint string 7 | BackendRedisQueuesSentinelHosts string 8 | BackendRedisQueuesSentinelRole string 9 | BackendRedisStorageEndpoint string 10 | BackendRedisStorageSentinelHosts string 11 | BackendRedisStorageSentinelRole string 12 | SystemDatabaseURL string 13 | SystemDatabaseSslCa string 14 | SystemDatabaseSslMode string 15 | SystemDatabaseSslCert string 16 | SystemDatabaseSslKey string 17 | SystemRedisURL string 18 | SystemRedisSentinelsHosts string 19 | SystemRedisSentinelsRole string 20 | ZyncDatabaseURL string 21 | ZyncDatabasePassword string 22 | ZyncDatabaseSslCa string 23 | ZyncDatabaseSslMode string 24 | ZyncDatabaseSslCert string 25 | ZyncDatabaseSslKey string 26 | SystemRedisSslCa string 27 | SystemRedisSslCert string 28 | SystemRedisSslKey string 29 | BackendRedisSslCa string 30 | BackendRedisSslCert string 31 | BackendRedisSslKey string 32 | BackendRedisQueuesSslCa string 33 | BackendRedisQueuesSslCert string 34 | BackendRedisQueuesSslKey string 35 | } 36 | 37 | func NewHighAvailabilityOptions() *HighAvailabilityOptions { 38 | return &HighAvailabilityOptions{} 39 | } 40 | 41 | func (h *HighAvailabilityOptions) Validate() error { 42 | validate := validator.New() 43 | return validate.Struct(h) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/hpa.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "github.com/3scale/3scale-operator/pkg/helper" 5 | hpa "k8s.io/api/autoscaling/v2" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func DefaultHpa(name string, namespace string) *hpa.HorizontalPodAutoscaler { 10 | minPods := helper.Int32Ptr(1) 11 | maxPods := int32(5) 12 | cpuPercent := helper.Int32Ptr(85) 13 | memoryPercent := helper.Int32Ptr(85) 14 | 15 | return &hpa.HorizontalPodAutoscaler{ 16 | ObjectMeta: metav1.ObjectMeta{ 17 | Name: name, 18 | Namespace: namespace, 19 | }, 20 | Spec: hpa.HorizontalPodAutoscalerSpec{ 21 | ScaleTargetRef: hpa.CrossVersionObjectReference{ 22 | Kind: "Deployment", 23 | Name: name, 24 | APIVersion: "apps/v1", 25 | }, 26 | MinReplicas: minPods, 27 | MaxReplicas: maxPods, 28 | Metrics: []hpa.MetricSpec{ 29 | { 30 | Type: hpa.ResourceMetricSourceType, 31 | Resource: &hpa.ResourceMetricSource{ 32 | Name: "memory", 33 | Target: hpa.MetricTarget{ 34 | Type: hpa.UtilizationMetricType, 35 | AverageUtilization: memoryPercent, 36 | }, 37 | }, 38 | }, 39 | { 40 | Type: hpa.ResourceMetricSourceType, 41 | Resource: &hpa.ResourceMetricSource{ 42 | Name: "cpu", 43 | Target: hpa.MetricTarget{ 44 | Type: hpa.UtilizationMetricType, 45 | AverageUtilization: cpuPercent, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/images.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | func ApicastImageURL() string { 4 | return "quay.io/3scale/apicast:latest" 5 | } 6 | 7 | func BackendImageURL() string { 8 | return "quay.io/3scale/apisonator:latest" 9 | } 10 | 11 | func SystemImageURL() string { 12 | return "quay.io/3scale/porta:latest" 13 | } 14 | 15 | func SystemSearchdImageURL() string { 16 | return "quay.io/3scale/searchd:latest" 17 | } 18 | 19 | func ZyncImageURL() string { 20 | return "quay.io/3scale/zync:latest" 21 | } 22 | 23 | func SystemMemcachedImageURL() string { 24 | return "mirror.gcr.io/library/memcached:1.5" 25 | } 26 | 27 | func ZyncPostgreSQLImageURL() string { 28 | return "quay.io/sclorg/postgresql-15-c8s" 29 | } 30 | 31 | func OCCLIImageURL() string { 32 | return "quay.io/openshift/origin-cli:4.7" 33 | } 34 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/memcached_options.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | v1 "k8s.io/api/core/v1" 6 | "k8s.io/apimachinery/pkg/api/resource" 7 | ) 8 | 9 | type MemcachedOptions struct { 10 | ImageTag string `validate:"required"` 11 | ResourceRequirements v1.ResourceRequirements `validate:"-"` 12 | 13 | Affinity *v1.Affinity `validate:"-"` 14 | Tolerations []v1.Toleration `validate:"-"` 15 | 16 | DeploymentLabels map[string]string `validate:"required"` 17 | PodTemplateLabels map[string]string `validate:"required"` 18 | 19 | PriorityClassName string `validate:"-"` 20 | TopologySpreadConstraints []v1.TopologySpreadConstraint `validate:"-"` 21 | PodTemplateAnnotations map[string]string `validate:"-"` 22 | } 23 | 24 | func NewMemcachedOptions() *MemcachedOptions { 25 | return &MemcachedOptions{} 26 | } 27 | 28 | func (m *MemcachedOptions) Validate() error { 29 | validate := validator.New() 30 | return validate.Struct(m) 31 | } 32 | 33 | func DefaultMemcachedResourceRequirements() v1.ResourceRequirements { 34 | return v1.ResourceRequirements{ 35 | Limits: v1.ResourceList{ 36 | v1.ResourceCPU: resource.MustParse("250m"), 37 | v1.ResourceMemory: resource.MustParse("96Mi"), 38 | }, 39 | Requests: v1.ResourceList{ 40 | v1.ResourceCPU: resource.MustParse("50m"), 41 | v1.ResourceMemory: resource.MustParse("64Mi"), 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/pod_disruption_budget.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | const ( 4 | PDB_MAX_UNAVAILABLE_POD_NUMBER = 1 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/3scale/amp/component/system_searchd_options.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | v1 "k8s.io/api/core/v1" 6 | "k8s.io/apimachinery/pkg/api/resource" 7 | ) 8 | 9 | type SearchdPVCOptions struct { 10 | StorageClass *string 11 | VolumeName string 12 | StorageRequests resource.Quantity `validate:"required"` 13 | } 14 | 15 | type SystemSearchdOptions struct { 16 | ContainerResourceRequirements v1.ResourceRequirements `validate:"required"` 17 | ImageTag string `validate:"required"` 18 | 19 | Affinity *v1.Affinity `validate:"-"` 20 | Tolerations []v1.Toleration `validate:"-"` 21 | 22 | Labels map[string]string `validate:"required"` 23 | PodTemplateLabels map[string]string `validate:"required"` 24 | 25 | PVCOptions SearchdPVCOptions `validate:"required"` 26 | PriorityClassName string `validate:"-"` 27 | TopologySpreadConstraints []v1.TopologySpreadConstraint `validate:"-"` 28 | PodTemplateAnnotations map[string]string `validate:"-"` 29 | 30 | SearchdDbTLSEnabled bool 31 | } 32 | 33 | func NewSystemSearchdOptions() *SystemSearchdOptions { 34 | return &SystemSearchdOptions{} 35 | } 36 | 37 | func (s *SystemSearchdOptions) Validate() error { 38 | validate := validator.New() 39 | return validate.Struct(s) 40 | } 41 | 42 | func DefaultSearchdContainerResourceRequirements() v1.ResourceRequirements { 43 | return v1.ResourceRequirements{ 44 | Limits: v1.ResourceList{ 45 | v1.ResourceCPU: resource.MustParse("1000m"), 46 | v1.ResourceMemory: resource.MustParse("512Mi"), 47 | }, 48 | Requests: v1.ResourceList{ 49 | v1.ResourceCPU: resource.MustParse("80m"), 50 | v1.ResourceMemory: resource.MustParse("250Mi"), 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/3scale/amp/main.go: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | // See the License for the specific language governing permissions and 11 | // limitations under the License. 12 | 13 | package main 14 | 15 | import "github.com/3scale/3scale-operator/pkg/3scale/amp/cmd" 16 | 17 | func main() { 18 | cmd.Execute() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/3scale/amp/operator/ampimages_reconciler.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 5 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 6 | "github.com/3scale/3scale-operator/pkg/reconcilers" 7 | 8 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 9 | ) 10 | 11 | type AMPImagesReconciler struct { 12 | *BaseAPIManagerLogicReconciler 13 | } 14 | 15 | func NewAMPImagesReconciler(baseAPIManagerLogicReconciler *BaseAPIManagerLogicReconciler) *AMPImagesReconciler { 16 | return &ImagesReconciler{ 17 | BaseAPIManagerLogicReconciler: baseAPIManagerLogicReconciler, 18 | } 19 | } 20 | 21 | func (r *AMPImagesReconciler) Reconcile() (reconcile.Result, error) { 22 | ampImages, err := AmpImages(r.apiManager) 23 | if err != nil { 24 | return reconcile.Result{}, err 25 | } 26 | 27 | err = r.ReconcileServiceAccount(ampImages.DeploymentsServiceAccount(), reconcilers.ServiceAccountImagePullPolicyMutator) 28 | if err != nil { 29 | return reconcile.Result{}, err 30 | } 31 | 32 | return reconcile.Result{}, nil 33 | } 34 | 35 | func AmpImages(apimanager *appsv1alpha1.APIManager) (*component.AmpImages, error) { 36 | optsProvider := NewAmpImagesOptionsProvider(apimanager) 37 | opts, err := optsProvider.GetAmpImagesOptions() 38 | if err != nil { 39 | return nil, err 40 | } 41 | return component.NewAmpImages(opts), nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/3scale/amp/operator/apimanager_utils.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 9 | ) 10 | 11 | const ( 12 | APIManagerSecretLabelPrefix = "secret.apimanager.apps.3scale.net/" 13 | ) 14 | 15 | func apimanagerSecretLabelKey(uid string) string { 16 | return fmt.Sprintf("%s%s", APIManagerSecretLabelPrefix, uid) 17 | } 18 | 19 | func replaceAPIManagerSecretLabels(apimanager *appsv1alpha1.APIManager, desiredSecretUIDs map[string]string) bool { 20 | existingLabels := apimanager.GetLabels() 21 | 22 | if existingLabels == nil { 23 | existingLabels = map[string]string{} 24 | } 25 | 26 | existingSecretLabels := map[string]string{} 27 | 28 | // existing Secret UIDs not included in desiredAPIUIDs are deleted 29 | for key, value := range existingLabels { 30 | if strings.HasPrefix(key, APIManagerSecretLabelPrefix) { 31 | existingSecretLabels[key] = value 32 | // it is safe to remove keys while looping in range 33 | delete(existingLabels, key) 34 | } 35 | } 36 | 37 | desiredSecretLabels := map[string]string{} 38 | for uid, watchedByStatus := range desiredSecretUIDs { 39 | desiredSecretLabels[apimanagerSecretLabelKey(uid)] = watchedByStatus 40 | existingLabels[apimanagerSecretLabelKey(uid)] = watchedByStatus 41 | } 42 | 43 | apimanager.SetLabels(existingLabels) 44 | 45 | return !reflect.DeepEqual(existingSecretLabels, desiredSecretLabels) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/3scale/amp/operator/dependency_reconciler.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 5 | ) 6 | 7 | // DependencyReconciler is an object that reconciles a dependency for a component 8 | type DependencyReconciler interface { 9 | Reconcile() (reconcile.Result, error) 10 | } 11 | 12 | // DependencyReconcilerConstructor is the standard function to instantiate 13 | // a DependencyReconciler 14 | type DependencyReconcilerConstructor func(*BaseAPIManagerLogicReconciler) DependencyReconciler 15 | 16 | // CompositeDependecyReconcilerConstructor creates a single DependencyReconcilerConstructor 17 | // from multiple ones that instantiates a CompositeDependencyReconciler with 18 | // the instantiated DependencyReconcilers 19 | func CompositeDependencyReconcilerConstructor(constructors ...DependencyReconcilerConstructor) DependencyReconcilerConstructor { 20 | return func(b *BaseAPIManagerLogicReconciler) DependencyReconciler { 21 | reconcilers := make([]DependencyReconciler, len(constructors)) 22 | 23 | for i, constructor := range constructors { 24 | reconcilers[i] = constructor(b) 25 | } 26 | 27 | return &CompositeDependencyReconciler{ 28 | Reconcilers: reconcilers, 29 | } 30 | } 31 | } 32 | 33 | type CompositeDependencyReconciler struct { 34 | Reconcilers []DependencyReconciler 35 | } 36 | 37 | var _ DependencyReconciler = &CompositeDependencyReconciler{} 38 | 39 | func (r *CompositeDependencyReconciler) Reconcile() (reconcile.Result, error) { 40 | for _, ir := range r.Reconcilers { 41 | result, err := ir.Reconcile() 42 | if result.Requeue || err != nil { 43 | return result, err 44 | } 45 | } 46 | 47 | return reconcile.Result{}, nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/3scale/amp/operator/images.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 5 | "github.com/3scale/3scale-operator/pkg/helper" 6 | ) 7 | 8 | func ApicastImageURL() string { 9 | return helper.GetEnvVar("RELATED_IMAGE_APICAST", component.ApicastImageURL()) 10 | } 11 | 12 | func BackendImageURL() string { 13 | return helper.GetEnvVar("RELATED_IMAGE_BACKEND", component.BackendImageURL()) 14 | } 15 | 16 | func SystemImageURL() string { 17 | return helper.GetEnvVar("RELATED_IMAGE_SYSTEM", component.SystemImageURL()) 18 | } 19 | 20 | func ZyncImageURL() string { 21 | return helper.GetEnvVar("RELATED_IMAGE_ZYNC", component.ZyncImageURL()) 22 | } 23 | 24 | func SystemMemcachedImageURL() string { 25 | return helper.GetEnvVar("RELATED_IMAGE_SYSTEM_MEMCACHED", component.SystemMemcachedImageURL()) 26 | } 27 | 28 | func SystemSearchdImageURL() string { 29 | return helper.GetEnvVar("RELATED_IMAGE_SYSTEM_SEARCHD", component.SystemSearchdImageURL()) 30 | } 31 | 32 | func ZyncPostgreSQLImageURL() string { 33 | return helper.GetEnvVar("RELATED_IMAGE_ZYNC_POSTGRESQL", component.ZyncPostgreSQLImageURL()) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/3scale/amp/operator/memcached_reconciler.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 5 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 6 | "github.com/3scale/3scale-operator/pkg/reconcilers" 7 | 8 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 9 | ) 10 | 11 | type MemcachedReconciler struct { 12 | *BaseAPIManagerLogicReconciler 13 | } 14 | 15 | func NewMemcachedReconciler(baseAPIManagerLogicReconciler *BaseAPIManagerLogicReconciler) *MemcachedReconciler { 16 | return &MemcachedReconciler{ 17 | BaseAPIManagerLogicReconciler: baseAPIManagerLogicReconciler, 18 | } 19 | } 20 | 21 | func (r *MemcachedReconciler) Reconcile() (reconcile.Result, error) { 22 | ampImages, err := AmpImages(r.apiManager) 23 | if err != nil { 24 | return reconcile.Result{}, err 25 | } 26 | 27 | memcached, err := Memcached(r.apiManager) 28 | if err != nil { 29 | return reconcile.Result{}, err 30 | } 31 | 32 | // Deployment 33 | mutator := reconcilers.DeploymentMutator( 34 | reconcilers.DeploymentContainerResourcesMutator, 35 | reconcilers.DeploymentAffinityMutator, 36 | reconcilers.DeploymentTolerationsMutator, 37 | reconcilers.DeploymentPodTemplateLabelsMutator, 38 | reconcilers.DeploymentPriorityClassMutator, 39 | reconcilers.DeploymentTopologySpreadConstraintsMutator, 40 | reconcilers.DeploymentPodTemplateAnnotationsMutator, 41 | reconcilers.DeploymentPodContainerImageMutator, 42 | ) 43 | err = r.ReconcileDeployment(memcached.Deployment(ampImages.Options.SystemMemcachedImage), mutator) 44 | if err != nil { 45 | return reconcile.Result{}, err 46 | } 47 | 48 | return reconcile.Result{}, nil 49 | } 50 | 51 | func Memcached(apimanager *appsv1alpha1.APIManager) (*component.Memcached, error) { 52 | optsProvider := NewMemcachedOptionsProvider(apimanager) 53 | opts, err := optsProvider.GetMemcachedOptions() 54 | if err != nil { 55 | return nil, err 56 | } 57 | return component.NewMemcached(opts), nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/3scale/amp/prometheusrules/backend_listener_rules.go: -------------------------------------------------------------------------------- 1 | package prometheusrules 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | 6 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 7 | ) 8 | 9 | func init() { 10 | PrometheusRuleFactories = append(PrometheusRuleFactories, NewBackendListenerPrometheusRuleFactory) 11 | } 12 | 13 | type BackendListenerPrometheusRuleFactory struct{} 14 | 15 | func NewBackendListenerPrometheusRuleFactory() PrometheusRuleFactory { 16 | return &BackendListenerPrometheusRuleFactory{} 17 | } 18 | 19 | func (b *BackendListenerPrometheusRuleFactory) Type() string { 20 | return "backend-listener" 21 | } 22 | 23 | func (b *BackendListenerPrometheusRuleFactory) PrometheusRule(_ bool, ns string) *monitoringv1.PrometheusRule { 24 | options, err := backendOptions(ns) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return component.NewBackend(options).BackendListenerPrometheusRules() 29 | } 30 | -------------------------------------------------------------------------------- /pkg/3scale/amp/prometheusrules/factory.go: -------------------------------------------------------------------------------- 1 | package prometheusrules 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | ) 6 | 7 | type PrometheusRuleFactory interface { 8 | PrometheusRule(compatPre49 bool, ns string) *monitoringv1.PrometheusRule 9 | Type() string 10 | } 11 | 12 | type PrometheusRuleFactoryBuilder = func() PrometheusRuleFactory 13 | 14 | // PrometheusRuleFactories is a list of prometheusrule factories 15 | var PrometheusRuleFactories []PrometheusRuleFactoryBuilder 16 | -------------------------------------------------------------------------------- /pkg/3scale/amp/prometheusrules/kube_state_metrics_rules.go: -------------------------------------------------------------------------------- 1 | package prometheusrules 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | 6 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 7 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 8 | ) 9 | 10 | func init() { 11 | PrometheusRuleFactories = append(PrometheusRuleFactories, NewKubeStateMetricsPrometheusRuleFactory) 12 | } 13 | 14 | type KubeStateMetricsPrometheusRuleFactory struct{} 15 | 16 | func NewKubeStateMetricsPrometheusRuleFactory() PrometheusRuleFactory { 17 | return &KubeStateMetricsPrometheusRuleFactory{} 18 | } 19 | 20 | func (s *KubeStateMetricsPrometheusRuleFactory) Type() string { 21 | return "threescale-kube-state-metrics" 22 | } 23 | 24 | func (s *KubeStateMetricsPrometheusRuleFactory) PrometheusRule(compatPre49 bool, ns string) *monitoringv1.PrometheusRule { 25 | sumRate := "sum_irate" 26 | if compatPre49 { 27 | sumRate = "sum_rate" 28 | } 29 | 30 | appLabel := appsv1alpha1.Default3scaleAppLabel 31 | return component.KubeStateMetricsPrometheusRules(sumRate, ns, appLabel) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/3scale/amp/prometheusrules/system_sidekiq_rules.go: -------------------------------------------------------------------------------- 1 | package prometheusrules 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | 6 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 7 | ) 8 | 9 | func init() { 10 | PrometheusRuleFactories = append(PrometheusRuleFactories, NewSystemSidekiqPrometheusRuleFactory) 11 | } 12 | 13 | type SystemSidekiqPrometheusRuleFactory struct{} 14 | 15 | func NewSystemSidekiqPrometheusRuleFactory() PrometheusRuleFactory { 16 | return &SystemSidekiqPrometheusRuleFactory{} 17 | } 18 | 19 | func (s *SystemSidekiqPrometheusRuleFactory) Type() string { 20 | return "system-sidekiq" 21 | } 22 | 23 | func (s *SystemSidekiqPrometheusRuleFactory) PrometheusRule(_ bool, ns string) *monitoringv1.PrometheusRule { 24 | options, err := systemOptions(ns) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return component.NewSystem(options).SystemSidekiqPrometheusRules() 29 | } 30 | -------------------------------------------------------------------------------- /pkg/3scale/amp/prometheusrules/zync_que_rules.go: -------------------------------------------------------------------------------- 1 | package prometheusrules 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | 6 | "github.com/3scale/3scale-operator/pkg/3scale/amp/component" 7 | ) 8 | 9 | func init() { 10 | PrometheusRuleFactories = append(PrometheusRuleFactories, NewZyncQuePrometheusRuleFactory) 11 | } 12 | 13 | type ZyncQuePrometheusRuleFactory struct{} 14 | 15 | func NewZyncQuePrometheusRuleFactory() PrometheusRuleFactory { 16 | return &ZyncQuePrometheusRuleFactory{} 17 | } 18 | 19 | func (s *ZyncQuePrometheusRuleFactory) Type() string { 20 | return "zync-que" 21 | } 22 | 23 | func (s *ZyncQuePrometheusRuleFactory) PrometheusRule(_ bool, ns string) *monitoringv1.PrometheusRule { 24 | options, err := zyncOptions(ns) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return component.NewZync(options).ZyncQuePrometheusRules() 29 | } 30 | -------------------------------------------------------------------------------- /pkg/apispkg/common/version_compare.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func CompareMinorVersions(v1, v2 string) (bool, error) { 10 | second1, err := extractSecondValue(v1) 11 | if err != nil { 12 | // handle error 13 | return false, err 14 | } 15 | 16 | second2, err := extractSecondValue(v2) 17 | if err != nil { 18 | // handle error 19 | return false, err 20 | } 21 | 22 | difference := second1 - second2 23 | result := difference != 0 && difference != 1 && difference != -1 24 | return result, err 25 | } 26 | 27 | func extractSecondValue(version string) (int, error) { 28 | parts := strings.Split(version, ".") 29 | if len(parts) < 2 { 30 | return 0, fmt.Errorf("invalid version format: %s", version) 31 | } 32 | 33 | second, err := strconv.Atoi(parts[1]) 34 | if err != nil { 35 | return 0, err 36 | } 37 | 38 | return second, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/apispkg/common/version_compare_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVersionCompare(t *testing.T) { 8 | cases := []struct { 9 | testName string 10 | currentVersion string 11 | incomingVersion string 12 | expectedResult bool 13 | }{ 14 | {"MultiHopDetected", "2.11", "2.15", true}, 15 | {"MultiHopNotDetected", "2.14", "2.15", false}, 16 | } 17 | 18 | for _, tc := range cases { 19 | t.Run(tc.testName, func(subT *testing.T) { 20 | value, _ := CompareMinorVersions(tc.currentVersion, tc.incomingVersion) 21 | if value != tc.expectedResult { 22 | subT.Fatalf("test failed for test case %s, expected %v but got %v", tc.testName, tc.expectedResult, value) 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/apispkg/helper/email.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 8 | 9 | // IsEmailValid checks the structure of the email address 10 | // Based on regex by W3C https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#email-state-typeemail 11 | func IsEmailValid(e string) bool { 12 | if len(e) < 3 && len(e) > 254 { 13 | return false 14 | } 15 | return emailRegex.MatchString(e) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | // Important: Run "make" to regenerate code after modifying/adding/removing any asset 9 | // adding -nometadata to not preserve size, mode, and modtime info. It should not be necessary as the content will be embedded in custom resources. 10 | //go:generate go-bindata --prefix assets -pkg $GOPACKAGE -nometadata -o bindata.go assets/... 11 | 12 | // SafeStringAsset Returns asset data as string 13 | // panic if not found or any err is detected 14 | func SafeStringAsset(name string) string { 15 | data, err := Asset(name) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | return string(data) 21 | } 22 | 23 | // TemplateAsset Executes one tamplate by applying it to a daata structure. 24 | // panic if not found or any err is detected 25 | func TemplateAsset(name string, data interface{}) string { 26 | tObj, err := template.New(name).Parse(SafeStringAsset(name)) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | var tpl bytes.Buffer 32 | err = tObj.Execute(&tpl, data) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | return tpl.String() 38 | } 39 | -------------------------------------------------------------------------------- /pkg/backup/apimanager_backup_options.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1" 5 | validator "github.com/go-playground/validator/v10" 6 | "k8s.io/apimachinery/pkg/types" 7 | ) 8 | 9 | type APIManagerBackupOptions struct { 10 | Namespace string `validate:"required"` // Namespace where the K8s related objects to the backup will be created/looked 11 | APIManagerBackupName string `validate:"required"` // Name of the APIManagerBackup CR. NOT the APIManager cr name 12 | APIManagerBackupUID types.UID `validate:"required"` // UID of the APIManagerBackup CR 13 | APIManagerName string `validate:"required"` // Name of the APIManager CR. NOT the APIManagerBackup cr name 14 | APIManager *appsv1alpha1.APIManager `validate:"required"` 15 | APIManagerBackupPVCOptions *APIManagerBackupPVCOptions `validate:"required"` 16 | OCCLIImageURL string `validate:"required"` 17 | } 18 | 19 | func NewAPIManagerBackupOptions() *APIManagerBackupOptions { 20 | return &APIManagerBackupOptions{} 21 | } 22 | 23 | func (a *APIManagerBackupOptions) Validate() error { 24 | validate := validator.New() 25 | return validate.Struct(a) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/backup/apimanager_backup_pvc_options.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | validator "github.com/go-playground/validator/v10" 5 | "k8s.io/apimachinery/pkg/api/resource" 6 | ) 7 | 8 | type APIManagerBackupPVCOptions struct { 9 | BackupDestinationPVC BackupDestinationPVC `validate:"required"` 10 | } 11 | 12 | type BackupDestinationPVC struct { 13 | Name string `validate:"required"` 14 | StorageClass *string 15 | VolumeName *string 16 | StorageRequests *resource.Quantity // TODO should we validate resource.Quantity in case we define it? 17 | } 18 | 19 | func NewAPIManagerBackupPVCOptions() *APIManagerBackupPVCOptions { 20 | return &APIManagerBackupPVCOptions{} 21 | } 22 | 23 | func (a *APIManagerBackupPVCOptions) Validate() error { 24 | validate := validator.New() 25 | return validate.Struct(a) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/controller/helper/application_entity.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | threescaleapi "github.com/3scale/3scale-porta-go-client/client" 5 | "github.com/go-logr/logr" 6 | ) 7 | 8 | type ApplicationEntity struct { 9 | client *threescaleapi.ThreeScaleClient 10 | ApplicationObj *threescaleapi.Application 11 | ApplicationList *threescaleapi.ApplicationList 12 | *threescaleapi.ApplicationPlanJSONList 13 | logger logr.Logger 14 | } 15 | 16 | func NewApplicationEntity(ApplicationObj *threescaleapi.Application, client *threescaleapi.ThreeScaleClient, logger logr.Logger) *ApplicationEntity { 17 | return &ApplicationEntity{ 18 | ApplicationObj: ApplicationObj, 19 | client: client, 20 | logger: logger.WithValues("Application", ApplicationObj.ID), 21 | } 22 | } 23 | 24 | func (b *ApplicationEntity) ID() int64 { 25 | return b.ApplicationObj.ID 26 | } 27 | 28 | func (b *ApplicationEntity) AppName() string { 29 | return b.ApplicationObj.AppName 30 | } 31 | 32 | func (b *ApplicationEntity) Description() string { 33 | return b.ApplicationObj.Description 34 | } 35 | 36 | func (b *ApplicationEntity) UserAccountID() string { 37 | return b.ApplicationObj.UserAccountID 38 | } 39 | 40 | func (b *ApplicationEntity) PlanID() int64 { 41 | return b.ApplicationObj.PlanID 42 | } 43 | 44 | func (b *ApplicationEntity) ProductId() int64 { 45 | return b.ApplicationObj.ServiceID 46 | } 47 | 48 | func (b *ApplicationEntity) ApplicationState() string { 49 | return b.ApplicationObj.State 50 | } 51 | -------------------------------------------------------------------------------- /pkg/controller/helper/backend_list.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" 8 | 9 | "github.com/go-logr/logr" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | // BackendList returns a list of backend custom resources where all elements: 14 | // - Sync state (ensure remote backend exist and in sync) 15 | // - Same 3scale provider Account 16 | func BackendList(ns string, cl client.Client, providerAccountURLStr string, logger logr.Logger) ([]capabilitiesv1beta1.Backend, error) { 17 | backendList := &capabilitiesv1beta1.BackendList{} 18 | opts := []client.ListOption{ 19 | client.InNamespace(ns), 20 | } 21 | err := cl.List(context.TODO(), backendList, opts...) 22 | logger.V(1).Info("Get list of Backend resources.", "Err", err) 23 | if err != nil { 24 | return nil, fmt.Errorf("BackendList: %w", err) 25 | } 26 | logger.V(1).Info("Backend resources", "total", len(backendList.Items)) 27 | 28 | validBackends := make([]capabilitiesv1beta1.Backend, 0) 29 | for idx := range backendList.Items { 30 | // Filter by synchronized 31 | if !backendList.Items[idx].IsSynced() { 32 | continue 33 | } 34 | 35 | backendProviderAccount, err := LookupProviderAccount(cl, ns, backendList.Items[idx].Spec.ProviderAccountRef, logger) 36 | if err != nil { 37 | return nil, fmt.Errorf("BackendList: %w", err) 38 | } 39 | 40 | // Filter by provider account 41 | if providerAccountURLStr != backendProviderAccount.AdminURLStr { 42 | continue 43 | } 44 | validBackends = append(validBackends, backendList.Items[idx]) 45 | } 46 | 47 | logger.V(1).Info("Backend valid resources", "total", len(validBackends)) 48 | return validBackends, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/controller/helper/product_list.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" 8 | 9 | "github.com/go-logr/logr" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | // ProductList returns a list of product custom resources where all elements: 14 | // - Sync state (ensure remote product exist and in sync) 15 | // - Same 3scale provider Account 16 | func ProductList(ns string, cl client.Client, providerAccountURLStr string, logger logr.Logger) ([]capabilitiesv1beta1.Product, error) { 17 | productList := &capabilitiesv1beta1.ProductList{} 18 | opts := []client.ListOption{ 19 | client.InNamespace(ns), 20 | } 21 | err := cl.List(context.TODO(), productList, opts...) 22 | logger.V(1).Info("Get list of Product resources.", "Err", err) 23 | if err != nil { 24 | return nil, fmt.Errorf("ProductList: %w", err) 25 | } 26 | logger.V(1).Info("Product resources", "total", len(productList.Items)) 27 | 28 | validProducts := make([]capabilitiesv1beta1.Product, 0) 29 | for idx := range productList.Items { 30 | // Filter by synchronized 31 | if !productList.Items[idx].IsSynced() { 32 | continue 33 | } 34 | 35 | productProviderAccount, err := LookupProviderAccount(cl, ns, productList.Items[idx].Spec.ProviderAccountRef, logger) 36 | if err != nil { 37 | return nil, fmt.Errorf("ProductList: %w", err) 38 | } 39 | 40 | // Filter by provider account 41 | if providerAccountURLStr != productProviderAccount.AdminURLStr { 42 | continue 43 | } 44 | validProducts = append(validProducts, productList.Items[idx]) 45 | } 46 | 47 | logger.V(1).Info("Product valid resources", "total", len(validProducts)) 48 | return validProducts, nil 49 | } 50 | 51 | func FindProductBySystemName(list []capabilitiesv1beta1.Product, systemName string) int { 52 | for idx := range list { 53 | if list[idx].Spec.SystemName == systemName { 54 | return idx 55 | } 56 | } 57 | return -1 58 | } 59 | -------------------------------------------------------------------------------- /pkg/controller/helper/shared.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | 6 | capabilitiesv1alpha1 "github.com/3scale/3scale-operator/apis/capabilities/v1alpha1" 7 | 8 | "github.com/go-logr/logr" 9 | corev1 "k8s.io/api/core/v1" 10 | k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | /* 14 | RetrieveTenantCR retrieves tenantCR of a tenant that matches the provider account org name 15 | - providerAccount 16 | - k8client 17 | - logger 18 | If the tenantList is empty it will return nil, nil 19 | If tenantCR for given providerAccount org is not present, it will return nil, nil 20 | */ 21 | func RetrieveTenantCR(providerAccount *ProviderAccount, client k8sclient.Client, logger logr.Logger, namespace string) (*capabilitiesv1alpha1.Tenant, error) { 22 | // Retrieve all product CRs that are under the same ns as the backend CR 23 | opts := k8sclient.ListOptions{ 24 | Namespace: namespace, 25 | } 26 | 27 | tenantList := &capabilitiesv1alpha1.TenantList{} 28 | err := client.List(context.TODO(), tenantList, &opts) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | for _, tenant := range tenantList.Items { 34 | tenantSecret, err := retrieveTenantSecret(client, &tenant) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | adminURL, ok := tenantSecret.Data[string("adminURL")] 40 | if !ok { 41 | return nil, err 42 | } 43 | 44 | if string(adminURL) == providerAccount.AdminURLStr { 45 | return &tenant, nil 46 | } 47 | } 48 | 49 | return nil, nil 50 | } 51 | 52 | /* 53 | retrieveTenantSecret retrieves tenants secret 54 | - k8client 55 | - tenantCR 56 | */ 57 | func retrieveTenantSecret(client k8sclient.Client, tenantCR *capabilitiesv1alpha1.Tenant) (*corev1.Secret, error) { 58 | secret := &corev1.Secret{} 59 | 60 | err := client.Get(context.TODO(), k8sclient.ObjectKey{Name: tenantCR.Spec.TenantSecretRef.Name, Namespace: tenantCR.Spec.TenantSecretRef.Namespace}, secret) 61 | if err != nil { 62 | return secret, err 63 | } 64 | 65 | return secret, nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/controller/helper/threescale_api_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func TestPortaClientInvalidURL(t *testing.T) { 9 | providerAccount := &ProviderAccount{AdminURLStr: ":foo", Token: "some token"} 10 | _, err := PortaClient(providerAccount, false) 11 | assert(t, err != nil, "error should not be nil") 12 | } 13 | 14 | func TestPortaClient(t *testing.T) { 15 | providerAccount := &ProviderAccount{AdminURLStr: "http://somedomain.example.com", Token: "some token"} 16 | _, err := PortaClient(providerAccount, false) 17 | ok(t, err) 18 | } 19 | 20 | func TestPortaClientFromURLStringInvalidURL(t *testing.T) { 21 | _, err := PortaClientFromURLString(":foo", "some token", false) 22 | assert(t, err != nil, "error should not be nil") 23 | } 24 | 25 | func TestPortaClientFromURLString(t *testing.T) { 26 | _, err := PortaClientFromURLString("http://somedomain.example.com", "some token", false) 27 | ok(t, err) 28 | } 29 | 30 | func TestPortaClientFromURL(t *testing.T) { 31 | url := &url.URL{} 32 | _, err := PortaClientFromURL(url, "some token", false) 33 | assert(t, err != nil, "error should not be nil") 34 | } 35 | -------------------------------------------------------------------------------- /pkg/controller/helper/url.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "net/url" 4 | 5 | func URLFromDomain(domain string) (*url.URL, error) { 6 | u, err := url.Parse(domain) 7 | if err != nil { 8 | return nil, err 9 | } 10 | u.Scheme = "https" 11 | return u, nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/controller/helper/url_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func TestURLFromDomain(t *testing.T) { 9 | cases := []struct { 10 | testName string 11 | domain string 12 | expectedErr bool 13 | expectedURL url.URL 14 | }{ 15 | {"Invalid domain", ":foo", true, url.URL{}}, 16 | { 17 | "Valid domain", "http://somedomain.example.com", false, 18 | url.URL{ 19 | Scheme: "https", 20 | Host: "somedomain.example.com", 21 | }, 22 | }, 23 | } 24 | 25 | for _, tc := range cases { 26 | t.Run(tc.testName, func(subT *testing.T) { 27 | res, err := URLFromDomain(tc.domain) 28 | if !tc.expectedErr { 29 | ok(subT, err) 30 | equals(subT, tc.expectedURL, *res) 31 | } else { 32 | assert(subT, err != nil, "error should not be nil") 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/crypto/rand/rand.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const ( 9 | lowercaseAlphabetCharset = "abcdefghijklmnopqrstuvwxyz" 10 | uppercaseAlphabetCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 11 | numericCharset = "0123456789" 12 | alphanumericCharset = lowercaseAlphabetCharset + uppercaseAlphabetCharset + numericCharset 13 | hexadecimalCharset = numericCharset + "ABCDEF" 14 | ) 15 | 16 | var randomGenerator *rand.Rand = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) 17 | 18 | // String generates random alphanumeric string of size 'size'. 19 | func String(length int) string { 20 | return StringWithCharset(length, alphanumericCharset) 21 | } 22 | 23 | // HexadecimalString generates random hexadecimal strings of size 'size' 24 | func HexadecimalString(length int) string { 25 | return StringWithCharset(length, hexadecimalCharset) 26 | } 27 | 28 | // StringWithCharset generates random string of length 'length' with all of its 29 | // random characters existing in and only in the 'charset' set of 30 | // strings 31 | func StringWithCharset(length int, charset string) string { 32 | result := make([]byte, length) 33 | 34 | for i := range result { 35 | result[i] = charset[randomGenerator.Int63()%int64(len(charset))] 36 | } 37 | 38 | return string(result) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/helper/annotations.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func ManagedByOperatorAnnotation() map[string]string { 4 | return map[string]string{ 5 | "annotations[managed_by]": "operator", 6 | } 7 | } 8 | 9 | func ManagedByOperatorAnnotationExists(annotations map[string]string) bool { 10 | if val, exists := annotations["managed_by"]; exists && val == "operator" { 11 | return true 12 | } 13 | return false 14 | } 15 | 16 | func ManagedByOperatorDeveloperAccountAnnotation() map[string]string { 17 | return map[string]string{ 18 | "managed_by": "operator", 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/helper/boolean_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func Any(arr []bool) bool { 4 | result := false 5 | for _, v := range arr { 6 | result = result || v 7 | } 8 | return result 9 | } 10 | 11 | func All(arr []bool) bool { 12 | result := true 13 | for _, v := range arr { 14 | result = result && v 15 | } 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /pkg/helper/cache.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Errors 8 | var ( 9 | ErrNonExistentKey = errors.New("non-existent key") 10 | ) 11 | 12 | // MemoryCache implements a simple memory cache 13 | // No expiration 14 | // No eviction policies 15 | // Not threadsafe 16 | type MemoryCache struct { 17 | store map[string]interface{} 18 | } 19 | 20 | func NewMemoryCache() *MemoryCache { 21 | return &MemoryCache{ 22 | store: make(map[string]interface{}), 23 | } 24 | } 25 | 26 | func (c *MemoryCache) Exists(key string) bool { 27 | _, ok := c.store[key] 28 | return ok 29 | } 30 | 31 | func (c *MemoryCache) Get(key string) (interface{}, error) { 32 | if !c.Exists(key) { 33 | return nil, ErrNonExistentKey 34 | } 35 | 36 | return c.store[key], nil 37 | } 38 | 39 | func (c *MemoryCache) Put(key string, data interface{}) { 40 | c.store[key] = data 41 | } 42 | -------------------------------------------------------------------------------- /pkg/helper/container_port_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import v1 "k8s.io/api/core/v1" 4 | 5 | func FindContainerPortByName(ports []v1.ContainerPort, name string) (v1.ContainerPort, bool) { 6 | for idx := range ports { 7 | if ports[idx].Name == name { 8 | return ports[idx], true 9 | } 10 | } 11 | 12 | return v1.ContainerPort{}, false 13 | } 14 | -------------------------------------------------------------------------------- /pkg/helper/deployment.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | k8sappsv1 "k8s.io/api/apps/v1" 5 | corev1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | // IsDeploymentAvailable returns true when the provided Deployment has the "Available" condition set to true 9 | func IsDeploymentAvailable(d *k8sappsv1.Deployment) bool { 10 | dConditions := d.Status.Conditions 11 | for _, dCondition := range dConditions { 12 | if dCondition.Type == k8sappsv1.DeploymentAvailable && dCondition.Status == corev1.ConditionTrue { 13 | return true 14 | } 15 | } 16 | return false 17 | } 18 | 19 | // IsDeploymentProgressing returns true when the provided Deployment is progressing with new ReplicaSet 20 | func IsDeploymentProgressing(d *k8sappsv1.Deployment) bool { 21 | if d.Status.UnavailableReplicas > 0 { 22 | return true 23 | } 24 | 25 | for _, dCondition := range d.Status.Conditions { 26 | if dCondition.Type == k8sappsv1.DeploymentProgressing && dCondition.Reason == "ReplicaSetUpdated" { 27 | return true 28 | } 29 | } 30 | 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /pkg/helper/k8s.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | serviceAccountDir = "/var/run/secrets/kubernetes.io/serviceaccount" 11 | ) 12 | 13 | // GetWatchNamespace returns the Namespace the operator should be watching for changes 14 | func GetWatchNamespace() (string, error) { 15 | // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE 16 | // which specifies the Namespace to watch. 17 | // An empty value means the operator is running with cluster scope. 18 | watchNamespaceEnvVar := "WATCH_NAMESPACE" 19 | 20 | ns, found := os.LookupEnv(watchNamespaceEnvVar) 21 | if !found { 22 | return "", fmt.Errorf("%s must be set", watchNamespaceEnvVar) 23 | } 24 | return ns, nil 25 | } 26 | 27 | // IsRunLocally checks if the operator is run locally 28 | func IsRunLocally() bool { 29 | return !IsRunInCluster() 30 | } 31 | 32 | // IsRunInCluster checks if the operator is run in cluster 33 | func IsRunInCluster() bool { 34 | _, err := os.Stat(serviceAccountDir) 35 | if err == nil { 36 | return true 37 | } 38 | 39 | return !os.IsNotExist(err) 40 | } 41 | 42 | // GetOperatorNamespace returns the namespace the operator should be running in. 43 | func GetOperatorNamespace() (string, error) { 44 | if IsRunLocally() { 45 | ns, err := GetWatchNamespace() 46 | if err != nil { 47 | return "", fmt.Errorf("running locally but WATCH_NAMESPACE not found") 48 | } 49 | return ns, nil 50 | } 51 | nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") 52 | if err != nil { 53 | if os.IsNotExist(err) { 54 | return "", fmt.Errorf("namespace not found for current environment") 55 | } 56 | return "", err 57 | } 58 | ns := strings.TrimSpace(string(nsBytes)) 59 | return ns, nil 60 | } 61 | 62 | func IsPreflightBypassed() bool { 63 | skipPreflightsValue := GetEnvVar("PREFLIGHT_CHECKS_BYPASS", "false") 64 | return skipPreflightsValue == "true" 65 | } 66 | -------------------------------------------------------------------------------- /pkg/helper/labels.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "github.com/3scale/3scale-operator/version" 5 | ) 6 | 7 | type ComponentType string 8 | 9 | const ( 10 | ApplicationType ComponentType = "application" 11 | InfrastructureType ComponentType = "infrastructure" 12 | ) 13 | 14 | func MeteringLabels(componentName string, componentType ComponentType) map[string]string { 15 | return map[string]string{ 16 | "com.company": "Red_Hat", 17 | "rht.prod_name": "Red_Hat_Integration", 18 | // It should be updated on release branch 19 | "rht.prod_ver": "master", 20 | "rht.comp": "3scale", 21 | "rht.comp_ver": version.ThreescaleVersionMajorMinor(), 22 | "rht.subcomp": componentName, 23 | "rht.subcomp_t": string(componentType), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/helper/loghttp.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httputil" 7 | ) 8 | 9 | const ( 10 | colorReset = "\033[0m" 11 | colorRed = "\033[31m" 12 | colorGreen = "\033[32m" 13 | ) 14 | 15 | // Transport implements http.RoundTripper. When set as Transport of http.Client, it executes HTTP requests with logging. 16 | // No field is mandatory. 17 | type Transport struct { 18 | Transport http.RoundTripper 19 | } 20 | 21 | // DefaultTransport is the default logging transport that wraps http.DefaultTransport. 22 | var DefaultTransport = &Transport{ 23 | Transport: http.DefaultTransport, 24 | } 25 | 26 | // RoundTrip is the core part of this module and implements http.RoundTripper. 27 | // Executes HTTP request with request/response logging. 28 | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 29 | t.logRequest(req) 30 | 31 | resp, err := t.transport().RoundTrip(req) 32 | if err != nil { 33 | return resp, err 34 | } 35 | 36 | t.logResponse(resp) 37 | 38 | return resp, err 39 | } 40 | 41 | func (t *Transport) logRequest(req *http.Request) { 42 | dump, _ := httputil.DumpRequestOut(req, true) 43 | fmt.Printf("%s%s%s\n", colorRed, string(dump), colorReset) 44 | } 45 | 46 | func (t *Transport) logResponse(resp *http.Response) { 47 | dump, _ := httputil.DumpResponse(resp, true) 48 | fmt.Printf("%s%s%s\n", colorGreen, string(dump), colorReset) 49 | } 50 | 51 | func (t *Transport) transport() http.RoundTripper { 52 | if t.Transport != nil { 53 | return t.Transport 54 | } 55 | 56 | return http.DefaultTransport 57 | } 58 | -------------------------------------------------------------------------------- /pkg/helper/maputils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "sort" 4 | 5 | func SortedMapStringStringValues(input map[string]string) []string { 6 | sortedValues := []string{} 7 | for _, v := range input { 8 | sortedValues = append(sortedValues, v) 9 | } 10 | sort.Slice(sortedValues, func(i, j int) bool { return sortedValues[i] < sortedValues[j] }) 11 | return sortedValues 12 | } 13 | 14 | func SortedMapStringStringKeys(input map[string]string) []string { 15 | sortedKeys := []string{} 16 | for k := range input { 17 | sortedKeys = append(sortedKeys, k) 18 | } 19 | sort.Slice(sortedKeys, func(i, j int) bool { return sortedKeys[i] < sortedKeys[j] }) 20 | return sortedKeys 21 | } 22 | 23 | func MapKeys[M ~map[K]V, K comparable, V any](m M) []K { 24 | r := make([]K, 0, len(m)) 25 | for k := range m { 26 | r = append(r, k) 27 | } 28 | return r 29 | } 30 | 31 | func MergeMapsStringString(map1, map2 map[string]string) map[string]string { 32 | merged := make(map[string]string) 33 | for key, value := range map1 { 34 | merged[key] = value 35 | } 36 | for key, value := range map2 { 37 | merged[key] = value 38 | } 39 | return merged 40 | } 41 | -------------------------------------------------------------------------------- /pkg/helper/object_meta_merger.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "sigs.k8s.io/controller-runtime/pkg/client" 4 | 5 | // From 6 | // https://github.com/openshift/library-go/blob/master/pkg/operator/resource/resourcemerge/object_merger.go 7 | 8 | // EnsureObjectMeta ensure Labels, Annotations 9 | func EnsureObjectMeta(existing, desired client.Object) bool { 10 | updated := false 11 | 12 | existingLabels := existing.GetLabels() 13 | existingAnnotations := existing.GetAnnotations() 14 | 15 | MergeMapStringString(&updated, &existingLabels, desired.GetLabels()) 16 | MergeMapStringString(&updated, &existingAnnotations, desired.GetAnnotations()) 17 | 18 | existing.SetLabels(existingLabels) 19 | existing.SetAnnotations(existingAnnotations) 20 | 21 | return updated 22 | } 23 | 24 | func EnsureString(modified *bool, existing *string, required string) { 25 | if required != *existing { 26 | *existing = required 27 | *modified = true 28 | } 29 | } 30 | 31 | func MergeMapStringString(modified *bool, existing *map[string]string, desired map[string]string) { 32 | if *existing == nil { 33 | *existing = map[string]string{} 34 | } 35 | 36 | for k, v := range desired { 37 | if existingVal, ok := (*existing)[k]; !ok || v != existingVal { 38 | (*existing)[k] = v 39 | *modified = true 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/helper/openshift.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | configv1 "github.com/openshift/api/config/v1" 8 | "golang.org/x/mod/semver" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | "k8s.io/apimachinery/pkg/types" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | func GetOpenshiftVersion(ctx context.Context, client client.Client) (string, bool, error) { 15 | clusterVersion := &configv1.ClusterVersion{} 16 | 17 | if err := client.Get(ctx, types.NamespacedName{ 18 | Name: "version", 19 | }, clusterVersion); err != nil { 20 | if errors.IsNotFound(err) { 21 | return "", false, nil 22 | } 23 | 24 | return "", false, err 25 | } 26 | 27 | return clusterVersion.Status.Desired.Version, true, nil 28 | } 29 | 30 | func CompareOpenshiftVersion(ctx context.Context, client client.Client, version string) (int, bool, error) { 31 | currentVersion, ok, err := GetOpenshiftVersion(ctx, client) 32 | if !ok || err != nil { 33 | return 0, ok, err 34 | } 35 | 36 | return semver.Compare(fmt.Sprintf("v%s", currentVersion), fmt.Sprintf("v%s", version)), true, nil 37 | } 38 | 39 | func SumRateForOpenshiftVersion(ctx context.Context, client client.Client) (string, error) { 40 | sumRate := "sum_irate" 41 | 42 | // Compare the current Openshft version to 4.9 43 | comparison, ok, err := CompareOpenshiftVersion(ctx, client, "4.9") 44 | if err != nil { 45 | return "", err 46 | } 47 | // If the version could not be found, return the default value 48 | if !ok { 49 | return sumRate, nil 50 | } 51 | 52 | // If the version is less than 4.9, use sum_rate 53 | if comparison < 0 { 54 | sumRate = "sum_rate" 55 | } 56 | 57 | return sumRate, nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/helper/pointer_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func Int32Ptr(i int32) *int32 { 4 | return &i 5 | } 6 | -------------------------------------------------------------------------------- /pkg/helper/reconcile_error_handler.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "github.com/go-logr/logr" 5 | ctrl "sigs.k8s.io/controller-runtime" 6 | ) 7 | 8 | // ReconcileErrorHandler reads the error type and based on that, it responsds with either a requeue true or false 9 | func ReconcileErrorHandler(err error, reqLogger logr.Logger) ctrl.Result { 10 | // On validation error, do not retry since there's something wrong with the spec - do not re-trigger the reconciler 11 | if IsInvalidSpecError(err) { 12 | reqLogger.Info("ERROR", "spec validation error", err) 13 | return ctrl.Result{} 14 | } 15 | 16 | // On wait error - requeue the reconciler 17 | if IsWaitError(err) { 18 | reqLogger.Info("ERROR", "wait error", err) 19 | return ctrl.Result{Requeue: true} 20 | } 21 | 22 | // On orphan error - do not re-trigger the reconciler 23 | if IsOrphanSpecError(err) { 24 | reqLogger.Info("ERROR", "orphan error", err) 25 | return ctrl.Result{} 26 | } 27 | 28 | reqLogger.Info("ERROR", "generic k8s error", err) 29 | return ctrl.Result{Requeue: true} 30 | } 31 | -------------------------------------------------------------------------------- /pkg/helper/requirements_common_helper_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVersionCompare(t *testing.T) { 8 | cases := []struct { 9 | testName string 10 | requiredVersion string 11 | currentVersion string 12 | expectedResult bool 13 | }{ 14 | {"IncomingMajorRequiredIsHigher", "7.0.0", "6.2", false}, 15 | {"IncomingMinorRequiredIsHigher", "6.3.0", "6.2", false}, 16 | {"IncomingMajorRequiredIsLower", "5.0.0", "6.0.0", true}, 17 | {"IncomingMinorRequiredIsLower", "6.1.0", "6.2", true}, 18 | {"VersionsMatch", "6.2.0", "6.2.0", true}, 19 | {"IncomingPatchVersionIsHigher", "6.2.1", "6.2.0", false}, 20 | {"IncomingPatchVersionIsLower", "6.2.0", "6.2.1", true}, 21 | } 22 | 23 | for _, tc := range cases { 24 | t.Run(tc.testName, func(subT *testing.T) { 25 | value, err := CompareVersions(tc.requiredVersion, tc.currentVersion) 26 | if err != nil { 27 | subT.Fatal(err) 28 | } 29 | if value != tc.expectedResult { 30 | subT.Fatalf("test failed for test case %s, expected %v but got %v", tc.testName, tc.expectedResult, value) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/helper/runtime_object.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | "sigs.k8s.io/yaml" 6 | ) 7 | 8 | func MarshalObjectToYAML(object runtime.Object) ([]byte, error) { 9 | serializedResult, err := runtime.DefaultUnstructuredConverter.ToUnstructured(object) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | return yaml.Marshal(serializedResult) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/helper/secretutils_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | func TestIsSecretWatchedBy3scale(t *testing.T) { 11 | labeledSecret := &v1.Secret{ 12 | ObjectMeta: metav1.ObjectMeta{ 13 | Name: "labeled-secret", 14 | Namespace: "test-namespace", 15 | Labels: map[string]string{ 16 | "apimanager.apps.3scale.net/watched-by": "apimanager", 17 | }, 18 | }, 19 | } 20 | unlabeledSecret := &v1.Secret{ 21 | ObjectMeta: metav1.ObjectMeta{ 22 | Name: "unlabeled-secret", 23 | Namespace: "test-namespace", 24 | }, 25 | } 26 | 27 | type args struct { 28 | secret *v1.Secret 29 | } 30 | tests := []struct { 31 | name string 32 | args args 33 | want bool 34 | }{ 35 | { 36 | name: "Secret doesn't have watched-by label", 37 | args: args{ 38 | secret: unlabeledSecret, 39 | }, 40 | want: false, 41 | }, 42 | { 43 | name: "Secret has watched-by label", 44 | args: args{ 45 | secret: labeledSecret, 46 | }, 47 | want: true, 48 | }, 49 | { 50 | name: "Secret doesn't exist", 51 | args: args{ 52 | secret: nil, 53 | }, 54 | want: false, 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | if got := IsSecretWatchedBy3scale(tt.args.secret); got != tt.want { 60 | t.Errorf("IsSecretWatchedBy3scale() = %v, want %v", got, tt.want) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/helper/slice_string_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | func ArrayStringDifference(a, b []string) []string { 4 | target := map[string]bool{} 5 | for _, x := range b { 6 | target[x] = true 7 | } 8 | 9 | result := []string{} 10 | for _, x := range a { 11 | if _, ok := target[x]; !ok { 12 | result = append(result, x) 13 | } 14 | } 15 | 16 | return result 17 | } 18 | 19 | func ArrayStringIntersection(a, b []string) []string { 20 | target := map[string]bool{} 21 | for _, x := range b { 22 | target[x] = true 23 | } 24 | 25 | result := []string{} 26 | for _, x := range a { 27 | if _, ok := target[x]; ok { 28 | result = append(result, x) 29 | } 30 | } 31 | 32 | return result 33 | } 34 | 35 | // ArrayFind returns the smallest index i at which x == a[i], 36 | // or len(a) if there is no such index. 37 | func ArrayFind(a []string, x string) int { 38 | for i, n := range a { 39 | if x == n { 40 | return i 41 | } 42 | } 43 | return len(a) 44 | } 45 | 46 | // ArrayContains tells whether a contains x. 47 | func ArrayContains(a []string, x string) bool { 48 | for _, n := range a { 49 | if x == n { 50 | return true 51 | } 52 | } 53 | return false 54 | } 55 | 56 | // https://newbedev.com/check-for-equality-on-slices-without-order 57 | func StringSliceEqualWithoutOrder(x, y []string) bool { 58 | if len(x) != len(y) { 59 | return false 60 | } 61 | // create a map of string -> int 62 | diff := make(map[string]int, len(x)) 63 | for _, _x := range x { 64 | // 0 value for int is 0, so just increment a counter for the string 65 | diff[_x]++ 66 | } 67 | for _, _y := range y { 68 | // If the string _y is not in diff bail out early 69 | if _, ok := diff[_y]; !ok { 70 | return false 71 | } 72 | diff[_y]-- 73 | if diff[_y] == 0 { 74 | delete(diff, _y) 75 | } 76 | } 77 | 78 | return len(diff) == 0 79 | } 80 | -------------------------------------------------------------------------------- /pkg/helper/task_runner.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/go-logr/logr" 9 | ) 10 | 11 | type task struct { 12 | Name string 13 | Run func(interface{}) error 14 | } 15 | 16 | // TaskRunner abstracts task running engine 17 | type TaskRunner interface { 18 | Run() error 19 | // AddTask register tasks to be executed sequentially 20 | // Tasks will be executed in order. First in, first to be executed. 21 | AddTask(name string, f func(interface{}) error) 22 | } 23 | 24 | type taskRunnerImpl struct { 25 | ctx interface{} 26 | taskList []task 27 | logger logr.Logger 28 | } 29 | 30 | // NewTaskRunner TaskRunner Constructor 31 | func NewTaskRunner(ctx interface{}, logger logr.Logger) TaskRunner { 32 | return &taskRunnerImpl{ 33 | ctx: ctx, 34 | taskList: []task{}, 35 | logger: logger, 36 | } 37 | } 38 | 39 | func (t *taskRunnerImpl) Run() error { 40 | reqerr := false 41 | for _, task := range t.taskList { 42 | start := time.Now() 43 | if err := task.Run(t.ctx); err != nil { 44 | if strings.Contains(err.Error(), "Method is used by the latest gateway configuration and cannot be deleted") { 45 | reqerr = true 46 | continue 47 | } 48 | return fmt.Errorf("task failed %s: %w", task.Name, err) 49 | } 50 | elapsed := time.Since(start) 51 | t.logger.V(1).Info("Measure", task.Name, elapsed) 52 | } 53 | if reqerr { 54 | return fmt.Errorf("method is used by the latest gateway configuration, it will be removed from 3scale only once the promotion without the mapping rule that uses the deleted method is done") 55 | } 56 | return nil 57 | } 58 | 59 | func (t *taskRunnerImpl) AddTask(name string, f func(interface{}) error) { 60 | t.taskList = append(t.taskList, task{Name: name, Run: f}) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/helper/volume_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // FindVolumeByName returns the smallest index i at which x.Name == a[i].Name, 8 | // or -1 if there is no such index. 9 | func FindVolumeByName(a []v1.Volume, name string) int { 10 | for i, n := range a { 11 | if n.Name == name { 12 | return i 13 | } 14 | } 15 | return -1 16 | } 17 | 18 | func VolumeFromSecretEqual(a v1.Volume, b v1.Volume) bool { 19 | if a.Secret == nil || b.Secret == nil { 20 | return false 21 | } 22 | 23 | return a.Name == b.Name && a.Secret.SecretName == b.Secret.SecretName 24 | } 25 | -------------------------------------------------------------------------------- /pkg/helper/volumemount_utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // FindVolumeMountByMountPath returns the smallest index i at which x.MountPath == a[i].MountPath, 8 | // or -1 if there is no such index. 9 | func FindVolumeMountByMountPath(a []v1.VolumeMount, x v1.VolumeMount) int { 10 | for i, n := range a { 11 | if n.MountPath == x.MountPath { 12 | return i 13 | } 14 | } 15 | return -1 16 | } 17 | 18 | // FindVolumeMountByMountPath returns the smallest index i at which x.MountPath == a[i].MountPath, 19 | // or -1 if there is no such index. 20 | func FindVolumeMountByName(a []v1.VolumeMount, name string) int { 21 | for i, n := range a { 22 | if n.Name == name { 23 | return i 24 | } 25 | } 26 | return -1 27 | } 28 | -------------------------------------------------------------------------------- /pkg/reconcilers/configmap.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "strings" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | ) 8 | 9 | func ConfigMapReconcileField(desired, existing *v1.ConfigMap, fieldName string) bool { 10 | updated := false 11 | 12 | if existingVal, ok := existing.Data[fieldName]; !ok { 13 | existing.Data[fieldName] = desired.Data[fieldName] 14 | updated = true 15 | } else { 16 | if desired.Data[fieldName] != existingVal { 17 | existing.Data[fieldName] = desired.Data[fieldName] 18 | updated = true 19 | } 20 | } 21 | return updated 22 | } 23 | 24 | func RedisConfigMapReconcileField(desired, existing *v1.ConfigMap, fieldName string) bool { 25 | updated := false 26 | 27 | if existingVal, ok := existing.Data[fieldName]; !ok { 28 | existing.Data[fieldName] = desired.Data[fieldName] 29 | updated = true 30 | } else { 31 | existingString := existingVal 32 | 33 | replicaOfString := "rename-command REPLICAOF \"\"" 34 | if !strings.Contains(existingString, replicaOfString) { 35 | existingString = existingString + "\n" + replicaOfString 36 | existing.Data[fieldName] = existingString 37 | updated = true 38 | } 39 | 40 | slaveOfString := "rename-command SLAVEOF \"\"" 41 | if !strings.Contains(existingString, slaveOfString) { 42 | existingString = existingString + "\n" + slaveOfString 43 | existing.Data[fieldName] = existingString 44 | updated = true 45 | } 46 | } 47 | return updated 48 | } 49 | -------------------------------------------------------------------------------- /pkg/reconcilers/grafanadashboards.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | grafanav1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1" 8 | grafanav1beta1 "github.com/grafana-operator/grafana-operator/v5/api/v1beta1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | func GenericGrafanaDashboardsMutator(existingObj, desiredObj client.Object) (bool, error) { 13 | var existingSpec, desiredSpec interface{} 14 | var existingType, desiredType string 15 | 16 | // Determine the type of the existing object 17 | switch existing := existingObj.(type) { 18 | case *grafanav1alpha1.GrafanaDashboard: 19 | existingSpec = existing.Spec 20 | existingType = "alpha" 21 | case *grafanav1beta1.GrafanaDashboard: 22 | existingSpec = existing.Spec 23 | existingType = "beta" 24 | default: 25 | return false, fmt.Errorf("%T is not a supported GrafanaDashboard type", existingObj) 26 | } 27 | 28 | // Determine the type of the desired object 29 | switch desired := desiredObj.(type) { 30 | case *grafanav1alpha1.GrafanaDashboard: 31 | desiredSpec = desired.Spec 32 | desiredType = "alpha" 33 | case *grafanav1beta1.GrafanaDashboard: 34 | desiredSpec = desired.Spec 35 | desiredType = "beta" 36 | default: 37 | return false, fmt.Errorf("%T is not a supported GrafanaDashboard type", desiredObj) 38 | } 39 | 40 | // Check if the types match 41 | if existingType != desiredType { 42 | return false, fmt.Errorf("mismatched types: existing is %s, desired is %s", existingType, desiredType) 43 | } 44 | 45 | updated := false 46 | 47 | // Compare and update specs 48 | if !reflect.DeepEqual(existingSpec, desiredSpec) { 49 | switch existing := existingObj.(type) { 50 | case *grafanav1alpha1.GrafanaDashboard: 51 | existing.Spec = desiredSpec.(grafanav1alpha1.GrafanaDashboardSpec) 52 | case *grafanav1beta1.GrafanaDashboard: 53 | existing.Spec = desiredSpec.(grafanav1beta1.GrafanaDashboardSpec) 54 | } 55 | updated = true 56 | } 57 | 58 | return updated, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/reconcilers/grafanadashboards_test.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "testing" 5 | 6 | grafanav1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1" 7 | ) 8 | 9 | func TestGenericGrafanaDashboardsMutatorWhenCopied(t *testing.T) { 10 | desired := &grafanav1alpha1.GrafanaDashboard{ 11 | Spec: grafanav1alpha1.GrafanaDashboardSpec{ 12 | Json: `{"somekey": "somevalue"}`, 13 | }, 14 | } 15 | 16 | existingTmp := desired.DeepCopyObject() 17 | existing, ok := existingTmp.(*grafanav1alpha1.GrafanaDashboard) 18 | if !ok { 19 | t.Fatal("grafanadashboard copy did not work") 20 | } 21 | 22 | update, err := GenericGrafanaDashboardsMutator(desired, existing) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | if update { 28 | t.Fatal("when existing and desired are cloned, reconciler reported update needed") 29 | } 30 | } 31 | 32 | func TestGenericGrafanaDashboardsMutatorWhenDiff(t *testing.T) { 33 | desired := &grafanav1alpha1.GrafanaDashboard{ 34 | Spec: grafanav1alpha1.GrafanaDashboardSpec{ 35 | Json: `{"somekey": "somevalue"}`, 36 | }, 37 | } 38 | 39 | existingTmp := desired.DeepCopyObject() 40 | existing, ok := existingTmp.(*grafanav1alpha1.GrafanaDashboard) 41 | if !ok { 42 | t.Fatal("grafanadashboard copy did not work") 43 | } 44 | 45 | existing.Spec.Json = `{"some_existing_key": "some_existing_value"}` 46 | 47 | update, err := GenericGrafanaDashboardsMutator(desired, existing) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | if !update { 53 | t.Fatal("when existing and desired are different, reconciler reported not update needed") 54 | } 55 | 56 | if existing.Spec.Json != desired.Spec.Json { 57 | t.Errorf("Spec.Json does not match. got [%s], expected [%s]", existing.Spec.Json, desired.Spec.Json) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/reconcilers/hpa.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | hpa "k8s.io/api/autoscaling/v2" 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func GenericHPAMutator(existingObj, desiredObj client.Object) (bool, error) { 12 | existing, ok := existingObj.(*hpa.HorizontalPodAutoscaler) 13 | if !ok { 14 | return false, fmt.Errorf("%T is not a *v2.HorizontalPodAutoscaler", existingObj) 15 | } 16 | desired, ok := desiredObj.(*hpa.HorizontalPodAutoscaler) 17 | if !ok { 18 | return false, fmt.Errorf("%T is not a *v2.HorizontalPodAutoscaler", desiredObj) 19 | } 20 | 21 | updated := false 22 | if !reflect.DeepEqual(desired.Spec, existing.Spec) { 23 | existing.Spec = desired.Spec 24 | updated = true 25 | } 26 | 27 | return updated, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/reconcilers/hpa_test.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/3scale/3scale-operator/pkg/helper" 7 | hpa "k8s.io/api/autoscaling/v2" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func hpaTestFactory(maxPods int32) *hpa.HorizontalPodAutoscaler { 12 | return &hpa.HorizontalPodAutoscaler{ 13 | ObjectMeta: metav1.ObjectMeta{ 14 | Name: "test", 15 | Namespace: "someNs", 16 | }, 17 | Spec: hpa.HorizontalPodAutoscalerSpec{ 18 | ScaleTargetRef: hpa.CrossVersionObjectReference{ 19 | Kind: "Deployment", 20 | Name: "test", 21 | APIVersion: "apps/v1", 22 | }, 23 | MinReplicas: helper.Int32Ptr(1), 24 | MaxReplicas: maxPods, 25 | Metrics: []hpa.MetricSpec{ 26 | { 27 | Type: hpa.ResourceMetricSourceType, 28 | Resource: &hpa.ResourceMetricSource{ 29 | Name: "memory", 30 | Target: hpa.MetricTarget{ 31 | Type: hpa.UtilizationMetricType, 32 | AverageUtilization: helper.Int32Ptr(90), 33 | }, 34 | }, 35 | }, 36 | { 37 | Type: hpa.ResourceMetricSourceType, 38 | Resource: &hpa.ResourceMetricSource{ 39 | Name: "cpu", 40 | Target: hpa.MetricTarget{ 41 | Type: hpa.UtilizationMetricType, 42 | AverageUtilization: helper.Int32Ptr(90), 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | } 49 | } 50 | 51 | func TestGenericHPAMutator(t *testing.T) { 52 | var existingMaxPods int32 = 1 53 | var desiredMaxPods int32 = 2 54 | 55 | existing := hpaTestFactory(existingMaxPods) 56 | desired := hpaTestFactory(desiredMaxPods) 57 | 58 | update, err := GenericHPAMutator(existing, desired) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | if !update { 63 | t.Fatal("when defaults can be applied, reconciler reported no update needed") 64 | } 65 | 66 | if existing.Spec.MaxReplicas != desiredMaxPods { 67 | t.Fatalf("MaxReplicas not reconciled. Expected: %d, got: %d", desiredMaxPods, existing.Spec.MaxReplicas) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/reconcilers/pod_disruption_budget.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | policyv1 "k8s.io/api/policy/v1" 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func GenericPDBMutator(existingObj, desiredObj client.Object) (bool, error) { 12 | existing, ok := existingObj.(*policyv1.PodDisruptionBudget) 13 | if !ok { 14 | return false, fmt.Errorf("%T is not a *v1.PodDisruptionBudget", existingObj) 15 | } 16 | desired, ok := desiredObj.(*policyv1.PodDisruptionBudget) 17 | if !ok { 18 | return false, fmt.Errorf("%T is not a *v1.PodDisruptionBudget", desiredObj) 19 | } 20 | 21 | updated := false 22 | if !reflect.DeepEqual(desired.Spec, existing.Spec) { 23 | existing.Spec = desired.Spec 24 | updated = true 25 | } 26 | 27 | return updated, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/reconcilers/pod_disruption_budget_test.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "testing" 5 | 6 | policyv1 "k8s.io/api/policy/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/util/intstr" 9 | ) 10 | 11 | func pdbTestFactory(maxUnavailable int32) *policyv1.PodDisruptionBudget { 12 | return &policyv1.PodDisruptionBudget{ 13 | ObjectMeta: metav1.ObjectMeta{ 14 | Name: "myPodDisruptionBudget", 15 | Namespace: "someNs", 16 | }, 17 | Spec: policyv1.PodDisruptionBudgetSpec{ 18 | Selector: &metav1.LabelSelector{ 19 | MatchLabels: map[string]string{"test1": "mytest1"}, 20 | }, 21 | MaxUnavailable: &intstr.IntOrString{IntVal: maxUnavailable}, 22 | }, 23 | } 24 | } 25 | 26 | func TestGenericPDBMutator(t *testing.T) { 27 | var existingMaxUnavailable int32 = 1 28 | var desiredMaxUnavailable int32 = 2 29 | 30 | existing := pdbTestFactory(existingMaxUnavailable) 31 | desired := pdbTestFactory(desiredMaxUnavailable) 32 | 33 | update, err := GenericPDBMutator(existing, desired) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if !update { 38 | t.Fatal("when defaults can be applied, reconciler reported no update needed") 39 | } 40 | 41 | if existing.Spec.MaxUnavailable.IntVal != desiredMaxUnavailable { 42 | t.Fatalf("Maxunavailable not reconciled. Expected: %d, got: %d", desiredMaxUnavailable, existing.Spec.MaxUnavailable.IntVal) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/reconcilers/pod_monitors.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func GenericPodMonitorMutator(existingObj, desiredObj client.Object) (bool, error) { 12 | existing, ok := existingObj.(*monitoringv1.PodMonitor) 13 | if !ok { 14 | return false, fmt.Errorf("%T is not a *monitoringv1.PodMonitor", existingObj) 15 | } 16 | desired, ok := desiredObj.(*monitoringv1.PodMonitor) 17 | if !ok { 18 | return false, fmt.Errorf("%T is not a *monitoringv1.PodMonitor", desiredObj) 19 | } 20 | 21 | updated := false 22 | if !reflect.DeepEqual(desired.Spec, existing.Spec) { 23 | existing.Spec = desired.Spec 24 | updated = true 25 | } 26 | 27 | return updated, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/reconcilers/pod_monitors_test.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "testing" 5 | 6 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | func podMonitoringTestFactory(port string) *monitoringv1.PodMonitor { 11 | return &monitoringv1.PodMonitor{ 12 | ObjectMeta: metav1.ObjectMeta{ 13 | Name: "test-app", 14 | }, 15 | Spec: monitoringv1.PodMonitorSpec{ 16 | PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{ 17 | { 18 | Port: port, 19 | Path: "/metrics", 20 | Scheme: "http", 21 | }, 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | func TestGenericPodMonitorMutator(t *testing.T) { 28 | existingPort := "8080" 29 | desiredPort := "9090" 30 | 31 | existing := podMonitoringTestFactory(existingPort) 32 | desired := podMonitoringTestFactory(desiredPort) 33 | 34 | update, err := GenericPodMonitorMutator(existing, desired) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !update { 39 | t.Fatal("when defaults can be applied, reconciler reported no update needed") 40 | } 41 | 42 | if existing.Spec.PodMetricsEndpoints[0].Port != desiredPort { 43 | t.Fatalf("PodMonitor not reconciled. Expected: %s, got: %s", desiredPort, existing.Spec.PodMetricsEndpoints[0].Port) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/reconcilers/prometheus_rules.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | "sigs.k8s.io/controller-runtime/pkg/client" 6 | ) 7 | 8 | // RemovePrometheusRulesMutator removes the 'ThreeScaleApicastRequestTime' alert 9 | func RemovePrometheusRulesMutator(existing, desired client.Object) (bool, error) { 10 | existingPrometheusRule := existing.(*monitoringv1.PrometheusRule) 11 | removed := false 12 | updatedRules := []monitoringv1.Rule{} 13 | group := existingPrometheusRule.Spec.Groups[0] 14 | 15 | for _, rule := range group.Rules { 16 | if rule.Alert != "ThreescaleApicastRequestTime" { 17 | updatedRules = append(updatedRules, rule) 18 | } else { 19 | removed = true 20 | } 21 | } 22 | if removed { 23 | group.Rules = updatedRules 24 | existingPrometheusRule.Spec.Groups[0] = group 25 | 26 | log.Info("Alert 'ThreescaleApicastRequestTime' removed from PrometheusRules") 27 | return true, nil 28 | } 29 | log.Info("Alert 'ThreescaleApicastRequestTime' not found, no update required.") 30 | return false, nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/reconcilers/role.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func RoleRuleMutator(existingObj, desiredObj client.Object) (bool, error) { 12 | existing, ok := existingObj.(*rbacv1.Role) 13 | if !ok { 14 | return false, fmt.Errorf("%T is not a *rbacv1.Role", existingObj) 15 | } 16 | desired, ok := desiredObj.(*rbacv1.Role) 17 | if !ok { 18 | return false, fmt.Errorf("%T is not a *rbacv1.Role", desiredObj) 19 | } 20 | 21 | updated := false 22 | 23 | if !reflect.DeepEqual(existing.Rules, desired.Rules) { 24 | existing.Rules = desired.Rules 25 | updated = true 26 | } 27 | 28 | return updated, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/reconcilers/secret_test.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/3scale/3scale-operator/pkg/helper" 7 | 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func TestDefaultsOnlySecretMutatorNoUpdateNeeded(t *testing.T) { 12 | desired := &v1.Secret{ 13 | StringData: map[string]string{ 14 | "a1": "a1Value", 15 | "a2": "a2Value", 16 | }, 17 | } 18 | existing := &v1.Secret{ 19 | StringData: map[string]string{ 20 | "a1": "other_a1_value", 21 | "a2": "other_a2_value", 22 | }, 23 | } 24 | existing.Data = helper.GetSecretDataFromStringData(existing.StringData) 25 | 26 | update, err := DefaultsOnlySecretMutator(existing, desired) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | if update { 32 | t.Fatal("when defaults cannot be applied, reconciler reported update needed") 33 | } 34 | } 35 | 36 | func TestDefaultsOnlySecretReconciler(t *testing.T) { 37 | desired := &v1.Secret{ 38 | StringData: map[string]string{ 39 | "a1": "a01Value", 40 | "a2": "a02Value", 41 | }, 42 | } 43 | existing := &v1.Secret{ 44 | StringData: map[string]string{ 45 | "a2": "other_a2_value", 46 | "a3": "a3Value", 47 | }, 48 | } 49 | existing.Data = helper.GetSecretDataFromStringData(existing.StringData) 50 | 51 | update, err := DefaultsOnlySecretMutator(existing, desired) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | if !update { 57 | t.Fatal("when defaults can be applied, reconciler reported no update needed") 58 | } 59 | 60 | _, ok := existing.StringData["a1"] 61 | if !ok { 62 | t.Fatal("existingSecret does not have a1 data") 63 | } 64 | 65 | a2Value, ok := existing.StringData["a2"] 66 | if !ok { 67 | t.Fatal("existingSecret does not have a2 data") 68 | } 69 | 70 | if a2Value != "other_a2_value" { 71 | t.Fatalf("existingSecret data not expected. Expected: 'other_a2_value', got: %s", a2Value) 72 | } 73 | 74 | _, ok = existing.StringData["a3"] 75 | if !ok { 76 | t.Fatal("existingSecret does not have a3 data") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pkg/restore/apimanager_restore_options.go: -------------------------------------------------------------------------------- 1 | package restore 2 | 3 | import ( 4 | validator "github.com/go-playground/validator/v10" 5 | "k8s.io/apimachinery/pkg/types" 6 | ) 7 | 8 | type APIManagerRestoreOptions struct { 9 | Namespace string `validate:"required"` // Namespace where the K8s related objects to the restore will be created/looked 10 | APIManagerRestoreName string `validate:"required"` // Name of the APIManagerRestore CR. NOT the backup or APIManager name 11 | APIManagerRestoreUID types.UID `validate:"required"` // UID of the APIManagerRestore CR 12 | 13 | APIManagerRestorePVCOptions *APIManagerRestorePVCOptions `validate:"required"` 14 | OCCLIImageURL string `validate:"required"` 15 | } 16 | 17 | func NewAPIManagerRestoreOptions() *APIManagerRestoreOptions { 18 | return &APIManagerRestoreOptions{} 19 | } 20 | 21 | func (a *APIManagerRestoreOptions) Validate() error { 22 | validate := validator.New() 23 | return validate.Struct(a) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/restore/apimanager_restore_pvc_options.go: -------------------------------------------------------------------------------- 1 | package restore 2 | 3 | import ( 4 | validator "github.com/go-playground/validator/v10" 5 | v1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | type APIManagerRestorePVCOptions struct { 9 | PersistentVolumeClaimVolumeSource v1.PersistentVolumeClaimVolumeSource `validate:"required"` 10 | } 11 | 12 | func NewAPIManagerRestorePVCOptions() *APIManagerRestorePVCOptions { 13 | return &APIManagerRestorePVCOptions{} 14 | } 15 | 16 | func (a *APIManagerRestorePVCOptions) Validate() error { 17 | validate := validator.New() 18 | return validate.Struct(a) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/restore/runtime_apimanager_restore_info.go: -------------------------------------------------------------------------------- 1 | package restore 2 | 3 | type RuntimeAPIManagerRestoreInfo struct { 4 | PVCStorageClass *string 5 | } 6 | -------------------------------------------------------------------------------- /test/manifests-version/deployment_version_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "path" 8 | "testing" 9 | 10 | "github.com/3scale/3scale-operator/version" 11 | 12 | appsv1 "k8s.io/api/apps/v1" 13 | utilyaml "k8s.io/apimachinery/pkg/util/yaml" 14 | ) 15 | 16 | func TestDeploymentVersions(t *testing.T) { 17 | root := "../../config/manager/" 18 | path := path.Join(root, "manager.yaml") 19 | yamlBytes, err := os.ReadFile(path) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | bytesReader := io.NopCloser(bytes.NewReader(yamlBytes)) 25 | yamlDocumentDecoder := utilyaml.NewDocumentDecoder(bytesReader) 26 | 27 | // Read and discard Namespace object from the yaml file 28 | res := make([]byte, len(yamlBytes)) 29 | _, err = yamlDocumentDecoder.Read(res) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | // Read the Deployment object from the yaml file 35 | n, err := yamlDocumentDecoder.Read(res) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | // Copy the Deployment object bytes length 41 | deploymentBytes := make([]byte, n) 42 | copy(deploymentBytes, res[:n]) 43 | 44 | // Decode the Deployment object 45 | deployment := appsv1.Deployment{} 46 | fd := bytes.NewReader(deploymentBytes) 47 | yamlDecoder := utilyaml.NewYAMLOrJSONDecoder(fd, fd.Len()) 48 | err = yamlDecoder.Decode(&deployment) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | if deployment.Kind != "Deployment" { 54 | t.Errorf("Parsed object is not a Deployment object") 55 | } 56 | 57 | if deployment.Spec.Template.Labels["rht.comp_ver"] != version.ThreescaleVersionMajorMinor() { 58 | t.Errorf("rht.comp_ver differ: expected: %s; found: %s", version.ThreescaleVersionMajorMinor(), deployment.Spec.Template.Labels["rht.comp_ver"]) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var ( 8 | Version = "0.13.0" 9 | threescaleRelease = "2.16.0" 10 | ) 11 | 12 | func ThreescaleVersionMajorMinor() string { 13 | parts := strings.Split(threescaleRelease, ".") 14 | if len(parts) >= 2 { 15 | return parts[0] + "." + parts[1] 16 | } 17 | return "" 18 | } 19 | 20 | func ThreescaleVersionMajorMinorPatch() string { 21 | return threescaleRelease 22 | } 23 | --------------------------------------------------------------------------------