├── .github
├── ISSUE_TEMPLATE
│ ├── bug.md
│ ├── config.yml
│ └── feature.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yaml
├── release.yml
└── workflows
│ ├── build.yaml
│ ├── checks.yaml
│ ├── docs.yaml
│ ├── lint.yaml
│ ├── publish-dev.yaml
│ ├── publish-release.yaml
│ └── tests.yaml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd
├── addon-operator
│ └── main.go
└── post-renderer
│ └── main.go
├── docs
├── book.toml
└── src
│ ├── HOOKS.md
│ ├── LIFECYCLE-STEPS.md
│ ├── LIFECYCLE.md
│ ├── MODULES.md
│ ├── OVERVIEW.md
│ ├── README.md
│ ├── RUNNING.md
│ ├── SUMMARY.md
│ ├── VALUES.md
│ ├── image
│ ├── global_values_flow.png
│ ├── logo-addon-operator-small.png
│ ├── module_values_flow.png
│ ├── readme-1.gif
│ ├── readme-2.gif
│ ├── readme-3.gif
│ ├── readme-4.gif
│ ├── readme-5.gif
│ └── readme-6.gif
│ └── metrics
│ ├── METRICS_FROM_HOOKS.md
│ ├── ROOT.md
│ └── SELF_METRICS.md
├── examples
├── 001-startup-global
│ ├── Dockerfile
│ ├── README.md
│ ├── addon-operator-cm.yaml
│ ├── addon-operator-pod.yaml
│ ├── addon-operator-rbac.yaml
│ ├── global-hooks
│ │ └── hook.sh
│ └── modules
│ │ └── README.md
├── 002-startup-global-high-availability
│ ├── Dockerfile
│ ├── README.md
│ ├── addon-operator-cm.yaml
│ ├── addon-operator-deployment.yaml
│ ├── addon-operator-rbac.yaml
│ ├── global-hooks
│ │ └── hook.sh
│ └── modules
│ │ └── README.md
├── 101-module-sysctl-tuner
│ ├── Dockerfile
│ ├── README.md
│ ├── addon-operator-cm.yaml
│ ├── addon-operator-pod.yaml
│ ├── addon-operator-rbac.yaml
│ └── modules
│ │ └── 001-sysctl-tuner
│ │ ├── Chart.yaml
│ │ ├── README.md
│ │ ├── hooks
│ │ └── module-hooks.sh
│ │ ├── templates
│ │ └── daemon-set.yaml
│ │ └── values.yaml
├── 201-sysctl-tuner-values
│ ├── Dockerfile
│ ├── README.md
│ ├── addon-operator-cm.yaml
│ ├── addon-operator-deploy.yaml
│ ├── addon-operator-rbac.yaml
│ └── modules
│ │ └── 001-sysctl-tuner
│ │ ├── Chart.yaml
│ │ ├── README.md
│ │ ├── hooks
│ │ └── module-hooks.sh
│ │ ├── templates
│ │ └── daemon-set.yaml
│ │ └── values.yaml
├── 202-module-symlinks
│ ├── Dockerfile
│ ├── README.md
│ ├── deploy
│ │ ├── cm.yaml
│ │ ├── deployment.yaml
│ │ └── rbac.yaml
│ ├── global-hooks
│ │ └── .gitkeep
│ ├── module_dir_1
│ │ └── 001-backend
│ │ │ ├── Chart.yaml
│ │ │ ├── hooks
│ │ │ └── startup
│ │ │ ├── templates
│ │ │ └── backend.yaml
│ │ │ └── values.yaml
│ ├── module_dir_2
│ │ └── 002-frontend
│ └── module_storage
│ │ └── 002-frontend
│ │ ├── Chart.yaml
│ │ ├── hooks
│ │ └── startup
│ │ ├── templates
│ │ └── frontend.yaml
│ │ └── values.yaml
└── 700-go-hook
│ ├── README.md
│ ├── global-hooks
│ └── global-go-hook.go
│ ├── modules
│ └── 001-module-go-hooks
│ │ └── hooks
│ │ └── go_hooks.go
│ └── register_go_hooks.go.tpl
├── go.mod
├── go.sum
├── pkg
├── addon-operator
│ ├── admission_http_server.go
│ ├── bootstrap.go
│ ├── converge
│ │ └── converge.go
│ ├── debug_server.go
│ ├── ensure_crds.go
│ ├── handler_manager_events.go
│ ├── handler_module_manager.go
│ ├── http_server.go
│ ├── kube_client.go
│ ├── metrics.go
│ ├── operator.go
│ ├── operator_test.go
│ ├── queue.go
│ ├── queue_test.go
│ └── testdata
│ │ ├── converge__main_queue_only
│ │ ├── config_map.yaml
│ │ ├── global-hooks
│ │ │ ├── hook01_startup_20_kube.sh
│ │ │ ├── hook02_startup_1_schedule.sh
│ │ │ └── hook03_startup_10_kube_schedule.sh
│ │ └── modules
│ │ │ ├── 000-module-alpha
│ │ │ ├── Chart.yaml
│ │ │ └── hooks
│ │ │ │ ├── hook01_startup_20_kube.sh
│ │ │ │ └── hook02_startup_1_schedule.sh
│ │ │ └── 001-module-beta
│ │ │ ├── Chart.yaml
│ │ │ └── hooks
│ │ │ ├── hook01_startup_20_kube.sh
│ │ │ └── hook02_after_delete_helm.sh
│ │ ├── log_task__wait_for_synchronization
│ │ ├── config_map.yaml
│ │ ├── global-hooks
│ │ │ └── hook01_startup_20_kube.sh
│ │ └── modules
│ │ │ └── 000-module-alpha
│ │ │ ├── Chart.yaml
│ │ │ └── hooks
│ │ │ └── hook01_startup_20_kube.sh
│ │ └── startup_tasks
│ │ └── global-hooks
│ │ ├── hook01_startup_20_kube.sh
│ │ ├── hook02_startup_1_schedule.sh
│ │ └── hook03_startup_10_kube_schedule.sh
├── app
│ ├── app.go
│ └── debug-cmd.go
├── helm
│ ├── client
│ │ └── client.go
│ ├── helm.go
│ ├── helm3lib
│ │ ├── helm3lib.go
│ │ ├── helm3lib_test.go
│ │ └── testdata
│ │ │ └── chart
│ │ │ ├── Chart.yaml
│ │ │ └── templates
│ │ │ └── 001-resources.yaml
│ ├── helm_test.go
│ ├── post_renderer
│ │ ├── post_renderer.go
│ │ └── post_renderer_test.go
│ ├── test
│ │ └── mock
│ │ │ └── mock.go
│ ├── version_detect.go
│ └── version_detect_test.go
├── helm_resources_manager
│ ├── helm_resources_manager.go
│ ├── resources_monitor.go
│ ├── test
│ │ └── mock
│ │ │ └── mock.go
│ └── types
│ │ └── types.go
├── hook
│ └── types
│ │ └── bindings.go
├── kube_config_manager
│ ├── backend
│ │ ├── backend.go
│ │ └── configmap
│ │ │ ├── configmap.go
│ │ │ └── configmap_test.go
│ ├── checksums.go
│ ├── checksums_test.go
│ ├── config
│ │ ├── config.go
│ │ └── event.go
│ ├── kube_config_manager.go
│ └── kube_config_manager_test.go
├── labels.go
├── module_manager
│ ├── environment_manager
│ │ ├── evironment_manager.go
│ │ ├── evironment_manager_test.go
│ │ ├── mount.go
│ │ └── mount_linux.go
│ ├── go_hook
│ │ ├── filter_result.go
│ │ ├── filter_result_test.go
│ │ ├── go_hook.go
│ │ ├── logger.go
│ │ └── metrics
│ │ │ └── collector.go
│ ├── loader
│ │ ├── fs
│ │ │ ├── fs.go
│ │ │ ├── fs_test.go
│ │ │ └── testdata
│ │ │ │ └── module_loader
│ │ │ │ ├── dir1
│ │ │ │ ├── 001-module-one
│ │ │ │ ├── 002-module-two
│ │ │ │ └── values.yaml
│ │ │ │ ├── dir2
│ │ │ │ ├── 012-mod-two
│ │ │ │ ├── 100-mod-one
│ │ │ │ └── values.yaml
│ │ │ │ ├── dir3
│ │ │ │ └── values.yaml
│ │ │ │ └── modules
│ │ │ │ ├── 001-module-one
│ │ │ │ └── .gitkeep
│ │ │ │ ├── 002-module-two
│ │ │ │ └── .gitkeep
│ │ │ │ └── 003-module-three
│ │ │ │ └── .gitkeep
│ │ └── loader.go
│ ├── models
│ │ ├── hooks
│ │ │ ├── dependency.go
│ │ │ ├── global_hook.go
│ │ │ ├── global_hook_config.go
│ │ │ ├── global_hook_test.go
│ │ │ ├── kind
│ │ │ │ ├── batch_hook.go
│ │ │ │ ├── batch_hook_test.go
│ │ │ │ ├── gohook.go
│ │ │ │ ├── gohook_test.go
│ │ │ │ ├── kind.go
│ │ │ │ ├── shellhook.go
│ │ │ │ └── shellhook_test.go
│ │ │ ├── module_hook.go
│ │ │ └── module_hook_config.go
│ │ ├── modules
│ │ │ ├── basic.go
│ │ │ ├── basic_test.go
│ │ │ ├── events
│ │ │ │ └── events.go
│ │ │ ├── global.go
│ │ │ ├── helm.go
│ │ │ ├── hook_storage.go
│ │ │ ├── module_options.go
│ │ │ ├── synchronization_state.go
│ │ │ ├── testdata
│ │ │ │ └── global
│ │ │ │ │ └── openapi
│ │ │ │ │ ├── config-values.yaml
│ │ │ │ │ └── values.yaml
│ │ │ ├── values_defaulting_transformers.go
│ │ │ ├── values_layered.go
│ │ │ ├── values_layered_test.go
│ │ │ ├── values_storage.go
│ │ │ └── values_storage_test.go
│ │ └── moduleset
│ │ │ ├── moduleset.go
│ │ │ └── moduleset_test.go
│ ├── module_manager.go
│ ├── module_manager_hooks.go
│ ├── module_manager_test.go
│ ├── scheduler
│ │ ├── extenders
│ │ │ ├── dynamically_enabled
│ │ │ │ ├── dynamic.go
│ │ │ │ └── dynamic_test.go
│ │ │ ├── error
│ │ │ │ └── permanent.go
│ │ │ ├── extenders.go
│ │ │ ├── kube_config
│ │ │ │ └── kube_config.go
│ │ │ ├── mock
│ │ │ │ └── extenders_mock.go
│ │ │ ├── script_enabled
│ │ │ │ ├── script.go
│ │ │ │ ├── script_test.go
│ │ │ │ └── testdata
│ │ │ │ │ ├── 015-admission-policy-engine
│ │ │ │ │ └── enabled
│ │ │ │ │ ├── 020-node-local-dns
│ │ │ │ │ └── enabled
│ │ │ │ │ ├── 031-foo-bar
│ │ │ │ │ └── enabled
│ │ │ │ │ ├── 045-chrony
│ │ │ │ │ └── enabled
│ │ │ │ │ └── 402-ingress-nginx
│ │ │ │ │ └── enabled
│ │ │ └── static
│ │ │ │ ├── static.go
│ │ │ │ └── static_test.go
│ │ ├── node
│ │ │ ├── mock
│ │ │ │ └── node_mock.go
│ │ │ ├── node.go
│ │ │ └── node_test.go
│ │ ├── scheduler.go
│ │ ├── scheduler_test.go
│ │ └── testdata
│ │ │ ├── 015-admission-policy-engine
│ │ │ └── enabled
│ │ │ ├── 042-kube-dns
│ │ │ └── enabled
│ │ │ ├── 133-foo-bar
│ │ │ └── enabled
│ │ │ ├── 20-cert-manager
│ │ │ └── enabled
│ │ │ ├── 30-openstack-cloud-provider
│ │ │ └── enabled
│ │ │ ├── 340-monitoring-applications
│ │ │ └── enabled
│ │ │ ├── 402-ingress-nginx
│ │ │ └── enabled
│ │ │ ├── 450-flant-integration
│ │ │ └── enabled
│ │ │ └── 909-test-echo
│ │ │ └── enabled
│ └── testdata
│ │ ├── get__global_hook
│ │ ├── global-hooks
│ │ │ ├── 000-all-bindings
│ │ │ │ └── hook
│ │ │ └── 100-nested-hook
│ │ │ │ └── sub
│ │ │ │ └── sub
│ │ │ │ └── hook
│ │ └── modules
│ │ │ └── .gitkeep
│ │ ├── get__global_hooks_in_order
│ │ ├── global-hooks
│ │ │ └── 000-before-all-binding-hooks
│ │ │ │ ├── a
│ │ │ │ ├── b
│ │ │ │ └── c
│ │ └── modules
│ │ │ └── .gitkeep
│ │ ├── get__module
│ │ └── modules
│ │ │ └── 000-module
│ │ │ └── .gitkeep
│ │ ├── get__module_hook
│ │ └── modules
│ │ │ ├── 000-all-bindings
│ │ │ └── hooks
│ │ │ │ └── all-bindings
│ │ │ └── 100-nested-hooks
│ │ │ └── hooks
│ │ │ └── sub
│ │ │ └── sub
│ │ │ └── nested-before-helm
│ │ ├── get__module_hooks_in_order
│ │ ├── config_map.yaml
│ │ └── modules
│ │ │ └── 107-after-helm-binding-hooks
│ │ │ ├── hooks
│ │ │ ├── a
│ │ │ ├── b
│ │ │ └── c
│ │ │ └── values.yaml
│ │ ├── load_and_validate_usage
│ │ ├── global
│ │ │ ├── hooks
│ │ │ │ └── hook
│ │ │ └── openapi
│ │ │ │ └── config-values.yaml
│ │ └── modules
│ │ │ └── 000-test-module
│ │ │ ├── hooks
│ │ │ └── hook
│ │ │ └── openapi
│ │ │ └── config-values.yaml
│ │ ├── load_values__common_and_module_and_kube
│ │ ├── config_map.yaml
│ │ └── modules
│ │ │ ├── 000-with-values-1
│ │ │ └── values.yaml
│ │ │ ├── 001-with-values-2
│ │ │ └── values.yaml
│ │ │ ├── 002-without-values
│ │ │ └── .gitkeep
│ │ │ ├── 003-with-kube-values
│ │ │ └── .gitkeep
│ │ │ └── values.yaml
│ │ ├── load_values__common_static_empty
│ │ └── modules
│ │ │ ├── .gitkeep
│ │ │ └── 000-module
│ │ │ └── .gitkeep
│ │ ├── load_values__common_static_global_only
│ │ └── modules
│ │ │ └── values.yaml
│ │ ├── load_values__module_apply_defaults
│ │ ├── config_map.yaml
│ │ ├── global-hooks
│ │ │ └── openapi
│ │ │ │ ├── config-values.yaml
│ │ │ │ └── values.yaml
│ │ └── modules
│ │ │ ├── 000-module-one
│ │ │ └── openapi
│ │ │ │ ├── config-values.yaml
│ │ │ │ └── values.yaml
│ │ │ └── values.yaml
│ │ ├── load_values__module_static_only
│ │ └── modules
│ │ │ ├── 000-with-values-1
│ │ │ └── values.yaml
│ │ │ └── 001-with-values-2
│ │ │ └── values.yaml
│ │ ├── loader
│ │ └── values.yaml
│ │ ├── modules_state__detect_cm_changes
│ │ ├── config_map.yaml
│ │ └── modules
│ │ │ ├── 001-module-one
│ │ │ └── .gitkeep
│ │ │ ├── 003-module-three
│ │ │ └── .gitkeep
│ │ │ └── values.yaml
│ │ ├── modules_state__no_cm__module_names_order
│ │ └── modules
│ │ │ ├── 000-module-c
│ │ │ ├── .gitkeep
│ │ │ └── values.yaml
│ │ │ ├── 100-module-a
│ │ │ └── .gitkeep
│ │ │ ├── 200-module-b
│ │ │ ├── .gitkeep
│ │ │ └── values.yaml
│ │ │ └── 300-module-disabled
│ │ │ └── values.yaml
│ │ ├── modules_state__no_cm__simple
│ │ └── modules
│ │ │ ├── 001-module-1
│ │ │ └── .gitkeep
│ │ │ ├── 003-module-3
│ │ │ └── .gitkeep
│ │ │ ├── 004-module-4
│ │ │ └── .gitkeep
│ │ │ ├── 007-module-7
│ │ │ └── .gitkeep
│ │ │ ├── 008-module-8
│ │ │ └── .gitkeep
│ │ │ ├── 009-module-9
│ │ │ └── .gitkeep
│ │ │ └── values.yaml
│ │ ├── modules_state__no_cm__with_enabled_scripts
│ │ └── modules
│ │ │ ├── 001-alpha
│ │ │ └── enabled
│ │ │ ├── 002-beta
│ │ │ └── enabled
│ │ │ ├── 003-gamma
│ │ │ └── enabled
│ │ │ ├── 004-delta
│ │ │ └── enabled
│ │ │ ├── 005-epsilon
│ │ │ └── enabled
│ │ │ ├── 006-zeta
│ │ │ └── enabled
│ │ │ ├── 007-eta
│ │ │ └── enabled
│ │ │ └── values.yaml
│ │ ├── test_delete_module
│ │ └── modules
│ │ │ └── 000-module
│ │ │ ├── .helmignore
│ │ │ ├── Chart.yaml
│ │ │ ├── hooks
│ │ │ ├── hook-1
│ │ │ └── hook-2
│ │ │ ├── templates
│ │ │ ├── NOTES.txt
│ │ │ ├── _helpers.tpl
│ │ │ ├── deployment.yaml
│ │ │ ├── ingress.yaml
│ │ │ └── service.yaml
│ │ │ └── values.yaml
│ │ ├── test_run_global_hook
│ │ ├── global-hooks
│ │ │ ├── 000-update-kube-config
│ │ │ │ └── merge_and_patch_values
│ │ │ └── 100-update-dynamic
│ │ │ │ └── merge_and_patch_values
│ │ └── modules
│ │ │ └── .gitkeep
│ │ ├── test_run_module
│ │ └── modules
│ │ │ └── 000-module
│ │ │ ├── .helmignore
│ │ │ ├── Chart.yaml
│ │ │ ├── hooks
│ │ │ ├── hook-1
│ │ │ ├── hook-2
│ │ │ ├── hook-3
│ │ │ └── hook-4
│ │ │ ├── templates
│ │ │ ├── NOTES.txt
│ │ │ ├── _helpers.tpl
│ │ │ ├── deployment.yaml
│ │ │ ├── ingress.yaml
│ │ │ └── service.yaml
│ │ │ └── values.yaml
│ │ └── test_run_module_hook
│ │ └── modules
│ │ ├── 000-update-kube-module-config
│ │ └── hooks
│ │ │ └── merge_and_patch_values
│ │ └── 100-update-module-dynamic
│ │ └── hooks
│ │ └── merge_and_patch_values
├── task
│ ├── apply-kube-config-values
│ │ └── task.go
│ ├── converge-modules
│ │ └── task.go
│ ├── discover-crds
│ │ └── service.go
│ ├── discover-helm-release
│ │ └── task.go
│ ├── global-hook-enable-kubernetes-bindings
│ │ └── task.go
│ ├── global-hook-enable-schedule-bindings
│ │ └── task.go
│ ├── global-hook-run
│ │ └── task.go
│ ├── global-hook-wait-kubernetes-synchronization
│ │ └── task.go
│ ├── helpers
│ │ └── helpers.go
│ ├── hook_metadata.go
│ ├── module-delete
│ │ └── task.go
│ ├── module-ensure-crds
│ │ └── task.go
│ ├── module-hook-run
│ │ └── task.go
│ ├── module-purge
│ │ └── task.go
│ ├── module-run
│ │ └── task.go
│ ├── parallel-module-run
│ │ └── task.go
│ ├── parallel
│ │ └── parallel.go
│ ├── queue
│ │ └── queue.go
│ ├── service
│ │ ├── converge.go
│ │ ├── logs.go
│ │ ├── metric.go
│ │ └── service.go
│ ├── task.go
│ └── test
│ │ └── task_metadata_test.go
├── utils
│ ├── chroot.go
│ ├── fschecksum.go
│ ├── fswalk.go
│ ├── helpers.go
│ ├── jsonpatch.go
│ ├── loader.go
│ ├── merge_labels.go
│ ├── mergemap.go
│ ├── mergemap_test.go
│ ├── module_config.go
│ ├── module_config_test.go
│ ├── module_list.go
│ ├── stdliblogtolog
│ │ ├── adapter.go
│ │ └── adapter_test.go
│ ├── values.go
│ ├── values_patch.go
│ ├── values_patch_test.go
│ └── values_test.go
└── values
│ └── validation
│ ├── cel
│ └── cel.go
│ ├── defaulting.go
│ ├── defaulting_test.go
│ ├── extend_test.go
│ ├── required_test.go
│ ├── schema
│ ├── additional-properties.go
│ ├── copy.go
│ ├── extend.go
│ ├── required-for-helm.go
│ └── transform.go
│ ├── schemas.go
│ ├── schemas_test.go
│ ├── testdata
│ ├── test-schema-bad.yaml
│ ├── test-schema-ok-project-2.yaml
│ ├── test-schema-ok-project.yaml
│ └── test-schema-ok.yaml
│ └── validator_test.go
└── sdk
├── registry.go
├── registry_test.go
├── sdk.go
└── test
├── sdk_test.go
└── simple_operator
├── global-hooks
└── go-hook.go
└── modules
├── 001-module-one
└── hooks
│ └── module-one-hook.go
└── 002-module-two
└── hooks
└── level1
└── sublevel
└── sub-sub-hook.go
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug report
3 | about: Report a bug to help us improve Addon-operator
4 | ---
5 |
11 |
12 | **Expected behavior (what you expected to happen)**:
13 |
14 | **Actual behavior (what actually happened)**:
15 |
16 | **Steps to reproduce**:
17 | 1. ...
18 | 2. ...
19 | 3. ...
20 |
21 | **Environment**:
22 | - Addon-operator version:
23 | - Helm version:
24 | - Kubernetes version:
25 | - Installation type (kubectl apply, helm chart, etc.):
26 |
27 | **Anything else we should know?**:
28 |
29 | **Additional information for debugging (if necessary)**:
30 |
31 | Hook script
32 |
33 |
34 |
35 | Logs
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 💬 Github Discussions
4 | url: https://github.com/flant/addon-operator/discussions
5 | about: Please ask and answer questions here
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Feature request
3 | about: Suggest an idea for Addon-operator
4 | ---
5 |
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 |
12 |
13 | **Describe the solution you'd like to see**
14 |
15 |
16 | **Describe alternatives you've considered**
17 |
18 |
19 | **Additional context**
20 |
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | #### Overview
10 |
11 |
12 |
13 | #### What this PR does / why we need it
14 |
15 |
21 |
22 | #### Special notes for your reviewer
23 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "weekly"
8 | # Maintain Go modules
9 | - package-ecosystem: "gomod"
10 | directory: "/"
11 | schedule:
12 | interval: "weekly"
13 | day: "monday"
14 | open-pull-requests-limit: 1
15 | ignore:
16 | - dependency-name: "k8s.io/api"
17 | - dependency-name: "k8s.io/apimachinery"
18 | - dependency-name: "k8s.io/client-go"
19 |
20 | - package-ecosystem: "docker"
21 | directory: "/"
22 | schedule:
23 | interval: "weekly"
24 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - release-note/ignore
5 | categories:
6 | - title: Exciting New Features 🎉
7 | labels:
8 | - release-note/new-feature
9 | - title: Enhancements 🚀
10 | labels:
11 | - enhancement
12 | - release-note/enhancement
13 | - title: Bug Fixes 🐛
14 | labels:
15 | - bug
16 | - release-note/bug
17 | - title: Breaking Changes 🛠
18 | labels:
19 | - release-note/breaking-change
20 | - title: Deprecations ❌
21 | labels:
22 | - release-note/deprecation
23 | - title: Dependency Updates ⬆️
24 | labels:
25 | - dependencies
26 | - release-note/dependencies
27 | - title: Other Changes
28 | labels:
29 | - "*"
30 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | # every push to a branch: build a binary
2 | name: Build
3 | on:
4 | pull_request:
5 | types: [opened, synchronize]
6 |
7 | jobs:
8 | build_binary:
9 | name: Build addon-operator binary
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Set up Go 1.23
13 | uses: actions/setup-go@v5
14 | with:
15 | go-version: '1.23'
16 | id: go
17 |
18 | - name: Check out addon-operator code
19 | uses: actions/checkout@v4
20 |
21 | - name: Restore Go modules
22 | id: go-modules-cache
23 | uses: actions/cache@v4
24 | with:
25 | path: |
26 | ~/go/pkg/mod
27 | key: ${{ runner.os }}-gomod-${{ hashFiles('go.mod', 'go.sum') }}
28 | restore-keys: |
29 | ${{ runner.os }}-gomod-
30 |
31 | - name: Download Go modules
32 | if: steps.go-modules-cache.outputs.cache-hit != 'true'
33 | run: |
34 | go mod download
35 | echo -n "Go modules unpacked size is: " && du -sh $HOME/go/pkg/mod
36 |
37 | - name: Build binary
38 | run: |
39 | export GOOS=linux
40 |
41 | go build ./cmd/addon-operator
--------------------------------------------------------------------------------
/.github/workflows/checks.yaml:
--------------------------------------------------------------------------------
1 | name: PR Checks
2 |
3 | on:
4 | pull_request:
5 | types: [opened, labeled, unlabeled, synchronize]
6 |
7 | jobs:
8 | release-label:
9 | name: Release note label
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Check minimum labels
14 | uses: mheap/github-action-required-labels@v5
15 | with:
16 | mode: minimum
17 | count: 1
18 | labels: "release-note/dependencies, dependencies, release-note/deprecation, release-note/breaking-change, release-note/bug, bug, release-note/enhancement, enhancement, release-note/new-feature, release-note/ignore"
19 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yaml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-20.04
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Setup mdBook
18 | uses: peaceiris/actions-mdbook@v2
19 | with:
20 | mdbook-version: '0.4.10'
21 |
22 | - run: mdbook build docs
23 |
24 | - name: Deploy
25 | uses: peaceiris/actions-gh-pages@v4
26 | if: ${{ github.ref == 'refs/heads/main' }}
27 | with:
28 | github_token: ${{ secrets.GITHUB_TOKEN }}
29 | publish_dir: ./docs/book
30 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yaml:
--------------------------------------------------------------------------------
1 | # every push to a branch:
2 | # - Run Go linters.
3 | # - Check grammar with codespell.
4 | name: Lint
5 | on:
6 | pull_request:
7 | types: [opened, synchronize]
8 |
9 | jobs:
10 | go_linters:
11 | name: Run Go linters
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Set up Go 1.23
15 | uses: actions/setup-go@v5
16 | with:
17 | go-version: '1.23'
18 | id: go
19 |
20 | - name: Check out addon-operator code
21 | uses: actions/checkout@v4
22 |
23 | - name: Restore Go modules
24 | id: go-modules-cache
25 | uses: actions/cache@v4
26 | with:
27 | path: |
28 | ~/go/pkg/mod
29 | key: ${{ runner.os }}-gomod-${{ hashFiles('go.mod', 'go.sum') }}
30 | restore-keys: |
31 | ${{ runner.os }}-gomod-
32 |
33 | - name: Download Go modules
34 | if: steps.go-modules-cache.outputs.cache-hit != 'true'
35 | run: |
36 | go mod download
37 | echo -n "Go modules unpacked size is: " && du -sh $HOME/go/pkg/mod
38 |
39 | - name: Run golangci-lint
40 | run: |
41 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b . v2.1.6
42 | ./golangci-lint run
43 |
44 | codespell:
45 | name: Run codespell
46 | runs-on: ubuntu-latest
47 | steps:
48 | - uses: actions/setup-python@v5
49 | with:
50 | python-version: 3.8
51 |
52 | - name: Check out addon-operator code
53 | uses: actions/checkout@v4
54 |
55 | - name: Run codespell
56 | run: |
57 | pip install codespell==v1.17.1
58 | codespell --skip=".git,go.mod,go.sum,*.log,*.gif,*.png" -L witht,eventtypes,uint,uptodate,afterall
59 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | # Run unit tests.
2 | # Starts for new and updated pull requests.
3 | name: Unit tests
4 | on:
5 | pull_request:
6 | types: [opened, synchronize]
7 |
8 | jobs:
9 | run_unit_tests:
10 | name: Run unit tests
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Set up Go 1.23
14 | uses: actions/setup-go@v5
15 | with:
16 | go-version: '1.23'
17 | id: go
18 |
19 | - name: Check out addon-operator code
20 | uses: actions/checkout@v4
21 |
22 | - name: Restore Go modules
23 | id: go-modules-cache
24 | uses: actions/cache@v4
25 | with:
26 | path: |
27 | ~/go/pkg/mod
28 | key: ${{ runner.os }}-gomod-${{ hashFiles('go.mod', 'go.sum') }}
29 | restore-keys: |
30 | ${{ runner.os }}-gomod-
31 |
32 | - name: Download Go modules
33 | if: steps.go-modules-cache.outputs.cache-hit != 'true'
34 | run: |
35 | go mod download
36 | echo -n "Go modules unpacked size is: " && du -sh $HOME/go/pkg/mod
37 |
38 | - name: Run unit tests
39 | run: |
40 | export GOOS=linux
41 |
42 | go test \
43 | --race \
44 | ./cmd/... ./pkg/...
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Graph images
9 | *.gv
10 | *gv.svg
11 |
12 | # Test binary, build with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # binary produced by go build
19 | /addon-operator
20 |
21 | .idea
22 | libjq
23 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/.gitmodules
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Prebuilt libjq.
2 | FROM --platform=${TARGETPLATFORM:-linux/amd64} flant/jq:b6be13d5-musl as libjq
3 |
4 | # Go builder.
5 |
6 | FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:1.23-alpine AS builder
7 |
8 |
9 | ARG appVersion=latest
10 | RUN apk --no-cache add git ca-certificates gcc musl-dev libc-dev binutils-gold
11 |
12 | # Cache-friendly download of go dependencies.
13 | ADD go.mod go.sum /app/
14 | WORKDIR /app
15 | RUN go mod download
16 |
17 | COPY --from=libjq /libjq /libjq
18 | ADD . /app
19 |
20 | # Clone shell-operator to get frameworks
21 | RUN git clone https://github.com/flant/shell-operator shell-operator-clone && \
22 | cd shell-operator-clone && \
23 | git checkout v1.7.2
24 |
25 | RUN shellOpVer=$(go list -m all | grep shell-operator | cut -d' ' -f 2-) \
26 | GOOS=linux \
27 | go build -ldflags="-linkmode external -extldflags '-static' -s -w -X 'github.com/flant/shell-operator/pkg/app.Version=$shellOpVer' -X 'github.com/flant/addon-operator/pkg/app.Version=$appVersion'" \
28 | -o addon-operator \
29 | ./cmd/addon-operator
30 |
31 | # Build helm post-renderer binary (required if helm3 binary is in use)
32 | RUN GOOS=linux \
33 | go build -o post-renderer ./cmd/post-renderer
34 |
35 | # Final image
36 | FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.21
37 | ARG TARGETPLATFORM
38 | # kubectl url has no variant (v7)
39 | # helm url has dashes and no variant (v7)
40 | RUN apk --no-cache add ca-certificates bash sed tini && \
41 | kubectlArch=$(echo ${TARGETPLATFORM:-linux/amd64} | sed 's/\/v7//') && \
42 | echo "Download kubectl for ${kubectlArch}" && \
43 | wget https://storage.googleapis.com/kubernetes-release/release/v1.25.5/bin/${kubectlArch}/kubectl -O /bin/kubectl && \
44 | chmod +x /bin/kubectl && \
45 | helmArch=$(echo ${TARGETPLATFORM:-linux/amd64} | sed 's/\//-/g;s/-v7//') && \
46 | wget https://get.helm.sh/helm-v3.10.3-${helmArch}.tar.gz -O /helm.tgz && \
47 | tar -z -x -C /bin -f /helm.tgz --strip-components=1 ${helmArch}/helm && \
48 | rm -f /helm.tgz
49 | COPY --from=libjq /bin/jq /usr/bin
50 | COPY --from=builder /app/addon-operator /
51 | COPY --from=builder /app/post-renderer /
52 | COPY --from=builder /app/shell-operator-clone/frameworks/shell/ /framework/shell/
53 | COPY --from=builder /app/shell-operator-clone/shell_lib.sh /
54 |
55 | WORKDIR /
56 |
57 | RUN mkdir /global-hooks /modules
58 | ENV MODULES_DIR /modules
59 | ENV GLOBAL_HOOKS_DIR /global-hooks
60 | ENTRYPOINT ["/sbin/tini", "--", "/addon-operator"]
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GO=$(shell which go)
2 | GIT=$(shell which git)
3 |
4 | .PHONY: go-check
5 | go-check:
6 | $(call error-if-empty,$(GO),go)
7 |
8 | .PHONY: git-check
9 | git-check:
10 | $(call error-if-empty,$(GIT),git)
11 |
12 | .PHONY: go-module-version
13 | go-module-version: go-check git-check
14 | @echo "go get $(shell $(GO) list ./cmd/addon-operator)@$(shell $(GIT) rev-parse HEAD)"
15 |
16 | .PHONY: test
17 | test: go-check
18 | @$(GO) test --race --cover ./...
19 |
20 | define error-if-empty
21 | @if [[ -z $(1) ]]; then echo "$(2) not installed"; false; fi
22 | endef
--------------------------------------------------------------------------------
/cmd/post-renderer/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "github.com/flant/addon-operator/pkg/helm/post_renderer"
10 | )
11 |
12 | func main() {
13 | inputBytes, err := io.ReadAll(os.Stdin)
14 | if err != nil {
15 | fmt.Fprintf(os.Stderr, "couldn't read input from stdin: %s", err)
16 | os.Exit(1)
17 | }
18 | buf := bytes.NewBuffer(inputBytes)
19 |
20 | renderer := post_renderer.NewPostRenderer(map[string]string{
21 | "heritage": "addon-operator",
22 | })
23 |
24 | outputBytes, err := renderer.Run(buf)
25 | if err != nil {
26 | fmt.Fprintf(os.Stderr, "could not render input from stdin: %s", err)
27 | os.Exit(1)
28 | }
29 |
30 | if _, err := os.Stdout.Write(outputBytes.Bytes()); err != nil {
31 | fmt.Fprintf(os.Stderr, "could not write rendered output to stdout: %s", err)
32 | os.Exit(1)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["The Addon-Operator Maintainers"]
3 | language = "en"
4 | multilingual = false
5 | src = "src"
6 | title = "Addon-operator"
7 |
8 | [output.html]
9 | curly-quotes = true
10 | git-repository-url = "https://github.com/flant/addon-operator"
11 |
--------------------------------------------------------------------------------
/docs/src/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Installation
12 |
13 | You may use a prepared image [flant/addon-operator][docker-hub] to install addon-operator in a cluster. The image comprises a binary `addon-operator` file as well as several required tools: `helm`, `kubectl`, `jq`, `bash`.
14 |
15 | The installation incorporates the image building process with *files of modules and hooks*, applying the necessary RBAC rights and deploying the image in the cluster.
16 |
17 | ## Examples
18 |
19 | To experiment with modules, hooks, and values we've prepared some [examples][examples].
20 |
21 | [Deckhouse Kubernetes Platform][deckhouse] was an initial reason to create addon-operator, thus [its modules][deckhouse-modules] might become a vital source of inspiration for implementing your own modules.
22 |
23 | Sharing your examples of using addon-operator is much appreciated. Please, use the [relevant Discussions section][discussions-sharing] for that.
24 |
25 | # Community
26 |
27 | Please feel free to reach developers/maintainers and users via [GitHub Discussions][discussions] for any questions regarding addon-operator.
28 |
29 | You're also welcome to follow [@flant_com][twitter] to stay informed about all our Open Source initiatives.
30 |
31 | # License
32 |
33 | Apache License 2.0, see [LICENSE][license].
34 |
35 | [deckhouse]: https://deckhouse.io/
36 | [deckhouse-modules]: https://github.com/deckhouse/deckhouse/tree/main/modules
37 | [discussions]: https://github.com/flant/addon-operator/discussions
38 | [discussions-sharing]: https://github.com/flant/addon-operator/discussions/categories/show-and-tell
39 | [docker-hub]: https://hub.docker.com/r/flant/addon-operator
40 | [examples]: https://github.com/flant/addon-operator/tree/main/examples
41 | [license]: https://github.com/flant/addon-operator/blob/main/LICENSE
42 | [twitter]: https://twitter.com/flant_com
43 |
--------------------------------------------------------------------------------
/docs/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | [Introduction](README.md)
4 |
5 | - [Overview](OVERVIEW.md)
6 | - [Running Addon-operator](RUNNING.md)
7 | - [Lifecycle](LIFECYCLE.md)
8 | - [Steps](LIFECYCLE-STEPS.md)
9 | - [Modules](MODULES.md)
10 | - [Hooks](HOOKS.md)
11 | - [Values](VALUES.md)
12 | - [Metrics](metrics/ROOT.md)
13 | - [Self metrics](metrics/SELF_METRICS.md)
14 | - [Return metrics from hooks](metrics/METRICS_FROM_HOOKS.md)
15 |
--------------------------------------------------------------------------------
/docs/src/image/global_values_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/global_values_flow.png
--------------------------------------------------------------------------------
/docs/src/image/logo-addon-operator-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/logo-addon-operator-small.png
--------------------------------------------------------------------------------
/docs/src/image/module_values_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/module_values_flow.png
--------------------------------------------------------------------------------
/docs/src/image/readme-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-1.gif
--------------------------------------------------------------------------------
/docs/src/image/readme-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-2.gif
--------------------------------------------------------------------------------
/docs/src/image/readme-3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-3.gif
--------------------------------------------------------------------------------
/docs/src/image/readme-4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-4.gif
--------------------------------------------------------------------------------
/docs/src/image/readme-5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-5.gif
--------------------------------------------------------------------------------
/docs/src/image/readme-6.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/docs/src/image/readme-6.gif
--------------------------------------------------------------------------------
/docs/src/metrics/ROOT.md:
--------------------------------------------------------------------------------
1 | # Addon-operator metrics
2 |
3 | The Addon-operator implements Prometheus target at `/metrics` endpoint. The default port is `9650`.
4 |
--------------------------------------------------------------------------------
/examples/001-startup-global/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM flant/addon-operator:latest
2 | ADD modules /modules
3 | ADD global-hooks /global-hooks
4 |
--------------------------------------------------------------------------------
/examples/001-startup-global/README.md:
--------------------------------------------------------------------------------
1 | ## onStartup global hooks example
2 |
3 | Example of a global hook written as bash script.
4 |
5 | ### run
6 |
7 | Build addon-operator image with custom scripts:
8 |
9 | ```
10 | docker build -t "registry.mycompany.com/addon-operator:startup-global" .
11 | docker push registry.mycompany.com/addon-operator:startup-global
12 | ```
13 |
14 | Edit image in addon-operator-pod.yaml and apply manifests:
15 |
16 | ```
17 | kubectl create ns example-startup-global
18 | kubectl -n example-startup-global apply -f addon-operator-rbac.yaml
19 | kubectl -n example-startup-global apply -f addon-operator-pod.yaml
20 | ```
21 |
22 | See in logs that hook.sh was run at startup:
23 |
24 | ```
25 | kubectl -n example-startup-global logs pod/addon-operator -f
26 | ...
27 | INFO : Initializing global hooks ...
28 | INFO : INIT: global hook 'hook.sh' ...
29 | ...
30 | INFO : TASK_RUN GlobalHookRun@ON_STARTUP hook.sh
31 | INFO : Running global hook 'hook.sh' binding 'ON_STARTUP' ...
32 | OnStartup global hook
33 | ...
34 | ```
35 |
36 | ### cleanup
37 |
38 | ```
39 | kubectl delete clusterrolebinding/addon-operator
40 | kubectl delete clusterrole/addon-operator
41 | kubectl delete ns/example-startup-global
42 | docker rmi registry.mycompany.com/addon-operator:startup-global
43 | ```
44 |
--------------------------------------------------------------------------------
/examples/001-startup-global/addon-operator-cm.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: addon-operator
6 | data:
7 | global: ""
8 |
--------------------------------------------------------------------------------
/examples/001-startup-global/addon-operator-pod.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Pod
4 | metadata:
5 | name: addon-operator
6 | spec:
7 | containers:
8 | - name: addon-operator
9 | image: registry.mycompany.com/addon-operator:startup-global
10 | imagePullPolicy: Always
11 | env:
12 | - name: ADDON_OPERATOR_NAMESPACE
13 | valueFrom:
14 | fieldRef:
15 | fieldPath: metadata.namespace
16 | serviceAccountName: addon-operator-acc
17 |
--------------------------------------------------------------------------------
/examples/001-startup-global/addon-operator-rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: addon-operator-acc
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRole
9 | metadata:
10 | name: addon-operator
11 | rules:
12 | - apiGroups:
13 | - "*"
14 | resources:
15 | - "*"
16 | verbs:
17 | - "*"
18 | - nonResourceURLs:
19 | - "*"
20 | verbs:
21 | - "*"
22 | ---
23 | apiVersion: rbac.authorization.k8s.io/v1
24 | kind: ClusterRoleBinding
25 | metadata:
26 | name: addon-operator
27 | roleRef:
28 | apiGroup: rbac.authorization.k8s.io
29 | kind: ClusterRole
30 | name: addon-operator
31 | subjects:
32 | - kind: ServiceAccount
33 | name: addon-operator-acc
34 | namespace: default
35 |
--------------------------------------------------------------------------------
/examples/001-startup-global/global-hooks/hook.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [[ $1 == "--config" ]] ; then
4 | echo '{"configVersion":"v1", "onStartup": 1}'
5 | else
6 | echo "OnStartup global shell hook"
7 | fi
8 |
--------------------------------------------------------------------------------
/examples/001-startup-global/modules/README.md:
--------------------------------------------------------------------------------
1 | > FIXME: `modules` directory is required even if only global-hooks are used
2 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM flant/addon-operator:latest
2 | ADD modules /modules
3 | ADD global-hooks /global-hooks
4 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/README.md:
--------------------------------------------------------------------------------
1 | ## onStartup global hooks example
2 |
3 | Example of a global hook written as bash script.
4 |
5 | ### run
6 |
7 | Build addon-operator image with custom scripts:
8 |
9 | ```
10 | docker build -t "registry.mycompany.com/addon-operator:startup-global" .
11 | docker push registry.mycompany.com/addon-operator:startup-global
12 | ```
13 |
14 | Edit image in addon-operator-pod.yaml and apply manifests:
15 |
16 | ```
17 | kubectl create ns example-startup-global
18 | kubectl -n example-startup-global apply -f addon-operator-rbac.yaml
19 | kubectl -n example-startup-global apply -f addon-operator-pod.yaml
20 | ```
21 |
22 | See in logs that hook.sh was run at startup:
23 |
24 | ```
25 | kubectl -n example-startup-global logs pod/addon-operator -f
26 | ...
27 | INFO : Initializing global hooks ...
28 | INFO : INIT: global hook 'hook.sh' ...
29 | ...
30 | INFO : TASK_RUN GlobalHookRun@ON_STARTUP hook.sh
31 | INFO : Running global hook 'hook.sh' binding 'ON_STARTUP' ...
32 | OnStartup global hook
33 | ...
34 | ```
35 |
36 | ### cleanup
37 |
38 | ```
39 | kubectl delete clusterrolebinding/addon-operator
40 | kubectl delete clusterrole/addon-operator
41 | kubectl delete ns/example-startup-global
42 | docker rmi registry.mycompany.com/addon-operator:startup-global
43 | ```
44 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/addon-operator-cm.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: addon-operator
6 | data:
7 | global: ""
8 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/addon-operator-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | name: addon-operator
6 | spec:
7 | replicas: 2
8 | selector:
9 | matchLabels:
10 | app: addon-operator
11 | strategy:
12 | rollingUpdate:
13 | maxSurge: 25%
14 | maxUnavailable: 1
15 | type: RollingUpdate
16 | template:
17 | metadata:
18 | labels:
19 | app: addon-operator
20 | spec:
21 | affinity:
22 | podAntiAffinity:
23 | preferredDuringSchedulingIgnoredDuringExecution:
24 | - podAffinityTerm:
25 | labelSelector:
26 | matchExpressions:
27 | - key: app
28 | operator: In
29 | values:
30 | - addon-operator
31 | topologyKey: kubernetes.io/hostname
32 | weight: 100
33 | containers:
34 | - env:
35 | - name: ADDON_OPERATOR_POD
36 | valueFrom:
37 | fieldRef:
38 | apiVersion: v1
39 | fieldPath: metadata.name
40 | - name: ADDON_OPERATOR_HA
41 | value: "true"
42 | - name: ADDON_OPERATOR_NAMESPACE
43 | valueFrom:
44 | fieldRef:
45 | apiVersion: v1
46 | fieldPath: metadata.namespace
47 | - name: ADDON_OPERATOR_LISTEN_ADDRESS
48 | valueFrom:
49 | fieldRef:
50 | apiVersion: v1
51 | fieldPath: status.podIP
52 | image: registry.mycompany.com/addon-operator:ha
53 | imagePullPolicy: IfNotPresent
54 | name: addon-operator
55 | readinessProbe:
56 | httpGet:
57 | path: /readyz
58 | port: 9650
59 | scheme: HTTP
60 | periodSeconds: 10
61 | successThreshold: 1
62 | timeoutSeconds: 1
63 | resources: {}
64 | terminationMessagePath: /dev/termination-log
65 | terminationMessagePolicy: File
66 | dnsPolicy: ClusterFirst
67 | restartPolicy: Always
68 | schedulerName: default-scheduler
69 | serviceAccount: addon-operator-acc
70 | serviceAccountName: addon-operator-acc
71 | terminationGracePeriodSeconds: 30
72 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/addon-operator-rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: addon-operator-acc
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRole
9 | metadata:
10 | name: addon-operator
11 | rules:
12 | - apiGroups:
13 | - "*"
14 | resources:
15 | - "*"
16 | verbs:
17 | - "*"
18 | - nonResourceURLs:
19 | - "*"
20 | verbs:
21 | - "*"
22 | ---
23 | apiVersion: rbac.authorization.k8s.io/v1
24 | kind: ClusterRoleBinding
25 | metadata:
26 | name: addon-operator
27 | roleRef:
28 | apiGroup: rbac.authorization.k8s.io
29 | kind: ClusterRole
30 | name: addon-operator
31 | subjects:
32 | - kind: ServiceAccount
33 | name: addon-operator-acc
34 | namespace: default
35 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/global-hooks/hook.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [[ $1 == "--config" ]] ; then
4 | echo '{"configVersion":"v1", "onStartup": 1}'
5 | else
6 | echo "OnStartup global shell hook"
7 | fi
8 |
--------------------------------------------------------------------------------
/examples/002-startup-global-high-availability/modules/README.md:
--------------------------------------------------------------------------------
1 | > FIXME: `modules` directory is required even if only global-hooks are used
2 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM flant/addon-operator:latest
2 | ADD modules /modules
3 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/addon-operator-cm.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: addon-operator
6 | data:
7 | global: ""
8 | sysctlTuner: |
9 | {}
10 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/addon-operator-pod.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Pod
4 | metadata:
5 | name: addon-operator
6 | spec:
7 | containers:
8 | - name: addon-operator
9 | image: registry.mycompany.com/addon-operator:module-sysctl-tuner
10 | imagePullPolicy: Always
11 | env:
12 | - name: ADDON_OPERATOR_NAMESPACE
13 | valueFrom:
14 | fieldRef:
15 | fieldPath: metadata.namespace
16 | serviceAccountName: addon-operator-acc
17 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/addon-operator-rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: addon-operator-acc
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1beta1
8 | kind: ClusterRole
9 | metadata:
10 | name: addon-operator
11 | rules:
12 | - apiGroups:
13 | - "*"
14 | resources:
15 | - "*"
16 | verbs:
17 | - "*"
18 | - nonResourceURLs:
19 | - "*"
20 | verbs:
21 | - "*"
22 | ---
23 | apiVersion: rbac.authorization.k8s.io/v1beta1
24 | kind: ClusterRoleBinding
25 | metadata:
26 | name: addon-operator
27 | roleRef:
28 | apiGroup: rbac.authorization.k8s.io
29 | kind: ClusterRole
30 | name: addon-operator
31 | subjects:
32 | - kind: ServiceAccount
33 | name: addon-operator-acc
34 | namespace: example-module-sysctl-tuner
35 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/modules/001-sysctl-tuner/Chart.yaml:
--------------------------------------------------------------------------------
1 | name: sysctl-tuner
2 | version: 0.1.0
3 |
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/modules/001-sysctl-tuner/README.md:
--------------------------------------------------------------------------------
1 | sysctl-tuner module
2 | ===================
3 |
4 | This module periodically applying systcl parametrs on nodes.
5 |
6 | Module is run in DaemonSet in privileged containers and apply
7 | parameters every 5 min.
--------------------------------------------------------------------------------
/examples/101-module-sysctl-tuner/modules/001-sysctl-tuner/hooks/module-hooks.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # A stub hook just to make sure that events are handled properly.
4 |
5 | if [[ $1 == "--config" ]] ; then
6 | cat < ../module_storage/002-frontend
14 | ...
15 | ├── module_storage
16 | │ └── 002-frontend
17 | │ ├── Chart.yaml
18 | │ ├── templates
19 | │ │ ├── deploy-frontend.yaml
20 | ...
21 | ```
22 |
23 | MODULES_DIR environment variable is used to specify two directories:
24 |
25 | ```
26 | - name: MODULES_DIR
27 | value: "/module_dir_1:/module_dir_2"
28 | ```
29 |
30 | ### Run
31 |
32 | Build addon-operator image with modules:
33 |
34 | ```
35 | docker build -t "localhost:5000/addon-operator:example-202" .
36 | docker push localhost:5000/addon-operator:example-202
37 | ```
38 |
39 | Edit image in addon-operator-deploy.yaml and apply manifests:
40 |
41 | ```
42 | kubectl create ns example-202
43 | kubectl -n example-202 apply -f deploy/cm.yaml
44 | kubectl -n example-202 apply -f deploy/rbac.yaml
45 | kubectl -n example-202 apply -f deploy/deployment.yaml
46 | ```
47 |
48 | List Pods to see that 'frontend' and 'backend' are installed:
49 |
50 | ```
51 | $ kubectl -n example-202 get po
52 |
53 | NAME READY STATUS RESTARTS AGE
54 | addon-operator-5c9df6d4b8-pj9nt 1/1 Running 0 16s
55 | backend-5b764d4464-rdxq4 1/1 Running 0 2m40s
56 | frontend-d8677b8d4-8wkqh 1/1 Running 0 13s
57 | ```
58 |
59 | See also enabled modules:
60 |
61 | ```
62 | $ kubectl -n example-202 exec deploy/addon-operator -- /addon-operator module list
63 |
64 | {"enabledModules":["backend","frontend"]}
65 | ```
66 |
67 | ### Cleanup
68 |
69 | ```
70 | kubectl delete clusterrolebinding/addon-operator-202
71 | kubectl delete clusterrole/addon-operator-202
72 | kubectl delete ns/example-202
73 | docker rmi localhost:5000/addon-operator:example-202
74 | ```
75 |
--------------------------------------------------------------------------------
/examples/202-module-symlinks/deploy/cm.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: addon-operator
6 | data: {}
7 |
--------------------------------------------------------------------------------
/examples/202-module-symlinks/deploy/deployment.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: addon-operator
6 | labels:
7 | app: addon-operator
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: addon-operator
13 | strategy:
14 | type: Recreate
15 | template:
16 | metadata:
17 | labels:
18 | app: addon-operator
19 | spec:
20 | containers:
21 | - name: addon-operator
22 | image: localhost:5000/addon-operator:example-202
23 | imagePullPolicy: Always
24 | env:
25 | - name: MODULES_DIR
26 | value: "/module_dir_1:/module_dir_2"
27 | - name: ADDON_OPERATOR_NAMESPACE
28 | valueFrom:
29 | fieldRef:
30 | fieldPath: metadata.namespace
31 | livenessProbe:
32 | httpGet:
33 | path: /healthz
34 | port: 9650
35 | serviceAccountName: addon-operator-202
36 |
37 |
--------------------------------------------------------------------------------
/examples/202-module-symlinks/deploy/rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: addon-operator-202
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRole
9 | metadata:
10 | name: addon-operator-202
11 | rules:
12 | - apiGroups: ['*']
13 | resources: ['*']
14 | verbs: ['*']
15 | - nonResourceURLs: ['*']
16 | verbs: ['*']
17 | ---
18 | apiVersion: rbac.authorization.k8s.io/v1
19 | kind: ClusterRoleBinding
20 | metadata:
21 | name: addon-operator-202
22 | roleRef:
23 | apiGroup: rbac.authorization.k8s.io
24 | kind: ClusterRole
25 | name: addon-operator-202
26 | subjects:
27 | - kind: ServiceAccount
28 | name: addon-operator-202
29 | namespace: example-202
30 |
--------------------------------------------------------------------------------
/examples/202-module-symlinks/global-hooks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/examples/202-module-symlinks/global-hooks/.gitkeep
--------------------------------------------------------------------------------
/examples/202-module-symlinks/module_dir_1/001-backend/Chart.yaml:
--------------------------------------------------------------------------------
1 | name: backend
2 | version: 1.0
3 |
--------------------------------------------------------------------------------
/examples/202-module-symlinks/module_dir_1/001-backend/hooks/startup:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ $1 == "--config" ]] ; then
4 | cat < 0
46 | }
47 |
48 | // HasEqualChecksum returns true if there is a checksum for name equal to the input checksum.
49 | func (c *Checksums) HasEqualChecksum(name string, checksum string) bool {
50 | for chk := range c.sums[name] {
51 | if chk == checksum {
52 | return true
53 | }
54 | }
55 | return false
56 | }
57 |
58 | func (c *Checksums) Names() map[string]struct{} {
59 | names := make(map[string]struct{})
60 | for name := range c.sums {
61 | names[name] = struct{}{}
62 | }
63 | return names
64 | }
65 |
66 | func (c *Checksums) Dump(moduleName string) map[string]struct{} {
67 | if checksums, has := c.sums[moduleName]; has {
68 | return checksums
69 | }
70 | return nil
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/kube_config_manager/checksums_test.go:
--------------------------------------------------------------------------------
1 | package kube_config_manager
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func Test_Checksums(t *testing.T) {
10 | c := NewChecksums()
11 |
12 | // Adding
13 | c.Add("global", "qwe")
14 |
15 | assert.True(t, c.HasChecksum("global"))
16 | assert.True(t, c.HasEqualChecksum("global", "qwe"))
17 | assert.False(t, c.HasChecksum("non-global"), "Should be false for non added name")
18 |
19 | // Names
20 | expectedNames := map[string]struct{}{
21 | "global": {},
22 | }
23 | assert.Equal(t, expectedNames, c.Names())
24 |
25 | c.Remove("global", "unknown-checksum")
26 | assert.True(t, c.HasEqualChecksum("global", "qwe"))
27 |
28 | c.Remove("global", "qwe")
29 | assert.False(t, c.HasEqualChecksum("global", "qwe"))
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/kube_config_manager/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/flant/addon-operator/pkg/utils"
5 | )
6 |
7 | type KubeConfig struct {
8 | Global *GlobalKubeConfig
9 | Modules map[string]*ModuleKubeConfig
10 | }
11 |
12 | type GlobalKubeConfig struct {
13 | Values utils.Values
14 | Checksum string
15 | }
16 |
17 | // GetValues returns global values, enrich them with top level key 'global'
18 | func (gkc GlobalKubeConfig) GetValues() utils.Values {
19 | if len(gkc.Values) == 0 {
20 | return gkc.Values
21 | }
22 |
23 | if gkc.Values.HasKey("global") {
24 | switch v := gkc.Values["global"].(type) {
25 | case map[string]interface{}:
26 | return utils.Values(v)
27 |
28 | case utils.Values:
29 | return v
30 | }
31 | }
32 |
33 | return gkc.Values
34 | }
35 |
36 | type ModuleKubeConfig struct {
37 | utils.ModuleConfig
38 | Checksum string
39 | }
40 |
41 | func NewConfig() *KubeConfig {
42 | return &KubeConfig{
43 | Modules: make(map[string]*ModuleKubeConfig),
44 | }
45 | }
46 |
47 | type (
48 | KubeConfigType string
49 | KubeConfigEvent struct {
50 | Type KubeConfigType
51 | ModuleEnabledStateChanged []string
52 | ModuleValuesChanged []string
53 | GlobalSectionChanged bool
54 | ModuleMaintenanceChanged map[string]utils.Maintenance
55 | }
56 | )
57 |
58 | const (
59 | KubeConfigChanged KubeConfigType = "Changed"
60 | KubeConfigInvalid KubeConfigType = "Invalid"
61 | )
62 |
--------------------------------------------------------------------------------
/pkg/kube_config_manager/config/event.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Op string
4 |
5 | const (
6 | EventDelete Op = "Delete"
7 | EventUpdate Op = "Update"
8 | EventAdd Op = "Add"
9 | )
10 |
11 | type Event struct {
12 | // Key possible values
13 | // "" - reset the whole config
14 | // "batch" - set global and modules config at once
15 | // "global" - set only global config
16 | // " - set only config for the module
17 | Key string
18 | Config *KubeConfig
19 | Err error
20 | Op Op
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/labels.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | const (
4 | LogKeyBinding = "binding"
5 | LogKeyBindingName = "binding.name"
6 | LogKeyEventType = "event.type"
7 | LogKeyHook = "hook"
8 | LogKeyHookType = "hook.type"
9 | LogKeyModule = "module"
10 | LogKeyQueue = "queue"
11 | LogKeyTaskID = "task.id"
12 | LogKeyTaskFlow = "task.flow"
13 | LogKeyWatchEvent = "watchEvent"
14 | )
15 |
16 | const (
17 | MetricKeyActivation = "activation"
18 | MetricKeyBinding = "binding"
19 | MetricKeyHook = "hook"
20 | MetricKeyModule = "module"
21 | MetricKeyQueue = "queue"
22 | )
23 |
--------------------------------------------------------------------------------
/pkg/module_manager/environment_manager/mount.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | package environment_manager
4 |
5 | func MountFn(_ string, _ string, _ string, _ uintptr, _ string, _ bool) error {
6 | return nil
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/module_manager/environment_manager/mount_linux.go:
--------------------------------------------------------------------------------
1 | package environment_manager
2 |
3 | import "syscall"
4 |
5 | func MountFn(source string, target string, fstype string, flags uintptr, data string, recursiveMount bool) error {
6 | if recursiveMount {
7 | flags = flags | syscall.MS_BIND | syscall.MS_REC
8 | }
9 |
10 | return syscall.Mount(source, target, fstype, flags, data)
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/module_manager/go_hook/filter_result.go:
--------------------------------------------------------------------------------
1 | package go_hook
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "reflect"
9 | "strings"
10 |
11 | sdkpkg "github.com/deckhouse/module-sdk/pkg"
12 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13 | )
14 |
15 | type FilterFunc func(*unstructured.Unstructured) (FilterResult, error)
16 |
17 | type FilterResult any
18 |
19 | type Wrapped struct {
20 | Wrapped any
21 | }
22 |
23 | var (
24 | ErrEmptyWrapped = errors.New("empty filter result")
25 | ErrUnmarshalToTypesNotMatch = errors.New("unmarshal error: input and output types not match")
26 | )
27 |
28 | func (f *Wrapped) UnmarshalTo(v any) error {
29 | if f.Wrapped == nil {
30 | return ErrEmptyWrapped
31 | }
32 |
33 | rv := reflect.ValueOf(v)
34 | if rv.Kind() != reflect.Pointer || rv.IsNil() {
35 | // error replace with "not pointer"
36 | return fmt.Errorf("reflect.TypeOf(v): %s", reflect.TypeOf(v))
37 | }
38 |
39 | rw := reflect.ValueOf(f.Wrapped)
40 | if rw.Kind() != reflect.Pointer || rw.IsNil() {
41 | rv.Elem().Set(rw)
42 |
43 | return nil
44 | }
45 |
46 | if rw.Type() != rv.Type() {
47 | return ErrUnmarshalToTypesNotMatch
48 | }
49 |
50 | rv.Elem().Set(rw.Elem())
51 |
52 | return nil
53 | }
54 |
55 | func (f *Wrapped) String() string {
56 | buf := bytes.NewBuffer([]byte{})
57 | _ = json.NewEncoder(buf).Encode(f.Wrapped)
58 |
59 | res := buf.String()
60 |
61 | res = strings.TrimSuffix(res, "\n")
62 |
63 | if strings.HasPrefix(res, "\"") {
64 | res = res[1 : len(res)-1]
65 | }
66 |
67 | return res
68 | }
69 |
70 | // type NewSnapshots map[string][]Wrapped
71 | type NewSnapshots map[string][]sdkpkg.Snapshot
72 |
73 | func (s NewSnapshots) Get(name string) []sdkpkg.Snapshot {
74 | return s[name]
75 | }
76 |
77 | type Snapshots map[string][]FilterResult
78 |
--------------------------------------------------------------------------------
/pkg/module_manager/go_hook/logger.go:
--------------------------------------------------------------------------------
1 | package go_hook
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/deckhouse/deckhouse/pkg/log"
7 | sdkpkg "github.com/deckhouse/module-sdk/pkg"
8 | )
9 |
10 | type Logger interface {
11 | sdkpkg.Logger
12 |
13 | // Deprecated: use Debug instead
14 | Debugf(format string, args ...any)
15 | // Deprecated: use Error instead
16 | Errorf(format string, args ...any)
17 | // Deprecated: use Fatal instead
18 | Fatalf(format string, args ...any)
19 | // Deprecated: use Info instead
20 | Infof(format string, args ...any)
21 | // Deprecated: use Log instead
22 | Logf(ctx context.Context, level log.Level, format string, args ...any)
23 | // Deprecated: use Warn instead
24 | Warnf(format string, args ...any)
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/fs_test.go:
--------------------------------------------------------------------------------
1 | package fs
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/deckhouse/deckhouse/pkg/log"
9 | . "github.com/onsi/gomega"
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestNewFileSystemLoader(t *testing.T) {
15 | tmpDir, _ := os.MkdirTemp("", "")
16 | defer os.RemoveAll(tmpDir)
17 |
18 | _ = os.MkdirAll(filepath.Join(tmpDir, "modules", "001-foo-bar"), 0o777)
19 |
20 | err := os.WriteFile(filepath.Join(tmpDir, "modules", "values.yaml"), []byte(`
21 | fooBarEnabled: true
22 | fooBar:
23 | replicas: 2
24 | hello:
25 | world: "bzzzz"
26 | `), 0o666)
27 | require.NoError(t, err)
28 |
29 | err = os.WriteFile(filepath.Join(tmpDir, "modules", "001-foo-bar", "values.yaml"), []byte(`
30 | fooBar:
31 | replicas: 3
32 | hello:
33 | world: "xxx"
34 | `), 0o666)
35 | require.NoError(t, err)
36 |
37 | _ = os.MkdirAll(filepath.Join(tmpDir, "modules", "001-foo-bar", "openapi"), 0o777)
38 |
39 | err = os.WriteFile(filepath.Join(tmpDir, "modules", "001-foo-bar", "values.yaml"), []byte(`
40 | fooBar:
41 | replicas: 3
42 | hello:
43 | world: "xxx"
44 | `), 0o666)
45 | require.NoError(t, err)
46 |
47 | loader := NewFileSystemLoader(filepath.Join(tmpDir, "modules"), log.NewNop())
48 | modules, err := loader.LoadModules()
49 | require.NoError(t, err)
50 | m := modules[0]
51 | assert.Equal(t, "foo-bar", m.GetName())
52 | assert.YAMLEq(t, `
53 | hello:
54 | world: xxx
55 | replicas: 3
56 | `, m.GetValues(false).AsString("yaml"))
57 | }
58 |
59 | func TestDirWithSymlinks(t *testing.T) {
60 | g := NewWithT(t)
61 | dir := "testdata/module_loader/dir1"
62 |
63 | ld := NewFileSystemLoader(dir, log.NewNop())
64 |
65 | mods, err := ld.LoadModules()
66 | g.Expect(err).ShouldNot(HaveOccurred())
67 |
68 | g.Expect(mods).Should(HaveLen(2))
69 | }
70 |
71 | func TestLoadMultiDir(t *testing.T) {
72 | g := NewWithT(t)
73 | dirs := "testdata/module_loader/dir2:testdata/module_loader/dir3"
74 |
75 | ld := NewFileSystemLoader(dirs, log.NewNop())
76 |
77 | mods, err := ld.LoadModules()
78 | g.Expect(err).ShouldNot(HaveOccurred())
79 |
80 | g.Expect(mods).Should(HaveLen(2))
81 | }
82 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir1/001-module-one:
--------------------------------------------------------------------------------
1 | ../modules/001-module-one
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir1/002-module-two:
--------------------------------------------------------------------------------
1 | ../modules/002-module-two
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir1/values.yaml:
--------------------------------------------------------------------------------
1 | moduleOne:
2 | param1: val1
3 | param2: val2
4 | moduleTwo:
5 | param1: val1
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir2/012-mod-two:
--------------------------------------------------------------------------------
1 | ../modules/002-module-two
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir2/100-mod-one:
--------------------------------------------------------------------------------
1 | ../modules/001-module-one
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir2/values.yaml:
--------------------------------------------------------------------------------
1 | modOne:
2 | param1: val2
3 | param2: val2
4 |
5 | modTwo:
6 | param1: val2
7 | param2: val2
8 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/dir3/values.yaml:
--------------------------------------------------------------------------------
1 | modOne:
2 | param1: val3
3 | param2: val3
4 | modTwo:
5 | param1: val3
6 | param2: val3
7 | moduleThree:
8 | param1: val3
9 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/modules/001-module-one/.gitkeep:
--------------------------------------------------------------------------------
1 | global:
2 | a: 1
3 | b: 2
4 | c: 3
5 | d: ["a", "b", "c"]
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/modules/002-module-two/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/loader/fs/testdata/module_loader/modules/002-module-two/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/loader/fs/testdata/module_loader/modules/003-module-three/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/loader/fs/testdata/module_loader/modules/003-module-three/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/loader/loader.go:
--------------------------------------------------------------------------------
1 | package loader
2 |
3 | import (
4 | "github.com/flant/addon-operator/pkg/module_manager/models/modules"
5 | )
6 |
7 | type ModuleLoader interface {
8 | LoadModules() ([]*modules.BasicModule, error)
9 | LoadModule(moduleSource string, modulePath string) (*modules.BasicModule, error)
10 | }
11 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/hooks/dependency.go:
--------------------------------------------------------------------------------
1 | package hooks
2 |
3 | import (
4 | "context"
5 |
6 | sdkpkg "github.com/deckhouse/module-sdk/pkg"
7 |
8 | environmentmanager "github.com/flant/addon-operator/pkg/module_manager/environment_manager"
9 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
10 | "github.com/flant/addon-operator/pkg/module_manager/models/hooks/kind"
11 | "github.com/flant/addon-operator/pkg/utils"
12 | bindingcontext "github.com/flant/shell-operator/pkg/hook/binding_context"
13 | "github.com/flant/shell-operator/pkg/hook/config"
14 | "github.com/flant/shell-operator/pkg/hook/controller"
15 | metricoperation "github.com/flant/shell-operator/pkg/metric_storage/operation"
16 | )
17 |
18 | type hooksMetricsStorage interface {
19 | SendBatch([]metricoperation.MetricOperation, map[string]string) error
20 | }
21 |
22 | type kubeConfigManager interface {
23 | SaveConfigValues(moduleName string, configValuesPatch utils.Values) error
24 | }
25 |
26 | type metricStorage interface {
27 | HistogramObserve(metric string, value float64, labels map[string]string, buckets []float64)
28 | GaugeSet(metric string, value float64, labels map[string]string)
29 | }
30 |
31 | type kubeObjectPatcher interface {
32 | ExecuteOperations([]sdkpkg.PatchCollectorOperation) error
33 | }
34 |
35 | type globalValuesGetter interface {
36 | GetValues(bool) utils.Values
37 | GetConfigValues(bool) utils.Values
38 | }
39 |
40 | // HookExecutionDependencyContainer container for all hook execution dependencies
41 | type HookExecutionDependencyContainer struct {
42 | HookMetricsStorage hooksMetricsStorage
43 | KubeConfigManager kubeConfigManager
44 | KubeObjectPatcher kubeObjectPatcher
45 | MetricStorage metricStorage
46 | GlobalValuesGetter globalValuesGetter
47 | EnvironmentManager *environmentmanager.Manager
48 | }
49 |
50 | type executableHook interface {
51 | GetName() string
52 | GetPath() string
53 |
54 | Execute(ctx context.Context, configVersion string, bContext []bindingcontext.BindingContext, moduleSafeName string, configValues, values utils.Values, logLabels map[string]string) (result *kind.HookResult, err error)
55 | RateLimitWait(ctx context.Context) error
56 |
57 | WithHookController(ctrl *controller.HookController)
58 | GetHookController() *controller.HookController
59 | WithTmpDir(tmpDir string)
60 |
61 | GetKind() kind.HookKind
62 |
63 | BackportHookConfig(cfg *config.HookConfig)
64 | GetHookConfigDescription() string
65 | }
66 |
67 | type hookConfigLoader gohook.HookConfigLoader
68 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/hooks/global_hook_test.go:
--------------------------------------------------------------------------------
1 | package hooks
2 |
3 | //
4 | // import (
5 | // "github.com/flant/addon-operator/pkg/module_manager/models/hooks"
6 | // "testing"
7 | //
8 | // . "github.com/onsi/gomega"
9 | //
10 | // . "github.com/flant/addon-operator/pkg/hook/types"
11 | // . "github.com/flant/shell-operator/pkg/hook/types"
12 | //)
13 | //
14 | // func Test_GlobalHook_WithConfig(t *testing.T) {
15 | // g := NewWithT(t)
16 | //
17 | // var gh *hooks.GlobalHook
18 | // var err error
19 | //
20 | // tests := []struct {
21 | // name string
22 | // config string
23 | // assertFn func()
24 | // }{
25 | // {
26 | // "asd",
27 | // `configVersion: v1
28 | //onStartup: 10
29 | //kubernetes:
30 | //- name: pods
31 | // kind: Pod
32 | //schedule:
33 | //- name: planned
34 | // crontab: '* * * * *'
35 | //afterAll: 22
36 | //beforeAll: 23
37 | // `,
38 | // func() {
39 | // g.Expect(err).ShouldNot(HaveOccurred())
40 | // g.Expect(gh).ShouldNot(BeNil())
41 | // config := gh.Config
42 | // g.Expect(gh.Order(OnStartup)).To(Equal(10.0))
43 | // g.Expect(gh.Order(BeforeAll)).To(Equal(23.0))
44 | // g.Expect(gh.Order(AfterAll)).To(Equal(22.0))
45 | // g.Expect(config.OnKubernetesEvents).To(HaveLen(1))
46 | // g.Expect(config.Schedules).To(HaveLen(1))
47 | //
48 | // g.Expect(gh.GetConfigDescription()).To(ContainSubstring("beforeAll:23, afterAll:22, OnStartup:10"))
49 | // },
50 | // },
51 | // }
52 | //
53 | // for _, tt := range tests {
54 | // t.Run(tt.name, func(t *testing.T) {
55 | // gh = NewGlobalHook("test", "/global-hooks/test.sh")
56 | // err = gh.WithConfig([]byte(tt.config))
57 | // tt.assertFn()
58 | // })
59 | // }
60 | //}
61 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/hooks/kind/gohook_test.go:
--------------------------------------------------------------------------------
1 | package kind
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | . "github.com/onsi/gomega"
8 |
9 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
10 | . "github.com/flant/shell-operator/pkg/hook/binding_context"
11 | )
12 |
13 | func Test_Config_GoHook(t *testing.T) {
14 | g := NewWithT(t)
15 |
16 | gh := NewGoHook(&gohook.HookConfig{
17 | OnAfterAll: &gohook.OrderedConfig{Order: 5},
18 | }, func(input *gohook.HookInput) error {
19 | input.Values.Set("test", "test")
20 | input.MetricsCollector.Set("test", 1.0, nil)
21 |
22 | return nil
23 | })
24 |
25 | bc := make([]BindingContext, 0)
26 |
27 | res, err := gh.Execute(context.Background(), "", bc, "", nil, nil, nil)
28 | g.Expect(err).ShouldNot(HaveOccurred())
29 | g.Expect(res.Patches).ShouldNot(BeEmpty())
30 | g.Expect(res.Metrics).ShouldNot(BeEmpty())
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/hooks/kind/kind.go:
--------------------------------------------------------------------------------
1 | package kind
2 |
3 | import (
4 | sdkpkg "github.com/deckhouse/module-sdk/pkg"
5 |
6 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
7 | "github.com/flant/addon-operator/pkg/utils"
8 | "github.com/flant/shell-operator/pkg/executor"
9 | metricoperation "github.com/flant/shell-operator/pkg/metric_storage/operation"
10 | )
11 |
12 | // HookKind kind of the hook
13 | type HookKind string
14 |
15 | var (
16 | // HookKindGo for go hooks
17 | HookKindGo HookKind = "go"
18 | // HookKindShell for shell hooks (bash, python, etc)
19 | HookKindShell HookKind = "shell"
20 | )
21 |
22 | // HookResult returns result of a hook execution
23 | type HookResult struct {
24 | Usage *executor.CmdUsage
25 | Patches map[utils.ValuesPatchType]*utils.ValuesPatch
26 | Metrics []metricoperation.MetricOperation
27 | ObjectPatcherOperations []sdkpkg.PatchCollectorOperation
28 | BindingActions []gohook.BindingAction
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/basic_test.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "encoding/json"
5 | "os"
6 | "testing"
7 |
8 | sdkutils "github.com/deckhouse/module-sdk/pkg/utils"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 |
12 | "github.com/flant/addon-operator/pkg/utils"
13 | )
14 |
15 | func TestHandleModulePatch(t *testing.T) {
16 | valuesStr := `
17 | foo:
18 | bar: baz
19 | `
20 | value, err := utils.NewValuesFromBytes([]byte(valuesStr))
21 | require.NoError(t, err)
22 | bm, err := NewBasicModule("test-1", "/tmp/test", 100, value, nil, nil)
23 | require.NoError(t, err)
24 |
25 | patch := utils.ValuesPatch{Operations: []*sdkutils.ValuesPatchOperation{
26 | {
27 | Op: "add",
28 | Path: "/test1/foo/xxx",
29 | Value: json.RawMessage(`"yyy"`),
30 | },
31 | {
32 | Op: "remove",
33 | Path: "/test1/foo/bar",
34 | Value: json.RawMessage(`"zxc"`),
35 | },
36 | }}
37 | res, err := bm.handleModuleValuesPatch(bm.GetValues(true), patch)
38 | require.NoError(t, err)
39 | assert.True(t, res.ValuesChanged)
40 | assert.YAMLEq(t, `
41 | foo:
42 | xxx: yyy
43 | `,
44 | res.Values.AsString("yaml"))
45 | }
46 |
47 | func TestIsFileBatchHook(t *testing.T) {
48 | hookPath := "./testdata/batchhook"
49 |
50 | err := os.WriteFile(hookPath, []byte(`#!/bin/bash
51 | if [ "${1}" == "hook" ] && [ "${2}" == "list" ]; then
52 | echo "Found 3 items"
53 | fi
54 | `), 0o555)
55 | require.NoError(t, err)
56 |
57 | defer os.Remove(hookPath)
58 |
59 | fileInfo, err := os.Stat(hookPath)
60 | require.NoError(t, err)
61 |
62 | err = IsFileBatchHook("moduleName", hookPath, fileInfo)
63 | require.NoError(t, err)
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/events/events.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | // ModuleEventType type of the event
4 | type ModuleEventType int
5 |
6 | const (
7 | ModuleRegistered ModuleEventType = iota
8 | ModuleEnabled
9 | ModuleDisabled
10 | ModuleStateChanged
11 | ModuleConfigChanged
12 |
13 | FirstConvergeDone
14 | )
15 |
16 | // ModuleEvent event model for hooks
17 | type ModuleEvent struct {
18 | ModuleName string
19 | EventType ModuleEventType
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/hook_storage.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "sort"
5 | "sync"
6 |
7 | "github.com/flant/addon-operator/pkg/module_manager/models/hooks"
8 | sh_op_types "github.com/flant/shell-operator/pkg/hook/types"
9 | )
10 |
11 | // HooksStorage keep module hooks in order
12 | type HooksStorage struct {
13 | registered bool
14 | controllersReady bool
15 | lock sync.RWMutex
16 | byBinding map[sh_op_types.BindingType][]*hooks.ModuleHook
17 | byName map[string]*hooks.ModuleHook
18 | }
19 |
20 | func newHooksStorage() *HooksStorage {
21 | return &HooksStorage{
22 | registered: false,
23 | byBinding: make(map[sh_op_types.BindingType][]*hooks.ModuleHook),
24 | byName: make(map[string]*hooks.ModuleHook),
25 | }
26 | }
27 |
28 | func (hs *HooksStorage) AddHook(hk *hooks.ModuleHook) {
29 | hs.lock.Lock()
30 | defer hs.lock.Unlock()
31 |
32 | hName := hk.GetName()
33 | hs.byName[hName] = hk
34 | for _, binding := range hk.GetHookConfig().Bindings() {
35 | hs.byBinding[binding] = append(hs.byBinding[binding], hk)
36 | }
37 | }
38 |
39 | func (hs *HooksStorage) getHooks(bt ...sh_op_types.BindingType) []*hooks.ModuleHook {
40 | hs.lock.RLock()
41 | defer hs.lock.RUnlock()
42 |
43 | if len(bt) > 0 {
44 | t := bt[0]
45 | res, ok := hs.byBinding[t]
46 | if !ok {
47 | return []*hooks.ModuleHook{}
48 | }
49 | sort.Slice(res, func(i, j int) bool {
50 | return res[i].Order(t) < res[j].Order(t)
51 | })
52 |
53 | return res
54 | }
55 |
56 | // return all hooks
57 | res := make([]*hooks.ModuleHook, 0, len(hs.byName))
58 | for _, h := range hs.byName {
59 | res = append(res, h)
60 | }
61 |
62 | sort.Slice(res, func(i, j int) bool {
63 | return res[i].GetName() < res[j].GetName()
64 | })
65 |
66 | return res
67 | }
68 |
69 | func (hs *HooksStorage) getHookByName(name string) *hooks.ModuleHook {
70 | hs.lock.RLock()
71 | defer hs.lock.RUnlock()
72 |
73 | return hs.byName[name]
74 | }
75 |
76 | func (hs *HooksStorage) clean() {
77 | hs.lock.Lock()
78 | defer hs.lock.Unlock()
79 |
80 | hs.byBinding = make(map[sh_op_types.BindingType][]*hooks.ModuleHook)
81 | hs.byName = make(map[string]*hooks.ModuleHook)
82 | hs.registered = false
83 | hs.controllersReady = false
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/module_options.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import "github.com/deckhouse/deckhouse/pkg/log"
4 |
5 | type Option func(optionsApplier ModuleOptionApplier)
6 |
7 | func (opt Option) Apply(o ModuleOptionApplier) {
8 | opt(o)
9 | }
10 |
11 | func WithLogger(logger *log.Logger) Option {
12 | return func(optionsApplier ModuleOptionApplier) {
13 | optionsApplier.WithLogger(logger)
14 | }
15 | }
16 |
17 | type ModuleOption interface {
18 | Apply(optsApplier ModuleOptionApplier)
19 | }
20 |
21 | type ModuleOptionApplier interface {
22 | WithLogger(logger *log.Logger)
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/values_defaulting_transformers.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "github.com/go-openapi/spec"
5 |
6 | "github.com/flant/addon-operator/pkg/utils"
7 | "github.com/flant/addon-operator/pkg/values/validation"
8 | )
9 |
10 | type transformer interface {
11 | Transform(values utils.Values) utils.Values
12 | }
13 |
14 | type applyDefaults struct {
15 | SchemaType validation.SchemaType
16 | Schemas map[validation.SchemaType]*spec.Schema
17 | }
18 |
19 | func (a *applyDefaults) Transform(values utils.Values) utils.Values {
20 | if a.Schemas == nil {
21 | return values
22 | }
23 |
24 | s := a.Schemas[a.SchemaType]
25 | if s == nil {
26 | return values
27 | }
28 |
29 | res := values.Copy()
30 | validation.ApplyDefaults(res, s)
31 |
32 | return res
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/values_layered.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import "github.com/flant/addon-operator/pkg/utils"
4 |
5 | type valuesTransform func(values utils.Values) utils.Values
6 |
7 | type valuesTransformer interface {
8 | Transform(values utils.Values) utils.Values
9 | }
10 |
11 | func mergeLayers(initial utils.Values, layers ...interface{}) utils.Values {
12 | res := utils.MergeValues(initial)
13 |
14 | for _, layer := range layers {
15 | switch layer := layer.(type) {
16 | case utils.Values:
17 | res = utils.MergeValues(res, layer)
18 | case map[string]interface{}:
19 | res = utils.MergeValues(res, layer)
20 | case string:
21 | // Ignore error to be handy for tests.
22 | tmp, _ := utils.NewValuesFromBytes([]byte(layer))
23 | res = utils.MergeValues(res, tmp)
24 | case valuesTransform:
25 | res = utils.MergeValues(res, layer(res))
26 | case valuesTransformer:
27 | res = utils.MergeValues(res, layer.Transform(res))
28 | case nil:
29 | continue
30 | }
31 | }
32 |
33 | return res
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/modules/values_layered_test.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/flant/addon-operator/pkg/utils"
9 | )
10 |
11 | func TestMergeLayers(t *testing.T) {
12 | globalValues := utils.Values{
13 | "global": map[string]interface{}{
14 | "enabledModules": []string{"module1", "module2"},
15 | "highAvailability": true,
16 | },
17 | }
18 | res := mergeLayers(
19 | utils.Values{},
20 | globalValues,
21 | utils.Values{
22 | "global": map[string]interface{}{
23 | "enabledModules": []string{"module3"},
24 | "logLevel": "Info",
25 | },
26 | },
27 | )
28 | assert.YAMLEq(t, `
29 | global:
30 | logLevel: "Info"
31 | highAvailability: true
32 | enabledModules:
33 | - module3
34 | `, res.AsString("yaml"))
35 |
36 | assert.YAMLEq(t, `
37 | global:
38 | highAvailability: true
39 | enabledModules:
40 | - module1
41 | - module2
42 | `, globalValues.AsString("yaml"))
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/module_manager/models/moduleset/moduleset_test.go:
--------------------------------------------------------------------------------
1 | package moduleset
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/gomega"
7 |
8 | "github.com/flant/addon-operator/pkg/module_manager/models/modules"
9 | )
10 |
11 | func TestBasicModuleSet(t *testing.T) {
12 | g := NewWithT(t)
13 | ms := new(ModulesSet)
14 |
15 | ms.Add(&modules.BasicModule{
16 | Name: "BasicModule-two",
17 | Order: 10,
18 | })
19 | ms.Add(&modules.BasicModule{
20 | Name: "BasicModule-one",
21 | Order: 5,
22 | })
23 | ms.Add(&modules.BasicModule{
24 | Name: "BasicModule-three-two",
25 | Order: 15,
26 | })
27 | ms.Add(&modules.BasicModule{
28 | Name: "BasicModule-four",
29 | Order: 1,
30 | })
31 | ms.Add(&modules.BasicModule{
32 | Name: "BasicModule-three-one",
33 | Order: 15,
34 | })
35 | // "overridden" BasicModule
36 | ms.Add(&modules.BasicModule{
37 | Name: "BasicModule-four",
38 | Order: 20,
39 | })
40 | ms.SetInited()
41 |
42 | expectNames := []string{
43 | "BasicModule-one",
44 | "BasicModule-two",
45 | "BasicModule-three-one",
46 | "BasicModule-three-two",
47 | "BasicModule-four",
48 | }
49 |
50 | g.Expect(ms.NamesInOrder()).Should(Equal(expectNames))
51 | g.Expect(ms.Has("BasicModule-four")).Should(BeTrue(), "should have BasicModule-four")
52 | g.Expect(ms.Get("BasicModule-four").Order).Should(Equal(uint32(20)), "should have BasicModule-four with order:20")
53 | g.Expect(ms.IsInited()).Should(BeTrue(), "should be inited")
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/dynamically_enabled/dynamic.go:
--------------------------------------------------------------------------------
1 | package dynamically_enabled
2 |
3 | import (
4 | "context"
5 | "log/slog"
6 | "sync"
7 |
8 | "github.com/deckhouse/deckhouse/pkg/log"
9 |
10 | "github.com/flant/addon-operator/pkg/module_manager/scheduler/extenders"
11 | )
12 |
13 | const (
14 | Name extenders.ExtenderName = "DynamicallyEnabled"
15 | )
16 |
17 | type Extender struct {
18 | notifyCh chan extenders.ExtenderEvent
19 | l sync.RWMutex
20 | modulesStatus map[string]bool
21 | }
22 |
23 | type DynamicExtenderEvent struct{}
24 |
25 | func NewExtender() *Extender {
26 | e := &Extender{
27 | modulesStatus: make(map[string]bool),
28 | }
29 | return e
30 | }
31 |
32 | func (e *Extender) UpdateStatus(moduleName, operation string, value bool) {
33 | e.l.Lock()
34 | switch operation {
35 | case "add":
36 | status, found := e.modulesStatus[moduleName]
37 | if !found || (found && status != value) {
38 | e.modulesStatus[moduleName] = value
39 | e.sendNotify()
40 | }
41 | case "remove":
42 | if _, found := e.modulesStatus[moduleName]; found {
43 | delete(e.modulesStatus, moduleName)
44 | e.sendNotify()
45 | }
46 | default:
47 | log.Warn("Unknown patch operation",
48 | slog.String("operation", operation))
49 | }
50 | e.l.Unlock()
51 | }
52 |
53 | func (e *Extender) sendNotify() {
54 | if e.notifyCh != nil {
55 | e.notifyCh <- extenders.ExtenderEvent{
56 | ExtenderName: Name,
57 | EncapsulatedEvent: DynamicExtenderEvent{},
58 | }
59 | }
60 | }
61 |
62 | func (e *Extender) Name() extenders.ExtenderName {
63 | return Name
64 | }
65 |
66 | func (e *Extender) Filter(moduleName string, _ map[string]string) (*bool, error) {
67 | e.l.RLock()
68 | defer e.l.RUnlock()
69 |
70 | if val, found := e.modulesStatus[moduleName]; found {
71 | return &val, nil
72 | }
73 |
74 | return nil, nil
75 | }
76 |
77 | func (e *Extender) IsTerminator() bool {
78 | return false
79 | }
80 |
81 | func (e *Extender) SetNotifyChannel(_ context.Context, ch chan extenders.ExtenderEvent) {
82 | e.notifyCh = ch
83 | }
84 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/dynamically_enabled/dynamic_test.go:
--------------------------------------------------------------------------------
1 | package dynamically_enabled
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "github.com/flant/addon-operator/pkg/module_manager/scheduler/extenders"
10 | )
11 |
12 | func TestUpdateStatus(t *testing.T) {
13 | de := NewExtender()
14 | ch := make(chan extenders.ExtenderEvent)
15 | de.SetNotifyChannel(context.TODO(), ch)
16 | var boolNilP *bool
17 |
18 | go func() {
19 | //nolint:revive
20 | for range ch {
21 | }
22 | }()
23 |
24 | de.UpdateStatus("l2-load-balancer", "add", true)
25 | de.UpdateStatus("node-local-dns", "remove", true)
26 | de.UpdateStatus("openstack-cloud-provider", "add", true)
27 | de.UpdateStatus("openstack-cloud-provider", "add", false)
28 | logLabels := map[string]string{}
29 |
30 | filterResult, _ := de.Filter("l2-load-balancer", logLabels)
31 | assert.Equal(t, true, *filterResult)
32 | filterResult, _ = de.Filter("node-local-dns", logLabels)
33 | assert.Equal(t, boolNilP, filterResult)
34 | filterResult, _ = de.Filter("openstack-cloud-provider", logLabels)
35 | assert.Equal(t, false, *filterResult)
36 |
37 | de.UpdateStatus("node-local-dns", "add", false)
38 | de.UpdateStatus("openstack-cloud-provider", "add", true)
39 |
40 | filterResult, _ = de.Filter("l2-load-balancer", logLabels)
41 | assert.Equal(t, true, *filterResult)
42 | filterResult, _ = de.Filter("node-local-dns", logLabels)
43 | assert.Equal(t, false, *filterResult)
44 | filterResult, _ = de.Filter("openstack-cloud-provider", logLabels)
45 | assert.Equal(t, true, *filterResult)
46 |
47 | de.UpdateStatus("l2-load-balancer", "remove", true)
48 |
49 | filterResult, _ = de.Filter("l2-load-balancer", logLabels)
50 | assert.Equal(t, boolNilP, filterResult)
51 | filterResult, _ = de.Filter("node-local-dns", logLabels)
52 | assert.Equal(t, false, *filterResult)
53 | filterResult, _ = de.Filter("openstack-cloud-provider", logLabels)
54 | assert.Equal(t, true, *filterResult)
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/error/permanent.go:
--------------------------------------------------------------------------------
1 | package error
2 |
3 | // PermanentError signals that the operation should stop the module manager immediately
4 | type PermanentError struct {
5 | Err error
6 | }
7 |
8 | func (e *PermanentError) Error() string {
9 | return e.Err.Error()
10 | }
11 |
12 | func (e *PermanentError) Unwrap() error {
13 | return e.Err
14 | }
15 |
16 | // Permanent wraps the given err in a *PermanentError.
17 | func Permanent(err error) *PermanentError {
18 | if err == nil {
19 | return nil
20 | }
21 | return &PermanentError{
22 | Err: err,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/extenders.go:
--------------------------------------------------------------------------------
1 | package extenders
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type ExtenderEvent struct {
8 | ExtenderName ExtenderName
9 | EncapsulatedEvent interface{}
10 | }
11 |
12 | type ExtenderName string
13 |
14 | type Extender interface {
15 | // Name returns the extender's name
16 | Name() ExtenderName
17 | // Filter returns the result of applying the extender
18 | Filter(moduleName string, logLabels map[string]string) (*bool, error)
19 | // IsTerminator marks extender that can only disable an enabled module if some requirement isn't met.
20 | // By design, terminators can't be overridden by other extenders.
21 | IsTerminator() bool
22 | }
23 |
24 | type NotificationExtender interface {
25 | // SetNotifyChannel sets output channel for an extender's events, to notify when module state could be changed during the runtime
26 | SetNotifyChannel(context.Context, chan ExtenderEvent)
27 | }
28 |
29 | type TopologicalExtender interface {
30 | // GetTopologicalHints returns the list of vertices that should be connected to the specified vertex
31 | GetTopologicalHints(string) []string
32 | }
33 |
34 | // Hail to enabled scripts
35 | type StatefulExtender interface {
36 | // SetModulesStateHelper sets a helper function to get the list of enabled modules according to the latest vertex state buffer
37 | SetModulesStateHelper(func() []string)
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/kube_config/kube_config.go:
--------------------------------------------------------------------------------
1 | package kube_config
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/flant/addon-operator/pkg/kube_config_manager/config"
7 | "github.com/flant/addon-operator/pkg/module_manager/scheduler/extenders"
8 | )
9 |
10 | const (
11 | Name extenders.ExtenderName = "KubeConfig"
12 | )
13 |
14 | type kubeConfigManager interface {
15 | IsModuleEnabled(moduleName string) *bool
16 | KubeConfigEventCh() chan config.KubeConfigEvent
17 | }
18 |
19 | type Extender struct {
20 | notifyCh chan extenders.ExtenderEvent
21 | kubeConfigManager kubeConfigManager
22 | }
23 |
24 | func NewExtender(kcm kubeConfigManager) *Extender {
25 | e := &Extender{
26 | kubeConfigManager: kcm,
27 | }
28 |
29 | return e
30 | }
31 |
32 | func (e *Extender) Name() extenders.ExtenderName {
33 | return Name
34 | }
35 |
36 | func (e *Extender) Filter(moduleName string, _ map[string]string) (*bool, error) {
37 | return e.kubeConfigManager.IsModuleEnabled(moduleName), nil
38 | }
39 |
40 | func (e *Extender) IsTerminator() bool {
41 | return false
42 | }
43 |
44 | func (e *Extender) sendNotify(kubeConfigEvent config.KubeConfigEvent) {
45 | if e.notifyCh != nil {
46 | e.notifyCh <- extenders.ExtenderEvent{
47 | ExtenderName: Name,
48 | EncapsulatedEvent: kubeConfigEvent,
49 | }
50 | }
51 | }
52 |
53 | func (e *Extender) SetNotifyChannel(ctx context.Context, ch chan extenders.ExtenderEvent) {
54 | e.notifyCh = ch
55 | go func() {
56 | for {
57 | select {
58 | case kubeConfigEvent := <-e.kubeConfigManager.KubeConfigEventCh():
59 | e.sendNotify(kubeConfigEvent)
60 | case <-ctx.Done():
61 | return
62 | }
63 | }
64 | }()
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/script_enabled/testdata/015-admission-policy-engine/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/extenders/script_enabled/testdata/015-admission-policy-engine/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/script_enabled/testdata/020-node-local-dns/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/extenders/script_enabled/testdata/020-node-local-dns/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/script_enabled/testdata/031-foo-bar/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/extenders/script_enabled/testdata/031-foo-bar/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/script_enabled/testdata/045-chrony/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/extenders/script_enabled/testdata/045-chrony/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/script_enabled/testdata/402-ingress-nginx/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | exit 0
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/extenders/static/static.go:
--------------------------------------------------------------------------------
1 | package static
2 |
3 | import (
4 | "log/slog"
5 | "os"
6 | "path/filepath"
7 | "strings"
8 |
9 | "github.com/deckhouse/deckhouse/pkg/log"
10 | "github.com/ettle/strcase"
11 | "gopkg.in/yaml.v3"
12 |
13 | "github.com/flant/addon-operator/pkg/module_manager/scheduler/extenders"
14 | "github.com/flant/addon-operator/pkg/utils"
15 | )
16 |
17 | const (
18 | Name extenders.ExtenderName = "Static"
19 | )
20 |
21 | type Extender struct {
22 | modulesStatus map[string]bool
23 | }
24 |
25 | func NewExtender(staticValuesFilePaths string) (*Extender, error) {
26 | result := make(map[string]bool)
27 | dirs := utils.SplitToPaths(staticValuesFilePaths)
28 | for _, dir := range dirs {
29 | valuesFile := filepath.Join(dir, "values.yaml")
30 | fileInfo, err := os.Stat(valuesFile)
31 | if err != nil {
32 | log.Warn("Couldn't stat file",
33 | slog.String("file", valuesFile))
34 | continue
35 | }
36 |
37 | if fileInfo.IsDir() {
38 | log.Error("File is a directory",
39 | slog.String("file", valuesFile))
40 | continue
41 | }
42 |
43 | f, err := os.Open(valuesFile)
44 | if err != nil {
45 | if os.IsNotExist(err) {
46 | log.Debug("File doesn't exist",
47 | slog.String("file", valuesFile))
48 | continue
49 | }
50 | return nil, err
51 | }
52 | defer f.Close()
53 |
54 | m := make(map[string]interface{})
55 |
56 | err = yaml.NewDecoder(f).Decode(&m)
57 | if err != nil {
58 | return nil, err
59 | }
60 |
61 | for k, v := range m {
62 | if strings.HasSuffix(k, utils.EnabledSuffix) {
63 | m := strings.TrimSuffix(k, utils.EnabledSuffix)
64 | keb := strcase.ToKebab(m)
65 | result[keb] = v.(bool)
66 | }
67 | }
68 | }
69 |
70 | e := &Extender{
71 | modulesStatus: result,
72 | }
73 |
74 | return e, nil
75 | }
76 |
77 | func (e *Extender) Name() extenders.ExtenderName {
78 | return Name
79 | }
80 |
81 | func (e *Extender) Filter(moduleName string, _ map[string]string) (*bool, error) {
82 | if val, found := e.modulesStatus[moduleName]; found {
83 | return &val, nil
84 | }
85 |
86 | return nil, nil
87 | }
88 |
89 | func (e *Extender) IsTerminator() bool {
90 | return false
91 | }
92 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/node/mock/node_mock.go:
--------------------------------------------------------------------------------
1 | package node_mock
2 |
3 | import (
4 | "context"
5 | "slices"
6 | )
7 |
8 | type MockModule struct {
9 | EnabledScriptResult bool
10 | EnabledScriptErr error
11 | EnabledModules *[]string
12 | ListOfRequiredModules []string
13 | Name string
14 | Path string
15 | Order uint32
16 | }
17 |
18 | func (m MockModule) GetName() string {
19 | return m.Name
20 | }
21 |
22 | func (m MockModule) GetPath() string {
23 | return m.Path
24 | }
25 |
26 | func (m MockModule) GetOrder() uint32 {
27 | return m.Order
28 | }
29 |
30 | func (m MockModule) RunEnabledScript(_ context.Context, _ string, _ []string, _ map[string]string) (bool, error) {
31 | if m.EnabledScriptErr != nil {
32 | return false, m.EnabledScriptErr
33 | }
34 |
35 | depsEnabled := true
36 | if len(m.ListOfRequiredModules) > 0 && m.EnabledModules != nil {
37 | for _, requiredModule := range m.ListOfRequiredModules {
38 | if !slices.Contains(*m.EnabledModules, requiredModule) {
39 | depsEnabled = false
40 | break
41 | }
42 | }
43 | }
44 |
45 | if depsEnabled && m.EnabledScriptResult && m.EnabledModules != nil {
46 | *m.EnabledModules = append(*m.EnabledModules, m.Name)
47 | }
48 |
49 | return depsEnabled && m.EnabledScriptResult, nil
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/node/node.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "context"
5 | "strconv"
6 | )
7 |
8 | type ModuleInterface interface {
9 | RunEnabledScript(context.Context, string, []string, map[string]string) (bool, error)
10 | GetName() string
11 | GetOrder() uint32
12 | GetPath() string
13 | }
14 |
15 | type NodeType string
16 |
17 | type NodeWeight uint32
18 |
19 | func (weight NodeWeight) String() string {
20 | return strconv.FormatUint(uint64(weight), 10)
21 | }
22 |
23 | func (weight NodeWeight) Int() int {
24 | return int(weight)
25 | }
26 |
27 | type Node struct {
28 | name string
29 | weight NodeWeight
30 | typ NodeType
31 | enabled bool
32 | updatedBy string
33 | module ModuleInterface
34 | }
35 |
36 | const (
37 | ModuleType NodeType = "module"
38 | WeightType NodeType = "weight"
39 | TypeAttribute string = "type"
40 | )
41 |
42 | func NewNode() *Node {
43 | return &Node{}
44 | }
45 |
46 | func (n *Node) WithName(name string) *Node {
47 | n.name = name
48 | return n
49 | }
50 |
51 | func (n *Node) WithWeight(order uint32) *Node {
52 | n.weight = NodeWeight(order)
53 | return n
54 | }
55 |
56 | func (n *Node) WithModule(module ModuleInterface) *Node {
57 | n.module = module
58 | return n
59 | }
60 |
61 | func (n *Node) WithType(typ NodeType) *Node {
62 | n.typ = typ
63 | return n
64 | }
65 |
66 | func (n Node) GetName() string {
67 | return n.name
68 | }
69 |
70 | func (n Node) GetWeight() NodeWeight {
71 | return n.weight
72 | }
73 |
74 | func (n Node) IsEnabled() bool {
75 | return n.enabled
76 | }
77 |
78 | func (n Node) GetType() NodeType {
79 | return n.typ
80 | }
81 |
82 | func (n Node) GetModule() ModuleInterface {
83 | return n.module
84 | }
85 |
86 | func (n Node) GetUpdatedBy() string {
87 | return n.updatedBy
88 | }
89 |
90 | func (n *Node) SetState(enabled bool) {
91 | n.enabled = enabled
92 | }
93 |
94 | func (n *Node) SetUpdatedBy(updatedBy string) {
95 | n.updatedBy = updatedBy
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/node/node_test.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | node_mock "github.com/flant/addon-operator/pkg/module_manager/scheduler/node/mock"
9 | )
10 |
11 | func TestNewNode(t *testing.T) {
12 | n := NewNode()
13 | assert.Equal(t, &Node{}, n)
14 |
15 | m := node_mock.MockModule{
16 | Name: "test-node",
17 | Order: 32,
18 | }
19 |
20 | n = NewNode().WithName("test-node").WithWeight(uint32(32)).WithType(ModuleType).WithModule(m)
21 | assert.Equal(t, &Node{
22 | name: "test-node",
23 | weight: 32,
24 | typ: ModuleType,
25 | module: m,
26 | }, n)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/015-admission-policy-engine/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/015-admission-policy-engine/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/042-kube-dns/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/042-kube-dns/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/133-foo-bar/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/133-foo-bar/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/20-cert-manager/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/20-cert-manager/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/30-openstack-cloud-provider/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/30-openstack-cloud-provider/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/340-monitoring-applications/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/340-monitoring-applications/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/402-ingress-nginx/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/402-ingress-nginx/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/450-flant-integration/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/450-flant-integration/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/scheduler/testdata/909-test-echo/enabled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/scheduler/testdata/909-test-echo/enabled
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hook/global-hooks/000-all-bindings/hook:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat << EOF
5 | configVersion: v1
6 | onStartup: 1
7 | afterAll: 1
8 | beforeAll: 1
9 | schedule:
10 | - crontab: "* * * * *"
11 | allowFailure: true
12 |
13 | kubernetes:
14 | - executeHookOnEvent: ["Added"]
15 | kind: "configmap"
16 | labelSelector:
17 | matchLabels:
18 | component: component1
19 | matchExpressions:
20 | - key: "tier"
21 | operator: "In"
22 | values: ["cache"]
23 | namespace:
24 | nameSelector:
25 | matchNames: ["namespace1"]
26 | jqFilter: ".items[] | del(.metadata, .field1)"
27 | allowFailure: true
28 |
29 | - kind: "namespace"
30 | labelSelector:
31 | matchLabels:
32 | component: component2
33 | matchExpressions:
34 | - key: "tier"
35 | operator: "In"
36 | values: ["cache"]
37 | namespace:
38 | nameSelector:
39 | matchNames: ["namespace2"]
40 | jqFilter: ".items[] | del(.metadata, .field2)"
41 | allowFailure: true
42 |
43 | - kind: "pod"
44 | labelSelector:
45 | matchLabels:
46 | component: component3
47 | matchExpressions:
48 | - key: "tier"
49 | operator: "In"
50 | values: ["cache"]
51 | namespace:
52 | nameSelector:
53 | matchNames: ["namespace2"]
54 | jqFilter: ".items[] | del(.metadata, .field3)"
55 | EOF
56 | fi
57 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hook/global-hooks/100-nested-hook/sub/sub/hook:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | echo "
5 | {
6 | \"beforeAll\": 1
7 | }
8 | "
9 | fi
10 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hook/modules/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/testdata/get__global_hook/modules/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hooks_in_order/global-hooks/000-before-all-binding-hooks/a:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | echo '{ "afterAll": 4 }'
5 | fi
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hooks_in_order/global-hooks/000-before-all-binding-hooks/b:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | echo '{ "afterAll": 2 }'
5 | fi
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hooks_in_order/global-hooks/000-before-all-binding-hooks/c:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | echo '{"afterAll": 3}'
5 | fi
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__global_hooks_in_order/modules/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/testdata/get__global_hooks_in_order/modules/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__module/modules/000-module/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/testdata/get__module/modules/000-module/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__module_hook/modules/000-all-bindings/hooks/all-bindings:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat << EOF
5 | configVersion: v1
6 | onStartup: 1
7 | afterHelm: 1
8 | beforeHelm: 1
9 | afterDeleteHelm: 1
10 |
11 | schedule:
12 | - crontab: "* * * * *"
13 | allowFailure: true
14 |
15 | kubernetes:
16 | - executeHookOnEvent: ["Added"]
17 | kind: "configmap"
18 | labelSelector:
19 | matchLabels:
20 | component: component1
21 | matchExpressions:
22 | - key: "tier"
23 | operator: "In"
24 | values: ["cache"]
25 | namespace:
26 | nameSelector:
27 | matchNames: ["namespace1"]
28 | jqFilter: ".items[] | del(.metadata, .field1)"
29 | allowFailure: true
30 |
31 | - kind: "namespace"
32 | labelSelector:
33 | matchLabels:
34 | component: component2
35 | matchExpressions:
36 | - key: "tier"
37 | operator: "In"
38 | values: ["cache"]
39 | namespace:
40 | nameSelector:
41 | matchNames: ["namespace2"]
42 | jqFilter: ".items[] | del(.metadata, .field2)"
43 | allowFailure: true
44 |
45 | - kind: "pod"
46 | labelSelector:
47 | matchLabels:
48 | component: component3
49 | matchExpressions:
50 | - key: "tier"
51 | operator: "In"
52 | values: ["cache"]
53 | namespace:
54 | nameSelector:
55 | matchNames: ["namespace2"]
56 | jqFilter: ".items[] | del(.metadata, .field3)"
57 | EOF
58 | fi
59 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/get__module_hook/modules/100-nested-hooks/hooks/sub/sub/nested-before-helm:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < $MODULE_ENABLED_RESULT
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/002-beta/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo false > $MODULE_ENABLED_RESULT
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/003-gamma/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # dependency: alpha
4 |
5 | # enabled if alpha is enabled
6 |
7 | cat ${VALUES_PATH:-/dev/null} | jq '.global.enabledModules | any(in({"alpha":1}))' > $MODULE_ENABLED_RESULT
8 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/004-delta/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # dependency: alpha
4 | # enabled if alpha is enabled
5 |
6 | cat ${VALUES_PATH:-/dev/null} | jq '.global.enabledModules | any(in({"alpha":1}))' > $MODULE_ENABLED_RESULT
7 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/005-epsilon/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo true > $MODULE_ENABLED_RESULT
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/006-zeta/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # dependency: gamma
4 | # dependency: delta
5 |
6 | # enabled if gamma and delta are enabled
7 |
8 | cat ${VALUES_PATH:-/dev/null} | jq '.global.enabledModules | any(in({"gamma":1,"delta":1}))' > $MODULE_ENABLED_RESULT
9 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/007-eta/enabled:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo true > $MODULE_ENABLED_RESULT
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/modules_state__no_cm__with_enabled_scripts/modules/values.yaml:
--------------------------------------------------------------------------------
1 | alphaEnabled: true
2 | betaEnabled: true
3 | gammaEnabled: true
4 | deltaEnabled: true
5 | epsilonEnabled: true
6 | zetaEnabled: true
7 | etaEnabled: true
8 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *~
18 | # Various IDEs
19 | .project
20 | .idea/
21 | *.tmproj
22 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | appVersion: "1.0"
3 | description: A Helm chart for Kubernetes
4 | name: 000-module
5 | version: 0.1.0
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/hooks/hook-1:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/module/afterDeleteHelm", "value": "value-from-after-delete-helm-20" }
12 | ]
13 | EOF
14 | fi
15 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/hooks/hook-2:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
11 | [
12 | { "op": "add", "path": "/module/afterDeleteHelm", "value": "value-from-after-delete-helm-10" }
13 | ]
14 | EOF
15 | fi
16 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | 1. Get the application URL by running these commands:
2 | {{- if .Values.ingress.enabled }}
3 | {{- range .Values.ingress.hosts }}
4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
5 | {{- end }}
6 | {{- else if contains "NodePort" .Values.service.type }}
7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "000-module.fullname" . }})
8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
9 | echo http://$NODE_IP:$NODE_PORT
10 | {{- else if contains "LoadBalancer" .Values.service.type }}
11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
12 | You can watch the status of by running 'kubectl get svc -w {{ template "000-module.fullname" . }}'
13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "000-module.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
14 | echo http://$SERVICE_IP:{{ .Values.service.port }}
15 | {{- else if contains "ClusterIP" .Values.service.type }}
16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "000-module.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
17 | echo "Visit http://127.0.0.1:8080 to use your application"
18 | kubectl port-forward $POD_NAME 8080:80
19 | {{- end }}
20 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "000-module.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | If release name contains chart name it will be used as a full name.
13 | */}}
14 | {{- define "000-module.fullname" -}}
15 | {{- if .Values.fullnameOverride -}}
16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- $name := default .Chart.Name .Values.nameOverride -}}
19 | {{- if contains $name .Release.Name -}}
20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
21 | {{- else -}}
22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
23 | {{- end -}}
24 | {{- end -}}
25 | {{- end -}}
26 |
27 | {{/*
28 | Create chart name and version as used by the chart label.
29 | */}}
30 | {{- define "000-module.chart" -}}
31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
32 | {{- end -}}
33 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1beta2
2 | kind: Deployment
3 | metadata:
4 | name: {{ template "000-module.fullname" . }}
5 | labels:
6 | app: {{ template "000-module.name" . }}
7 | chart: {{ template "000-module.chart" . }}
8 | release: {{ .Release.Name }}
9 | heritage: {{ .Release.Service }}
10 | spec:
11 | replicas: {{ .Values.replicaCount }}
12 | selector:
13 | matchLabels:
14 | app: {{ template "000-module.name" . }}
15 | release: {{ .Release.Name }}
16 | template:
17 | metadata:
18 | labels:
19 | app: {{ template "000-module.name" . }}
20 | release: {{ .Release.Name }}
21 | spec:
22 | containers:
23 | - name: {{ .Chart.Name }}
24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
25 | imagePullPolicy: {{ .Values.image.pullPolicy }}
26 | ports:
27 | - name: http
28 | containerPort: 80
29 | protocol: TCP
30 | livenessProbe:
31 | httpGet:
32 | path: /
33 | port: http
34 | readinessProbe:
35 | httpGet:
36 | path: /
37 | port: http
38 | resources:
39 | {{ toYaml .Values.resources | indent 12 }}
40 | {{- with .Values.nodeSelector }}
41 | nodeSelector:
42 | {{ toYaml . | indent 8 }}
43 | {{- end }}
44 | {{- with .Values.affinity }}
45 | affinity:
46 | {{ toYaml . | indent 8 }}
47 | {{- end }}
48 | {{- with .Values.tolerations }}
49 | tolerations:
50 | {{ toYaml . | indent 8 }}
51 | {{- end }}
52 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | {{- $fullName := include "000-module.fullname" . -}}
3 | {{- $servicePort := .Values.service.port -}}
4 | {{- $ingressPath := .Values.ingress.path -}}
5 | apiVersion: extensions/v1beta1
6 | kind: Ingress
7 | metadata:
8 | name: {{ $fullName }}
9 | labels:
10 | app: {{ template "000-module.name" . }}
11 | chart: {{ template "000-module.chart" . }}
12 | release: {{ .Release.Name }}
13 | heritage: {{ .Release.Service }}
14 | {{- with .Values.ingress.annotations }}
15 | annotations:
16 | {{ toYaml . | indent 4 }}
17 | {{- end }}
18 | spec:
19 | {{- if .Values.ingress.tls }}
20 | tls:
21 | {{- range .Values.ingress.tls }}
22 | - hosts:
23 | {{- range .hosts }}
24 | - {{ . }}
25 | {{- end }}
26 | secretName: {{ .secretName }}
27 | {{- end }}
28 | {{- end }}
29 | rules:
30 | {{- range .Values.ingress.hosts }}
31 | - host: {{ . }}
32 | http:
33 | paths:
34 | - path: {{ $ingressPath }}
35 | backend:
36 | serviceName: {{ $fullName }}
37 | servicePort: http
38 | {{- end }}
39 | {{- end }}
40 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ template "000-module.fullname" . }}
5 | labels:
6 | app: {{ template "000-module.name" . }}
7 | chart: {{ template "000-module.chart" . }}
8 | release: {{ .Release.Name }}
9 | heritage: {{ .Release.Service }}
10 | spec:
11 | type: {{ .Values.service.type }}
12 | ports:
13 | - port: {{ .Values.service.port }}
14 | targetPort: http
15 | protocol: TCP
16 | name: http
17 | selector:
18 | app: {{ template "000-module.name" . }}
19 | release: {{ .Release.Name }}
20 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_delete_module/modules/000-module/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for 000-module.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | module:
6 | imageName: "nginx:stable"
7 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_global_hook/global-hooks/000-update-kube-config/merge_and_patch_values:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$CONFIG_VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/global/a", "value": 2 },
12 | { "op": "remove", "path": "/global/b" },
13 | { "op": "add", "path": "/global/c", "value": [3] }
14 | ]
15 | EOF
16 | fi
17 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_global_hook/global-hooks/100-update-dynamic/merge_and_patch_values:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/global/a", "value": 9 },
12 | { "op": "add", "path": "/global/c", "value": "10" }
13 | ]
14 | EOF
15 | fi
16 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_global_hook/modules/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flant/addon-operator/74df397cd4ffe42e73b915009a543e491f581ab6/pkg/module_manager/testdata/test_run_global_hook/modules/.gitkeep
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *~
18 | # Various IDEs
19 | .project
20 | .idea/
21 | *.tmproj
22 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | appVersion: "1.0"
3 | description: A Helm chart for Kubernetes
4 | name: 000-module
5 | version: 0.1.0
6 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/hooks/hook-1:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/module/beforeHelm", "value": "value-from-before-helm-20" }
12 | ]
13 | EOF
14 | fi
15 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/hooks/hook-2:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/module/afterHelm", "value": "value-from-before-helm-2" }
12 | ]
13 | EOF
14 | fi
15 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/hooks/hook-3:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/module/beforeHelm", "value": "value-from-before-helm-1" }
12 | ]
13 | EOF
14 | fi
15 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/hooks/hook-4:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/module/afterHelm", "value": "value-from-after-helm" }
12 | ]
13 | EOF
14 | fi
15 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | 1. Get the application URL by running these commands:
2 | {{- if .Values.ingress.enabled }}
3 | {{- range .Values.ingress.hosts }}
4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
5 | {{- end }}
6 | {{- else if contains "NodePort" .Values.service.type }}
7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "000-module.fullname" . }})
8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
9 | echo http://$NODE_IP:$NODE_PORT
10 | {{- else if contains "LoadBalancer" .Values.service.type }}
11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
12 | You can watch the status of by running 'kubectl get svc -w {{ template "000-module.fullname" . }}'
13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "000-module.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
14 | echo http://$SERVICE_IP:{{ .Values.service.port }}
15 | {{- else if contains "ClusterIP" .Values.service.type }}
16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "000-module.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
17 | echo "Visit http://127.0.0.1:8080 to use your application"
18 | kubectl port-forward $POD_NAME 8080:80
19 | {{- end }}
20 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "000-module.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | If release name contains chart name it will be used as a full name.
13 | */}}
14 | {{- define "000-module.fullname" -}}
15 | {{- if .Values.fullnameOverride -}}
16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- $name := default .Chart.Name .Values.nameOverride -}}
19 | {{- if contains $name .Release.Name -}}
20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
21 | {{- else -}}
22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
23 | {{- end -}}
24 | {{- end -}}
25 | {{- end -}}
26 |
27 | {{/*
28 | Create chart name and version as used by the chart label.
29 | */}}
30 | {{- define "000-module.chart" -}}
31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
32 | {{- end -}}
33 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1beta2
2 | kind: Deployment
3 | metadata:
4 | name: {{ template "000-module.fullname" . }}
5 | labels:
6 | app: {{ template "000-module.name" . }}
7 | chart: {{ template "000-module.chart" . }}
8 | release: {{ .Release.Name }}
9 | heritage: {{ .Release.Service }}
10 | spec:
11 | replicas: {{ .Values.replicaCount }}
12 | selector:
13 | matchLabels:
14 | app: {{ template "000-module.name" . }}
15 | release: {{ .Release.Name }}
16 | template:
17 | metadata:
18 | labels:
19 | app: {{ template "000-module.name" . }}
20 | release: {{ .Release.Name }}
21 | spec:
22 | containers:
23 | - name: {{ .Chart.Name }}
24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
25 | imagePullPolicy: {{ .Values.image.pullPolicy }}
26 | ports:
27 | - name: http
28 | containerPort: 80
29 | protocol: TCP
30 | livenessProbe:
31 | httpGet:
32 | path: /
33 | port: http
34 | readinessProbe:
35 | httpGet:
36 | path: /
37 | port: http
38 | resources:
39 | {{ toYaml .Values.resources | indent 12 }}
40 | {{- with .Values.nodeSelector }}
41 | nodeSelector:
42 | {{ toYaml . | indent 8 }}
43 | {{- end }}
44 | {{- with .Values.affinity }}
45 | affinity:
46 | {{ toYaml . | indent 8 }}
47 | {{- end }}
48 | {{- with .Values.tolerations }}
49 | tolerations:
50 | {{ toYaml . | indent 8 }}
51 | {{- end }}
52 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | {{- $fullName := include "000-module.fullname" . -}}
3 | {{- $servicePort := .Values.service.port -}}
4 | {{- $ingressPath := .Values.ingress.path -}}
5 | apiVersion: extensions/v1beta1
6 | kind: Ingress
7 | metadata:
8 | name: {{ $fullName }}
9 | labels:
10 | app: {{ template "000-module.name" . }}
11 | chart: {{ template "000-module.chart" . }}
12 | release: {{ .Release.Name }}
13 | heritage: {{ .Release.Service }}
14 | {{- with .Values.ingress.annotations }}
15 | annotations:
16 | {{ toYaml . | indent 4 }}
17 | {{- end }}
18 | spec:
19 | {{- if .Values.ingress.tls }}
20 | tls:
21 | {{- range .Values.ingress.tls }}
22 | - hosts:
23 | {{- range .hosts }}
24 | - {{ . }}
25 | {{- end }}
26 | secretName: {{ .secretName }}
27 | {{- end }}
28 | {{- end }}
29 | rules:
30 | {{- range .Values.ingress.hosts }}
31 | - host: {{ . }}
32 | http:
33 | paths:
34 | - path: {{ $ingressPath }}
35 | backend:
36 | serviceName: {{ $fullName }}
37 | servicePort: http
38 | {{- end }}
39 | {{- end }}
40 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ template "000-module.fullname" . }}
5 | labels:
6 | app: {{ template "000-module.name" . }}
7 | chart: {{ template "000-module.chart" . }}
8 | release: {{ .Release.Name }}
9 | heritage: {{ .Release.Service }}
10 | spec:
11 | type: {{ .Values.service.type }}
12 | ports:
13 | - port: {{ .Values.service.port }}
14 | targetPort: http
15 | protocol: TCP
16 | name: http
17 | selector:
18 | app: {{ template "000-module.name" . }}
19 | release: {{ .Release.Name }}
20 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module/modules/000-module/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for 000-module.
2 | module:
3 | imageName: "nginx:stable"
4 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module_hook/modules/000-update-kube-module-config/hooks/merge_and_patch_values:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$CONFIG_VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/updateKubeModuleConfig/a", "value": 2 },
12 | { "op": "remove", "path": "/updateKubeModuleConfig/b" },
13 | { "op": "add", "path": "/updateKubeModuleConfig/c", "value": [3] }
14 | ]
15 | EOF
16 | fi
17 |
--------------------------------------------------------------------------------
/pkg/module_manager/testdata/test_run_module_hook/modules/100-update-module-dynamic/hooks/merge_and_patch_values:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | if [[ "$1" == "--config" ]]; then
4 | cat < "$VALUES_JSON_PATCH_PATH"
10 | [
11 | { "op": "add", "path": "/updateModuleDynamic/a", "value": 9 },
12 | { "op": "add", "path": "/updateModuleDynamic/c", "value": "10" }
13 | ]
14 | EOF
15 | fi
16 |
--------------------------------------------------------------------------------
/pkg/task/discover-crds/service.go:
--------------------------------------------------------------------------------
1 | package discovercrds
2 |
3 | import "sync"
4 |
5 | type DiscoveredGVKs struct {
6 | mu sync.Mutex
7 | discoveredGVKs map[string]struct{}
8 | }
9 |
10 | func NewDiscoveredGVKs() *DiscoveredGVKs {
11 | return &DiscoveredGVKs{
12 | discoveredGVKs: make(map[string]struct{}),
13 | }
14 | }
15 |
16 | func (d *DiscoveredGVKs) AddGVK(crds ...string) {
17 | d.mu.Lock()
18 | defer d.mu.Unlock()
19 |
20 | for _, crd := range crds {
21 | d.discoveredGVKs[crd] = struct{}{}
22 | }
23 | }
24 |
25 | func (d *DiscoveredGVKs) ProcessGVKs(processor func(crdList []string)) {
26 | d.mu.Lock()
27 | defer d.mu.Unlock()
28 |
29 | if len(d.discoveredGVKs) == 0 {
30 | return
31 | }
32 |
33 | gvkList := make([]string, 0, len(d.discoveredGVKs))
34 | for gvk := range d.discoveredGVKs {
35 | gvkList = append(gvkList, gvk)
36 | }
37 |
38 | processor(gvkList)
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/task/global-hook-enable-schedule-bindings/task.go:
--------------------------------------------------------------------------------
1 | package globalhookenableschedulebindings
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/deckhouse/deckhouse/pkg/log"
7 | "go.opentelemetry.io/otel"
8 |
9 | "github.com/flant/addon-operator/pkg/module_manager"
10 | "github.com/flant/addon-operator/pkg/task"
11 | sh_task "github.com/flant/shell-operator/pkg/task"
12 | "github.com/flant/shell-operator/pkg/task/queue"
13 | )
14 |
15 | const (
16 | taskName = "global-hook-enable-schedule-bindings"
17 | )
18 |
19 | type TaskDependencies interface {
20 | GetModuleManager() *module_manager.ModuleManager
21 | }
22 |
23 | // Task represents a handler for enabling schedule bindings on global hooks
24 | type Task struct {
25 | shellTask sh_task.Task
26 | moduleManager *module_manager.ModuleManager
27 | logger *log.Logger
28 | }
29 |
30 | // RegisterTaskHandler returns a function that creates a Task handler
31 | func RegisterTaskHandler(config TaskDependencies) func(t sh_task.Task, logger *log.Logger) task.Task {
32 | return func(t sh_task.Task, logger *log.Logger) task.Task {
33 | return NewTask(
34 | t,
35 | config.GetModuleManager(),
36 | logger.Named("global-hook-enable-schedule-bindings"),
37 | )
38 | }
39 | }
40 |
41 | // NewTask creates a new Task instance.
42 | func NewTask(shellTask sh_task.Task, moduleManager *module_manager.ModuleManager, logger *log.Logger) *Task {
43 | return &Task{
44 | shellTask: shellTask,
45 | moduleManager: moduleManager,
46 | logger: logger,
47 | }
48 | }
49 |
50 | func (s *Task) Handle(ctx context.Context) queue.TaskResult {
51 | _, span := otel.Tracer(taskName).Start(ctx, "handle")
52 | defer span.End()
53 |
54 | result := queue.TaskResult{}
55 |
56 | hm := task.HookMetadataAccessor(s.shellTask)
57 |
58 | globalHook := s.moduleManager.GetGlobalHook(hm.HookName)
59 | globalHook.GetHookController().EnableScheduleBindings()
60 |
61 | result.Status = queue.Success
62 |
63 | return result
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/task/global-hook-wait-kubernetes-synchronization/task.go:
--------------------------------------------------------------------------------
1 | package globalhookwaitkubernetessynchronization
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/deckhouse/deckhouse/pkg/log"
8 | "go.opentelemetry.io/otel"
9 |
10 | "github.com/flant/addon-operator/pkg/module_manager"
11 | "github.com/flant/addon-operator/pkg/task"
12 | sh_task "github.com/flant/shell-operator/pkg/task"
13 | "github.com/flant/shell-operator/pkg/task/queue"
14 | )
15 |
16 | const (
17 | taskName = "global-hook-wait-kubernetes-synchronization"
18 | )
19 |
20 | // TaskDependencies defines the interface for accessing necessary components
21 | type TaskDependencies interface {
22 | GetModuleManager() *module_manager.ModuleManager
23 | }
24 |
25 | // RegisterTaskHandler creates a factory function for global hook wait kubernetes synchronization tasks
26 | func RegisterTaskHandler(svc TaskDependencies) func(t sh_task.Task, logger *log.Logger) task.Task {
27 | return func(t sh_task.Task, logger *log.Logger) task.Task {
28 | return NewTask(
29 | t,
30 | svc.GetModuleManager(),
31 | logger.Named("global-hook-wait-kubernetes-synchronization"),
32 | )
33 | }
34 | }
35 |
36 | // Task handles waiting for kubernetes synchronization for global hooks
37 | type Task struct {
38 | shellTask sh_task.Task
39 | moduleManager *module_manager.ModuleManager
40 | logger *log.Logger
41 | }
42 |
43 | // NewTask creates a new task handler for global hook wait kubernetes synchronization
44 | func NewTask(
45 | shellTask sh_task.Task,
46 | moduleManager *module_manager.ModuleManager,
47 | logger *log.Logger,
48 | ) *Task {
49 | return &Task{
50 | shellTask: shellTask,
51 | moduleManager: moduleManager,
52 | logger: logger,
53 | }
54 | }
55 |
56 | func (s *Task) Handle(ctx context.Context) queue.TaskResult {
57 | _, span := otel.Tracer(taskName).Start(ctx, "handle")
58 | defer span.End()
59 |
60 | res := queue.TaskResult{
61 | Status: queue.Success,
62 | }
63 |
64 | if s.moduleManager.GlobalSynchronizationNeeded() && !s.moduleManager.GlobalSynchronizationState().IsCompleted() {
65 | // dump state
66 | s.moduleManager.GlobalSynchronizationState().DebugDumpState(s.logger)
67 | s.shellTask.WithQueuedAt(time.Now())
68 |
69 | res.Status = queue.Repeat
70 | } else {
71 | s.logger.Info("Synchronization done for all global hooks")
72 | }
73 |
74 | return res
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/task/module-purge/task.go:
--------------------------------------------------------------------------------
1 | package modulepurge
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/deckhouse/deckhouse/pkg/log"
7 | "go.opentelemetry.io/otel"
8 |
9 | "github.com/flant/addon-operator/pkg/helm"
10 | "github.com/flant/addon-operator/pkg/task"
11 | sh_task "github.com/flant/shell-operator/pkg/task"
12 | "github.com/flant/shell-operator/pkg/task/queue"
13 | )
14 |
15 | const (
16 | taskName = "module-purge"
17 | )
18 |
19 | // TaskDependencies defines the interface for accessing necessary components
20 | type TaskDependencies interface {
21 | GetHelm() *helm.ClientFactory
22 | }
23 |
24 | // RegisterTaskHandler creates a factory function for ModulePurge tasks
25 | func RegisterTaskHandler(svc TaskDependencies) func(t sh_task.Task, logger *log.Logger) task.Task {
26 | return func(t sh_task.Task, logger *log.Logger) task.Task {
27 | return NewTask(
28 | t,
29 | svc.GetHelm(),
30 | logger.Named("module-purge"),
31 | )
32 | }
33 | }
34 |
35 | // Task handles purging modules
36 | type Task struct {
37 | shellTask sh_task.Task
38 | helm *helm.ClientFactory
39 | logger *log.Logger
40 | }
41 |
42 | // NewTask creates a new task handler for module purging
43 | func NewTask(
44 | shellTask sh_task.Task,
45 | helm *helm.ClientFactory,
46 | logger *log.Logger,
47 | ) *Task {
48 | return &Task{
49 | shellTask: shellTask,
50 | helm: helm,
51 | logger: logger,
52 | }
53 | }
54 |
55 | func (s *Task) Handle(ctx context.Context) queue.TaskResult {
56 | _, span := otel.Tracer(taskName).Start(ctx, "handle")
57 | defer span.End()
58 |
59 | var res queue.TaskResult
60 |
61 | s.logger.Debug("Module purge start")
62 |
63 | hm := task.HookMetadataAccessor(s.shellTask)
64 | helmClientOptions := []helm.ClientOption{
65 | helm.WithLogLabels(s.shellTask.GetLogLabels()),
66 | }
67 |
68 | err := s.helm.NewClient(s.logger.Named("helm-client"), helmClientOptions...).DeleteRelease(hm.ModuleName)
69 | if err != nil {
70 | // Purge is for unknown modules, just print warning.
71 | s.logger.Warn("Module purge failed, no retry.", log.Err(err))
72 | } else {
73 | s.logger.Debug("Module purge success")
74 | }
75 |
76 | res.Status = queue.Success
77 |
78 | return res
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/task/parallel/parallel.go:
--------------------------------------------------------------------------------
1 | package parallel
2 |
3 | import "sync"
4 |
5 | type queueEvent struct {
6 | moduleName string
7 | errMsg string
8 | succeeded bool
9 | }
10 |
11 | func (e queueEvent) ModuleName() string {
12 | return e.moduleName
13 | }
14 |
15 | func (e queueEvent) ErrorMessage() string {
16 | return e.errMsg
17 | }
18 |
19 | func (e queueEvent) Succeeded() bool {
20 | return e.succeeded
21 | }
22 |
23 | type TaskChannel chan queueEvent
24 |
25 | func NewTaskChannel() TaskChannel {
26 | return TaskChannel(make(chan queueEvent))
27 | }
28 |
29 | func (t TaskChannel) SendSuccess(moduleName string) {
30 | t <- queueEvent{
31 | moduleName: moduleName,
32 | succeeded: true,
33 | }
34 | }
35 |
36 | func (t TaskChannel) SendFailure(moduleName string, errMsg string) {
37 | t <- queueEvent{
38 | moduleName: moduleName,
39 | errMsg: errMsg,
40 | succeeded: false,
41 | }
42 | }
43 |
44 | type TaskChannels struct {
45 | l sync.Mutex
46 | channels map[string]TaskChannel
47 | }
48 |
49 | func NewTaskChannels() *TaskChannels {
50 | return &TaskChannels{
51 | channels: make(map[string]TaskChannel),
52 | }
53 | }
54 |
55 | func (pq *TaskChannels) Channels() []string {
56 | pq.l.Lock()
57 | defer pq.l.Unlock()
58 |
59 | ids := make([]string, 0, len(pq.channels))
60 |
61 | for id := range pq.channels {
62 | ids = append(ids, id)
63 | }
64 |
65 | return ids
66 | }
67 |
68 | func (pq *TaskChannels) Set(id string, c TaskChannel) {
69 | pq.l.Lock()
70 | pq.channels[id] = c
71 | pq.l.Unlock()
72 | }
73 |
74 | func (pq *TaskChannels) Get(id string) (TaskChannel, bool) {
75 | pq.l.Lock()
76 | defer pq.l.Unlock()
77 | c, ok := pq.channels[id]
78 | return c, ok
79 | }
80 |
81 | func (pq *TaskChannels) Delete(id string) {
82 | pq.l.Lock()
83 | delete(pq.channels, id)
84 | pq.l.Unlock()
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/task/service/logs.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "context"
5 | "log/slog"
6 |
7 | "github.com/deckhouse/deckhouse/pkg/log"
8 |
9 | "github.com/flant/addon-operator/pkg"
10 | "github.com/flant/addon-operator/pkg/module_manager/models/modules"
11 | "github.com/flant/addon-operator/pkg/task"
12 | "github.com/flant/addon-operator/pkg/task/helpers"
13 | sh_task "github.com/flant/shell-operator/pkg/task"
14 | "github.com/flant/shell-operator/pkg/task/queue"
15 | )
16 |
17 | // logTaskStart prints info about task at start. Also prints event source info from task props.
18 | func (s *TaskHandlerService) logTaskStart(tsk sh_task.Task, logger *log.Logger) {
19 | // Prevent excess messages for highly frequent tasks.
20 | if tsk.GetType() == task.GlobalHookWaitKubernetesSynchronization {
21 | return
22 | }
23 |
24 | if tsk.GetType() == task.ModuleRun {
25 | hm := task.HookMetadataAccessor(tsk)
26 | baseModule := s.moduleManager.GetModule(hm.ModuleName)
27 |
28 | if baseModule.GetPhase() == modules.WaitForSynchronization {
29 | return
30 | }
31 | }
32 |
33 | logger = logger.With(pkg.LogKeyTaskFlow, "start")
34 |
35 | if triggeredBy, ok := tsk.GetProp("triggered-by").([]slog.Attr); ok {
36 | for _, attr := range triggeredBy {
37 | logger = logger.With(attr)
38 | }
39 | }
40 |
41 | logger.Info(helpers.TaskDescriptionForTaskFlowLog(tsk, "start", s.taskPhase(tsk), ""))
42 | }
43 |
44 | // logTaskEnd prints info about task at the end. Info level used only for the ConvergeModules task.
45 | func (s *TaskHandlerService) logTaskEnd(tsk sh_task.Task, result queue.TaskResult, logger *log.Logger) {
46 | logger = logger.With(pkg.LogKeyTaskFlow, "end")
47 |
48 | level := log.LevelDebug
49 | if tsk.GetType() == task.ConvergeModules {
50 | level = log.LevelInfo
51 | }
52 |
53 | logger.Log(context.TODO(), level.Level(), helpers.TaskDescriptionForTaskFlowLog(tsk, "end", s.taskPhase(tsk), string(result.Status)))
54 | }
55 |
56 | func (s *TaskHandlerService) taskPhase(tsk sh_task.Task) string {
57 | switch tsk.GetType() {
58 | case task.ConvergeModules:
59 | // return string(s.ConvergeState.Phase)
60 | case task.ModuleRun:
61 | hm := task.HookMetadataAccessor(tsk)
62 | mod := s.moduleManager.GetModule(hm.ModuleName)
63 | return string(mod.GetPhase())
64 | }
65 | return ""
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/task/service/metric.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/flant/addon-operator/pkg"
7 | "github.com/flant/addon-operator/pkg/task"
8 | sh_task "github.com/flant/shell-operator/pkg/task"
9 | )
10 |
11 | // UpdateWaitInQueueMetric increases task_wait_in_queue_seconds_total counter for the task type.
12 | // TODO pass queue name from handler, not from task
13 | func (s *TaskHandlerService) UpdateWaitInQueueMetric(t sh_task.Task) {
14 | metricLabels := map[string]string{
15 | "module": "",
16 | "hook": "",
17 | pkg.MetricKeyBinding: string(t.GetType()),
18 | "queue": t.GetQueueName(),
19 | }
20 |
21 | hm := task.HookMetadataAccessor(t)
22 |
23 | switch t.GetType() {
24 | case task.GlobalHookRun,
25 | task.GlobalHookEnableScheduleBindings,
26 | task.GlobalHookEnableKubernetesBindings,
27 | task.GlobalHookWaitKubernetesSynchronization:
28 | metricLabels["hook"] = hm.HookName
29 |
30 | case task.ModuleRun,
31 | task.ModuleDelete,
32 | task.ModuleHookRun,
33 | task.ModulePurge:
34 | metricLabels["module"] = hm.ModuleName
35 |
36 | case task.ConvergeModules,
37 | task.DiscoverHelmReleases:
38 | // no action required
39 | }
40 |
41 | if t.GetType() == task.GlobalHookRun {
42 | // set binding name instead of type
43 | metricLabels[pkg.MetricKeyBinding] = hm.Binding
44 | }
45 | if t.GetType() == task.ModuleHookRun {
46 | // set binding name instead of type
47 | metricLabels["hook"] = hm.HookName
48 | metricLabels[pkg.MetricKeyBinding] = hm.Binding
49 | }
50 |
51 | taskWaitTime := time.Since(t.GetQueuedAt()).Seconds()
52 | s.metricStorage.CounterAdd("{PREFIX}task_wait_in_queue_seconds_total", taskWaitTime, metricLabels)
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/task/task.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/flant/shell-operator/pkg/task"
7 | "github.com/flant/shell-operator/pkg/task/queue"
8 | )
9 |
10 | // Addon-operator specific task types
11 | const (
12 | // GlobalHookRun runs a global hook.
13 | GlobalHookRun task.TaskType = "GlobalHookRun"
14 | // ModuleHookRun runs schedule or kubernetes hook.
15 | ModuleHookRun task.TaskType = "ModuleHookRun"
16 | // ModuleDelete runs helm delete/afterHelmDelete sequence.
17 | ModuleDelete task.TaskType = "ModuleDelete"
18 | // ModuleRun runs beforeHelm/helm upgrade/afterHelm sequence.
19 | ModuleRun task.TaskType = "ModuleRun"
20 | // ParallelModuleRun runs beforeHelm/helm upgrade/afterHelm sequence for a bunch of modules in parallel.
21 | ParallelModuleRun task.TaskType = "ParallelModuleRun"
22 | // ModulePurge - delete unknown helm release (no module in ModulesDir)
23 | ModulePurge task.TaskType = "ModulePurge"
24 | // ModuleEnsureCRDs runs ensureCRDs task for enabled module
25 | ModuleEnsureCRDs task.TaskType = "ModuleEnsureCRDs"
26 |
27 | // DiscoverHelmReleases lists helm releases to detect unknown modules and initiate enabled modules list.
28 | DiscoverHelmReleases task.TaskType = "DiscoverHelmReleases"
29 |
30 | // ConvergeModules runs beforeAll/run modules/afterAll sequence for all enabled modules.
31 | ConvergeModules task.TaskType = "ConvergeModules"
32 |
33 | // ApplyKubeConfigValues validates and updates modules' values
34 | ApplyKubeConfigValues task.TaskType = "ApplyKubeConfigValues"
35 |
36 | GlobalHookEnableKubernetesBindings task.TaskType = "GlobalHookEnableKubernetesBindings"
37 | GlobalHookWaitKubernetesSynchronization task.TaskType = "GlobalHookWaitKubernetesSynchronization"
38 | GlobalHookEnableScheduleBindings task.TaskType = "GlobalHookEnableScheduleBindings"
39 | )
40 |
41 | type Task interface {
42 | Handle(ctx context.Context) queue.TaskResult
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/task/test/task_metadata_test.go:
--------------------------------------------------------------------------------
1 | package task_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/gomega"
7 |
8 | . "github.com/flant/addon-operator/pkg/hook/types"
9 | . "github.com/flant/addon-operator/pkg/task"
10 | "github.com/flant/shell-operator/pkg/hook/types"
11 | sh_task "github.com/flant/shell-operator/pkg/task"
12 | )
13 |
14 | func Test_MetadataAccessor(tT *testing.T) {
15 | g := NewWithT(tT)
16 | t := sh_task.NewTask(ModuleRun)
17 |
18 | t.WithMetadata(HookMetadata{
19 | BindingType: BeforeAll,
20 | ModuleName: "module-name",
21 | EventDescription: "ReloadAllTasks",
22 | DoModuleStartup: true,
23 | })
24 |
25 | hm := HookMetadataAccessor(t)
26 |
27 | g.Expect(hm.ModuleName).Should(Equal("module-name"))
28 | }
29 |
30 | func Test_TaskDescription(t *testing.T) {
31 | tests := []struct {
32 | name string
33 | metadata HookMetadata
34 | expect string
35 | }{
36 | {
37 | "global hook",
38 | HookMetadata{
39 | BindingType: BeforeAll,
40 | ModuleName: "module-name",
41 | HookName: "hook.sh",
42 | EventDescription: "ReloadAllTasks",
43 | DoModuleStartup: true,
44 | },
45 | "beforeAll:hook.sh:ReloadAllTasks",
46 | },
47 | {
48 | "module run",
49 | HookMetadata{
50 | ModuleName: "module-name",
51 | EventDescription: "BootstrapMainQueue",
52 | },
53 | "module-name:BootstrapMainQueue",
54 | },
55 | {
56 | "module run with DoModuleStartup",
57 | HookMetadata{
58 | ModuleName: "module-name",
59 | EventDescription: "GlobalValuesChanged",
60 | DoModuleStartup: true,
61 | },
62 | "module-name:doStartup:GlobalValuesChanged",
63 | },
64 | {
65 | "module hook",
66 | HookMetadata{
67 | BindingType: types.OnKubernetesEvent,
68 | ModuleName: "module",
69 | HookName: "module/hook.sh",
70 | EventDescription: "Kubernetes",
71 | },
72 | "kubernetes:module/hook.sh:Kubernetes",
73 | },
74 | }
75 |
76 | for _, tt := range tests {
77 | t.Run(tt.name, func(t *testing.T) {
78 | g := NewWithT(t)
79 | g.Expect(tt.metadata.GetDescription()).To(Equal(tt.expect))
80 | })
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/utils/chroot.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/flant/addon-operator/pkg/app"
7 | )
8 |
9 | func GetModuleChrootPath(moduleName string) string {
10 | if len(app.ShellChrootDir) > 0 {
11 | return fmt.Sprintf("%s/%s", app.ShellChrootDir, moduleName)
12 | }
13 |
14 | return ""
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/utils/fschecksum.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 | "os"
8 | "path"
9 | "sort"
10 | )
11 |
12 | func CalculateStringsChecksum(stringArr ...string) string {
13 | hasher := md5.New()
14 | sort.Strings(stringArr)
15 | for _, value := range stringArr {
16 | _, _ = hasher.Write([]byte(value))
17 | }
18 | return hex.EncodeToString(hasher.Sum(nil))
19 | }
20 |
21 | func CalculateChecksumOfFile(path string) (string, error) {
22 | content, err := os.ReadFile(path)
23 | if err != nil {
24 | return "", err
25 | }
26 | return CalculateStringsChecksum(string(content)), nil
27 | }
28 |
29 | func CalculateChecksumOfDirectory(dir string) (string, error) {
30 | res := ""
31 |
32 | var checkErr error
33 | files, err := FilesFromRoot(dir, func(dir string, name string, _ os.FileInfo) bool {
34 | fPath := path.Join(dir, name)
35 | checksum, err := CalculateChecksumOfFile(fPath)
36 | if err != nil {
37 | // return only bad files for logging
38 | checkErr = err
39 | return true
40 | }
41 | res = CalculateStringsChecksum(res, checksum)
42 | // good files are skipped
43 | return false
44 | })
45 | if err != nil {
46 | return "", err
47 | }
48 | if checkErr != nil {
49 | return "", fmt.Errorf("calculate checksum of %+v: %v", files, err)
50 | }
51 |
52 | return res, nil
53 | }
54 |
55 | func CalculateChecksumOfPaths(paths ...string) (string, error) {
56 | res := ""
57 |
58 | for _, aPath := range paths {
59 | fileInfo, err := os.Stat(aPath)
60 | if err != nil {
61 | return "", err
62 | }
63 |
64 | var checksum string
65 | if fileInfo.IsDir() {
66 | checksum, err = CalculateChecksumOfDirectory(aPath)
67 | } else {
68 | checksum, err = CalculateChecksumOfFile(aPath)
69 | }
70 |
71 | if err != nil {
72 | return "", err
73 | }
74 | res = CalculateStringsChecksum(res, checksum)
75 | }
76 |
77 | return res, nil
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/utils/helpers.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "os"
4 |
5 | func DumpData(filePath string, data []byte) error {
6 | err := os.WriteFile(filePath, data, 0o644)
7 | if err != nil {
8 | return err
9 | }
10 | return nil
11 | }
12 |
13 | func CreateEmptyWritableFile(filePath string) error {
14 | file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
15 | if err != nil {
16 | return nil
17 | }
18 |
19 | _ = file.Close()
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/utils/merge_labels.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "log/slog"
5 |
6 | "github.com/deckhouse/deckhouse/pkg/log"
7 | )
8 |
9 | // MergeLabels merges several maps into one. Last map keys overrides keys from first maps.
10 | //
11 | // Can be used to copy a map if just one argument is used.
12 | func MergeLabels(labelsMaps ...map[string]string) map[string]string {
13 | labels := make(map[string]string)
14 | for _, labelsMap := range labelsMaps {
15 | for k, v := range labelsMap {
16 | labels[k] = v
17 | }
18 | }
19 | return labels
20 | }
21 |
22 | func EnrichLoggerWithLabels(logger *log.Logger, labelsMaps ...map[string]string) *log.Logger {
23 | loggerEntry := logger
24 |
25 | for _, labels := range labelsMaps {
26 | for k, v := range labels {
27 | loggerEntry = loggerEntry.With(slog.String(k, v))
28 | }
29 | }
30 |
31 | return loggerEntry
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/utils/mergemap.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | const maxDepth = 32
8 |
9 | // mergeMap recursively merges the src and dst maps. Key conflicts are resolved by
10 | // preferring src, or recursively descending, if both src and dst are maps.
11 | func mergeMap(dst, src map[string]interface{}) map[string]interface{} {
12 | return merge(dst, src, 0)
13 | }
14 |
15 | func merge(dst, src map[string]interface{}, depth int) map[string]interface{} {
16 | if depth > maxDepth {
17 | panic("too deep!")
18 | }
19 |
20 | for key, srcVal := range src {
21 | srcMap, srcMapOk := mapify(srcVal)
22 | if dstVal, ok := dst[key]; ok {
23 | dstMap, dstMapOk := mapify(dstVal)
24 | if srcMapOk && dstMapOk {
25 | dst[key] = merge(dstMap, srcMap, depth+1)
26 | continue
27 | }
28 | }
29 |
30 | if srcMapOk {
31 | dst[key] = deepCopyMap(srcMap)
32 | } else {
33 | dst[key] = srcVal
34 | }
35 | }
36 | return dst
37 | }
38 |
39 | func mapify(i interface{}) (map[string]interface{}, bool) {
40 | switch v := i.(type) {
41 | case map[string]interface{}:
42 | return v, true
43 | case Values:
44 | return v, true
45 | }
46 |
47 | value := reflect.ValueOf(i)
48 | if value.Kind() == reflect.Map {
49 | m := make(map[string]interface{}, value.Len())
50 | for _, k := range value.MapKeys() {
51 | m[k.String()] = value.MapIndex(k).Interface()
52 | }
53 | return m, true
54 | }
55 | return map[string]interface{}{}, false
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/utils/stdliblogtolog/adapter.go:
--------------------------------------------------------------------------------
1 | package stdliblogtolog
2 |
3 | import (
4 | "context"
5 | "io"
6 | stdlog "log"
7 | "strings"
8 |
9 | "github.com/deckhouse/deckhouse/pkg/log"
10 | logctx "github.com/deckhouse/deckhouse/pkg/log/context"
11 | )
12 |
13 | func InitAdapter(logger *log.Logger) {
14 | stdlog.SetOutput(&writer{logger: logger.Named("helm")})
15 | }
16 |
17 | var _ io.Writer = (*writer)(nil)
18 |
19 | type writer struct {
20 | logger *log.Logger
21 | }
22 |
23 | func (w *writer) Write(msg []byte) (int, error) {
24 | // There is no loglevel for stdlib logger
25 | w.logger.Log(logctx.SetCustomKeyContext(context.Background()), log.LevelInfo.Level(), strings.TrimSpace(string(msg)))
26 |
27 | return 0, nil
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/utils/stdliblogtolog/adapter_test.go:
--------------------------------------------------------------------------------
1 | package stdliblogtolog
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/json"
7 | "fmt"
8 | stdlog "log"
9 | "testing"
10 |
11 | "github.com/deckhouse/deckhouse/pkg/log"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | type testLogLine struct {
16 | Level string `json:"level"`
17 | Message string `json:"msg"`
18 | Logger string `json:"logger"`
19 | }
20 |
21 | func TestStdlibLogAdapter(t *testing.T) {
22 | t.Run("Simple", func(t *testing.T) {
23 | buf := bytes.Buffer{}
24 | logger := log.NewLogger(log.Options{})
25 |
26 | logger.SetOutput(&buf)
27 |
28 | InitAdapter(logger)
29 |
30 | stdlog.Print("test string for a check")
31 | stdlog.Print("another string")
32 |
33 | scanner := bufio.NewScanner(bytes.NewReader(buf.Bytes()))
34 |
35 | scanner.Scan()
36 | assertLogLine(t, scanner.Text(), "test string for a check")
37 |
38 | scanner.Scan()
39 | assertLogLine(t, scanner.Text(), "another string")
40 | })
41 | }
42 |
43 | func assertLogLine(t *testing.T, line string, expected string) {
44 | logLine := testLogLine{}
45 |
46 | fmt.Println(line)
47 | err := json.Unmarshal([]byte(line), &logLine)
48 | require.NoError(t, err)
49 | require.Equal(t, "helm", logLine.Logger)
50 | require.Equal(t, "info", logLine.Level)
51 | require.Contains(t, logLine.Message, expected)
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/values/validation/defaulting.go:
--------------------------------------------------------------------------------
1 | package validation
2 |
3 | import (
4 | "github.com/go-openapi/spec"
5 | "k8s.io/apimachinery/pkg/runtime"
6 |
7 | "github.com/flant/addon-operator/pkg/utils"
8 | )
9 |
10 | // ApplyDefaults traverses an object and apply default values from OpenAPI schema.
11 | // It returns true if obj is changed.
12 | //
13 | // See https://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/algorithm.go
14 | //
15 | // Note: check only Properties for object type and List validation for array type.
16 | func ApplyDefaults(obj interface{}, s *spec.Schema) bool {
17 | if s == nil {
18 | return false
19 | }
20 |
21 | res := false
22 |
23 | // Support utils.Values
24 | switch vals := obj.(type) {
25 | case utils.Values:
26 | obj = map[string]interface{}(vals)
27 | case *utils.Values:
28 | // rare case
29 | obj = map[string]interface{}(*vals)
30 | }
31 |
32 | switch obj := obj.(type) {
33 | case map[string]interface{}:
34 | // Apply defaults to properties
35 | for k, prop := range s.Properties {
36 | if prop.Default == nil {
37 | continue
38 | }
39 | if _, found := obj[k]; !found {
40 | obj[k] = runtime.DeepCopyJSONValue(prop.Default)
41 | res = true
42 | }
43 | }
44 | // Apply to deeper levels.
45 | for k, v := range obj {
46 | if prop, found := s.Properties[k]; found {
47 | deepRes := ApplyDefaults(v, &prop)
48 | res = res || deepRes
49 | }
50 | }
51 | case []interface{}:
52 | // If the 'items' section is not specified in the schema, addon-operator will panic here.
53 | // The schema itself should be validated earlier before applying defaults,
54 | // but having a panic in runtime is much bigger problem.
55 | if s.Items == nil {
56 | return res
57 | }
58 |
59 | // Only List validation is supported.
60 | // See https://json-schema.org/understanding-json-schema/reference/array.html#list-validation
61 | for _, v := range obj {
62 | deepRes := ApplyDefaults(v, s.Items.Schema)
63 | res = res || deepRes
64 | }
65 | default:
66 | // scalars, no action
67 | }
68 |
69 | return res
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/values/validation/extend_test.go:
--------------------------------------------------------------------------------
1 | package validation_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/gomega"
7 |
8 | "github.com/flant/addon-operator/pkg/module_manager/models/modules"
9 | "github.com/flant/addon-operator/pkg/utils"
10 | )
11 |
12 | func Test_Validate_Extended(t *testing.T) {
13 | g := NewWithT(t)
14 |
15 | var err error
16 |
17 | configValuesYaml := `
18 | type: object
19 | additionalProperties: false
20 | required:
21 | - param1
22 | #minProperties: 2
23 | properties:
24 | param1:
25 | type: string
26 | enum:
27 | - val1
28 | param2:
29 | type: string
30 | `
31 | valuesYaml := `
32 | x-extend:
33 | schema: "config-values.yaml"
34 | type: object
35 | additionalProperties: false
36 | required:
37 | - memParam
38 | #minProperties: 2
39 | properties:
40 | memParam:
41 | type: string
42 | enum:
43 | - val1
44 | `
45 |
46 | // TODO: static values
47 | valuesStorage, err := modules.NewValuesStorage("moduleName", nil, []byte(configValuesYaml), []byte(valuesYaml))
48 | g.Expect(err).ShouldNot(HaveOccurred())
49 |
50 | var moduleValues utils.Values
51 |
52 | moduleValues, err = utils.NewValuesFromBytes([]byte(`
53 | moduleName:
54 | param1: val1
55 | param2: val2
56 | `))
57 | g.Expect(err).ShouldNot(HaveOccurred())
58 |
59 | mErr := valuesStorage.GetSchemaStorage().ValidateValues("moduleName", moduleValues)
60 |
61 | g.Expect(mErr).Should(HaveOccurred())
62 |
63 | moduleValues, err = utils.NewValuesFromBytes([]byte(`
64 | moduleName:
65 | param1: val1
66 | memParam: val1
67 | `))
68 | g.Expect(err).ShouldNot(HaveOccurred())
69 |
70 | mErr = valuesStorage.GetSchemaStorage().ValidateValues("moduleName", moduleValues)
71 |
72 | g.Expect(mErr).ShouldNot(HaveOccurred())
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/values/validation/schema/additional-properties.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import "github.com/go-openapi/spec"
4 |
5 | type AdditionalPropertiesTransformer struct {
6 | Parent *spec.Schema
7 | }
8 |
9 | // Transform sets undefined AdditionalProperties to false recursively.
10 | func (t *AdditionalPropertiesTransformer) Transform(s *spec.Schema) *spec.Schema {
11 | if s == nil {
12 | return nil
13 | }
14 |
15 | if s.AdditionalProperties == nil {
16 | s.AdditionalProperties = &spec.SchemaOrBool{
17 | Allows: false,
18 | }
19 | }
20 |
21 | for k, prop := range s.Properties {
22 | if prop.AdditionalProperties == nil {
23 | prop.AdditionalProperties = &spec.SchemaOrBool{
24 | Allows: false,
25 | }
26 | ts := prop
27 | s.Properties[k] = *t.Transform(&ts)
28 | }
29 | }
30 |
31 | if s.Items != nil {
32 | if s.Items.Schema != nil {
33 | s.Items.Schema = t.Transform(s.Items.Schema)
34 | }
35 | for i, item := range s.Items.Schemas {
36 | ts := item
37 | s.Items.Schemas[i] = *t.Transform(&ts)
38 | }
39 | }
40 |
41 | return s
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/values/validation/schema/copy.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import "github.com/go-openapi/spec"
4 |
5 | type CopyTransformer struct{}
6 |
7 | func (t *CopyTransformer) Transform(s *spec.Schema) *spec.Schema {
8 | tmpBytes, _ := s.MarshalJSON()
9 | res := new(spec.Schema)
10 | _ = res.UnmarshalJSON(tmpBytes)
11 | return res
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/values/validation/schema/required-for-helm.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import (
4 | "github.com/go-openapi/spec"
5 | )
6 |
7 | type RequiredForHelmTransformer struct{}
8 |
9 | const XRequiredForHelm = "x-required-for-helm"
10 |
11 | func (t *RequiredForHelmTransformer) Transform(s *spec.Schema) *spec.Schema {
12 | if s == nil {
13 | return s
14 | }
15 |
16 | s.Required = MergeRequiredFields(s.Extensions, s.Required)
17 |
18 | // Deep transform.
19 | transformRequired(s.Properties)
20 | return s
21 | }
22 |
23 | func transformRequired(props map[string]spec.Schema) {
24 | for k, prop := range props {
25 | prop.Required = MergeRequiredFields(prop.Extensions, prop.Required)
26 | props[k] = prop
27 | transformRequired(props[k].Properties)
28 | }
29 | }
30 |
31 | func MergeArrays(ar1 []string, ar2 []string) []string {
32 | res := make([]string, 0)
33 | m := make(map[string]struct{})
34 | for _, item := range ar1 {
35 | res = append(res, item)
36 | m[item] = struct{}{}
37 | }
38 | for _, item := range ar2 {
39 | if _, ok := m[item]; !ok {
40 | res = append(res, item)
41 | }
42 | }
43 | return res
44 | }
45 |
46 | func MergeRequiredFields(ext spec.Extensions, required []string) []string {
47 | var xReqFields []string
48 | _, hasField := ext[XRequiredForHelm]
49 | if !hasField {
50 | return required
51 | }
52 | field, ok := ext.GetString(XRequiredForHelm)
53 | if ok {
54 | xReqFields = []string{field}
55 | } else {
56 | xReqFields, _ = ext.GetStringSlice(XRequiredForHelm)
57 | }
58 | // Merge x-required with required
59 | return MergeArrays(required, xReqFields)
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/values/validation/schema/transform.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import "github.com/go-openapi/spec"
4 |
5 | type SchemaTransformer interface {
6 | Transform(s *spec.Schema) *spec.Schema
7 | }
8 |
9 | func TransformSchema(s *spec.Schema, transformers ...SchemaTransformer) *spec.Schema {
10 | for _, transformer := range transformers {
11 | s = transformer.Transform(s)
12 | }
13 | return s
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/values/validation/testdata/test-schema-bad.yaml:
--------------------------------------------------------------------------------
1 | type: object
2 | additionalProperties: false
3 | required:
4 | - project
5 | - clusterName
6 | minProperties: 2
7 | properties:
8 | param1:
9 | type: string
10 | enum:
11 | - val1
12 | param2:
13 | type: ssttrriingg
14 |
--------------------------------------------------------------------------------
/pkg/values/validation/testdata/test-schema-ok-project-2.yaml:
--------------------------------------------------------------------------------
1 | # Hide OpenAPI schema inside array to test loading from the $ref-ed fragment.
2 | # Also test local definitions. Note $ref works from the document root, not
3 | # from the fragment root (see test-schema-ok.yaml).
4 | versions:
5 | - version: v1
6 | OpenAPISchema:
7 | definitions:
8 | version:
9 | type: number
10 | description:
11 | type: string
12 | type: object
13 | properties:
14 | project:
15 | type: object
16 | properties: &common_project
17 | name:
18 | type: string
19 | version:
20 | $ref: '#/versions/0/OpenAPISchema/definitions/version'
21 | activeProject:
22 | type: object
23 | properties: *common_project
24 | archive:
25 | type: array
26 | items:
27 | type: object
28 | properties:
29 | <<: *common_project
30 | description:
31 | $ref: '#/versions/0/OpenAPISchema/definitions/description'
32 |
--------------------------------------------------------------------------------
/pkg/values/validation/testdata/test-schema-ok-project.yaml:
--------------------------------------------------------------------------------
1 | type: object
2 | properties:
3 | project:
4 | type: object
5 | properties: &common_project
6 | name:
7 | type: string
8 | version:
9 | type: number
10 | activeProject:
11 | type: object
12 | properties: *common_project
13 | archive:
14 | type: array
15 | items:
16 | type: object
17 | properties:
18 | <<: *common_project
19 | description:
20 | type: string
21 |
--------------------------------------------------------------------------------
/pkg/values/validation/testdata/test-schema-ok.yaml:
--------------------------------------------------------------------------------
1 | type: object
2 | additionalProperties: false
3 | required:
4 | - clusterName
5 | minProperties: 2
6 | properties:
7 | clusterName:
8 | type: string
9 | clusterHostname:
10 | type: string
11 | project:
12 | type: object
13 | properties: &common_project
14 | name:
15 | type: string
16 | version:
17 | type: number
18 | activeProject:
19 | type: object
20 | properties: *common_project
21 | archive:
22 | type: array
23 | items:
24 | type: object
25 | properties:
26 | <<: *common_project
27 | description:
28 | type: string
29 | externalProjects:
30 | $ref: 'testdata/test-schema-ok-project.yaml'
31 | fragmentedProjects:
32 | $ref: 'testdata/test-schema-ok-project-2.yaml#/versions/0/OpenAPISchema'
33 |
--------------------------------------------------------------------------------
/sdk/registry_test.go:
--------------------------------------------------------------------------------
1 | package sdk
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 |
9 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
10 | "github.com/flant/addon-operator/pkg/module_manager/models/hooks/kind"
11 | )
12 |
13 | func TestRegister(t *testing.T) {
14 | t.Run("Hook with OnStartup and Kubernetes bindings should panic", func(t *testing.T) {
15 | hook := kind.NewGoHook(
16 | &gohook.HookConfig{
17 | OnStartup: &gohook.OrderedConfig{Order: 1},
18 | Kubernetes: []gohook.KubernetesConfig{
19 | {
20 | Name: "test",
21 | ApiVersion: "v1",
22 | Kind: "Pod",
23 | FilterFunc: nil,
24 | },
25 | },
26 | },
27 | nil,
28 | )
29 |
30 | defer func() {
31 | r := recover()
32 | require.NotEmpty(t, r)
33 | assert.Equal(t, bindingsPanicMsg, r)
34 | }()
35 | Registry().Add(hook)
36 | })
37 |
38 | t.Run("Hook with OnStartup should not panic", func(t *testing.T) {
39 | hook := kind.NewGoHook(
40 | &gohook.HookConfig{
41 | OnStartup: &gohook.OrderedConfig{Order: 1},
42 | },
43 | nil,
44 | )
45 |
46 | defer func() {
47 | r := recover()
48 | assert.NotEqual(t, bindingsPanicMsg, r)
49 | }()
50 | Registry().Add(hook)
51 | })
52 |
53 | t.Run("Hook with Kubernetes binding should not panic", func(t *testing.T) {
54 | hook := kind.NewGoHook(
55 | &gohook.HookConfig{
56 | Kubernetes: []gohook.KubernetesConfig{
57 | {
58 | Name: "test",
59 | ApiVersion: "v1",
60 | Kind: "Pod",
61 | FilterFunc: nil,
62 | },
63 | },
64 | },
65 | nil,
66 | )
67 |
68 | defer func() {
69 | r := recover()
70 | assert.NotEqual(t, bindingsPanicMsg, r)
71 | }()
72 | Registry().Add(hook)
73 | })
74 | }
75 |
--------------------------------------------------------------------------------
/sdk/sdk.go:
--------------------------------------------------------------------------------
1 | package sdk
2 |
3 | import (
4 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
5 | "k8s.io/apimachinery/pkg/runtime"
6 | )
7 |
8 | func ToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
9 | content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
10 | return &unstructured.Unstructured{Object: content}, err
11 | }
12 |
13 | func FromUnstructured(unstructuredObj *unstructured.Unstructured, obj interface{}) error {
14 | return runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.UnstructuredContent(), obj)
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/test/sdk_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/gomega"
7 |
8 | "github.com/flant/addon-operator/pkg/module_manager/models/hooks/kind"
9 | "github.com/flant/addon-operator/sdk"
10 | _ "github.com/flant/addon-operator/sdk/test/simple_operator/global-hooks"
11 | _ "github.com/flant/addon-operator/sdk/test/simple_operator/modules/001-module-one/hooks"
12 | _ "github.com/flant/addon-operator/sdk/test/simple_operator/modules/002-module-two/hooks/level1/sublevel"
13 | )
14 |
15 | func Test_HookMetadata_from_runtime(t *testing.T) {
16 | g := NewWithT(t)
17 |
18 | hookList := sdk.Registry().Hooks()
19 | g.Expect(len(hookList)).Should(Equal(3))
20 |
21 | globalHooks := sdk.Registry().GetGlobalHooks()
22 | g.Expect(len(globalHooks)).Should(Equal(1))
23 |
24 | hooks := map[string]*kind.GoHook{}
25 |
26 | for _, h := range hookList {
27 | hooks[h.GetName()] = h
28 | }
29 |
30 | hm, ok := hooks["go-hook.go"]
31 | g.Expect(ok).To(BeTrue(), "global go-hook.go should be registered")
32 | g.Expect(hm.GetPath()).To(Equal("/global-hooks/go-hook.go"))
33 |
34 | hm, ok = hooks["001-module-one/hooks/module-one-hook.go"]
35 | g.Expect(ok).To(BeTrue(), "module-one-hook.go should be registered")
36 | g.Expect(hm.GetPath()).To(Equal("/modules/001-module-one/hooks/module-one-hook.go"))
37 |
38 | hm, ok = hooks["002-module-two/hooks/level1/sublevel/sub-sub-hook.go"]
39 | g.Expect(ok).To(BeTrue(), "sub-sub-hook.go should be registered")
40 | g.Expect(hm.GetPath()).To(Equal("/modules/002-module-two/hooks/level1/sublevel/sub-sub-hook.go"))
41 | }
42 |
--------------------------------------------------------------------------------
/sdk/test/simple_operator/global-hooks/go-hook.go:
--------------------------------------------------------------------------------
1 | package global_hooks
2 |
3 | import (
4 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
5 | "github.com/flant/addon-operator/sdk"
6 | )
7 |
8 | func init() {
9 | // TODO: remove global logger?
10 | sdk.RegisterFunc(&gohook.HookConfig{}, main)
11 | }
12 |
13 | func main(_ *gohook.HookInput) error {
14 | return nil
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/test/simple_operator/modules/001-module-one/hooks/module-one-hook.go:
--------------------------------------------------------------------------------
1 | package hooks
2 |
3 | import (
4 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
5 | "github.com/flant/addon-operator/sdk"
6 | )
7 |
8 | // TODO: remove global logger?
9 | var _ = sdk.RegisterFunc(&gohook.HookConfig{}, main)
10 |
11 | func main(_ *gohook.HookInput) error {
12 | return nil
13 | }
14 |
--------------------------------------------------------------------------------
/sdk/test/simple_operator/modules/002-module-two/hooks/level1/sublevel/sub-sub-hook.go:
--------------------------------------------------------------------------------
1 | package sublevel
2 |
3 | import (
4 | gohook "github.com/flant/addon-operator/pkg/module_manager/go_hook"
5 | "github.com/flant/addon-operator/sdk"
6 | )
7 |
8 | // TODO: remove global logger?
9 | var _ = sdk.RegisterFunc(&gohook.HookConfig{}, main)
10 |
11 | func main(_ *gohook.HookInput) error {
12 | return nil
13 | }
14 |
--------------------------------------------------------------------------------