├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── codecov.yml └── workflows │ ├── build.yaml │ ├── codeql-analysis.yml │ ├── pr-linter.yml │ ├── release.yaml │ └── validation.yml ├── .gitignore ├── .gitlab-ci.yml ├── .golangci.toml ├── .goreleaser-for-darwin.yaml ├── .goreleaser-for-linux.yaml ├── .goreleaser-for-windows.yaml ├── Dockerfile ├── LICENSE ├── LICENSE-3rdparty.csv ├── Makefile ├── NOTICE ├── PROJECT ├── README.md ├── RELEASING.md ├── api ├── go.mod ├── go.sum └── v1alpha1 │ ├── const.go │ ├── extendeddaemonset_default.go │ ├── extendeddaemonset_types.go │ ├── extendeddaemonset_validate.go │ ├── extendeddaemonset_validate_test.go │ ├── extendeddaemonsetreplicaset_types.go │ ├── extendeddaemonsetsetting_types.go │ ├── groupversion_info.go │ ├── test │ └── new.go │ ├── utils.go │ ├── zz_generated.deepcopy.go │ └── zz_generated.openapi.go ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── datadoghq.com_extendeddaemonsetreplicasets.yaml │ ├── datadoghq.com_extendeddaemonsets.yaml │ ├── datadoghq.com_extendeddaemonsetsettings.yaml │ └── extendeddaemonset.clusterserviceversion.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── chart └── app-example │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── extendeddaemonset.yaml │ ├── serviceaccount.yaml │ └── tests │ │ ├── role.yaml │ │ ├── service-account.yaml │ │ └── test-update.yaml │ └── values.yaml ├── check-eds.Dockerfile ├── cmd ├── check-eds │ ├── main.go │ ├── root │ │ └── root.go │ └── upgrade │ │ └── upgrade.go └── kubectl-eds │ └── main.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── v1 │ │ │ ├── datadoghq.com_extendeddaemonsetreplicasets.yaml │ │ │ ├── datadoghq.com_extendeddaemonsets.yaml │ │ │ └── datadoghq.com_extendeddaemonsetsettings.yaml │ │ └── v1beta1 │ │ │ ├── datadoghq.com_extendeddaemonsetreplicasets.yaml │ │ │ ├── datadoghq.com_extendeddaemonsets.yaml │ │ │ └── datadoghq.com_extendeddaemonsetsettings.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_extendeddaemonsetreplicasets.yaml │ │ ├── cainjection_in_extendeddaemonsets.yaml │ │ ├── cainjection_in_extendeddaemonsetsettings.yaml │ │ ├── webhook_in_extendeddaemonsetreplicasets.yaml │ │ ├── webhook_in_extendeddaemonsets.yaml │ │ └── webhook_in_extendeddaemonsetsettings.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── extendeddaemonset.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── extendeddaemonset_editor_role.yaml │ ├── extendeddaemonset_viewer_role.yaml │ ├── extendeddaemonsetreplicaset_editor_role.yaml │ ├── extendeddaemonsetreplicaset_viewer_role.yaml │ ├── extendeddaemonsetsetting_editor_role.yaml │ ├── extendeddaemonsetsetting_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml ├── samples │ ├── eds_v1alpha1_basic.yaml │ ├── eds_v1alpha1_nodeexclusion.yaml │ ├── edssetting_v1alpha1_basic.yaml │ ├── ers_v1alpha1.yaml │ └── kustomization.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── controllers ├── doc.go ├── extendeddaemonset │ ├── conditions │ │ └── update.go │ ├── controller.go │ ├── controller_test.go │ ├── doc.go │ ├── metrics.go │ ├── utils.go │ └── utils_test.go ├── extendeddaemonset_controller.go ├── extendeddaemonset_e2e_test.go ├── extendeddaemonset_test.go ├── extendeddaemonsetreplicaset │ ├── conditions │ │ └── update.go │ ├── controller.go │ ├── controller_test.go │ ├── doc.go │ ├── filters.go │ ├── filters_test.go │ ├── metrics.go │ ├── scheduler │ │ ├── predicates.go │ │ └── predicates_test.go │ ├── strategy │ │ ├── canary.go │ │ ├── canary_test.go │ │ ├── doc.go │ │ ├── limits │ │ │ ├── limits.go │ │ │ └── limits_test.go │ │ ├── rollingupdate.go │ │ ├── rollingupdate_test.go │ │ ├── type.go │ │ ├── unknown.go │ │ ├── utils.go │ │ └── utils_test.go │ └── utils.go ├── extendeddaemonsetreplicaset_controller.go ├── extendeddaemonsetsetting │ ├── controller.go │ ├── controller_test.go │ ├── doc.go │ └── utils.go ├── extendeddaemonsetsetting_controller.go ├── fipsonly.go ├── podtemplate │ ├── controller.go │ ├── controller_test.go │ └── doc.go ├── podtemplate_controller.go ├── setup.go ├── suite_e2e_test.go ├── suite_helpers_test.go ├── suite_test.go ├── suite_unit_test.go └── testutils │ ├── doc.go │ ├── new.go │ └── node.go ├── docs └── canary-worflows.md ├── examples ├── failure-cases │ ├── foo-eds_config_error.yaml │ ├── foo-eds_image_pull_error.yaml │ ├── foo-eds_restarts.yaml │ └── foo-eds_slow_start_missing_volume.yaml ├── foo-eds_exclude-node.yaml ├── foo-eds_v1.yaml ├── foo-eds_v2.yaml └── kind-cluster-configuration.yaml ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── hack ├── boilerplate.go.txt ├── install-golangci-lint.sh ├── install-kubebuilder-tools.sh ├── install-kubebuilder.sh ├── install-kustomize.sh ├── install-operator-sdk.sh ├── install-wwhrd.sh ├── install-yq.sh ├── license.sh ├── os-env.sh ├── patch-crd-metadata.yaml ├── patch-crd-v1-protocol-kube1.18.yaml ├── patch-crd-v1beta1-protocol-kube1.18.yaml ├── patch-crds.sh ├── patch-gitlab.sh ├── release │ ├── cluster-service-version-patch.yaml │ ├── eds-plugin-tmpl.yaml │ └── generate-plugin-manifest.sh └── verify-license.sh ├── main.go ├── pkg ├── config │ └── config.go ├── controller │ ├── debug │ │ └── register.go │ ├── metrics │ │ ├── custom_metrics.go │ │ ├── doc.go │ │ ├── interface.go │ │ ├── ksmetrics.go │ │ ├── leader.go │ │ └── register.go │ ├── test │ │ └── new.go │ └── utils │ │ ├── affinity │ │ ├── affinity.go │ │ └── affinity_test.go │ │ ├── comparison │ │ ├── comparison.go │ │ └── comparison_test.go │ │ ├── doc.go │ │ ├── enqueue │ │ └── enqueue.go │ │ ├── labels.go │ │ ├── labels_test.go │ │ ├── labelselector.go │ │ ├── labelselector_test.go │ │ ├── list.go │ │ ├── list_test.go │ │ ├── pod │ │ ├── const.go │ │ ├── create.go │ │ ├── create_test.go │ │ ├── doc.go │ │ ├── pod.go │ │ └── pod_test.go │ │ ├── result.go │ │ └── result_test.go ├── plugin │ ├── canary │ │ ├── canary.go │ │ ├── doc.go │ │ ├── fail.go │ │ ├── pause.go │ │ ├── pods.go │ │ └── validate.go │ ├── common │ │ ├── client.go │ │ ├── doc.go │ │ ├── pods.go │ │ ├── table.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── diff │ │ ├── diff.go │ │ └── doc.go │ ├── extendeddaemonset.go │ ├── freeze │ │ ├── doc.go │ │ └── rollout.go │ ├── get │ │ ├── common.go │ │ ├── doc.go │ │ ├── get.go │ │ └── geters.go │ ├── pause │ │ ├── doc.go │ │ └── rollingupdate.go │ └── pods │ │ └── pods.go └── version │ └── version.go ├── repository.datadog.yml ├── service.datadog.yaml ├── test ├── README.md └── cluster-kind.yaml └── tools.go /.dockerignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /chart 3 | /vendor 4 | /hack 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ for syntax 2 | # Rules are matched bottom-to-top, so one team can own subdirectories 3 | # and another the rest of the directory. 4 | 5 | # All your base 6 | * @Datadog/container-ecosystems 7 | 8 | # Documentation 9 | README.md @DataDog/documentation @Datadog/container-ecosystems 10 | /docs/ @DataDog/documentation @Datadog/container-ecosystems 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | 24 | **Environment (please complete the following information):** 25 | - Kubernetes version: [e.g. 1.14] 26 | - Kubernetes distribution [e.g. gke, openshift,...] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do? 2 | 3 | A brief description of the change being made with this pull request. 4 | 5 | ### Motivation 6 | 7 | What inspired you to submit this pull request? 8 | 9 | ### Additional Notes 10 | 11 | Anything else we should know when reviewing? 12 | 13 | ### Describe your test plan 14 | 15 | Write there any instructions and details you may have to test your PR. 16 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "65...100" 9 | 10 | status: 11 | project: yes 12 | patch: 13 | default: 14 | enabled: yes # must be yes|true to enable this status 15 | target: 80% # specify the target "X%" coverage to hit 16 | branches: null # -> see "branch patterns" below 17 | threshold: null # allowed to drop X% and still result in a "success" commit status 18 | if_no_uploads: error # will post commit status of "error" if no coverage reports we uploaded 19 | # options: success, error, failure 20 | if_not_found: success 21 | if_ci_failed: success 22 | changes: no 23 | 24 | parsers: 25 | gcov: 26 | branch_detection: 27 | conditional: yes 28 | loop: yes 29 | method: no 30 | macro: no 31 | 32 | comment: 33 | layout: "reach, diff, flags, files, footer" 34 | behavior: default 35 | require_changes: no 36 | 37 | ignore: 38 | - "test/**/*" # ignore folders and all its contents 39 | - "**/zz_generated.*.go" # api generated files -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | env: 4 | GO_VERSION: 1.22 5 | 6 | on: 7 | push: 8 | 9 | # Permission forced by repo-level setting; only elevate on job-level 10 | permissions: 11 | contents: read 12 | # packages: read 13 | 14 | jobs: 15 | build-linux-binary: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | # https://github.com/marketplace/actions/goreleaser-action 19 | contents: write 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | - name: gcc install 26 | run: sudo apt-get update; sudo apt install gcc-aarch64-linux-gnu 27 | - name: Get tag 28 | uses: little-core-labs/get-git-tag@v3.0.2 29 | id: tag 30 | - name: Set up Go 31 | uses: actions/setup-go@v3 32 | with: 33 | go-version: ${{ env.GO_VERSION }} 34 | - name: Run GoReleaser 35 | uses: goreleaser/goreleaser-action@v3 36 | with: 37 | version: "2.4.1" 38 | args: build --skip=validate --config .goreleaser-for-linux.yaml 39 | build-darwin-binary: 40 | runs-on: macos-latest 41 | permissions: 42 | # https://github.com/marketplace/actions/goreleaser-action 43 | contents: write 44 | steps: 45 | - uses: actions/checkout@v3 46 | with: 47 | fetch-depth: 0 48 | - name: Get tag 49 | uses: little-core-labs/get-git-tag@v3.0.2 50 | id: tag 51 | - name: Set up Go 52 | uses: actions/setup-go@v3 53 | with: 54 | go-version: ${{ env.GO_VERSION }} 55 | - name: Build 56 | uses: goreleaser/goreleaser-action@v3 57 | with: 58 | args: build --skip=validate --config .goreleaser-for-darwin.yaml 59 | build-windows-binary: 60 | runs-on: ubuntu-latest 61 | permissions: 62 | # https://github.com/marketplace/actions/goreleaser-action 63 | contents: write 64 | steps: 65 | - uses: actions/checkout@v3 66 | with: 67 | fetch-depth: 0 68 | - name: Get tag 69 | uses: little-core-labs/get-git-tag@v3.0.2 70 | id: tag 71 | - name: Set up Go 72 | uses: actions/setup-go@v3 73 | with: 74 | go-version: ${{ env.GO_VERSION }} 75 | - name: Build 76 | uses: goreleaser/goreleaser-action@v3 77 | with: 78 | args: build --skip=validate --config .goreleaser-for-windows.yaml 79 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | env: 4 | GO_VERSION: 1.22 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | # The branches below must be a subset of the branches above 11 | branches: [ main ] 12 | 13 | permissions: {} 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ 'go' ] 28 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 29 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v3 34 | 35 | # Initializes the CodeQL tools for scanning. 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@v2 38 | with: 39 | languages: ${{ matrix.language }} 40 | # If you wish to specify custom queries, you can do so here or in a config file. 41 | # By default, queries listed here will override any specified in a config file. 42 | # Prefix the list here with "+" to use these queries and those in the config file. 43 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 44 | 45 | - name: Set up Go 46 | uses: actions/setup-go@v1 47 | with: 48 | go-version: ${{ env.GO_VERSION }} 49 | id: go 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v2 54 | 55 | - name: Perform CodeQL Analysis 56 | uses: github/codeql-action/analyze@v2 57 | -------------------------------------------------------------------------------- /.github/workflows/pr-linter.yml: -------------------------------------------------------------------------------- 1 | name: pull request linter 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | 6 | permissions: {} 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | pull-requests: write 14 | steps: 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v1 17 | - name: Verify Pull Request Labels 18 | uses: jesusvasquez333/verify-pr-label-action@v1.4.0 19 | with: 20 | github-token: '${{ secrets.GITHUB_TOKEN }}' 21 | valid-labels: 'bug, enhancement, documentation, tooling' 22 | pull-request-number: '${{ github.event.pull_request.number }}' 23 | disable-reviews: true 24 | check-milestone: 25 | name: Check Milestone 26 | runs-on: ubuntu-latest 27 | permissions: 28 | pull-requests: read 29 | steps: 30 | - if: github.event.pull_request.milestone == null && !contains(toJson(github.event.pull_request.labels.*.name), 'qa/skip-qa') 31 | run: echo "::error::Missing milestone or \`qa/skip-qa\` label" && exit 1 32 | -------------------------------------------------------------------------------- /.github/workflows/validation.yml: -------------------------------------------------------------------------------- 1 | name: validation 2 | on: [push, pull_request] 3 | 4 | # Permission forced by repo-level setting; only elevate on job-level 5 | permissions: 6 | contents: read 7 | # packages: read 8 | 9 | env: 10 | GO111MODULE: "on" 11 | PROJECTNAME: "extendeddaemonset" 12 | GO_VERSION: 1.22 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Set up Go 18 | uses: actions/setup-go@v1 19 | with: 20 | go-version: ${{ env.GO_VERSION }} 21 | id: go 22 | - name: Install required packages 23 | uses: mstksg/get-package@v1 24 | with: 25 | apt-get: mercurial jq build-essential 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v1 28 | - name: Install tools 29 | run: | 30 | make install-tools 31 | - name: Run build 32 | run: | 33 | make manager 34 | - name: Run unit/control plane tests (fake cluster) 35 | run: | 36 | make test 37 | - uses: codecov/codecov-action@v2 38 | with: 39 | token: ${{ secrets.CODECOV_TOKEN }} 40 | file: cover.out 41 | flags: unittests 42 | e2e: 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | k8s: 47 | - v1.14.10 48 | - v1.16.9 49 | - v1.18.4 50 | - v1.21.1 51 | steps: 52 | - name: Set up Go 53 | uses: actions/setup-go@v1 54 | with: 55 | go-version: ${{ env.GO_VERSION }} 56 | id: go 57 | - name: Install required packages 58 | uses: mstksg/get-package@v1 59 | with: 60 | apt-get: mercurial jq build-essential 61 | - name: Check out code into the Go module directory 62 | uses: actions/checkout@v1 63 | - name: Install tools 64 | run: | 65 | make install-tools 66 | - name: Setup kind for e2e - kube ${{ matrix.k8s }} 67 | uses: helm/kind-action@v1.2.0 68 | with: 69 | config: test/cluster-kind.yaml 70 | cluster_name: eds-e2e 71 | wait: 600s 72 | - name: Run e2e tests - kube ${{ matrix.k8s }} 73 | run: | 74 | export PATH=$PATH:$(pwd)/bin 75 | kubectl cluster-info --context kind-eds-e2e 76 | kubectl get pods -n kube-system 77 | make e2e 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Build Files 2 | build/_output 3 | build/_test 4 | /controller 5 | /kubectl-eds 6 | /test/fake-process/fake-process 7 | test/e2e/global-manifest.yaml 8 | deploy/test/empty.yaml 9 | # Code coverage report 10 | /.cover 11 | /bin 12 | # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 13 | ### Emacs ### 14 | # -*- mode: gitignore; -*- 15 | .DS_Store 16 | *~ 17 | \#*\# 18 | /.emacs.desktop 19 | /.emacs.desktop.lock 20 | *.elc 21 | auto-save-list 22 | tramp 23 | .\#* 24 | # Org-mode 25 | .org-id-locations 26 | *_archive 27 | # flymake-mode 28 | *_flymake.* 29 | # eshell files 30 | /eshell/history 31 | /eshell/lastdir 32 | # elpa packages 33 | /elpa/ 34 | # reftex files 35 | *.rel 36 | # AUCTeX auto folder 37 | /auto/ 38 | # cask packages 39 | .cask/ 40 | dist/ 41 | # Flycheck 42 | flycheck_*.el 43 | # server auth directory 44 | /server/ 45 | # projectiles files 46 | .projectile 47 | projectile-bookmarks.eld 48 | # directory configuration 49 | .dir-locals.el 50 | # saveplace 51 | places 52 | # url cache 53 | url/cache/ 54 | # cedet 55 | ede-projects.el 56 | # smex 57 | smex-items 58 | # company-statistics 59 | company-statistics-cache.el 60 | # anaconda-mode 61 | anaconda-mode/ 62 | ### Go ### 63 | # Binaries for programs and plugins 64 | *.exe 65 | *.exe~ 66 | *.dll 67 | *.so 68 | *.dylib 69 | # Test binary, build with 'go test -c' 70 | *.test 71 | # Output of the go coverage tool, specifically when used with LiteIDE 72 | *.out 73 | ### Vim ### 74 | # swap 75 | .sw[a-p] 76 | .*.sw[a-p] 77 | # session 78 | Session.vim 79 | # temporary 80 | .netrwhist 81 | # auto-generated tag files 82 | tags 83 | ### VisualStudioCode ### 84 | .vscode/* 85 | .history 86 | # End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 87 | /vendor 88 | # GoLand 89 | .idea 90 | -------------------------------------------------------------------------------- /.golangci.toml: -------------------------------------------------------------------------------- 1 | [run] 2 | deadline = "5m" 3 | 4 | [linters-settings] 5 | 6 | [linters-settings.govet] 7 | check-shadowing = true 8 | 9 | [linters-settings.gocyclo] 10 | min-complexity = 12.0 11 | 12 | [linters-settings.maligned] 13 | suggest-new = true 14 | 15 | [linters-settings.goconst] 16 | min-len = 3.0 17 | min-occurrences = 3.0 18 | 19 | [linters-settings.misspell] 20 | locale = "US" 21 | 22 | [linters-settings.gci] 23 | local-prefixes = "github.com/DataDog/extendeddaemonset" 24 | 25 | [linters-settings.godot] 26 | exclude = [ 27 | ' \+', 28 | ] 29 | 30 | [linters-settings.errcheck] 31 | exclude-functions = [ 32 | "fmt.Fprintf" 33 | ] 34 | 35 | [linters] 36 | enable-all = true 37 | disable = [ 38 | "exhaustruct", 39 | "loggercheck", 40 | "nonamedreturns", 41 | "revive", 42 | "lll", 43 | "gosec", 44 | "dupl", 45 | "gocyclo", 46 | "gochecknoinits", 47 | "gochecknoglobals", 48 | "funlen", 49 | "gocognit", 50 | "cyclop", 51 | "gomnd", 52 | "wrapcheck", 53 | "forcetypeassert", 54 | "err113", 55 | "testpackage", 56 | "wastedassign", 57 | "thelper", 58 | "paralleltest", 59 | "nestif", 60 | "exhaustive", 61 | "gci", 62 | "wsl", 63 | "godox", 64 | "godot", 65 | "nlreturn", 66 | "varnamelen", 67 | "tagliatelle", 68 | "containedctx", 69 | "contextcheck", 70 | "nilnil", 71 | "ireturn", 72 | "maintidx", 73 | "gocritic", 74 | "errchkjson", 75 | "gofumpt", 76 | "unparam", 77 | "depguard", 78 | "mnd", 79 | ] 80 | 81 | [issues] 82 | exclude-use-default = false 83 | max-per-linter = 0 84 | max-same-issues = 0 85 | exclude = [] -------------------------------------------------------------------------------- /.goreleaser-for-darwin.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | - go generate ./... 5 | builds: 6 | - id: kubectl-eds 7 | goos: 8 | - darwin 9 | goarch: 10 | - amd64 11 | - arm64 12 | env: 13 | - CGO_ENABLED=1 14 | main: ./cmd/kubectl-eds/main.go 15 | ldflags: -w -X ${BUILDINFOPKG}.Commit=${GIT_COMMIT} -X ${BUILDINFOPKG}.Version=${VERSION} -X ${BUILDINFOPKG}.BuildTime=${DATE} -s 16 | binary: kubectl-eds 17 | archives: 18 | - id: kubectl-eds 19 | builds: 20 | - kubectl-eds 21 | name_template: "kubectl-eds_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 22 | wrap_in_directory: false 23 | format: zip 24 | files: 25 | - LICENSE 26 | checksum: 27 | name_template: "checksums.txt" 28 | snapshot: 29 | name_template: "{{ .Tag }}-next" 30 | changelog: 31 | sort: asc 32 | filters: 33 | exclude: 34 | - '^docs:' 35 | - '^test:' 36 | -------------------------------------------------------------------------------- /.goreleaser-for-linux.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | - go generate ./... 5 | builds: 6 | - id: kubectl-eds 7 | goos: 8 | - linux 9 | goarch: 10 | - amd64 11 | - arm64 12 | env: 13 | - CGO_ENABLED=1 14 | overrides: 15 | - goos: linux 16 | goarch: arm64 17 | env: 18 | - CC=aarch64-linux-gnu-gcc 19 | main: ./cmd/kubectl-eds/main.go 20 | ldflags: -w -X ${BUILDINFOPKG}.Commit=${GIT_COMMIT} -X ${BUILDINFOPKG}.Version=${VERSION} -X ${BUILDINFOPKG}.BuildTime=${DATE} -s 21 | binary: kubectl-eds 22 | archives: 23 | - id: kubectl-eds 24 | builds: 25 | - kubectl-eds 26 | name_template: "kubectl-eds_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 27 | wrap_in_directory: false 28 | format: zip 29 | files: 30 | - LICENSE 31 | checksum: 32 | name_template: "checksums.txt" 33 | snapshot: 34 | name_template: "{{ .Tag }}-next" 35 | changelog: 36 | sort: asc 37 | filters: 38 | exclude: 39 | - '^docs:' 40 | - '^test:' 41 | -------------------------------------------------------------------------------- /.goreleaser-for-windows.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | - go generate ./... 5 | builds: 6 | - id: kubectl-eds 7 | goos: 8 | - windows 9 | goarch: 10 | - amd64 11 | - arm64 12 | env: 13 | - CGO_ENABLED=0 14 | main: ./cmd/kubectl-eds/main.go 15 | ldflags: -w -X ${BUILDINFOPKG}.Commit=${GIT_COMMIT} -X ${BUILDINFOPKG}.Version=${VERSION} -X ${BUILDINFOPKG}.BuildTime=${DATE} -s 16 | binary: kubectl-eds 17 | archives: 18 | - id: kubectl-eds 19 | builds: 20 | - kubectl-eds 21 | name_template: "kubectl-eds_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 22 | wrap_in_directory: false 23 | format: zip 24 | files: 25 | - LICENSE 26 | checksum: 27 | name_template: 'checksums.txt' 28 | snapshot: 29 | name_template: "{{ .Tag }}-next" 30 | changelog: 31 | sort: asc 32 | filters: 33 | exclude: 34 | - '^docs:' 35 | - '^test:' 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG FIPS_ENABLED=false 2 | 3 | # Build the manager binary 4 | FROM golang:1.22 AS builder 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | 11 | COPY api/go.mod api/go.mod 12 | COPY api/go.sum api/go.sum 13 | 14 | COPY go.work go.work 15 | COPY go.work.sum go.work.sum 16 | # cache deps before building and copying source so that we don't need to re-download as much 17 | # and so that source changes don't invalidate our downloaded layer 18 | RUN go mod download 19 | WORKDIR /workspace/api 20 | RUN go mod download 21 | WORKDIR /workspace 22 | 23 | # Copy the go source 24 | COPY main.go main.go 25 | COPY api/ api/ 26 | COPY controllers/ controllers/ 27 | COPY pkg/ pkg/ 28 | 29 | # Build 30 | ARG LDFLAGS 31 | ARG GOARCH 32 | ARG FIPS_ENABLED 33 | RUN echo "FIPS_ENABLED is: $FIPS_ENABLED" 34 | RUN if [ "$FIPS_ENABLED" = "true" ]; then \ 35 | CGO_ENABLED=1 GOEXPERIMENT=boringcrypto GOOS=linux GOARCH=${GOARCH} GO111MODULE=on go build -tags fips -a -ldflags "${LDFLAGS}" -o manager main.go; \ 36 | else \ 37 | CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} GO111MODULE=on go build -a -ldflags "${LDFLAGS}" -o manager main.go; \ 38 | fi 39 | 40 | 41 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest 42 | WORKDIR / 43 | COPY --from=builder /workspace/manager . 44 | USER 1001 45 | 46 | ENTRYPOINT ["/manager"] 47 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Datadog extendeddaemonset 2 | Copyright 2016-2020 Datadog, Inc. 3 | 4 | This product includes software developed at Datadog (https://www.datadoghq.com/). -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: com 2 | layout: go.kubebuilder.io/v2 3 | projectName: extendeddaemonset 4 | repo: github.com/DataDog/extendeddaemonset 5 | resources: 6 | - controller: true 7 | domain: com 8 | group: datadoghq 9 | kind: ExtendedDaemonSet 10 | path: github.com/DataDog/extendeddaemonset/api/v1alpha1 11 | version: v1alpha1 12 | - controller: true 13 | domain: com 14 | group: datadoghq 15 | kind: ExtendedDaemonsetSetting 16 | path: github.com/DataDog/extendeddaemonset/api/v1alpha1 17 | version: v1alpha1 18 | - controller: true 19 | domain: com 20 | group: datadoghq 21 | kind: ExtendedDaemonSetReplicaSet 22 | path: github.com/DataDog/extendeddaemonset/api/v1alpha1 23 | version: v1alpha1 24 | version: "3" 25 | plugins: 26 | go.sdk.operatorframework.io/v2-alpha: {} 27 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Overview 4 | 5 | The release process is based on freezing `main`, merging fixes to a dedicated release branch and releasing release candidates as things progress. Once we have a final version, the release branch is merged into `main` and the freeze is lifted. 6 | 7 | ## Steps 8 | 9 | ### Create the release branch and the first release candidate 10 | 11 | 1. Checkout the repository on the correct branch and changeset (`main`). 12 | 2. Create a new branch: `git checkout -b vX.Y`. 13 | 3. Prepare the first release candidate by running the bundle generation: `make VERSION=x.v.z-rc.1 bundle`. 14 | 4. Commit all the changes generated from the previous command: 15 | 16 | ```console 17 | $ git add . 18 | $ git commit -S -m "release vX.Y.X-rc.1" 19 | ``` 20 | 21 | 5. Add release tag: `git tag vX.Y.Z-rc.1`. 22 | 6. Push the generated commit and tag to the repostory branch. 23 | 24 | ```console 25 | $ git push origin vX.Y 26 | $ git push origin vX.Y.Z-rc.1 27 | ``` 28 | 29 | ### Create a release candidate after a bug fix 30 | 31 | **Note:** The fix must be merged to the release branch `vX.Y`, not `main`. 32 | 33 | 1. Update the release branch `vX.Y` locally by pulling the bug fix merged upstream (`git fetch`, `git pull`) 34 | 2. Prepare the release candidate by running the bundle generation: `make VERSION=x.v.z-rc.w bundle`. 35 | 3. Commit all the changes generated from the previous command: 36 | 37 | ```console 38 | $ git add . 39 | $ git commit -S -m "release vX.Y.X-rc.W" 40 | ``` 41 | 42 | 4. Add release tag: `git tag vX.Y.Z-rc.W`. 43 | 5. Push the generated commit and tag to the repostory branch. 44 | 45 | ```console 46 | $ git push origin vX.Y 47 | $ git push origin vX.Y.Z-rc.W 48 | ``` 49 | 50 | ### Create the final version 51 | 52 | 1. Update the release branch `vX.Y` locally by pulling the bug fix merged upstream (`git fetch`, `git pull`) 53 | 2. Prepare the final release version by running the bundle generation: `make VERSION=x.v.z bundle`. 54 | 3. Commit all the changes generated from the previous command: 55 | 56 | ```console 57 | $ git add . 58 | $ git commit -S -m "release vX.Y.X" 59 | ``` 60 | 61 | 4. Add release tag: `git tag vX.Y.Z`. 62 | 5. Push the generated commit and tag to the repostory branch. 63 | 64 | ```console 65 | $ git push origin vX.Y 66 | $ git push origin vX.Y.Z 67 | ``` 68 | 69 | 6. Merge `vX.Y` into `main` 70 | 71 | -------------------------------------------------------------------------------- /api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DataDog/extendeddaemonset/api 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.4 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | k8s.io/api v0.31.1 10 | k8s.io/apimachinery v0.31.1 11 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 12 | sigs.k8s.io/controller-runtime v0.19.0 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 17 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 18 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 19 | github.com/go-logr/logr v1.4.2 // indirect 20 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 21 | github.com/go-openapi/jsonreference v0.20.2 // indirect 22 | github.com/go-openapi/swag v0.23.0 // indirect 23 | github.com/gogo/protobuf v1.3.2 // indirect 24 | github.com/google/gnostic-models v0.6.9 // indirect 25 | github.com/google/gofuzz v1.2.0 // indirect 26 | github.com/josharian/intern v1.0.0 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/mailru/easyjson v0.7.7 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 32 | github.com/x448/float16 v0.8.4 // indirect 33 | golang.org/x/net v0.28.0 // indirect 34 | golang.org/x/text v0.17.0 // indirect 35 | google.golang.org/protobuf v1.35.1 // indirect 36 | gopkg.in/inf.v0 v0.9.1 // indirect 37 | gopkg.in/yaml.v2 v2.4.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | k8s.io/klog/v2 v2.130.1 // indirect 40 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 41 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 42 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /api/v1alpha1/const.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package v1alpha1 7 | 8 | const ( 9 | // ExtendedDaemonSetNameLabelKey label key use to link a ExtendedDaemonSetReplicaSet to a ExtendedDaemonSet. 10 | ExtendedDaemonSetNameLabelKey = "extendeddaemonset.datadoghq.com/name" 11 | // ExtendedDaemonSetReplicaSetNameLabelKey label key use to link a Pod to a ExtendedDaemonSetReplicaSet. 12 | ExtendedDaemonSetReplicaSetNameLabelKey = "extendeddaemonsetreplicaset.datadoghq.com/name" 13 | // ExtendedDaemonSetSettingNameLabelKey label key use to link a Pod to a ExtendedDaemonSetSetting name. 14 | ExtendedDaemonSetSettingNameLabelKey = "extendeddaemonsetsetting.datadoghq.com/name" 15 | // ExtendedDaemonSetSettingNamespaceLabelKey label key use to link a Pod to a ExtendedDaemonSetSetting namespace. 16 | ExtendedDaemonSetSettingNamespaceLabelKey = "extendeddaemonsetsetting.datadoghq.com/namespace" 17 | // ExtendedDaemonSetReplicaSetCanaryLabelKey label key used to identify canary Pods. 18 | ExtendedDaemonSetReplicaSetCanaryLabelKey = "extendeddaemonsetreplicaset.datadoghq.com/canary" 19 | // ExtendedDaemonSetReplicaSetCanaryLabelValue label value used to identify canary Pods. 20 | ExtendedDaemonSetReplicaSetCanaryLabelValue = "true" 21 | // MD5ExtendedDaemonSetAnnotationKey annotation key use on Pods in order to identify which PodTemplateSpec have been used to generate it. 22 | MD5ExtendedDaemonSetAnnotationKey = "extendeddaemonset.datadoghq.com/templatehash" 23 | // ExtendedDaemonSetCanaryValidAnnotationKey annotation key used on Pods in order to detect if a canary deployment is considered valid. 24 | ExtendedDaemonSetCanaryValidAnnotationKey = "extendeddaemonset.datadoghq.com/canary-valid" 25 | // ExtendedDaemonSetCanaryPausedAnnotationKey annotation key used on ExtendedDaemonset in order to detect if a canary deployment is paused. 26 | ExtendedDaemonSetCanaryPausedAnnotationKey = "extendeddaemonset.datadoghq.com/canary-paused" 27 | // ExtendedDaemonSetCanaryPausedReasonAnnotationKey annotation key used on ExtendedDaemonset to provide a reason that the a canary deployment is paused. 28 | ExtendedDaemonSetCanaryPausedReasonAnnotationKey = "extendeddaemonset.datadoghq.com/canary-paused-reason" 29 | // ExtendedDaemonSetCanaryUnpausedAnnotationKey annotation key used on ExtendedDaemonset in order to detect if a canary deployment is manually unpaused. 30 | ExtendedDaemonSetCanaryUnpausedAnnotationKey = "extendeddaemonset.datadoghq.com/canary-unpaused" 31 | // ExtendedDaemonSetOldDaemonsetAnnotationKey annotation key used on ExtendedDaemonset in order to inform the controller that old Daemonset's pod. 32 | // should be taken into consideration during the initial rolling-update. 33 | ExtendedDaemonSetOldDaemonsetAnnotationKey = "extendeddaemonset.datadoghq.com/old-daemonset" 34 | // ExtendedDaemonSetRessourceNodeAnnotationKey annotation key used on Node to overwrite the resource allocated to a specific container linked to an ExtendedDaemonset 35 | // The value format is: .. . 36 | ExtendedDaemonSetRessourceNodeAnnotationKey = "resources.extendeddaemonset.datadoghq.com/%s.%s.%s" 37 | // MD5NodeExtendedDaemonSetAnnotationKey annotation key use on Pods in order to identify which Node Resources Overwride have been used to generate it. 38 | MD5NodeExtendedDaemonSetAnnotationKey = "extendeddaemonset.datadoghq.com/nodehash" 39 | // ExtendedDaemonSetRollingUpdatePausedAnnotationKey annotation key used on ExtendedDaemonset in order to detect if a rolling update is paused. 40 | ExtendedDaemonSetRollingUpdatePausedAnnotationKey = "extendeddaemonset.datadoghq.com/rolling-update-paused" 41 | // ExtendedDaemonSetRolloutFrozenAnnotationKey annotation key used on ExtendedDaemonset in order to detect if a rollout is frozen. 42 | ExtendedDaemonSetRolloutFrozenAnnotationKey = "extendeddaemonset.datadoghq.com/rollout-frozen" 43 | 44 | // ValueStringTrue is the string value of bool `true`. 45 | ValueStringTrue = "true" 46 | // ValueStringFalse is the string value of bool `false`. 47 | ValueStringFalse = "false" 48 | ) 49 | -------------------------------------------------------------------------------- /api/v1alpha1/extendeddaemonset_validate.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | package v1alpha1 7 | 8 | import "errors" 9 | 10 | var ( 11 | // ErrInvalidAutoFailRestarts is returned in case of a validation failure for maxRestarts in autoFail. 12 | ErrInvalidAutoFailRestarts = errors.New("canary autoFail.maxRestarts must be higher than autoPause.maxRestarts") 13 | // ErrDurationWithManualValidationMode is returned when validationMode=manual and duration is specified. 14 | ErrDurationWithManualValidationMode = errors.New("canary duration does not have effect with validationMode=manual") 15 | // ErrNoRestartsDurationWithManualValidationMode is returned when validationMode=manual and noRestartsDuration is specified. 16 | ErrNoRestartsDurationWithManualValidationMode = errors.New("canary noRestartsDuration does not have effect with validationMode=manual") 17 | // ErrInvalidCanaryTimeout is returned when the autoFail canaryTimeout is invalid. 18 | ErrInvalidCanaryTimeout = errors.New("canary autoFail.canaryTimeout must be greater than the canary duration") 19 | ) 20 | 21 | // ValidateExtendedDaemonSetSpec validates an ExtendedDaemonSet spec 22 | // returns true if yes, else no. 23 | func ValidateExtendedDaemonSetSpec(spec *ExtendedDaemonSetSpec) error { 24 | if canary := spec.Strategy.Canary; canary != nil { 25 | if *canary.AutoFail.Enabled && *canary.AutoPause.Enabled && *canary.AutoFail.MaxRestarts < *canary.AutoPause.MaxRestarts { 26 | return ErrInvalidAutoFailRestarts 27 | } 28 | 29 | if *canary.AutoFail.Enabled && canary.AutoFail.CanaryTimeout != nil && canary.AutoFail.CanaryTimeout.Duration <= canary.Duration.Duration { 30 | return ErrInvalidCanaryTimeout 31 | } 32 | 33 | if canary.ValidationMode == ExtendedDaemonSetSpecStrategyCanaryValidationModeManual { 34 | if canary.Duration != nil { 35 | return ErrDurationWithManualValidationMode 36 | } 37 | if canary.NoRestartsDuration != nil { 38 | return ErrNoRestartsDurationWithManualValidationMode 39 | } 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /api/v1alpha1/extendeddaemonsetsetting_types.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package v1alpha1 7 | 8 | import ( 9 | autoscalingv1 "k8s.io/api/autoscaling/v1" 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | // ExtendedDaemonsetSettingSpec is the Schema for the extendeddaemonsetsetting API 15 | // +k8s:openapi-gen=true 16 | type ExtendedDaemonsetSettingSpec struct { 17 | // Reference contains enough information to let you identify the referred resource. 18 | Reference *autoscalingv1.CrossVersionObjectReference `json:"reference"` 19 | // NodeSelector lists labels that must be present on nodes to trigger the usage of this resource. 20 | NodeSelector metav1.LabelSelector `json:"nodeSelector"` 21 | // Containers contains a list of container spec override. 22 | // +listType=map 23 | // +listMapKey=name 24 | Containers []ExtendedDaemonsetSettingContainerSpec `json:"containers,omitempty"` 25 | } 26 | 27 | // ExtendedDaemonsetSettingContainerSpec defines the resources override for a container identified by its name 28 | // +k8s:openapi-gen=true 29 | type ExtendedDaemonsetSettingContainerSpec struct { 30 | Name string `json:"name"` 31 | Resources corev1.ResourceRequirements `json:"resources"` 32 | } 33 | 34 | // ExtendedDaemonsetSettingStatusStatus defines the readable status in ExtendedDaemonsetSettingStatus. 35 | type ExtendedDaemonsetSettingStatusStatus string 36 | 37 | const ( 38 | // ExtendedDaemonsetSettingStatusValid status when ExtendedDaemonsetSetting is valide. 39 | ExtendedDaemonsetSettingStatusValid ExtendedDaemonsetSettingStatusStatus = "valid" 40 | // ExtendedDaemonsetSettingStatusError status when ExtendedDaemonsetSetting is in error state. 41 | ExtendedDaemonsetSettingStatusError ExtendedDaemonsetSettingStatusStatus = "error" 42 | ) 43 | 44 | // ExtendedDaemonsetSettingStatus defines the observed state of ExtendedDaemonsetSetting. 45 | // +k8s:openapi-gen=true 46 | type ExtendedDaemonsetSettingStatus struct { 47 | Status ExtendedDaemonsetSettingStatusStatus `json:"status"` 48 | Error string `json:"error,omitempty"` 49 | } 50 | 51 | // ExtendedDaemonsetSetting is the Schema for the extendeddaemonsetsettings API. 52 | // +kubebuilder:object:root=true 53 | // +kubebuilder:subresource:status 54 | // +kubebuilder:resource:path=extendeddaemonsetsettings,scope=Namespaced 55 | // +kubebuilder:printcolumn:name="status",type="string",JSONPath=".status.status" 56 | // +kubebuilder:printcolumn:name="node selector",type="string",JSONPath=".spec.nodeSelector" 57 | // +kubebuilder:printcolumn:name="error",type="string",JSONPath=".status.error" 58 | // +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp" 59 | // +k8s:openapi-gen=true 60 | // +genclient 61 | type ExtendedDaemonsetSetting struct { 62 | metav1.TypeMeta `json:",inline"` 63 | metav1.ObjectMeta `json:"metadata,omitempty"` 64 | 65 | Spec ExtendedDaemonsetSettingSpec `json:"spec,omitempty"` 66 | Status ExtendedDaemonsetSettingStatus `json:"status,omitempty"` 67 | } 68 | 69 | // ExtendedDaemonsetSettingList contains a list of ExtendedDaemonsetSetting 70 | // +kubebuilder:object:root=true 71 | type ExtendedDaemonsetSettingList struct { 72 | metav1.TypeMeta `json:",inline"` 73 | metav1.ListMeta `json:"metadata,omitempty"` 74 | Items []ExtendedDaemonsetSetting `json:"items"` 75 | } 76 | 77 | func init() { 78 | SchemeBuilder.Register(&ExtendedDaemonsetSetting{}, &ExtendedDaemonsetSettingList{}) 79 | } 80 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package v1alpha1 contains API Schema definitions for the datadoghq v1alpha1 API group 7 | // +kubebuilder:object:generate=true 8 | // +groupName=datadoghq.com 9 | package v1alpha1 10 | 11 | import ( 12 | "k8s.io/apimachinery/pkg/runtime/schema" 13 | "sigs.k8s.io/controller-runtime/pkg/scheme" 14 | ) 15 | 16 | var ( 17 | // GroupVersion is group version used to register these objects. 18 | GroupVersion = schema.GroupVersion{Group: "datadoghq.com", Version: "v1alpha1"} 19 | 20 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 21 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 22 | 23 | // AddToScheme adds the types in this group-version to the given scheme. 24 | AddToScheme = SchemeBuilder.AddToScheme 25 | ) 26 | -------------------------------------------------------------------------------- /api/v1alpha1/utils.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package v1alpha1 7 | 8 | // NewInt32 returns pointer on a new int32 value instance. 9 | func NewInt32(i int32) *int32 { 10 | return &i 11 | } 12 | 13 | // NewBool returns pointer to a new bool value instance. 14 | func NewBool(b bool) *bool { 15 | return &b 16 | } 17 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=extendeddaemonset 8 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 9 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.4.0+git 10 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v2 11 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 12 | 13 | # Labels for testing. 14 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 15 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 16 | 17 | # Copy files to locations specified by labels. 18 | COPY bundle/manifests /manifests/ 19 | COPY bundle/metadata /metadata/ 20 | COPY bundle/tests/scorecard /tests/scorecard/ 21 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: extendeddaemonset 7 | operators.operatorframework.io.bundle.channels.v1: alpha 8 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.4.0+git 9 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v2 10 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 11 | 12 | # Annotations for testing. 13 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 14 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 15 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:master 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | - entrypoint: 16 | - scorecard-test 17 | - olm-bundle-validation 18 | image: quay.io/operator-framework/scorecard-test:master 19 | labels: 20 | suite: olm 21 | test: olm-bundle-validation-test 22 | - entrypoint: 23 | - scorecard-test 24 | - olm-crds-have-validation 25 | image: quay.io/operator-framework/scorecard-test:master 26 | labels: 27 | suite: olm 28 | test: olm-crds-have-validation-test 29 | - entrypoint: 30 | - scorecard-test 31 | - olm-crds-have-resources 32 | image: quay.io/operator-framework/scorecard-test:master 33 | labels: 34 | suite: olm 35 | test: olm-crds-have-resources-test 36 | - entrypoint: 37 | - scorecard-test 38 | - olm-spec-descriptors 39 | image: quay.io/operator-framework/scorecard-test:master 40 | labels: 41 | suite: olm 42 | test: olm-spec-descriptors-test 43 | - entrypoint: 44 | - scorecard-test 45 | - olm-status-descriptors 46 | image: quay.io/operator-framework/scorecard-test:master 47 | labels: 48 | suite: olm 49 | test: olm-status-descriptors-test 50 | -------------------------------------------------------------------------------- /chart/app-example/.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 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /chart/app-example/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: app-example 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | appVersion: 1.16.0 24 | -------------------------------------------------------------------------------- /chart/app-example/README.md: -------------------------------------------------------------------------------- 1 | # APP Example 2 | 3 | ## Introduction 4 | 5 | This chart allows you to deploy a "dummy" application that uses the ExtendedDaemonset. 6 | 7 | Thanks to this chart you can test several ExtendedDaemonset features, such as: 8 | 9 | * The Canary deployment strategy 10 | * The `extendeddaemonset-check` util pod with the `helm test` command. 11 | * The possibility to override a `Pod` resources for a specific `Node` thanks to an 12 | `ExtendedDaemonsetSettings` resource. 13 | 14 | ## Deployment 15 | 16 | Before deploying this chart, you should have deployed the `ExtendedDaemonset` Controller with the following command: `helm install eds-controller ./chart/extendeddaemonset`. 17 | 18 | Now you can deploy the `app-example` with default values this: `helm install foo ./chart/app-example`. 19 | 20 | ### Canary deployment 21 | 22 | ### Check if a canary deployment finished 23 | 24 | Helm3 introduces the `helm test` command, which can be used to validate a "complex" deployment, was looking only 25 | at the state if the pod is not enough. It is also useful when the chart contains CRDs because Helm is not aware 26 | of how to understand the status of a CRD. 27 | 28 | This chart contains a test "Pod" (manifests present in `app-example/templates/tests`) that can check if a 29 | Extendedaemonset update is finished. 30 | 31 | To simulate an update, the following command will update the docker image tag for the "foo" application: 32 | `helm upgrade foo ./chart/app-example --set image.tag=stable`. 33 | 34 | The `ExtendedDaemonset` status should have been moved to `canary` 35 | 36 | ```console 37 | $ kubectl get eds 38 | NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE IGNORED UNRESPONSIVE NODES STATUS REASON ACTIVE RS CANARY RS AGE 39 | foo-app-example 3 3 3 3 3 Canary foo-app-example-db2sk foo-app-example-76kz8 1h 40 | ``` 41 | 42 | With the default configuration, the Canary deployment is set to 3min. During this period only one pod has been updated. 43 | 44 | The command `helm test foo` starts a Pod with a specific container that checks the `ExtendedDaemonset` status. The command returns when the canary deployment and the rolling-up are finished. 45 | -------------------------------------------------------------------------------- /chart/app-example/templates/NOTES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/extendeddaemonset/5097b3bb01d8cf71cd414175a361354824bc840f/chart/app-example/templates/NOTES.txt -------------------------------------------------------------------------------- /chart/app-example/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "app-example.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "app-example.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "app-example.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "app-example.labels" -}} 37 | helm.sh/chart: {{ include "app-example.chart" . }} 38 | {{ include "app-example.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "app-example.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "app-example.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "app-example.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "app-example.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /chart/app-example/templates/extendeddaemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: {{ include "app-example.fullname" . }} 5 | labels: 6 | {{- include "app-example.labels" . | nindent 4 }} 7 | spec: 8 | strategy: 9 | {{- toYaml .Values.strategy | nindent 4 }} 10 | template: 11 | spec: 12 | {{- with .Values.imagePullSecrets }} 13 | imagePullSecrets: 14 | {{- toYaml . | nindent 8 }} 15 | {{- end }} 16 | serviceAccountName: {{ include "app-example.serviceAccountName" . }} 17 | securityContext: 18 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 19 | containers: 20 | - name: {{ .Chart.Name }} 21 | securityContext: 22 | {{- toYaml .Values.securityContext | nindent 12 }} 23 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 24 | imagePullPolicy: {{ .Values.image.pullPolicy }} 25 | resources: 26 | {{- toYaml .Values.resources | nindent 12 }} 27 | {{- with .Values.tolerations }} 28 | tolerations: 29 | {{- toYaml . | nindent 6 }} 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /chart/app-example/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "app-example.serviceAccountName" . }} 6 | labels: 7 | {{- include "app-example.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /chart/app-example/templates/tests/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: "{{ include "app-example.fullname" . }}-test" 6 | labels: 7 | {{- include "app-example.labels" . | nindent 4 }} 8 | annotations: 9 | "helm.sh/hook": test 10 | rules: 11 | - apiGroups: 12 | - datadoghq.com 13 | resources: 14 | - 'extendeddaemonsets' 15 | - 'extendeddaemonsets/status' 16 | verbs: 17 | - 'get' 18 | --- 19 | kind: RoleBinding 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | metadata: 22 | name: "{{ include "app-example.fullname" . }}-test" 23 | labels: 24 | {{- include "app-example.labels" . | nindent 4 }} 25 | annotations: 26 | "helm.sh/hook": test 27 | subjects: 28 | - kind: ServiceAccount 29 | name: "{{ include "app-example.serviceAccountName" . }}-test" 30 | roleRef: 31 | kind: Role 32 | name: "{{ include "app-example.fullname" . }}-test" 33 | apiGroup: rbac.authorization.k8s.io 34 | {{- end -}} 35 | -------------------------------------------------------------------------------- /chart/app-example/templates/tests/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "app-example.serviceAccountName" . }}-test 6 | labels: 7 | {{- include "app-example.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} -------------------------------------------------------------------------------- /chart/app-example/templates/tests/test-update.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "app-example.fullname" . }}-test" 5 | labels: 6 | {{- include "app-example.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | serviceAccountName: {{ include "app-example.serviceAccountName" . }}-test 11 | containers: 12 | - name: {{ .Release.Name }}-check-eds 13 | image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag | default .Chart.AppVersion }}" 14 | imagePullPolicy: {{ .Values.test.image.pullPolicy }} 15 | args: 16 | - upgrade 17 | - -n={{ .Release.Namespace }} 18 | - {{ include "app-example.fullname" . }} 19 | restartPolicy: Never 20 | 21 | -------------------------------------------------------------------------------- /chart/app-example/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for app-example. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | image: 6 | repository: nginx 7 | pullPolicy: IfNotPresent 8 | # Overrides the image tag whose default is the chart appVersion. 9 | tag: "latest" 10 | 11 | imagePullSecrets: [] 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | strategy: 16 | canary: 17 | replicas: 1 18 | duration: 3m 19 | rollingUpdate: 20 | maxParallelPodCreation: 1 21 | slowStartInterval Duration: 1m 22 | 23 | test: 24 | image: 25 | repository: datadog/extendeddaemonset-check 26 | pullPolicy: IfNotPresent 27 | # Overrides the image tag whose default is the chart appVersion. 28 | tag: "latest" 29 | rbac: 30 | create: true 31 | 32 | serviceAccount: 33 | # Specifies whether a service account should be created 34 | create: true 35 | # Annotations to add to the service account 36 | annotations: {} 37 | # The name of the service account to use. 38 | # If not set and create is true, a name is generated using the fullname template 39 | name: "" 40 | 41 | podAnnotations: {} 42 | 43 | podSecurityContext: {} 44 | # fsGroup: 2000 45 | 46 | securityContext: {} 47 | # capabilities: 48 | # drop: 49 | # - ALL 50 | # readOnlyRootFilesystem: true 51 | # runAsNonRoot: true 52 | # runAsUser: 1000 53 | 54 | resources: {} 55 | # We usually recommend not to specify default resources and to leave this as a conscious 56 | # choice for the user. This also increases chances charts run on environments with little 57 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 58 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 59 | # limits: 60 | # cpu: 100m 61 | # memory: 128Mi 62 | # requests: 63 | # cpu: 100m 64 | # memory: 128Mi 65 | 66 | tolerations: 67 | - operator: Exists -------------------------------------------------------------------------------- /check-eds.Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | 9 | COPY api/go.mod api/go.mod 10 | COPY api/go.sum api/go.sum 11 | 12 | COPY go.work go.work 13 | COPY go.work.sum go.work.sum 14 | # cache deps before building and copying source so that we don't need to re-download as much 15 | # and so that source changes don't invalidate our downloaded layer 16 | RUN go mod download 17 | WORKDIR /workspace/api 18 | RUN go mod download 19 | WORKDIR /workspace 20 | 21 | # Copy the go source 22 | COPY cmd/check-eds/ cmd/check-eds/ 23 | COPY api/ api/ 24 | COPY pkg/ pkg/ 25 | 26 | # Build 27 | ARG LDFLAGS 28 | ARG GOARCH 29 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} GO111MODULE=on go build -a -ldflags "${LDFLAGS}" -o check-eds cmd/check-eds/main.go 30 | 31 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest 32 | WORKDIR / 33 | COPY --from=builder /workspace/check-eds . 34 | USER 1001 35 | 36 | ENTRYPOINT ["/check-eds"] 37 | -------------------------------------------------------------------------------- /cmd/check-eds/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/pflag" 7 | "k8s.io/cli-runtime/pkg/genericclioptions" 8 | 9 | "github.com/DataDog/extendeddaemonset/cmd/check-eds/root" 10 | ) 11 | 12 | // edscheck is use to validate a extendeddaemonset deployment 13 | // main usecase it to validate a helm deployment 14 | 15 | func main() { 16 | flags := pflag.NewFlagSet("eds-checks", pflag.ExitOnError) 17 | pflag.CommandLine = flags 18 | 19 | root := root.NewCmdRoot(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) 20 | if err := root.Execute(); err != nil { 21 | os.Exit(1) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cmd/check-eds/root/root.go: -------------------------------------------------------------------------------- 1 | // Package root contains root plugin command logic. 2 | package root 3 | 4 | import ( 5 | "github.com/spf13/cobra" 6 | "k8s.io/cli-runtime/pkg/genericclioptions" 7 | 8 | "github.com/DataDog/extendeddaemonset/cmd/check-eds/upgrade" 9 | ) 10 | 11 | // Options provides information required to manage Root. 12 | type Options struct { 13 | configFlags *genericclioptions.ConfigFlags 14 | genericclioptions.IOStreams 15 | } 16 | 17 | // NewRootOptions provides an instance of Options with default values. 18 | func NewRootOptions(streams genericclioptions.IOStreams) *Options { 19 | return &Options{ 20 | configFlags: genericclioptions.NewConfigFlags(false), 21 | 22 | IOStreams: streams, 23 | } 24 | } 25 | 26 | // NewCmdRoot provides a cobra command wrapping Options. 27 | func NewCmdRoot(streams genericclioptions.IOStreams) *cobra.Command { 28 | o := NewRootOptions(streams) 29 | 30 | cmd := &cobra.Command{ 31 | Use: "check-eds [subcommand] [flags]", 32 | } 33 | 34 | o.configFlags.AddFlags(cmd.Flags()) 35 | 36 | cmd.AddCommand(upgrade.NewCmdUpgrade(streams)) 37 | 38 | return cmd 39 | } 40 | 41 | // Complete sets all information required for processing the command. 42 | func (o *Options) Complete(cmd *cobra.Command, args []string) error { 43 | return nil 44 | } 45 | 46 | // Root ensures that all required arguments and flag values are provided. 47 | func (o *Options) Root() error { 48 | return nil 49 | } 50 | 51 | // Run use to run the command. 52 | func (o *Options) Run() error { 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /cmd/kubectl-eds/main.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package main 7 | 8 | import ( 9 | "os" 10 | 11 | "github.com/spf13/pflag" 12 | "k8s.io/cli-runtime/pkg/genericclioptions" 13 | _ "k8s.io/client-go/plugin/pkg/client/auth" 14 | 15 | "github.com/DataDog/extendeddaemonset/pkg/plugin" 16 | ) 17 | 18 | func main() { 19 | flags := pflag.NewFlagSet("kubectl-eds", pflag.ExitOnError) 20 | pflag.CommandLine = flags 21 | 22 | root := plugin.NewCmdExtendedDaemonset(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) 23 | if err := root.Execute(); err != nil { 24 | os.Exit(1) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for 4 | # breaking changes 5 | apiVersion: cert-manager.io/v1alpha2 6 | kind: Issuer 7 | metadata: 8 | name: selfsigned-issuer 9 | namespace: system 10 | spec: 11 | selfSigned: {} 12 | --- 13 | apiVersion: cert-manager.io/v1alpha2 14 | kind: Certificate 15 | metadata: 16 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 17 | namespace: system 18 | spec: 19 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 20 | dnsNames: 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 22 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 27 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/v1/datadoghq.com_extendeddaemonsets.yaml 6 | - bases/v1/datadoghq.com_extendeddaemonsetsettings.yaml 7 | - bases/v1/datadoghq.com_extendeddaemonsetreplicasets.yaml 8 | # +kubebuilder:scaffold:crdkustomizeresource 9 | 10 | patchesStrategicMerge: 11 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 12 | # patches here are for enabling the conversion webhook for each CRD 13 | #- patches/webhook_in_extendeddaemonsets.yaml 14 | #- patches/webhook_in_extendeddaemonsetsettings.yaml 15 | #- patches/webhook_in_extendeddaemonsetreplicasets.yaml 16 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 17 | 18 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 19 | # patches here are for enabling the CA injection for each CRD 20 | #- patches/cainjection_in_extendeddaemonsets.yaml 21 | #- patches/cainjection_in_extendeddaemonsetsettings.yaml 22 | #- patches/cainjection_in_extendeddaemonsetreplicasets.yaml 23 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 24 | 25 | # the following config is for teaching kustomize how to do kustomization for CRDs. 26 | configurations: 27 | - kustomizeconfig.yaml 28 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_extendeddaemonsetreplicasets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: extendeddaemonsetreplicasets.datadoghq.com 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_extendeddaemonsets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: extendeddaemonsets.datadoghq.com 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_extendeddaemonsetsettings.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: extendeddaemonsetsettings.datadoghq.com 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_extendeddaemonsetreplicasets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: extendeddaemonsetreplicasets.datadoghq.com 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_extendeddaemonsets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: extendeddaemonsets.datadoghq.com 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_extendeddaemonsetsettings.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: extendeddaemonsetsettings.datadoghq.com 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: default 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: eds- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | #- manager_auth_proxy_patch.yaml 32 | 33 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 34 | # crd/kustomization.yaml 35 | #- manager_webhook_patch.yaml 36 | 37 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 38 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 39 | # 'CERTMANAGER' needs to be enabled to use ca injection 40 | #- webhookcainjection_patch.yaml 41 | 42 | # the following config is for teaching kustomize how to do var substitution 43 | vars: 44 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 45 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 46 | # objref: 47 | # kind: Certificate 48 | # group: cert-manager.io 49 | # version: v1alpha2 50 | # name: serving-cert # this name should match the one in certificate.yaml 51 | # fieldref: 52 | # fieldpath: metadata.namespace 53 | #- name: CERTIFICATE_NAME 54 | # objref: 55 | # kind: Certificate 56 | # group: cert-manager.io 57 | # version: v1alpha2 58 | # name: serving-cert # this name should match the one in certificate.yaml 59 | #- name: SERVICE_NAMESPACE # namespace of the service 60 | # objref: 61 | # kind: Service 62 | # version: v1 63 | # name: webhook-service 64 | # fieldref: 65 | # fieldpath: metadata.namespace 66 | #- name: SERVICE_NAME 67 | # objref: 68 | # kind: Service 69 | # version: v1 70 | # name: webhook-service 71 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: datadog/extendeddaemonset 8 | newTag: latest 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: extendeddaemonset 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | app.kubernetes.io/name: extendeddaemonset 15 | spec: 16 | selector: 17 | matchLabels: 18 | app.kubernetes.io/name: extendeddaemonset 19 | replicas: 2 20 | template: 21 | metadata: 22 | labels: 23 | app.kubernetes.io/name: extendeddaemonset 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | args: 29 | - --enable-leader-election 30 | - --pprof 31 | image: controller:latest 32 | imagePullPolicy: Always 33 | name: eds-manager 34 | env: 35 | - name: WATCH_NAMESPACE 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: metadata.namespace 39 | - name: POD_NAME 40 | valueFrom: 41 | fieldRef: 42 | fieldPath: metadata.name 43 | resources: 44 | limits: 45 | cpu: 100m 46 | memory: 100Mi 47 | requests: 48 | cpu: 100m 49 | memory: 100Mi 50 | ports: 51 | - name: metrics 52 | containerPort: 8080 53 | protocol: TCP 54 | livenessProbe: 55 | httpGet: 56 | path: /healthz/ 57 | port: 8081 58 | periodSeconds: 10 59 | terminationGracePeriodSeconds: 10 60 | -------------------------------------------------------------------------------- /config/manifests/bases/extendeddaemonset.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Full Lifecycle 7 | operators.operatorframework.io/builder: operator-sdk-v1.0.0 8 | operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 9 | name: extendeddaemonset.v0.0.0 10 | namespace: placeholder 11 | spec: 12 | apiservicedefinitions: {} 13 | customresourcedefinitions: 14 | owned: 15 | - description: ExtendedDaemonSetReplicaSet is the Schema for the extendeddaemonsetreplicasets API. 16 | displayName: Extended Daemon Set Replica Set 17 | kind: ExtendedDaemonSetReplicaSet 18 | name: extendeddaemonsetreplicasets.datadoghq.com 19 | version: v1alpha1 20 | - description: ExtendedDaemonSet is the Schema for the extendeddaemonsets API. 21 | displayName: Extended Daemon Set 22 | kind: ExtendedDaemonSet 23 | name: extendeddaemonsets.datadoghq.com 24 | version: v1alpha1 25 | - description: ExtendedDaemonsetSetting is the Schema for the extendeddaemonsetsettings API. 26 | displayName: Extended Daemonset Setting 27 | kind: ExtendedDaemonsetSetting 28 | name: extendeddaemonsetsettings.datadoghq.com 29 | version: v1alpha1 30 | description: |- 31 | ExtendedDaemonSet aims to provide a new implementation of the Kubernetes DaemonSet resource with key features: 32 | * Canary Deployment: Deploy a new DaemonSet version with only a few nodes. 33 | * Custom Rolling Update: Improve the default rolling update logic available in Kubernetes batch/v1 Daemonset. 34 | displayName: extendeddaemonset 35 | icon: 36 | - base64data: "" 37 | mediatype: "" 38 | install: 39 | spec: 40 | deployments: null 41 | strategy: "" 42 | installModes: 43 | - supported: true 44 | type: OwnNamespace 45 | - supported: true 46 | type: SingleNamespace 47 | - supported: false 48 | type: MultiNamespace 49 | - supported: true 50 | type: AllNamespaces 51 | keywords: 52 | - Daemonset 53 | - Canary 54 | - ExtendedDaemonset 55 | - Deployment 56 | links: 57 | - name: Documentation 58 | url: https://github.com/DataDog/extendeddaemonset 59 | maintainers: 60 | - email: cedric@datadoghq.com 61 | name: Cedric Lamoriniere 62 | maturity: alpha 63 | provider: 64 | name: Datadog 65 | url: https://github.com/DataDog/extendeddaemonset 66 | version: 0.0.0 67 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../default 3 | - ../samples 4 | - ../scorecard 5 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonset_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit extendeddaemonsets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonset-editor-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsets 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - datadoghq.com 21 | resources: 22 | - extendeddaemonsets/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonset_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view extendeddaemonsets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonset-viewer-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - datadoghq.com 17 | resources: 18 | - extendeddaemonsets/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonsetreplicaset_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit extendeddaemonsetreplicasets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonsetreplicaset-editor-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsetreplicasets 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - datadoghq.com 21 | resources: 22 | - extendeddaemonsetreplicasets/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonsetreplicaset_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view extendeddaemonsetreplicasets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonsetreplicaset-viewer-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsetreplicasets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - datadoghq.com 17 | resources: 18 | - extendeddaemonsetreplicasets/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonsetsetting_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit extendeddaemonsetsettings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonsetsetting-editor-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsetsettings 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - datadoghq.com 21 | resources: 22 | - extendeddaemonsetsettings/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/extendeddaemonsetsetting_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view extendeddaemonsetsettings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: extendeddaemonsetsetting-viewer-role 6 | rules: 7 | - apiGroups: 8 | - datadoghq.com 9 | resources: 10 | - extendeddaemonsetsettings 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - datadoghq.com 17 | resources: 18 | - extendeddaemonsetsettings/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | # - auth_proxy_service.yaml 10 | # - auth_proxy_role.yaml 11 | # - auth_proxy_role_binding.yaml 12 | # - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | - patch 34 | - apiGroups: 35 | - "coordination.k8s.io" 36 | resources: 37 | - leases 38 | verbs: 39 | - get 40 | - list 41 | - watch 42 | - create 43 | - update 44 | - patch 45 | - delete 46 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - events 11 | verbs: 12 | - create 13 | - apiGroups: 14 | - "" 15 | resources: 16 | - nodes 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - pods 25 | - podtemplates 26 | verbs: 27 | - create 28 | - delete 29 | - get 30 | - list 31 | - patch 32 | - update 33 | - watch 34 | - apiGroups: 35 | - "" 36 | resources: 37 | - services 38 | verbs: 39 | - get 40 | - watch 41 | - apiGroups: 42 | - datadoghq.com 43 | resources: 44 | - extendeddaemonsetreplicasets 45 | - extendeddaemonsets 46 | - extendeddaemonsetsettings 47 | verbs: 48 | - create 49 | - delete 50 | - get 51 | - list 52 | - patch 53 | - update 54 | - watch 55 | - apiGroups: 56 | - datadoghq.com 57 | resources: 58 | - extendeddaemonsetreplicasets/status 59 | - extendeddaemonsets/status 60 | - extendeddaemonsetsettings/status 61 | verbs: 62 | - get 63 | - patch 64 | - update 65 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/eds_v1alpha1_basic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 2 9 | duration: 30m 10 | rollingUpdate: 11 | maxParallelPodCreation: 10 12 | slowStartIntervalDuration: 2m 13 | template: 14 | spec: 15 | containers: 16 | - name: daemon 17 | image: registry.k8s.io/pause:3.0 18 | -------------------------------------------------------------------------------- /config/samples/eds_v1alpha1_nodeexclusion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | rollingUpdate: 11 | maxParallelPodCreation: 1 12 | slowStartIntervalDuration: 1m 13 | template: 14 | spec: 15 | containers: 16 | - name: daemon 17 | image: registry.k8s.io/pause:3.0 18 | tolerations: 19 | - operator: Exists 20 | affinity: 21 | nodeAffinity: 22 | requiredDuringSchedulingIgnoredDuringExecution: 23 | nodeSelectorTerms: 24 | - matchExpressions: 25 | - key: extendeddaemonset.datadoghq.com/exclude 26 | operator: NotIn 27 | values: 28 | - foo 29 | -------------------------------------------------------------------------------- /config/samples/edssetting_v1alpha1_basic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonsetSetting 3 | metadata: 4 | name: example-extendeddaemonsetsetting 5 | spec: 6 | nodeSelector: 7 | matchLabels: 8 | overwrite: foo-daemon 9 | reference: 10 | kind: ExtendedDaemonset 11 | name: foo 12 | containers: 13 | - name: daemon 14 | resources: 15 | requests: 16 | cpu: "0.1" 17 | memory: "30m" 18 | -------------------------------------------------------------------------------- /config/samples/ers_v1alpha1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSetReplicaSet 3 | metadata: 4 | name: automatically-generated-by-extended-daemon-set 5 | 6 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - eds_v1alpha1_basic.yaml 4 | - edssetting_v1alpha1_basic.yaml 5 | - ers_v1alpha1.yaml 6 | # +kubebuilder:scaffold:manifestskustomizesamples 7 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | # +kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:master 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:master 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:master 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:master 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:master 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:master 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /controllers/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package controllers contains controllers logics: 7 | // * extendeddaemonset 8 | // * extendeddaemonsetreplicaset 9 | // * extendeddaemonsetsettings 10 | package controllers 11 | -------------------------------------------------------------------------------- /controllers/extendeddaemonset/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package extendeddaemonset contains ExtendedDaemonset controller logic. 7 | package extendeddaemonset 8 | -------------------------------------------------------------------------------- /controllers/extendeddaemonset_controller.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-logr/logr" 12 | corev1 "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/client-go/tools/record" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 19 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonset" 20 | "github.com/DataDog/extendeddaemonset/pkg/controller/utils/enqueue" 21 | ) 22 | 23 | // ExtendedDaemonSetReconciler reconciles a ExtendedDaemonSet object. 24 | type ExtendedDaemonSetReconciler struct { 25 | client.Client 26 | Log logr.Logger 27 | Scheme *runtime.Scheme 28 | Recorder record.EventRecorder 29 | Options extendeddaemonset.ReconcilerOptions 30 | internal *extendeddaemonset.Reconciler 31 | } 32 | 33 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsets,verbs=get;list;watch;create;update;patch;delete 34 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsets/status,verbs=get;update;patch 35 | // +kubebuilder:rbac:groups="",resources=events,verbs=create 36 | // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete 37 | // +kubebuilder:rbac:groups="",resources=services,verbs=get;watch 38 | // +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch 39 | 40 | // Reconcile loop for ExtendedDaemonSet. 41 | func (r *ExtendedDaemonSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 42 | return r.internal.Reconcile(ctx, req) 43 | } 44 | 45 | // SetupWithManager creates a new ExtendedDaemonSet controller. 46 | func (r *ExtendedDaemonSetReconciler) SetupWithManager(mgr ctrl.Manager) error { 47 | internal, err := extendeddaemonset.NewReconciler(r.Options, r.Client, r.Scheme, r.Log, r.Recorder) 48 | if err != nil { 49 | return err 50 | } 51 | r.internal = internal 52 | 53 | return ctrl.NewControllerManagedBy(mgr). 54 | For(&datadoghqv1alpha1.ExtendedDaemonSet{}). 55 | Owns(&datadoghqv1alpha1.ExtendedDaemonSetReplicaSet{}). 56 | Watches(&corev1.Pod{}, &enqueue.RequestForExtendedDaemonSetLabel{}). 57 | Complete(r) 58 | } 59 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package extendeddaemonsetreplicaset contains ExtendedDaemonsetReplicaset controller logic. 7 | package extendeddaemonsetreplicaset 8 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/strategy/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package strategy contains the different ExtendedDaemonsetReplicaset pod's strategies: 7 | // * canary 8 | // * rolling-update 9 | // * unknow 10 | package strategy 11 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/strategy/limits/limits.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package limits contains function to calculate pod create/deletion limits. 7 | package limits 8 | 9 | // Parameters use to provide the parameters to the Calculation function. 10 | type Parameters struct { 11 | NbNodes int 12 | NbPods int 13 | NbAvailablesPod int 14 | NbOldAvailablesPod int 15 | NbCreatedPod int 16 | NbUnresponsiveNodes int 17 | NbOldUnavailablePods int 18 | 19 | MaxPodCreation int 20 | MaxUnavailablePod int 21 | MaxUnschedulablePod int 22 | } 23 | 24 | // CalculatePodToCreateAndDelete from the parameters return: 25 | // * nbCreation: the number of pods to create 26 | // * nbDeletion: the number of pods to delete. 27 | func CalculatePodToCreateAndDelete(params Parameters) (nbCreation, nbDeletion int) { 28 | nbCreation = params.NbNodes - params.NbPods 29 | if nbCreation > params.MaxPodCreation { 30 | nbCreation = params.MaxPodCreation 31 | } 32 | if nbCreation < 0 { 33 | nbCreation = 0 34 | } 35 | 36 | effectiveUnresponsive := params.NbUnresponsiveNodes 37 | if effectiveUnresponsive > params.MaxUnschedulablePod { 38 | effectiveUnresponsive = params.MaxUnschedulablePod 39 | } 40 | 41 | nbDeletion = params.MaxUnavailablePod - (params.NbNodes - effectiveUnresponsive - params.NbAvailablesPod - params.NbOldAvailablesPod) + params.NbOldUnavailablePods 42 | 43 | if nbDeletion > params.MaxUnavailablePod { 44 | nbDeletion = params.MaxUnavailablePod 45 | } 46 | 47 | if nbDeletion < 0 { 48 | nbDeletion = 0 49 | } 50 | 51 | return nbCreation, nbDeletion 52 | } 53 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/strategy/type.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package strategy 7 | 8 | import ( 9 | "github.com/go-logr/logr" 10 | corev1 "k8s.io/api/core/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 12 | 13 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 14 | ) 15 | 16 | // ReplicaSetStatus repesent the status of a ReplicaSet. 17 | type ReplicaSetStatus string 18 | 19 | const ( 20 | // ReplicaSetStatusActive the ReplicaSet is currently active. 21 | ReplicaSetStatusActive ReplicaSetStatus = "active" 22 | // ReplicaSetStatusCanary the ReplicaSet is currently in canary mode. 23 | ReplicaSetStatusCanary ReplicaSetStatus = "canary" 24 | // ReplicaSetStatusCanaryFailed the ReplicaSet is currently in canary failed mode. 25 | ReplicaSetStatusCanaryFailed ReplicaSetStatus = "canary-failed" 26 | // ReplicaSetStatusUnknown the controller is not able to define the ReplicaSet status. 27 | ReplicaSetStatusUnknown ReplicaSetStatus = "unknown" 28 | ) 29 | 30 | // Parameters use to store all the parameter need to a strategy. 31 | type Parameters struct { 32 | MinPodUpdate int32 33 | MaxPodUpdate int32 34 | 35 | EDSName string 36 | Strategy *datadoghqv1alpha1.ExtendedDaemonSetSpecStrategy 37 | Replicaset *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet 38 | ReplicaSetStatus string 39 | 40 | NewStatus *datadoghqv1alpha1.ExtendedDaemonSetReplicaSetStatus 41 | 42 | CanaryNodes []string 43 | 44 | NodeByName map[string]*NodeItem 45 | PodByNodeName map[*NodeItem]*corev1.Pod 46 | PodToCleanUp []*corev1.Pod 47 | UnscheduledPods []*corev1.Pod 48 | 49 | Logger logr.Logger 50 | } 51 | 52 | // Result information returns by a strategy. 53 | type Result struct { 54 | // PodsToCreate list of NodeItem for Pods creation. 55 | PodsToCreate []*NodeItem 56 | // PodsToDelete list of NodeItem for Pods deletion. 57 | PodsToDelete []*NodeItem 58 | 59 | UnscheduledNodesDueToResourcesConstraints []string 60 | 61 | // IsFrozen represents frozen status of the deployment. 62 | IsFrozen bool 63 | // IsPaused represents paused status of the deployment. 64 | IsPaused bool 65 | // PausedReason provides the reason for the paused deployment. 66 | PausedReason datadoghqv1alpha1.ExtendedDaemonSetStatusReason 67 | // IsUnpaused represents if the deployment was manually unpaused. 68 | IsUnpaused bool 69 | 70 | // IsFailed represents failed state of the deployment. 71 | IsFailed bool 72 | // FailedReason provides the reason for the failed deployment. 73 | FailedReason datadoghqv1alpha1.ExtendedDaemonSetStatusReason 74 | 75 | NewStatus *datadoghqv1alpha1.ExtendedDaemonSetReplicaSetStatus 76 | Result reconcile.Result 77 | } 78 | 79 | // NodeList list of NodeItem. 80 | type NodeList struct { 81 | Items []*NodeItem 82 | } 83 | 84 | // NodeItem used to store all informations needs to create or delete a pod. 85 | type NodeItem struct { 86 | Node *corev1.Node 87 | ExtendedDaemonsetSetting *datadoghqv1alpha1.ExtendedDaemonsetSetting 88 | } 89 | 90 | // NewNodeItem used to create new NodeItem instance. 91 | func NewNodeItem(node *corev1.Node, edsNode *datadoghqv1alpha1.ExtendedDaemonsetSetting) *NodeItem { 92 | return &NodeItem{ 93 | Node: node, 94 | ExtendedDaemonsetSetting: edsNode, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/strategy/unknown.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package strategy 7 | 8 | import ( 9 | "time" 10 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | podutils "github.com/DataDog/extendeddaemonset/pkg/controller/utils/pod" 15 | ) 16 | 17 | // ManageUnknown use to manage ReplicaSet with unknown status. 18 | func ManageUnknown(client client.Client, params *Parameters) (*Result, error) { 19 | result := &Result{} 20 | // remove canary node if define 21 | for _, nodeName := range params.CanaryNodes { 22 | delete(params.PodByNodeName, params.NodeByName[nodeName]) 23 | } 24 | now := time.Now() 25 | metaNow := metav1.NewTime(now) 26 | var desiredPods, currentPods, availablePods, readyPods, nbIgnoredUnresponsiveNodes int32 27 | 28 | for node, pod := range params.PodByNodeName { 29 | desiredPods++ 30 | if pod != nil { 31 | if compareCurrentPodWithNewPod(params, pod, node) { 32 | if podutils.HasPodSchedulerIssue(pod) { 33 | nbIgnoredUnresponsiveNodes++ 34 | 35 | continue 36 | } 37 | 38 | currentPods++ 39 | if podutils.IsPodAvailable(pod, 0, metaNow) { 40 | availablePods++ 41 | } 42 | if podutils.IsPodReady(pod) { 43 | readyPods++ 44 | } 45 | } 46 | } 47 | } 48 | 49 | result.NewStatus = params.NewStatus.DeepCopy() 50 | result.NewStatus.Status = string(ReplicaSetStatusUnknown) 51 | result.NewStatus.Desired = 0 52 | result.NewStatus.Ready = readyPods 53 | result.NewStatus.Current = currentPods 54 | result.NewStatus.Available = availablePods 55 | result.NewStatus.IgnoredUnresponsiveNodes = nbIgnoredUnresponsiveNodes 56 | params.Logger.V(1).Info("Status:", "Desired", result.NewStatus.Desired, "Ready", readyPods, "Available", availablePods) 57 | 58 | if result.NewStatus.Desired != result.NewStatus.Ready { 59 | result.Result.Requeue = true 60 | } 61 | result.Result.RequeueAfter = time.Second 62 | 63 | return result, nil 64 | } 65 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset/utils.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package extendeddaemonsetreplicaset 7 | 8 | import ( 9 | "context" 10 | "sync" 11 | 12 | "github.com/go-logr/logr" 13 | corev1 "k8s.io/api/core/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | 17 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 18 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonsetreplicaset/strategy" 19 | podutils "github.com/DataDog/extendeddaemonset/pkg/controller/utils/pod" 20 | ) 21 | 22 | func createPods(logger logr.Logger, client client.Client, scheme *runtime.Scheme, podAffinitySupported bool, replicaset *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet, podsToCreate []*strategy.NodeItem) []error { 23 | var errs []error 24 | var wg sync.WaitGroup 25 | errsChan := make(chan error, len(podsToCreate)) 26 | for id := range podsToCreate { 27 | wg.Add(1) 28 | go func(id int) { 29 | defer wg.Done() 30 | nodeItem := podsToCreate[id] 31 | newPod, err := podutils.CreatePodFromDaemonSetReplicaSet(scheme, replicaset, nodeItem.Node, nodeItem.ExtendedDaemonsetSetting, podAffinitySupported) 32 | if err != nil { 33 | logger.Error(err, "Generate pod template failed", "name", newPod.GenerateName) 34 | errsChan <- err 35 | } 36 | logger.V(1).Info("Create pod", "name", newPod.GenerateName, "node", podsToCreate[id], "addAffinity", podAffinitySupported) 37 | err = client.Create(context.TODO(), newPod) 38 | if err != nil { 39 | logger.Error(err, "Create pod failed", "name", newPod.GenerateName) 40 | errsChan <- err 41 | } 42 | }(id) 43 | } 44 | go func() { 45 | wg.Wait() 46 | close(errsChan) 47 | }() 48 | 49 | for err := range errsChan { 50 | if err != nil { 51 | errs = append(errs, err) 52 | } 53 | } 54 | 55 | return errs 56 | } 57 | 58 | func deletePods(logger logr.Logger, c client.Client, podByNodeName map[*strategy.NodeItem]*corev1.Pod, nodes []*strategy.NodeItem) []error { 59 | var errs []error 60 | var wg sync.WaitGroup 61 | errsChan := make(chan error, len(nodes)) 62 | for _, node := range nodes { 63 | wg.Add(1) 64 | go func(n *strategy.NodeItem) { 65 | defer wg.Done() 66 | logger.V(1).Info("Delete pod", "name", podByNodeName[n].Name, "node", n.Node.Name) 67 | err := c.Delete(context.TODO(), podByNodeName[n]) 68 | if err != nil { 69 | errsChan <- err 70 | } 71 | }(node) 72 | } 73 | go func() { 74 | wg.Wait() 75 | close(errsChan) 76 | }() 77 | 78 | for err := range errsChan { 79 | if err != nil { 80 | errs = append(errs, err) 81 | } 82 | } 83 | 84 | return errs 85 | } 86 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetreplicaset_controller.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-logr/logr" 12 | corev1 "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/client-go/tools/record" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 19 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonsetreplicaset" 20 | "github.com/DataDog/extendeddaemonset/pkg/controller/utils/enqueue" 21 | ) 22 | 23 | // ExtendedDaemonSetReplicaSetReconciler reconciles a ExtendedDaemonSetReplicaSet object. 24 | type ExtendedDaemonSetReplicaSetReconciler struct { 25 | client.Client 26 | Log logr.Logger 27 | Scheme *runtime.Scheme 28 | Recorder record.EventRecorder 29 | Options extendeddaemonsetreplicaset.ReconcilerOptions 30 | internal *extendeddaemonsetreplicaset.Reconciler 31 | } 32 | 33 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsetreplicasets,verbs=get;list;watch;create;update;patch;delete 34 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsetreplicasets/status,verbs=get;update;patch 35 | 36 | // Reconcile loop for ExtendedDaemonSetReplicaSet. 37 | func (r *ExtendedDaemonSetReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 38 | return r.internal.Reconcile(ctx, req) 39 | } 40 | 41 | // SetupWithManager creates a new ExtendedDaemonSetReplicaSet controller. 42 | func (r *ExtendedDaemonSetReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error { 43 | internal, err := extendeddaemonsetreplicaset.NewReconciler(r.Options, r.Client, r.Scheme, r.Log, r.Recorder) 44 | if err != nil { 45 | return err 46 | } 47 | r.internal = internal 48 | 49 | return ctrl.NewControllerManagedBy(mgr). 50 | For(&datadoghqv1alpha1.ExtendedDaemonSetReplicaSet{}). 51 | Owns(&corev1.Pod{}). 52 | Watches(&datadoghqv1alpha1.ExtendedDaemonSet{}, &enqueue.RequestForExtendedDaemonSetStatus{}). 53 | Complete(r) 54 | } 55 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetsetting/doc.go: -------------------------------------------------------------------------------- 1 | // Package extendeddaemonsetsetting contains The ExtendeddaemonsetSetting controller 2 | package extendeddaemonsetsetting 3 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetsetting/utils.go: -------------------------------------------------------------------------------- 1 | package extendeddaemonsetsetting 2 | 3 | import ( 4 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 5 | ) 6 | 7 | type edsNodeByCreationTimestampAndPhase []*datadoghqv1alpha1.ExtendedDaemonsetSetting 8 | 9 | func (o edsNodeByCreationTimestampAndPhase) Len() int { return len(o) } 10 | func (o edsNodeByCreationTimestampAndPhase) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 11 | 12 | func (o edsNodeByCreationTimestampAndPhase) Less(i, j int) bool { 13 | if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { 14 | return o[i].Name > o[j].Name 15 | } 16 | 17 | return o[j].CreationTimestamp.Before(&o[i].CreationTimestamp) 18 | } 19 | -------------------------------------------------------------------------------- /controllers/extendeddaemonsetsetting_controller.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-logr/logr" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/client-go/tools/record" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | 17 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 18 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonsetsetting" 19 | ) 20 | 21 | // ExtendedDaemonsetSettingReconciler reconciles a ExtendedDaemonsetSetting object. 22 | type ExtendedDaemonsetSettingReconciler struct { 23 | client.Client 24 | Log logr.Logger 25 | Scheme *runtime.Scheme 26 | Recorder record.EventRecorder 27 | Options extendeddaemonsetsetting.ReconcilerOptions 28 | internal *extendeddaemonsetsetting.Reconciler 29 | } 30 | 31 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsetsettings,verbs=get;list;watch;create;update;patch;delete 32 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsetsettings/status,verbs=get;update;patch 33 | 34 | // Reconcile loop for ExtendedDaemonsetSetting. 35 | func (r *ExtendedDaemonsetSettingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 36 | return r.internal.Reconcile(ctx, req) 37 | } 38 | 39 | // SetupWithManager creates a new ExtendedDaemonsetSetting controller. 40 | func (r *ExtendedDaemonsetSettingReconciler) SetupWithManager(mgr ctrl.Manager) error { 41 | internal, err := extendeddaemonsetsetting.NewReconciler(r.Options, r.Client, r.Scheme, r.Log, r.Recorder) 42 | if err != nil { 43 | return err 44 | } 45 | r.internal = internal 46 | 47 | return ctrl.NewControllerManagedBy(mgr). 48 | For(&datadoghqv1alpha1.ExtendedDaemonsetSetting{}). 49 | Complete(r) 50 | } 51 | -------------------------------------------------------------------------------- /controllers/fipsonly.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | //go:build fips 7 | 8 | package controllers 9 | 10 | import _ "crypto/tls/fipsonly" 11 | -------------------------------------------------------------------------------- /controllers/podtemplate/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-present Datadog, Inc. 5 | 6 | // Package podtemplate contains ExtendedDaemonset - PodTemplate controller logic. 7 | package podtemplate 8 | -------------------------------------------------------------------------------- /controllers/podtemplate_controller.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-logr/logr" 12 | corev1 "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/client-go/tools/record" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 19 | "github.com/DataDog/extendeddaemonset/controllers/podtemplate" 20 | ) 21 | 22 | // PodTemplateReconciler reconciles a PodTemplate object. 23 | type PodTemplateReconciler struct { 24 | client.Client 25 | Log logr.Logger 26 | Scheme *runtime.Scheme 27 | Recorder record.EventRecorder 28 | Options podtemplate.ReconcilerOptions 29 | internal *podtemplate.Reconciler 30 | } 31 | 32 | // +kubebuilder:rbac:groups=datadoghq.com,resources=extendeddaemonsets,verbs=get;list;watch 33 | // +kubebuilder:rbac:groups="",resources=podtemplates,verbs=get;list;watch;create;update;patch;delete 34 | 35 | // Reconcile loop for PodTemplate. 36 | func (r *PodTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 37 | return r.internal.Reconcile(ctx, req) 38 | } 39 | 40 | // SetupWithManager creates a new PodTemplate controller. 41 | func (r *PodTemplateReconciler) SetupWithManager(mgr ctrl.Manager) error { 42 | internal, err := podtemplate.NewReconciler(r.Options, r.Client, r.Scheme, r.Log, r.Recorder) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | r.internal = internal 48 | 49 | return ctrl.NewControllerManagedBy(mgr). 50 | For(&datadoghqv1alpha1.ExtendedDaemonSet{}). 51 | Owns(&corev1.PodTemplate{}). 52 | Complete(r) 53 | } 54 | -------------------------------------------------------------------------------- /controllers/setup.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "fmt" 10 | 11 | ctrl "sigs.k8s.io/controller-runtime" 12 | "sigs.k8s.io/controller-runtime/pkg/manager" 13 | 14 | "github.com/DataDog/extendeddaemonset/api/v1alpha1" 15 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonset" 16 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonsetreplicaset" 17 | "github.com/DataDog/extendeddaemonset/controllers/extendeddaemonsetsetting" 18 | "github.com/DataDog/extendeddaemonset/controllers/podtemplate" 19 | ) 20 | 21 | // SetupControllers start all controllers (also used by unit and e2e tests). 22 | func SetupControllers(mgr manager.Manager, nodeAffinityMatchSupport bool, defaultValidationMode v1alpha1.ExtendedDaemonSetSpecStrategyCanaryValidationMode) error { 23 | if err := (&ExtendedDaemonSetReconciler{ 24 | Client: mgr.GetClient(), 25 | Log: ctrl.Log.WithName("controllers").WithName("ExtendedDaemonSet"), 26 | Scheme: mgr.GetScheme(), 27 | Recorder: mgr.GetEventRecorderFor("ExtendedDaemonSet"), 28 | Options: extendeddaemonset.ReconcilerOptions{ 29 | DefaultValidationMode: defaultValidationMode, 30 | }, 31 | }).SetupWithManager(mgr); err != nil { 32 | return fmt.Errorf("unable to create controller ExtendedDaemonSet: %w", err) 33 | } 34 | 35 | if err := (&ExtendedDaemonsetSettingReconciler{ 36 | Client: mgr.GetClient(), 37 | Log: ctrl.Log.WithName("controllers").WithName("ExtendedDaemonsetSetting"), 38 | Scheme: mgr.GetScheme(), 39 | Recorder: mgr.GetEventRecorderFor("ExtendedDaemonsetSetting"), 40 | Options: extendeddaemonsetsetting.ReconcilerOptions{}, 41 | }).SetupWithManager(mgr); err != nil { 42 | return fmt.Errorf("unable to create controller ExtendedDaemonsetSetting: %w", err) 43 | } 44 | 45 | if err := (&ExtendedDaemonSetReplicaSetReconciler{ 46 | Client: mgr.GetClient(), 47 | Log: ctrl.Log.WithName("controllers").WithName("ExtendedDaemonSetReplicaSet"), 48 | Scheme: mgr.GetScheme(), 49 | Recorder: mgr.GetEventRecorderFor("ExtendedDaemonSetReplicaSet"), 50 | Options: extendeddaemonsetreplicaset.ReconcilerOptions{ 51 | IsNodeAffinitySupported: nodeAffinityMatchSupport, 52 | }, 53 | }).SetupWithManager(mgr); err != nil { 54 | return fmt.Errorf("unable to create controller ExtendedDaemonSetReplicaSet: %w", err) 55 | } 56 | 57 | if err := (&PodTemplateReconciler{ 58 | Client: mgr.GetClient(), 59 | Log: ctrl.Log.WithName("controllers").WithName("PodTemplate"), 60 | Scheme: mgr.GetScheme(), 61 | Recorder: mgr.GetEventRecorderFor("PodTemplate"), 62 | Options: podtemplate.ReconcilerOptions{}, 63 | }).SetupWithManager(mgr); err != nil { 64 | return fmt.Errorf("unable to create controller PodTemplate: %w", err) 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /controllers/suite_e2e_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | //go:build e2e 7 | // +build e2e 8 | 9 | package controllers 10 | 11 | import ( 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | func initTestConfig() *testConfigOptions { 17 | return &testConfigOptions{ 18 | useExistingCluster: true, 19 | crdVersion: "v1", 20 | namespace: fmt.Sprintf("eds-%d", time.Now().Unix()), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /controllers/suite_helpers_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package controllers 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | 12 | . "github.com/onsi/ginkgo" 13 | "k8s.io/apimachinery/pkg/types" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 17 | ) 18 | 19 | type condFn func() bool 20 | 21 | func withGet(nsName types.NamespacedName, obj client.Object, desc string, condition condFn) condFn { 22 | return func() bool { 23 | err := k8sClient.Get(context.Background(), nsName, obj) 24 | if err != nil { 25 | fmt.Fprintf(GinkgoWriter, "Failed to get %s [%s/%s]: %v", desc, nsName.Namespace, nsName.Name, err) 26 | 27 | return false 28 | } 29 | 30 | return condition() 31 | } 32 | } 33 | 34 | func withList(listOptions []client.ListOption, obj client.ObjectList, desc string, condition condFn) condFn { 35 | return func() bool { 36 | err := k8sClient.List(context.Background(), obj, listOptions...) 37 | if err != nil { 38 | fmt.Fprintf(GinkgoWriter, "Failed to list %s: %v", desc, err) 39 | 40 | return false 41 | } 42 | 43 | return condition() 44 | } 45 | } 46 | 47 | func withEDS(nsName types.NamespacedName, eds *datadoghqv1alpha1.ExtendedDaemonSet, condition condFn) condFn { 48 | return withGet(nsName, eds, "EDS", condition) 49 | } 50 | 51 | func withERS(nsName types.NamespacedName, ers *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet, condition condFn) condFn { 52 | return withGet(nsName, ers, "ERS", condition) 53 | } 54 | -------------------------------------------------------------------------------- /controllers/suite_unit_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | //go:build !e2e 7 | // +build !e2e 8 | 9 | package controllers 10 | 11 | func initTestConfig() *testConfigOptions { 12 | return &testConfigOptions{ 13 | useExistingCluster: false, 14 | crdVersion: "v1", 15 | namespace: defaultNamespace, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /controllers/testutils/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package testutils contains helper function used for the unit-tests. 7 | package testutils 8 | -------------------------------------------------------------------------------- /controllers/testutils/node.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package testutils 7 | 8 | import ( 9 | corev1 "k8s.io/api/core/v1" 10 | "k8s.io/apimachinery/pkg/api/resource" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | // NewNode returns a fake, but ready, K8S node. 15 | func NewNode(name string, labels map[string]string) *corev1.Node { 16 | return &corev1.Node{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: name, 19 | Labels: labels, 20 | }, 21 | Spec: corev1.NodeSpec{ 22 | Unschedulable: false, 23 | }, 24 | Status: corev1.NodeStatus{ 25 | Allocatable: corev1.ResourceList{ 26 | corev1.ResourceCPU: resource.MustParse("1"), 27 | corev1.ResourceMemory: resource.MustParse("1Gi"), 28 | corev1.ResourceEphemeralStorage: resource.MustParse("10Gi"), 29 | }, 30 | Capacity: corev1.ResourceList{ 31 | corev1.ResourceCPU: resource.MustParse("1"), 32 | corev1.ResourceMemory: resource.MustParse("1Gi"), 33 | corev1.ResourceEphemeralStorage: resource.MustParse("10Gi"), 34 | }, 35 | Phase: corev1.NodeRunning, 36 | }, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/failure-cases/foo-eds_config_error.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | noRestartsDuration: 2m 11 | autoPause: 12 | enabled: true 13 | maxRestarts: 2 14 | autoFail: 15 | enabled: true 16 | maxRestarts: 10 17 | maxRestartsDuration: 10m 18 | rollingUpdate: 19 | maxParallelPodCreation: 1 20 | maxUnavailable: 2 21 | slowStartIntervalDuration: 1m 22 | template: 23 | spec: 24 | containers: 25 | - name: daemon 26 | image: gcr.io/google-containers/alpine-with-bash:1.0 27 | command: 28 | - "tail" 29 | - "-f" 30 | - "/dev/null" 31 | env: 32 | - name: MISSING 33 | valueFrom: 34 | configMapKeyRef: 35 | name: configmap-missing 36 | key: missing 37 | tolerations: 38 | - operator: Exists 39 | -------------------------------------------------------------------------------- /examples/failure-cases/foo-eds_image_pull_error.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | noRestartsDuration: 2m 11 | autoPause: 12 | enabled: true 13 | maxRestarts: 2 14 | autoFail: 15 | enabled: true 16 | maxRestarts: 10 17 | maxRestartsDuration: 10m 18 | rollingUpdate: 19 | maxParallelPodCreation: 1 20 | maxUnavailable: 2 21 | slowStartIntervalDuration: 1m 22 | template: 23 | spec: 24 | containers: 25 | - name: daemon 26 | image: registry.k8s.io/missing 27 | tolerations: 28 | - operator: Exists 29 | -------------------------------------------------------------------------------- /examples/failure-cases/foo-eds_restarts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | noRestartsDuration: 2m 11 | autoPause: 12 | enabled: true 13 | maxRestarts: 2 14 | autoFail: 15 | enabled: true 16 | maxRestarts: 5 17 | maxRestartsDuration: 10m 18 | rollingUpdate: 19 | maxParallelPodCreation: 1 20 | maxUnavailable: 2 21 | slowStartIntervalDuration: 1m 22 | template: 23 | spec: 24 | containers: 25 | - name: daemon 26 | image: registry.k8s.io/pause:3.1 27 | command: ["does", "-not", "-exist"] 28 | tolerations: 29 | - operator: Exists 30 | -------------------------------------------------------------------------------- /examples/failure-cases/foo-eds_slow_start_missing_volume.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | noRestartsDuration: 2m 11 | autoPause: 12 | enabled: true 13 | maxRestarts: 2 14 | maxSlowStartDuration: 2m 15 | autoFail: 16 | enabled: true 17 | maxRestarts: 10 18 | maxRestartsDuration: 10m 19 | rollingUpdate: 20 | maxParallelPodCreation: 1 21 | maxUnavailable: 2 22 | slowStartIntervalDuration: 1m 23 | template: 24 | spec: 25 | containers: 26 | - name: daemon 27 | image: gcr.io/google-containers/alpine-with-bash:1.0 28 | command: 29 | - "tail" 30 | - "-f" 31 | - "/dev/null" 32 | volumeMounts: 33 | - name: "missing-config-map" 34 | mountPath: "/etc/missing" 35 | volumes: 36 | - name: "missing-config-map" 37 | configMap: 38 | name: "missing" 39 | 40 | tolerations: 41 | - operator: Exists -------------------------------------------------------------------------------- /examples/foo-eds_exclude-node.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | rollingUpdate: 11 | maxParallelPodCreation: 1 12 | slowStartIntervalDuration: 1m 13 | template: 14 | spec: 15 | containers: 16 | - name: daemon 17 | image: registry.k8s.io/pause:3.0 18 | tolerations: 19 | - operator: Exists 20 | affinity: 21 | nodeAffinity: 22 | requiredDuringSchedulingIgnoredDuringExecution: 23 | nodeSelectorTerms: 24 | - matchExpressions: 25 | - key: extendeddaemonset.datadoghq.com/exclude 26 | operator: NotIn 27 | values: 28 | - foo 29 | -------------------------------------------------------------------------------- /examples/foo-eds_v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | autoFail: 11 | enabled: true 12 | canaryTimeout: 10m 13 | autoPause: 14 | enabled: true 15 | maxRestarts: 5 16 | rollingUpdate: 17 | maxParallelPodCreation: 1 18 | maxUnavailable: 2 19 | slowStartIntervalDuration: 1m 20 | template: 21 | spec: 22 | containers: 23 | - name: daemon 24 | image: registry.k8s.io/pause:3.0 25 | tolerations: 26 | - operator: Exists 27 | -------------------------------------------------------------------------------- /examples/foo-eds_v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datadoghq.com/v1alpha1 2 | kind: ExtendedDaemonSet 3 | metadata: 4 | name: foo 5 | spec: 6 | strategy: 7 | canary: 8 | replicas: 1 9 | duration: 5m 10 | autoFail: 11 | enabled: true 12 | canaryTimeout: 10m 13 | autoPause: 14 | enabled: true 15 | maxRestarts: 5 16 | rollingUpdate: 17 | maxParallelPodCreation: 1 18 | maxUnavailable: 2 19 | slowStartIntervalDuration: 1m 20 | template: 21 | spec: 22 | containers: 23 | - name: daemon 24 | image: registry.k8s.io/pause:3.1 25 | tolerations: 26 | - operator: Exists 27 | -------------------------------------------------------------------------------- /examples/kind-cluster-configuration.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.22.0 2 | 3 | toolchain go1.22.4 4 | 5 | use ( 6 | . 7 | api 8 | ) 9 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | -------------------------------------------------------------------------------- /hack/install-kubebuilder-tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | ROOT=$(git rev-parse --show-toplevel) 8 | WORK_DIR=$(mktemp -d) 9 | cleanup() { 10 | rm -rf "$WORK_DIR" 11 | } 12 | trap "cleanup" EXIT SIGINT 13 | 14 | VERSION=$1 15 | 16 | TEST_DEPS_PATH=${2:-"$ROOT/bin/kubebuilder-tools"} 17 | 18 | if [ -z "$VERSION" ]; 19 | then 20 | echo "usage: bin/install-kubebuilder-tools.sh " 21 | exit 1 22 | fi 23 | 24 | 25 | OS=$(go env GOOS) 26 | ARCH=$(go env GOARCH) 27 | 28 | curl -L https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-${VERSION}-${OS}-${ARCH}.tar.gz | tar -xz -C $WORK_DIR 29 | 30 | # move to repo_path/bin/kubebuilder - you'll need to set the KUBEBUILDER_ASSETS env var with 31 | rm -rf ${TEST_DEPS_PATH} 32 | mkdir -p ${TEST_DEPS_PATH} 33 | mv $WORK_DIR/kubebuilder/bin/ ${TEST_DEPS_PATH} 34 | -------------------------------------------------------------------------------- /hack/install-kubebuilder.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | ROOT=$(git rev-parse --show-toplevel) 8 | 9 | VERSION=$1 10 | 11 | if [ -z "$VERSION" ]; 12 | then 13 | echo "usage: bin/install-kubebuilder.sh " 14 | exit 1 15 | fi 16 | 17 | os=$(go env GOOS) 18 | arch=$(go env GOARCH) 19 | 20 | # download kubebuilder and extract it to tmp 21 | curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${VERSION}/kubebuilder_${os}_${arch} --output $ROOT/bin/kubebuilder 22 | -------------------------------------------------------------------------------- /hack/install-operator-sdk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | RELEASE_VERSION=$1 5 | ROOT=$(pwd) 6 | 7 | if [ -z "$RELEASE_VERSION" ]; 8 | then 9 | echo "usage: bin/install-operator-sdk.sh " 10 | exit 1 11 | fi 12 | 13 | # copy binary in current repo 14 | mkdir -p "$ROOT/bin" 15 | 16 | WORK_DIR=$(mktemp -d) 17 | 18 | # deletes the temp directory 19 | function cleanup { 20 | rm -rf "$WORK_DIR" 21 | echo "Deleted temp working directory $WORK_DIR" 22 | } 23 | 24 | # register the cleanup function to be called on the EXIT signal 25 | trap cleanup EXIT 26 | 27 | uname_os() { 28 | os=$(uname -s | tr '[:upper:]' '[:lower:]') 29 | case "$os" in 30 | msys_nt) os="windows" ;; 31 | esac 32 | echo "$os" 33 | } 34 | 35 | OS=$(uname_os) 36 | 37 | 38 | mkdir -p bin 39 | 40 | cd "$WORK_DIR" 41 | if [ "$OS" == "darwin" ]; then 42 | echo "darwin" 43 | curl -OJL "https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk_darwin_amd64" 44 | mv operator-sdk_darwin_amd64 "$ROOT/bin/operator-sdk" 45 | else 46 | echo "linux" 47 | curl -OL "https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk_linux_amd64" 48 | mv operator-sdk_linux_amd64 "$ROOT/bin/operator-sdk" 49 | fi 50 | 51 | chmod +x "$ROOT/bin/operator-sdk" 52 | -------------------------------------------------------------------------------- /hack/install-wwhrd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | set -o errexit 5 | set -o nounset 6 | set -o pipefail 7 | 8 | ROOT=$(git rev-parse --show-toplevel) 9 | WORK_DIR=`mktemp -d` 10 | cleanup() { 11 | rm -rf "$WORK_DIR" 12 | } 13 | trap "cleanup" EXIT SIGINT 14 | 15 | VERSION=$1 16 | TARBALL="wwhrd_${VERSION}_$(uname)_amd64.tar.gz" 17 | 18 | if [ -z "$VERSION" ]; 19 | then 20 | echo "usage: bin/install-wwhrd.sh " 21 | exit 1 22 | fi 23 | 24 | cd $WORK_DIR 25 | curl -Lo ${TARBALL} https://github.com/frapposelli/wwhrd/releases/download/v${VERSION}/${TARBALL} && tar -C . -xzf $TARBALL 26 | 27 | chmod +x wwhrd 28 | mkdir -p $ROOT/bin 29 | mv wwhrd $ROOT/bin/wwhrd 30 | -------------------------------------------------------------------------------- /hack/install-yq.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | ROOT=$(git rev-parse --show-toplevel) 8 | WORK_DIR=`mktemp -d` 9 | cleanup() { 10 | rm -rf "$WORK_DIR" 11 | } 12 | trap "cleanup" EXIT SIGINT 13 | 14 | VERSION=$1 15 | BINARY="yq_$(uname)_amd64" 16 | 17 | if [ -z "$VERSION" ]; 18 | then 19 | echo "usage: bin/install-yq.sh " 20 | exit 1 21 | fi 22 | 23 | cd $WORK_DIR 24 | curl -Lo ${BINARY} https://github.com/mikefarah/yq/releases/download/$VERSION/$BINARY 25 | 26 | chmod +x $BINARY 27 | mkdir -p $ROOT/bin 28 | mv $BINARY $ROOT/bin/yq 29 | -------------------------------------------------------------------------------- /hack/license.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exo pipefail 4 | 5 | export LC_ALL=C 6 | 7 | cd $(dirname $0)/.. 8 | ROOT=$(git rev-parse --show-toplevel) 9 | 10 | $ROOT/bin/wwhrd list 11 | 12 | echo Component,Origin,License > LICENSE-3rdparty.csv 13 | echo 'core,"github.com/frapposelli/wwhrd",MIT' >> LICENSE-3rdparty.csv 14 | unset grep 15 | $ROOT/bin/wwhrd list --no-color |& grep "Found License" | awk '{print $6,$5}' | sed -E "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | sed s/" license="/,/ | sed s/package=/core,/ | sort >> LICENSE-3rdparty.csv 16 | -------------------------------------------------------------------------------- /hack/os-env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | uname_os() { 4 | os=$(uname -s | tr '[:upper:]' '[:lower:]') 5 | case "$os" in 6 | msys_nt) os="windows" ;; 7 | esac 8 | echo "$os" 9 | } 10 | 11 | OS=$(uname_os) 12 | ARCH=$(uname -m) 13 | PLATFORM="$OS-$ARCH" 14 | ROOT=$(git rev-parse --show-toplevel) 15 | export SED="sed -i" 16 | if [ "$PLATFORM" == "darwin-arm64" ]; then 17 | export SED="sed -i ''" 18 | elif [ "$OS" == "darwin" ]; then 19 | export SED="sed -i .bak" 20 | fi 21 | -------------------------------------------------------------------------------- /hack/patch-crd-v1-protocol-kube1.18.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | versions: 3 | - schema: 4 | openAPIV3Schema: 5 | properties: 6 | spec: 7 | properties: 8 | template: 9 | properties: 10 | spec: 11 | properties: 12 | containers: 13 | items: 14 | properties: 15 | ports: 16 | items: 17 | required: 18 | - containerPort 19 | - protocol 20 | ephemeralContainers: 21 | items: 22 | properties: 23 | ports: 24 | items: 25 | required: 26 | - containerPort 27 | - protocol 28 | initContainers: 29 | items: 30 | properties: 31 | ports: 32 | items: 33 | required: 34 | - containerPort 35 | - protocol -------------------------------------------------------------------------------- /hack/patch-crd-v1beta1-protocol-kube1.18.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | validation: 3 | openAPIV3Schema: 4 | properties: 5 | spec: 6 | properties: 7 | template: 8 | properties: 9 | spec: 10 | properties: 11 | containers: 12 | items: 13 | properties: 14 | ports: 15 | items: 16 | required: 17 | - containerPort 18 | - protocol 19 | ephemeralContainers: 20 | items: 21 | properties: 22 | ports: 23 | items: 24 | required: 25 | - containerPort 26 | - protocol 27 | initContainers: 28 | items: 29 | properties: 30 | ports: 31 | items: 32 | required: 33 | - containerPort 34 | - protocol -------------------------------------------------------------------------------- /hack/patch-crds.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ROOT_DIR=$(git rev-parse --show-toplevel) 6 | YQ="$ROOT_DIR/bin/yq" 7 | 8 | v1beta1=config/crd/bases/v1beta1 9 | v1=config/crd/bases/v1 10 | 11 | # Remove "x-kubernetes-*" as only supported in Kubernetes 1.16+. 12 | # Users of Kubernetes < 1.16 need to use v1beta1, others need to use v1 13 | # 14 | # Cannot use directly yq -d .. 'spec.validation.openAPIV3Schema.properties.**.x-kubernetes-*' 15 | # as for some reason, yq takes several minutes to execute this command 16 | for crd in $(ls "$ROOT_DIR/$v1beta1") 17 | do 18 | for path in $($YQ r "$ROOT_DIR/$v1beta1/$crd" 'spec.validation.openAPIV3Schema.properties.**.x-kubernetes-*' --printMode p) 19 | do 20 | $YQ d -i "$ROOT_DIR/$v1beta1/$crd" $path 21 | done 22 | done 23 | 24 | # Update the `protocol` attribute of v1.ContainerPort to required as default is not yet supported 25 | # See: https://github.com/kubernetes/api/blob/master/core/v1/types.go#L2165 26 | # Until issue is fixed: https://github.com/kubernetes-sigs/controller-tools/issues/438 and integrated in operator-sdk 27 | $YQ m -i "$ROOT_DIR/$v1beta1/datadoghq.com_extendeddaemonsetreplicasets.yaml" "$ROOT_DIR/hack/patch-crd-v1beta1-protocol-kube1.18.yaml" 28 | $YQ m -i "$ROOT_DIR/$v1beta1/datadoghq.com_extendeddaemonsets.yaml" "$ROOT_DIR/hack/patch-crd-v1beta1-protocol-kube1.18.yaml" 29 | $YQ m -i "$ROOT_DIR/$v1/datadoghq.com_extendeddaemonsetreplicasets.yaml" "$ROOT_DIR/hack/patch-crd-v1-protocol-kube1.18.yaml" 30 | $YQ m -i "$ROOT_DIR/$v1/datadoghq.com_extendeddaemonsets.yaml" "$ROOT_DIR/hack/patch-crd-v1-protocol-kube1.18.yaml" 31 | 32 | 33 | # Update `metadata` attribute of v1.PodTemplateSpec to properly validate the 34 | # resource's metadata, since the automatically generated validation is 35 | # insufficient. 36 | patch_CRD_metadata="$ROOT_DIR/hack/patch-crd-metadata.yaml" 37 | 38 | # Different object path in v1beta1 and v1. Notice that v1 assumes that there's just 1 version 39 | openAPIV3Schema_path_v1beta1="spec.validation.openAPIV3Schema" 40 | openAPIV3Schema_path="spec.versions[0].schema.openAPIV3Schema" 41 | metadata_subpath="properties.spec.properties.template.properties.metadata" 42 | 43 | $YQ w -i "$ROOT_DIR/$v1beta1/datadoghq.com_extendeddaemonsetreplicasets.yaml" "$openAPIV3Schema_path_v1beta1"."$metadata_subpath" -f "$patch_CRD_metadata" 44 | $YQ w -i "$ROOT_DIR/$v1beta1/datadoghq.com_extendeddaemonsets.yaml" "$openAPIV3Schema_path_v1beta1"."$metadata_subpath" -f "$patch_CRD_metadata" 45 | $YQ w -i "$ROOT_DIR/$v1/datadoghq.com_extendeddaemonsetreplicasets.yaml" "$openAPIV3Schema_path"."$metadata_subpath" -f "$patch_CRD_metadata" 46 | $YQ w -i "$ROOT_DIR/$v1/datadoghq.com_extendeddaemonsets.yaml" "$openAPIV3Schema_path"."$metadata_subpath" -f "$patch_CRD_metadata" 47 | -------------------------------------------------------------------------------- /hack/patch-gitlab.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | SCRIPTS_DIR="$(dirname "$0")" 6 | source "$SCRIPTS_DIR/os-env.sh" 7 | 8 | ROOT_DIR=$(git rev-parse --show-toplevel) 9 | GITLAB_FILE_PATH=$ROOT_DIR/.gitlab-ci.yml 10 | SERVICE_FILE_PATH=$ROOT_DIR/service.datadog.yaml 11 | 12 | # Read arg as VERSION 13 | VERSION=$1 14 | 15 | if [ -z "$VERSION" ]; 16 | then 17 | echo "usage: hack/patch-gitlab.sh " 18 | exit 1 19 | fi 20 | 21 | # Update CONDUCTOR_BUILD_TAG envvar in Gitlab file, which is used in Conductor-triggered image build jobs 22 | # Replace . with -, add v 23 | VERSION_SLUG=v$(echo $VERSION | sed "s/\./-/g") 24 | # Update envvar in Gitlab file 25 | $SED "s/CONDUCTOR_BUILD_TAG: .*$/CONDUCTOR_BUILD_TAG: $VERSION_SLUG/g" $GITLAB_FILE_PATH 26 | 27 | # Update branch that runs Conductor job, add v 28 | BRANCH=v$(echo $VERSION | sed -r "s/([^.]+.[^.]*).*/\1/") 29 | $SED "s/branch: .*$/branch: \"$BRANCH\"/g" $SERVICE_FILE_PATH 30 | -------------------------------------------------------------------------------- /hack/release/cluster-service-version-patch.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | annotations: 3 | capabilities: Full Lifecycle 4 | categories: Application Runtime 5 | certified: "true" 6 | containerImage: datadog/extendeddaemonset:0.2.0 7 | description: |- 8 | ExtendedDaemonSet aims to provide a new implementation of the Kubernetes DaemonSet resource with key features: 9 | 10 | * Canary Deployment: Deploy a new DaemonSet version with only a few nodes. 11 | * Custom Rolling Update: Improve the default rolling update logic available in Kubernetes batch/v1 Daemonset. 12 | repository: https://github.com/DataDog/extendeddaemonset 13 | support: Datadog Inc. 14 | alm-examples: |- 15 | [{ 16 | "apiVersion": "datadoghq.com/v1alpha1", 17 | "kind": "ExtendedDaemonSet", 18 | "metadata": { 19 | "name": "foo" 20 | }, 21 | "spec": { 22 | "strategy": { 23 | "canary": { 24 | "replicas": 1, 25 | "duration": "5m" 26 | }, 27 | "rollingUpdate": { 28 | "maxParallelPodCreation": 1, 29 | "slowStartIntervalDuration": "1m" 30 | } 31 | }, 32 | "template": { 33 | "spec": { 34 | "containers": [ 35 | { 36 | "name": "daemon", 37 | "image": "registry.k8s.io/pause:3.0" 38 | } 39 | ], 40 | "tolerations": [ 41 | { 42 | "operator": "Exists" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | }] 49 | spec: 50 | description: ExtendedDaemonSet aims to provide a new implementation of 51 | the Kubernetes DaemonSet resource with key features such as canary deployment 52 | and custom rolling update strategy. 53 | icon: 54 | keywords: 55 | - Daemonset 56 | - Canary 57 | - ExtendedDaemonset 58 | - Deployment 59 | links: 60 | - name: Documentation 61 | url: https://github.com/DataDog/extendeddaemonset 62 | maintainers: 63 | - email: cedric@datadoghq.com 64 | name: Cedric Lamoriniere 65 | provider: 66 | name: Datadog 67 | -------------------------------------------------------------------------------- /hack/release/eds-plugin-tmpl.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: eds 5 | spec: 6 | version: "PLACEHOLDER_TAG" 7 | shortDescription: Easily interact and manage ExtendedDaemonset resources. 8 | description: | 9 | The ExtendedDaemonset kubectl plugin provides useful utilities to operate daemonsets 10 | via the ExtendedDaemonset controller and the ExtendedDaemonset CRD. 11 | homepage: https://github.com/DataDog/extendeddaemonset 12 | platforms: 13 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_darwin_amd64.zip 14 | sha256: "PLACEHOLDER_SHA_AMD_DARWIN" 15 | bin: kubectl-eds 16 | files: 17 | - from: kubectl-eds 18 | to: . 19 | - from: LICENSE 20 | to: . 21 | selector: 22 | matchLabels: 23 | os: darwin 24 | arch: amd64 25 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_linux_amd64.zip 26 | sha256: "PLACEHOLDER_SHA_AMD_LINUX" 27 | bin: kubectl-eds 28 | files: 29 | - from: kubectl-eds 30 | to: . 31 | - from: LICENSE 32 | to: . 33 | selector: 34 | matchLabels: 35 | os: linux 36 | arch: amd64 37 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_windows_amd64.zip 38 | sha256: "PLACEHOLDER_SHA_AMD_WINDOWS" 39 | bin: kubectl-eds.exe 40 | files: 41 | - from: kubectl-eds.exe 42 | to: . 43 | - from: LICENSE 44 | to: . 45 | selector: 46 | matchLabels: 47 | os: windows 48 | arch: amd64 49 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_darwin_arm64.zip 50 | sha256: "PLACEHOLDER_SHA_ARM_DARWIN" 51 | bin: kubectl-eds 52 | files: 53 | - from: kubectl-eds 54 | to: . 55 | - from: LICENSE 56 | to: . 57 | selector: 58 | matchLabels: 59 | os: darwin 60 | arch: arm64 61 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_linux_arm64.zip 62 | sha256: "PLACEHOLDER_SHA_ARM_LINUX" 63 | bin: kubectl-eds 64 | files: 65 | - from: kubectl-eds 66 | to: . 67 | - from: LICENSE 68 | to: . 69 | selector: 70 | matchLabels: 71 | os: linux 72 | arch: arm64 73 | - uri: https://github.com/DataDog/extendeddaemonset/releases/download/PLACEHOLDER_TAG/kubectl-eds_PLACEHOLDER_VERSION_windows_arm64.zip 74 | sha256: "PLACEHOLDER_SHA_ARM_WINDOWS" 75 | bin: kubectl-eds.exe 76 | files: 77 | - from: kubectl-eds.exe 78 | to: . 79 | - from: LICENSE 80 | to: . 81 | selector: 82 | matchLabels: 83 | os: windows 84 | arch: arm64 85 | -------------------------------------------------------------------------------- /hack/release/generate-plugin-manifest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source "$(dirname $0)/../os-env.sh" 5 | 6 | TAG="" 7 | if [ $# -gt 0 ]; then 8 | TAG=$1 9 | echo "TAG=$TAG" 10 | else 11 | echo "First parameter should be the new TAG" 12 | exit 1 13 | fi 14 | VERSION=${TAG:1} 15 | 16 | GIT_ROOT=$(git rev-parse --show-toplevel) 17 | PLUGIN_NAME=kubectl-eds 18 | OUTPUT_FOLDER=$GIT_ROOT/dist 19 | TARBALL_NAME="$PLUGIN_NAME_$VERSION.tar.gz" 20 | 21 | DARWIN_AMD64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "darwin_amd64" | awk '{print $1}') 22 | WINDOWS_AMD64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "windows_amd64" | awk '{print $1}') 23 | LINUX_AMD64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "linux_amd64" | awk '{print $1}') 24 | DARWIN_ARM64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "darwin_arm64" | awk '{print $1}') 25 | WINDOWS_ARM64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "windows_arm64" | awk '{print $1}') 26 | LINUX_ARM64=$(grep $PLUGIN_NAME $OUTPUT_FOLDER/checksums.txt | grep "linux_arm64" | awk '{print $1}') 27 | 28 | echo "DARWIN_AMD64=$DARWIN_AMD64" 29 | echo "WINDOWS_AMD64=$WINDOWS_AMD64" 30 | echo "LINUX_AMD64=$LINUX_AMD64" 31 | echo "DARWIN_ARM64=$DARWIN_ARM64" 32 | echo "WINDOWS_ARM64=$WINDOWS_ARM64" 33 | echo "LINUX_ARM64=$LINUX_ARM64" 34 | 35 | cp $GIT_ROOT/hack/release/eds-plugin-tmpl.yaml $OUTPUT_FOLDER/eds-plugin.yaml 36 | 37 | $SED "s/PLACEHOLDER_TAG/$TAG/g" $OUTPUT_FOLDER/eds-plugin.yaml 38 | $SED "s/PLACEHOLDER_VERSION/$VERSION/g" $OUTPUT_FOLDER/eds-plugin.yaml 39 | $SED "s/PLACEHOLDER_SHA_AMD_DARWIN/$DARWIN_AMD64/g" $OUTPUT_FOLDER/eds-plugin.yaml 40 | $SED "s/PLACEHOLDER_SHA_AMD_LINUX/$LINUX_AMD64/g" $OUTPUT_FOLDER/eds-plugin.yaml 41 | $SED "s/PLACEHOLDER_SHA_AMD_WINDOWS/$WINDOWS_AMD64/g" $OUTPUT_FOLDER/eds-plugin.yaml 42 | $SED "s/PLACEHOLDER_SHA_ARM_DARWIN/$DARWIN_ARM64/g" $OUTPUT_FOLDER/eds-plugin.yaml 43 | $SED "s/PLACEHOLDER_SHA_ARM_LINUX/$LINUX_ARMD64/g" $OUTPUT_FOLDER/eds-plugin.yaml 44 | $SED "s/PLACEHOLDER_SHA_ARM_WINDOWS/$WINDOWS_ARM64/g" $OUTPUT_FOLDER/eds-plugin.yaml 45 | -------------------------------------------------------------------------------- /hack/verify-license.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -e 7 | 8 | ROOT=$(git rev-parse --show-toplevel) 9 | cd $ROOT 10 | 11 | ./hack/license.sh 12 | 13 | DIFF=$(git --no-pager diff LICENSE-3rdparty.csv) 14 | if [[ "${DIFF}x" != "x" ]] 15 | then 16 | echo "License outdated:" >&2 17 | git --no-pager diff LICENSE-3rdparty.csv >&2 18 | exit 2 19 | fi 20 | 21 | DIFF=$(git ls-files docs/ --exclude-standard --others) 22 | if [[ "${DIFF}x" != "x" ]] 23 | then 24 | echo "License removed:" >&2 25 | echo ${DIFF} >&2 26 | exit 2 27 | fi 28 | exit 0 29 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package config contains EDS controller configuration. 7 | package config 8 | 9 | import ( 10 | "os" 11 | "strings" 12 | 13 | "github.com/go-logr/logr" 14 | "k8s.io/client-go/rest" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/cache" 17 | ) 18 | 19 | const ( 20 | // NodeAffinityMatchSupportEnvVar use to know if the scheduler support this feature: 21 | // https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/#scheduled-by-default-scheduler-enabled-by-default-since-1-12. 22 | NodeAffinityMatchSupportEnvVar = "EDS_NODEAFFINITYMATCH_SUPPORT" 23 | 24 | // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE 25 | // which specifies the Namespace to watch. 26 | // An empty value means the operator is running with cluster scope. 27 | WatchNamespaceEnvVar = "WATCH_NAMESPACE" 28 | // ValidationModeEnvVar is the constant for env variable EDS_VALIDATION_MODE 29 | // It allows to override default validationMode setting for ExtendedDaemonSetSpecStrategyCanary. 30 | ValidationModeEnvVar = "EDS_VALIDATION_MODE" 31 | ) 32 | 33 | // GetWatchNamespaces returns the Namespaces the operator should be watching for changes. 34 | func GetWatchNamespaces() []string { 35 | ns, found := os.LookupEnv(WatchNamespaceEnvVar) 36 | if !found { 37 | return nil 38 | } 39 | 40 | // Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2) 41 | if strings.Contains(ns, ",") { 42 | return strings.Split(ns, ",") 43 | } 44 | 45 | return []string{ns} 46 | } 47 | 48 | // ManagerOptionsWithNamespaces returns an updated Options with namespaces information. 49 | func ManagerOptionsWithNamespaces(logger logr.Logger, opt ctrl.Options) ctrl.Options { 50 | namespaces := GetWatchNamespaces() 51 | switch { 52 | case len(namespaces) == 0: 53 | logger.Info("Manager will watch and manage resources in all namespaces") 54 | case len(namespaces) == 1: 55 | logger.Info("Manager will be watching namespace", namespaces[0]) 56 | opt.Cache.DefaultNamespaces = map[string]cache.Config{namespaces[0]: {}} 57 | case len(namespaces) > 1: 58 | // configure cluster-scoped with MultiNamespacedCacheBuilder 59 | logger.Info("Manager will be watching multiple namespaces", namespaces) 60 | opt.NewCache = func(config *rest.Config, opts cache.Options) (cache.Cache, error) { 61 | // https://github.com/kubernetes-sigs/controller-runtime/commit/3e35cab1ea29145d8699259b079633a2b8cfc116 62 | nsConfigs := make(map[string]cache.Config) 63 | for _, ns := range namespaces { 64 | nsConfigs[ns] = cache.Config{} 65 | } 66 | opts.DefaultNamespaces = nsConfigs 67 | return cache.New(config, opts) 68 | } 69 | } 70 | 71 | return opt 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/debug/register.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package debug contains debuging helpers. 7 | package debug 8 | 9 | import ( 10 | "net/http" 11 | "net/http/pprof" 12 | ) 13 | 14 | // Options used to provide configuration options. 15 | type Options struct { 16 | CmdLine bool 17 | Profile bool 18 | Symbol bool 19 | Trace bool 20 | } 21 | 22 | // DefaultOptions returns default options configuration. 23 | func DefaultOptions() *Options { 24 | return &Options{ 25 | CmdLine: true, 26 | Profile: true, 27 | Symbol: true, 28 | Trace: true, 29 | } 30 | } 31 | 32 | // GetExtraMetricHandlers creates debug endpoints. 33 | func GetExtraMetricHandlers() map[string]http.Handler { 34 | handlers := make(map[string]http.Handler) 35 | handlers["debug/pprof"] = http.HandlerFunc(pprof.Index) 36 | handlers["/debug/pprof/cmdline"] = http.HandlerFunc(pprof.Index) 37 | handlers["/debug/pprof/cmdline"] = http.HandlerFunc(pprof.Index) 38 | handlers["/debug/pprof/symobol"] = http.HandlerFunc(pprof.Index) 39 | handlers["/debug/pprof/trace"] = http.HandlerFunc(pprof.Index) 40 | return handlers 41 | } 42 | -------------------------------------------------------------------------------- /pkg/controller/metrics/custom_metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/DataDog/extendeddaemonset/pkg/controller/utils" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "sigs.k8s.io/controller-runtime/pkg/metrics" 8 | ) 9 | 10 | var ( 11 | ersRollingUpdateStuck = prometheus.NewGaugeVec( 12 | prometheus.GaugeOpts{ 13 | Name: "ers_rolling_update_stuck", 14 | Help: "1 if the number of unavailable pods is higher than maxUnavailable, 0 otherwise", 15 | }, 16 | []string{ 17 | utils.ResourceNamePromLabel, 18 | utils.ResourceNamespacePromLabel, 19 | }, 20 | ) 21 | 22 | ersMetrics = []*prometheus.GaugeVec{ 23 | ersRollingUpdateStuck, 24 | } 25 | ) 26 | 27 | func promLabels(name, namespace string) prometheus.Labels { 28 | return prometheus.Labels{ 29 | utils.ResourceNamePromLabel: name, 30 | utils.ResourceNamespacePromLabel: namespace, 31 | } 32 | } 33 | 34 | func boolToFloat64(b bool) float64 { 35 | if b { 36 | return 1.0 37 | } 38 | 39 | return 0.0 40 | } 41 | 42 | // SetRollingUpdateStuckMetric updates the ers_rolling_update_stuck metric value 43 | // To be called by the ERS rolling update logic. 44 | func SetRollingUpdateStuckMetric(ersName, ersNamespace string, isStuck bool) { 45 | ersRollingUpdateStuck.With(promLabels(ersName, ersNamespace)).Set(boolToFloat64(isStuck)) 46 | } 47 | 48 | // DeleteERSMetrics removes metrics related to a specific ERS 49 | // To be called by the EDS controller when removing ERS objects. 50 | func DeleteERSMetrics(ersName, ersNamespace string) { 51 | for _, metric := range ersMetrics { 52 | metric.Delete(promLabels(ersName, ersNamespace)) 53 | } 54 | } 55 | 56 | func init() { 57 | // Register custom metrics with the global prometheus registry 58 | for _, metric := range ersMetrics { 59 | metrics.Registry.MustRegister(metric) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/controller/metrics/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package metrics contains controller metrics generation functions. 7 | package metrics 8 | -------------------------------------------------------------------------------- /pkg/controller/metrics/interface.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "k8s.io/client-go/tools/cache" 5 | generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" 6 | ) 7 | 8 | // Handler use to registry controller metrics. 9 | type Handler interface { 10 | RegisterStore(generators []generator.FamilyGenerator, expectedType interface{}, lw cache.ListerWatcher) error 11 | } 12 | -------------------------------------------------------------------------------- /pkg/controller/metrics/ksmetrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | "k8s.io/apimachinery/pkg/runtime/schema" 9 | "k8s.io/apimachinery/pkg/runtime/serializer" 10 | "k8s.io/apimachinery/pkg/watch" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/cache" 13 | generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" 14 | metricsstore "k8s.io/kube-state-metrics/v2/pkg/metrics_store" 15 | "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 16 | "sigs.k8s.io/controller-runtime/pkg/client/config" 17 | 18 | edsconfig "github.com/DataDog/extendeddaemonset/pkg/config" 19 | ) 20 | 21 | // AddMetrics add given metricFamilies for type given in gvk. 22 | func AddMetrics(gvk schema.GroupVersionKind, scheme *runtime.Scheme, h Handler, metricFamilies []generator.FamilyGenerator) error { 23 | restConfig, err := config.GetConfig() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | httpClient, err := rest.HTTPClientFor(restConfig) 29 | if err != nil { 30 | return err 31 | } 32 | serializerCodec := serializer.NewCodecFactory(scheme) 33 | paramCodec := runtime.NewParameterCodec(scheme) 34 | 35 | restMapper, err := apiutil.NewDynamicRESTMapper(restConfig, httpClient) 36 | if err != nil { 37 | return err 38 | } 39 | mapping, err := restMapper.RESTMapping(gvk.GroupKind()) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | restClient, err := apiutil.RESTClientForGVK(gvk, false, restConfig, serializerCodec, httpClient) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | obj, err := scheme.New(gvk) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | listGVK := gvk.GroupVersion().WithKind(gvk.Kind + "List") 55 | listObj, err := scheme.New(listGVK) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | namespaces := edsconfig.GetWatchNamespaces() 61 | if len(namespaces) == 0 { 62 | namespaces = append(namespaces, "") 63 | } 64 | for _, ns := range namespaces { 65 | lw := &cache.ListWatch{ 66 | ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { 67 | res := listObj.DeepCopyObject() 68 | localErr := restClient.Get().NamespaceIfScoped(ns, ns != "").Resource(mapping.Resource.Resource).VersionedParams(&opts, paramCodec).Do(context.Background()).Into(res) 69 | 70 | return res, localErr 71 | }, 72 | WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { 73 | opts.Watch = true 74 | 75 | return restClient.Get().NamespaceIfScoped(ns, ns != "").Resource(mapping.Resource.Resource).VersionedParams(&opts, paramCodec).Watch(context.Background()) 76 | }, 77 | } 78 | 79 | err = h.RegisterStore(metricFamilies, obj, lw) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // newMetricsStore return new metrics store. 89 | func newMetricsStore(generators []generator.FamilyGenerator, expectedType interface{}, lw cache.ListerWatcher) *metricsstore.MetricsStore { 90 | // Generate collector per namespace. 91 | composedMetricGenFuncs := generator.ComposeMetricGenFuncs(generators) 92 | headers := generator.ExtractMetricFamilyHeaders(generators) 93 | store := metricsstore.NewMetricsStore(headers, composedMetricGenFuncs) 94 | reflectorPerNamespace(context.TODO(), lw, expectedType, store) 95 | 96 | return store 97 | } 98 | 99 | func reflectorPerNamespace( 100 | ctx context.Context, 101 | lw cache.ListerWatcher, 102 | expectedType interface{}, 103 | store cache.Store, 104 | ) { 105 | reflector := cache.NewReflector(lw, expectedType, store, 0) 106 | go reflector.Run(ctx.Done()) 107 | } 108 | -------------------------------------------------------------------------------- /pkg/controller/metrics/leader.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "sigs.k8s.io/controller-runtime/pkg/metrics" 8 | ) 9 | 10 | const ( 11 | leaderLabel = "leader" 12 | ) 13 | 14 | var ( 15 | isLeader = false 16 | edsLeader = prometheus.NewGaugeVec( 17 | prometheus.GaugeOpts{ 18 | Name: "eds_controller_leader", 19 | Help: "Whether EDS instance is currently leader not", 20 | }, []string{ 21 | leaderLabel, 22 | }, 23 | ) 24 | ) 25 | 26 | // SetLeader sets the edsLeader gauge. 27 | func SetLeader(leaderValue bool) { 28 | if isLeader != leaderValue { 29 | edsLeader.Delete(prometheus.Labels{leaderLabel: strconv.FormatBool(isLeader)}) 30 | } 31 | 32 | isLeader = leaderValue 33 | edsLeader.With(prometheus.Labels{leaderLabel: strconv.FormatBool(isLeader)}).Set(1) 34 | } 35 | 36 | func init() { 37 | SetLeader(false) 38 | // Register custom metrics with the global prometheus registry 39 | metrics.Registry.MustRegister(edsLeader) 40 | // Register custom metrics with KSMetrics registry 41 | ksmExtraMetricsRegistry.MustRegister(edsLeader) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/controller/metrics/register.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package metrics 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/prometheus/common/expfmt" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/client-go/tools/cache" 15 | generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" 16 | metricsstore "k8s.io/kube-state-metrics/v2/pkg/metrics_store" 17 | ctrl "sigs.k8s.io/controller-runtime" 18 | ) 19 | 20 | var ( 21 | metricsHandler []func(*runtime.Scheme, Handler) error 22 | ksmExtraMetricsRegistry = prometheus.NewRegistry() 23 | log = ctrl.Log.WithName("ksmetrics") 24 | ) 25 | 26 | // RegisterHandlerFunc register a function to be added to endpoint when its registered. 27 | func RegisterHandlerFunc(h func(*runtime.Scheme, Handler) error) { 28 | metricsHandler = append(metricsHandler, h) 29 | } 30 | 31 | // GetExtraMetricHandlers return handler for a KSM endpoint. 32 | func GetExtraMetricHandlers(scheme *runtime.Scheme) (map[string]http.Handler, error) { 33 | handler := &storesHandler{} 34 | for _, metricsHandler := range metricsHandler { 35 | if err := metricsHandler(scheme, handler); err != nil { 36 | return nil, err 37 | } 38 | } 39 | handlers := make(map[string]http.Handler) 40 | handlers["/ksmetrics"] = http.HandlerFunc(handler.serveKsmHTTP) 41 | return handlers, nil 42 | } 43 | 44 | func (h *storesHandler) serveKsmHTTP(w http.ResponseWriter, r *http.Request) { 45 | resHeader := w.Header() 46 | // 0.0.4 is the exposition format version of prometheus 47 | // https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format 48 | resHeader.Set("Content-Type", `text/plain; version=`+"0.0.4") 49 | 50 | // Write KSM families 51 | if err := metricsstore.NewMetricsWriter(h.stores...).WriteAll(w); err != nil { 52 | log.Error(err, "Unable to write metrics") 53 | } 54 | 55 | // Write extra metrics 56 | metrics, err := ksmExtraMetricsRegistry.Gather() 57 | if err == nil { 58 | for _, m := range metrics { 59 | _, err = expfmt.MetricFamilyToText(w, m) 60 | if err != nil { 61 | log.Error(err, "Unable to write metrics", "metricFamily", m.GetName()) 62 | } 63 | } 64 | } else { 65 | log.Error(err, "Unable to export extra metrics") 66 | } 67 | } 68 | 69 | func (h *storesHandler) RegisterStore(generators []generator.FamilyGenerator, expectedType interface{}, lw cache.ListerWatcher) error { 70 | store := newMetricsStore(generators, expectedType, lw) 71 | h.stores = append(h.stores, store) 72 | 73 | return nil 74 | } 75 | 76 | type storesHandler struct { 77 | stores []*metricsstore.MetricsStore 78 | } 79 | -------------------------------------------------------------------------------- /pkg/controller/test/new.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package test contains test helpers. 7 | package test 8 | 9 | import ( 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | 13 | utilaffinity "github.com/DataDog/extendeddaemonset/pkg/controller/utils/affinity" 14 | ) 15 | 16 | // NewNodeOptions store NewNode options. 17 | type NewNodeOptions struct { 18 | Annotations map[string]string 19 | Labels map[string]string 20 | Conditions []corev1.NodeCondition 21 | Taints []corev1.Taint 22 | Unschedulable bool 23 | } 24 | 25 | // NewNode returns new node instance. 26 | func NewNode(name string, opts *NewNodeOptions) *corev1.Node { 27 | node := &corev1.Node{ 28 | TypeMeta: metav1.TypeMeta{ 29 | Kind: "Node", 30 | APIVersion: "v1", 31 | }, 32 | ObjectMeta: metav1.ObjectMeta{ 33 | Name: name, 34 | Labels: map[string]string{}, 35 | Annotations: map[string]string{}, 36 | }, 37 | } 38 | 39 | if opts != nil { 40 | if opts.Annotations != nil { 41 | for key, value := range opts.Annotations { 42 | node.Annotations[key] = value 43 | } 44 | } 45 | if opts.Labels != nil { 46 | for key, value := range opts.Labels { 47 | node.Labels[key] = value 48 | } 49 | } 50 | 51 | node.Spec.Unschedulable = opts.Unschedulable 52 | node.Status.Conditions = append(node.Status.Conditions, opts.Conditions...) 53 | node.Spec.Taints = append(node.Spec.Taints, opts.Taints...) 54 | } 55 | 56 | return node 57 | } 58 | 59 | // NewPodOptions store NewPod options. 60 | type NewPodOptions struct { 61 | CreationTimestamp metav1.Time 62 | Annotations map[string]string 63 | Labels map[string]string 64 | Phase corev1.PodPhase 65 | Reason string 66 | ContainerStatuses []corev1.ContainerStatus 67 | Resources corev1.ResourceRequirements 68 | NodeSelector map[string]string 69 | Tolerations []corev1.Toleration 70 | Affinity corev1.Affinity 71 | } 72 | 73 | // NewPod used to return new pod instance. 74 | func NewPod(namespace, name, nodeName string, opts *NewPodOptions) *corev1.Pod { 75 | pod := &corev1.Pod{ 76 | TypeMeta: metav1.TypeMeta{ 77 | Kind: "Pod", 78 | APIVersion: "v1", 79 | }, 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Name: name, 82 | Namespace: namespace, 83 | Labels: map[string]string{}, 84 | Annotations: map[string]string{}, 85 | }, 86 | Spec: corev1.PodSpec{ 87 | NodeName: nodeName, 88 | Affinity: &corev1.Affinity{}, 89 | Containers: []corev1.Container{ 90 | { 91 | Name: name, 92 | }, 93 | }, 94 | }, 95 | } 96 | if opts != nil { 97 | pod.CreationTimestamp = opts.CreationTimestamp 98 | 99 | pod.Spec.Containers[0].Resources = opts.Resources 100 | 101 | if opts.Annotations != nil { 102 | for key, value := range opts.Annotations { 103 | pod.Annotations[key] = value 104 | } 105 | } 106 | if opts.Labels != nil { 107 | for key, value := range opts.Labels { 108 | pod.Labels[key] = value 109 | } 110 | } 111 | if opts.NodeSelector != nil { 112 | pod.Spec.NodeSelector = map[string]string{} 113 | for key, value := range opts.NodeSelector { 114 | pod.Spec.NodeSelector[key] = value 115 | } 116 | } 117 | pod.Status.Phase = opts.Phase 118 | pod.Status.Reason = opts.Reason 119 | pod.Status.ContainerStatuses = opts.ContainerStatuses 120 | pod.Spec.Tolerations = append(pod.Spec.Tolerations, opts.Tolerations...) 121 | pod.Spec.Affinity = &opts.Affinity 122 | } 123 | 124 | if nodeName != "" { 125 | utilaffinity.ReplaceNodeNameNodeAffinity(pod.Spec.Affinity, nodeName) 126 | } 127 | 128 | return pod 129 | } 130 | -------------------------------------------------------------------------------- /pkg/controller/utils/affinity/affinity.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package affinity contains Pod affinity functions helper. 7 | package affinity 8 | 9 | import ( 10 | v1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | const ( 14 | // NodeFieldSelectorKeyNodeName field path use to select the Node. 15 | NodeFieldSelectorKeyNodeName = "metadata.name" 16 | ) 17 | 18 | // ReplaceNodeNameNodeAffinity replaces the RequiredDuringSchedulingIgnoredDuringExecution 19 | // NodeAffinity of the given affinity with a new NodeAffinity that selects the given nodeName. 20 | // Note that this function assumes that no NodeAffinity conflicts with the selected nodeName. 21 | func ReplaceNodeNameNodeAffinity(affinity *v1.Affinity, nodename string) *v1.Affinity { 22 | nodeSelReq := v1.NodeSelectorRequirement{ 23 | Key: NodeFieldSelectorKeyNodeName, 24 | Operator: v1.NodeSelectorOpIn, 25 | Values: []string{nodename}, 26 | } 27 | 28 | nodeSelector := &v1.NodeSelector{ 29 | NodeSelectorTerms: []v1.NodeSelectorTerm{ 30 | { 31 | MatchFields: []v1.NodeSelectorRequirement{nodeSelReq}, 32 | }, 33 | }, 34 | } 35 | 36 | if affinity == nil { 37 | return &v1.Affinity{ 38 | NodeAffinity: &v1.NodeAffinity{ 39 | RequiredDuringSchedulingIgnoredDuringExecution: nodeSelector, 40 | }, 41 | } 42 | } 43 | 44 | if affinity.NodeAffinity == nil { 45 | affinity.NodeAffinity = &v1.NodeAffinity{ 46 | RequiredDuringSchedulingIgnoredDuringExecution: nodeSelector, 47 | } 48 | 49 | return affinity 50 | } 51 | 52 | nodeAffinity := affinity.NodeAffinity 53 | 54 | if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { 55 | nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = nodeSelector 56 | 57 | return affinity 58 | } 59 | 60 | // Build new NodeSelectorTerm list to add the Node name field selector 61 | newSelectorTerms := []v1.NodeSelectorTerm{} 62 | for _, term := range nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms { 63 | newTerm := term.DeepCopy() 64 | 65 | if len(newTerm.MatchFields) == 0 { 66 | newTerm.MatchFields = []v1.NodeSelectorRequirement{nodeSelReq} 67 | } else { 68 | keyNodeNameFound := false 69 | for id, matchField := range newTerm.MatchFields { 70 | if matchField.Key == NodeFieldSelectorKeyNodeName { 71 | newTerm.MatchFields[id] = nodeSelReq 72 | keyNodeNameFound = true 73 | } 74 | } 75 | if !keyNodeNameFound { 76 | newTerm.MatchFields = append(newTerm.MatchFields, nodeSelReq) 77 | } 78 | } 79 | newSelectorTerms = append(newSelectorTerms, *newTerm) 80 | } 81 | nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = newSelectorTerms 82 | 83 | return affinity 84 | } 85 | 86 | // GetNodeNameFromAffinity return the Node name from the pod affinity configuration. 87 | func GetNodeNameFromAffinity(affinity *v1.Affinity) string { 88 | if affinity == nil { 89 | return "" 90 | } 91 | 92 | if affinity.NodeAffinity != nil && affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { 93 | selector := affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution 94 | for _, term := range selector.NodeSelectorTerms { 95 | for _, field := range term.MatchFields { 96 | if field.Key == NodeFieldSelectorKeyNodeName && len(field.Values) > 0 { 97 | return field.Values[0] 98 | } 99 | } 100 | } 101 | } 102 | 103 | return "" 104 | } 105 | -------------------------------------------------------------------------------- /pkg/controller/utils/comparison/comparison.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package comparison contains object comparison functions. 7 | package comparison 8 | 9 | import ( 10 | "bytes" 11 | 12 | // #nosec 13 | "crypto/md5" 14 | "encoding/hex" 15 | "encoding/json" 16 | "fmt" 17 | "io" 18 | "sort" 19 | "strings" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | 23 | datadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" 24 | ) 25 | 26 | // IsReplicaSetUpToDate returns true if the ExtendedDaemonSetReplicaSet is up to date with the ExtendedDaemonSet pod template. 27 | func IsReplicaSetUpToDate(rs *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet, daemonset *datadoghqv1alpha1.ExtendedDaemonSet) bool { 28 | hash, err := GenerateMD5PodTemplateSpec(&daemonset.Spec.Template) 29 | if err != nil { 30 | return false 31 | } 32 | 33 | return ComparePodTemplateSpecMD5Hash(hash, rs) 34 | } 35 | 36 | // ComparePodTemplateSpecMD5Hash used to compare a md5 hash with the one setted in Deployment annotation. 37 | func ComparePodTemplateSpecMD5Hash(hash string, rs *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet) bool { 38 | if val, ok := rs.Annotations[string(datadoghqv1alpha1.MD5ExtendedDaemonSetAnnotationKey)]; ok && val == hash { 39 | return true 40 | } 41 | 42 | return false 43 | } 44 | 45 | // GenerateMD5PodTemplateSpec used to generate the DeploymentSpec MD5 hash. 46 | func GenerateMD5PodTemplateSpec(tpl *corev1.PodTemplateSpec) (string, error) { 47 | b, err := json.Marshal(tpl) 48 | if err != nil { 49 | return "", err 50 | } 51 | /* #nosec */ 52 | hash := md5.New() 53 | _, err = io.Copy(hash, bytes.NewReader(b)) 54 | if err != nil { 55 | return "", err 56 | } 57 | 58 | return hex.EncodeToString(hash.Sum(nil)), nil 59 | } 60 | 61 | // SetMD5PodTemplateSpecAnnotation used to set the md5 annotation key/value from the ExtendedDaemonSetReplicaSet.Spec.Template. 62 | func SetMD5PodTemplateSpecAnnotation(rs *datadoghqv1alpha1.ExtendedDaemonSetReplicaSet, daemonset *datadoghqv1alpha1.ExtendedDaemonSet) (string, error) { 63 | md5Spec, err := GenerateMD5PodTemplateSpec(&daemonset.Spec.Template) 64 | if err != nil { 65 | return "", fmt.Errorf("unable to generates the JobSpec MD5, %w", err) 66 | } 67 | if rs.Annotations == nil { 68 | rs.SetAnnotations(map[string]string{}) 69 | } 70 | rs.Annotations[string(datadoghqv1alpha1.MD5ExtendedDaemonSetAnnotationKey)] = md5Spec 71 | 72 | return md5Spec, nil 73 | } 74 | 75 | // StringsContains contains tells whether a contains x. 76 | func StringsContains(a []string, x string) bool { 77 | for _, n := range a { 78 | if x == n { 79 | return true 80 | } 81 | } 82 | 83 | return false 84 | } 85 | 86 | // GenerateHashFromEDSResourceNodeAnnotation is used to generate the MD5 hash from EDS Node annotations that allow a user 87 | // to overwrites the containers resources specification for a specific Node. 88 | func GenerateHashFromEDSResourceNodeAnnotation(edsNamespace, edsName string, nodeAnnotations map[string]string) string { 89 | // build prefix for this specific eds 90 | prefixKey := fmt.Sprintf(datadoghqv1alpha1.ExtendedDaemonSetRessourceNodeAnnotationKey, edsNamespace, edsName, "") 91 | 92 | resourcesAnnotations := []string{} 93 | for key, value := range nodeAnnotations { 94 | if strings.HasPrefix(key, prefixKey) { 95 | resourcesAnnotations = append(resourcesAnnotations, fmt.Sprintf("%s=%s", key, value)) 96 | } 97 | } 98 | if len(resourcesAnnotations) == 0 { 99 | // no annotation == no hash 100 | return "" 101 | } 102 | sort.Strings(resourcesAnnotations) 103 | /* #nosec */ 104 | hash := md5.New() 105 | for _, val := range resourcesAnnotations { 106 | _, _ = hash.Write([]byte(val)) 107 | } 108 | 109 | return hex.EncodeToString(hash.Sum(nil)) 110 | } 111 | -------------------------------------------------------------------------------- /pkg/controller/utils/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package utils contains set of utils functions 7 | package utils 8 | -------------------------------------------------------------------------------- /pkg/controller/utils/labels.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "regexp" 10 | "sort" 11 | 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | ) 14 | 15 | const ( 16 | // ResourceNamePromLabel refers to the resource name label key. 17 | ResourceNamePromLabel = "name" 18 | // ResourceNamespacePromLabel refers to the resource namespace label key. 19 | ResourceNamespacePromLabel = "namespace" 20 | ) 21 | 22 | var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) 23 | 24 | // GetLabelsValues returns name and namespace as label values. 25 | func GetLabelsValues(obj *metav1.ObjectMeta) ([]string, []string) { 26 | return []string{ 27 | ResourceNamespacePromLabel, 28 | ResourceNamePromLabel, 29 | }, []string{obj.GetNamespace(), obj.GetName()} 30 | } 31 | 32 | // BuildInfoLabels build the lists of label keys and values from the ObjectMeta Labels. 33 | func BuildInfoLabels(obj *metav1.ObjectMeta) ([]string, []string) { 34 | labelKeys := []string{} 35 | for key := range obj.Labels { 36 | labelKeys = append(labelKeys, sanitizeLabelName(key)) 37 | } 38 | sort.Strings(labelKeys) 39 | 40 | labelValues := make([]string, len(obj.Labels)) 41 | for i, key := range labelKeys { 42 | labelValues[i] = obj.Labels[key] 43 | } 44 | 45 | return labelKeys, labelValues 46 | } 47 | 48 | func sanitizeLabelName(s string) string { 49 | return invalidLabelCharRE.ReplaceAllString(s, "_") 50 | } 51 | -------------------------------------------------------------------------------- /pkg/controller/utils/labels_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "reflect" 10 | "testing" 11 | 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | ) 14 | 15 | func TestBuildInfoLabels(t *testing.T) { 16 | type args struct { 17 | obj *metav1.ObjectMeta 18 | } 19 | tests := []struct { 20 | name string 21 | args args 22 | want []string 23 | want1 []string 24 | }{ 25 | { 26 | name: "empty labels map", 27 | args: args{ 28 | obj: &metav1.ObjectMeta{ 29 | Labels: nil, 30 | }, 31 | }, 32 | want: []string{}, 33 | want1: []string{}, 34 | }, 35 | { 36 | name: "2 labels in map", 37 | args: args{ 38 | obj: &metav1.ObjectMeta{ 39 | Labels: map[string]string{ 40 | "foo": "bar", 41 | "tic": "tac", 42 | }, 43 | }, 44 | }, 45 | want: []string{"foo", "tic"}, 46 | want1: []string{"bar", "tac"}, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | got, got1 := BuildInfoLabels(tt.args.obj) 52 | if !reflect.DeepEqual(got, tt.want) { 53 | t.Errorf("BuildInfoLabels() got = %#v, want %#v", got, tt.want) 54 | } 55 | if !reflect.DeepEqual(got1, tt.want1) { 56 | t.Errorf("BuildInfoLabels() got1 = %#v, want %#v", got1, tt.want1) 57 | } 58 | }) 59 | } 60 | } 61 | 62 | func Test_sanitizeLabelName(t *testing.T) { 63 | tests := []struct { 64 | name string 65 | input string 66 | want string 67 | }{ 68 | { 69 | name: "no change", 70 | input: "hello", 71 | want: "hello", 72 | }, 73 | { 74 | name: "one invalid character", 75 | input: "hello!", 76 | want: "hello_", 77 | }, 78 | { 79 | name: "two invalid characters", 80 | input: "h*ello!", 81 | want: "h_ello_", 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | got := sanitizeLabelName(tt.input) 87 | if got != tt.want { 88 | t.Errorf("sanitizeLabelName() got = %#v, want %#v", got, tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pkg/controller/utils/labelselector.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/go-logr/logr" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/labels" 7 | "k8s.io/apimachinery/pkg/selection" 8 | ) 9 | 10 | // ConvertLabelSelector converts a "k8s.io/apimachinery/pkg/apis/meta/v1".LabelSelector as found in manifests spec section into a "k8s.io/apimachinery/pkg/labels".Selector to be used to filter list operations. 11 | func ConvertLabelSelector(logger logr.Logger, inSelector *metav1.LabelSelector) (labels.Selector, error) { 12 | outSelector := labels.NewSelector() 13 | if inSelector != nil { 14 | for key, value := range inSelector.MatchLabels { 15 | req, err := labels.NewRequirement(key, selection.In, []string{value}) 16 | if err != nil { 17 | logger.Error(err, "NewRequirement") 18 | 19 | return outSelector, err 20 | } 21 | outSelector = outSelector.Add(*req) 22 | } 23 | 24 | for _, expr := range inSelector.MatchExpressions { 25 | var op selection.Operator 26 | switch expr.Operator { 27 | case metav1.LabelSelectorOpIn: 28 | op = selection.In 29 | case metav1.LabelSelectorOpNotIn: 30 | op = selection.NotIn 31 | case metav1.LabelSelectorOpExists: 32 | op = selection.Exists 33 | case metav1.LabelSelectorOpDoesNotExist: 34 | op = selection.DoesNotExist 35 | default: 36 | logger.Info("Invalid Operator:", expr.Operator) 37 | 38 | continue 39 | } 40 | req, err := labels.NewRequirement(expr.Key, op, expr.Values) 41 | if err != nil { 42 | logger.Error(err, "NewRequirement") 43 | 44 | return outSelector, err 45 | } 46 | outSelector = outSelector.Add(*req) 47 | } 48 | } 49 | 50 | return outSelector, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/controller/utils/labelselector_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/labels" 9 | "k8s.io/apimachinery/pkg/selection" 10 | logf "sigs.k8s.io/controller-runtime/pkg/log" 11 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 12 | ) 13 | 14 | func TestConvertLabelSelector(t *testing.T) { 15 | logf.SetLogger(zap.New()) 16 | log := logf.Log.WithName("TestConvertLabelSelector") 17 | 18 | foobarReq, _ := labels.NewRequirement("foo", selection.In, []string{"bar"}) 19 | bazquxReq, _ := labels.NewRequirement("baz", selection.In, []string{"qux"}) 20 | 21 | tests := []struct { 22 | name string 23 | input metav1.LabelSelector 24 | want labels.Selector 25 | wantErr bool 26 | }{ 27 | { 28 | name: "match labels", 29 | input: metav1.LabelSelector{ 30 | MatchLabels: map[string]string{ 31 | "foo": "bar", 32 | "baz": "qux", 33 | }, 34 | }, 35 | want: labels.NewSelector().Add(*foobarReq).Add(*bazquxReq), 36 | wantErr: false, 37 | }, 38 | { 39 | name: "match expressions", 40 | input: metav1.LabelSelector{ 41 | MatchExpressions: []metav1.LabelSelectorRequirement{ 42 | { 43 | Key: "foo", 44 | Operator: metav1.LabelSelectorOpIn, 45 | Values: []string{"bar"}, 46 | }, 47 | { 48 | Key: "baz", 49 | Operator: metav1.LabelSelectorOpIn, 50 | Values: []string{"qux"}, 51 | }, 52 | }, 53 | }, 54 | want: labels.NewSelector().Add(*foobarReq).Add(*bazquxReq), 55 | wantErr: false, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | reqLogger := log.WithValues("test:", tt.name) 61 | got, err := ConvertLabelSelector(reqLogger, &tt.input) 62 | if (err != nil) != tt.wantErr { 63 | t.Errorf("ConvertLabelSelector() error = %v, wantErr %v", err, tt.wantErr) 64 | 65 | return 66 | } 67 | if !reflect.DeepEqual(got, tt.want) { 68 | t.Errorf("ConvertLabelSelector() = %#v, want %#v", got, tt.want) 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/utils/list.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | // ContainsString check if a slice contains a specific string. 9 | func ContainsString(list []string, s string) bool { 10 | for _, v := range list { 11 | if v == s { 12 | return true 13 | } 14 | } 15 | 16 | return false 17 | } 18 | 19 | // RemoveString remove a specifici string from a slice. 20 | func RemoveString(list []string, s string) []string { 21 | for i, v := range list { 22 | if v == s { 23 | list = append(list[:i], list[i+1:]...) 24 | } 25 | } 26 | 27 | return list 28 | } 29 | -------------------------------------------------------------------------------- /pkg/controller/utils/list_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestContainsString(t *testing.T) { 15 | list := []string{ 16 | "hello", 17 | "goodbye", 18 | "see you", 19 | "hi!", 20 | } 21 | s := "hi!" 22 | containsString := ContainsString(list, s) 23 | assert.True(t, containsString) 24 | 25 | s = "see you?" 26 | containsString = ContainsString(list, s) 27 | assert.False(t, containsString) 28 | 29 | s = "see" 30 | containsString = ContainsString(list, s) 31 | assert.False(t, containsString) 32 | 33 | s = "see you" 34 | containsString = ContainsString(list, s) 35 | assert.True(t, containsString) 36 | } 37 | 38 | func TestRemoveString(t *testing.T) { 39 | list := []string{ 40 | "hello", 41 | "goodbye", 42 | "see you", 43 | "hi!", 44 | } 45 | s := "hi!" 46 | result := RemoveString(list, s) 47 | assert.NotContains(t, result, s) 48 | 49 | s = "see you?" 50 | result = RemoveString(list, s) 51 | assert.Equal(t, list, result) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/controller/utils/pod/const.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package pod 7 | 8 | import ( 9 | corev1 "k8s.io/api/core/v1" 10 | ) 11 | 12 | const ( 13 | // DaemonsetClusterAutoscalerPodAnnotationKey use to inform the cluster-autoscaler that a pod 14 | // should be considered as a DaemonSet pod. 15 | DaemonsetClusterAutoscalerPodAnnotationKey = "cluster-autoscaler.kubernetes.io/daemonset-pod" 16 | ) 17 | 18 | // Should be const but GO doesn't support const structures. 19 | var ( 20 | // StandardDaemonSetTolerations contains the tolerations that the EDS controller should add to 21 | // all pods it manages. 22 | // For consistency, this list must be in sync with the tolerations that are automatically added 23 | // by the regular kubernetes DaemonSet controller: 24 | // https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/#taints-and-tolerations. 25 | StandardDaemonSetTolerations = []corev1.Toleration{ 26 | { 27 | Key: "node.kubernetes.io/not-ready", 28 | Operator: corev1.TolerationOpExists, 29 | Effect: corev1.TaintEffectNoExecute, 30 | }, 31 | { 32 | Key: "node.kubernetes.io/unreachable", 33 | Operator: corev1.TolerationOpExists, 34 | Effect: corev1.TaintEffectNoExecute, 35 | }, 36 | { 37 | Key: "node.kubernetes.io/disk-pressure", 38 | Operator: corev1.TolerationOpExists, 39 | Effect: corev1.TaintEffectNoSchedule, 40 | }, 41 | { 42 | Key: "node.kubernetes.io/memory-pressure", 43 | Operator: corev1.TolerationOpExists, 44 | Effect: corev1.TaintEffectNoSchedule, 45 | }, 46 | { 47 | Key: "node.kubernetes.io/unschedulable", 48 | Operator: corev1.TolerationOpExists, 49 | Effect: corev1.TaintEffectNoSchedule, 50 | }, 51 | { 52 | Key: "node.kubernetes.io/network-unavailable", 53 | Operator: corev1.TolerationOpExists, 54 | Effect: corev1.TaintEffectNoSchedule, 55 | }, 56 | } 57 | ) 58 | -------------------------------------------------------------------------------- /pkg/controller/utils/pod/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package pod contains helper functions around Pod object. 7 | package pod 8 | -------------------------------------------------------------------------------- /pkg/controller/utils/result.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 10 | ) 11 | 12 | // MergeResult use to merge two reconcile.Result struct. 13 | func MergeResult(r1, r2 reconcile.Result) reconcile.Result { 14 | r := reconcile.Result{} 15 | if r1.Requeue || r2.Requeue { 16 | r.Requeue = true 17 | } 18 | if r1.RequeueAfter+r2.RequeueAfter > 0 { 19 | switch { 20 | case r2.RequeueAfter == 0: 21 | r.RequeueAfter = r1.RequeueAfter 22 | case r1.RequeueAfter == 0: 23 | r.RequeueAfter = r2.RequeueAfter 24 | case r1.RequeueAfter > r2.RequeueAfter: 25 | r.RequeueAfter = r2.RequeueAfter 26 | default: 27 | r.RequeueAfter = r1.RequeueAfter 28 | } 29 | } 30 | 31 | return r 32 | } 33 | -------------------------------------------------------------------------------- /pkg/controller/utils/result_test.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package utils 7 | 8 | import ( 9 | "reflect" 10 | "testing" 11 | "time" 12 | 13 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 14 | ) 15 | 16 | func TestMergeResult(t *testing.T) { 17 | type args struct { 18 | r1 reconcile.Result 19 | r2 reconcile.Result 20 | } 21 | tests := []struct { 22 | name string 23 | args args 24 | want reconcile.Result 25 | }{ 26 | { 27 | name: "both empty", 28 | args: args{ 29 | r1: reconcile.Result{}, 30 | r2: reconcile.Result{}, 31 | }, 32 | want: reconcile.Result{}, 33 | }, 34 | { 35 | name: "r1 Requeue == true", 36 | args: args{ 37 | r1: reconcile.Result{Requeue: true}, 38 | r2: reconcile.Result{}, 39 | }, 40 | want: reconcile.Result{Requeue: true}, 41 | }, 42 | { 43 | name: "r1 RequeueAfter == time.Second", 44 | args: args{ 45 | r1: reconcile.Result{RequeueAfter: time.Second}, 46 | r2: reconcile.Result{}, 47 | }, 48 | want: reconcile.Result{RequeueAfter: time.Second}, 49 | }, 50 | { 51 | name: "r2 RequeueAfter == time.Second", 52 | args: args{ 53 | r1: reconcile.Result{}, 54 | r2: reconcile.Result{RequeueAfter: time.Second}, 55 | }, 56 | want: reconcile.Result{RequeueAfter: time.Second}, 57 | }, 58 | { 59 | name: "r1 Requeue == true", 60 | args: args{ 61 | r1: reconcile.Result{Requeue: true}, 62 | r2: reconcile.Result{}, 63 | }, 64 | want: reconcile.Result{Requeue: true}, 65 | }, 66 | { 67 | name: "r1 RequeueAfter == time.Second, r2 RequeueAfter == time.Minute", 68 | args: args{ 69 | r1: reconcile.Result{RequeueAfter: time.Second}, 70 | r2: reconcile.Result{RequeueAfter: time.Minute}, 71 | }, 72 | want: reconcile.Result{RequeueAfter: time.Second}, 73 | }, 74 | { 75 | name: "r1 RequeueAfter == time.Minute, r2 RequeueAfter == time.Second", 76 | args: args{ 77 | r1: reconcile.Result{RequeueAfter: time.Minute}, 78 | r2: reconcile.Result{RequeueAfter: time.Second}, 79 | }, 80 | want: reconcile.Result{RequeueAfter: time.Second}, 81 | }, 82 | } 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | if got := MergeResult(tt.args.r1, tt.args.r2); !reflect.DeepEqual(got, tt.want) { 86 | t.Errorf("MergeResult() = %v, want %v", got, tt.want) 87 | } 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pkg/plugin/canary/canary.go: -------------------------------------------------------------------------------- 1 | package canary 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "k8s.io/cli-runtime/pkg/genericclioptions" 6 | ) 7 | 8 | // NewCmdCanary provides a cobra command to control canary deployments. 9 | func NewCmdCanary(streams genericclioptions.IOStreams) *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "canary [subcommand] [flags]", 12 | Short: "control ExtendedDaemonSet canary deployment", 13 | } 14 | 15 | cmd.AddCommand(newCmdValidate(streams)) 16 | cmd.AddCommand(newCmdPause(streams)) 17 | cmd.AddCommand(newCmdUnpause(streams)) 18 | cmd.AddCommand(newCmdFail(streams)) 19 | cmd.AddCommand(newCmdPods(streams)) 20 | 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /pkg/plugin/canary/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package canary contains kubectl canary command logic. 7 | package canary 8 | -------------------------------------------------------------------------------- /pkg/plugin/canary/pods.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | package canary 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | 12 | "github.com/spf13/cobra" 13 | "k8s.io/cli-runtime/pkg/genericclioptions" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | "github.com/DataDog/extendeddaemonset/pkg/plugin/common" 17 | ) 18 | 19 | var podsExample = ` 20 | # list the canary pods 21 | %[1]s canary pods foo 22 | ` 23 | 24 | // podsOptions provides information required to manage ExtendedDaemonSet. 25 | type podsOptions struct { 26 | client client.Client 27 | genericclioptions.IOStreams 28 | configFlags *genericclioptions.ConfigFlags 29 | args []string 30 | userNamespace string 31 | userExtendedDaemonSetName string 32 | } 33 | 34 | // newPodsOptions provides an instance of podsOptions with default values. 35 | func newPodsOptions(streams genericclioptions.IOStreams) *podsOptions { 36 | return &podsOptions{ 37 | configFlags: genericclioptions.NewConfigFlags(false), 38 | IOStreams: streams, 39 | } 40 | } 41 | 42 | // newCmdPods provides a cobra command wrapping podsOptions. 43 | func newCmdPods(streams genericclioptions.IOStreams) *cobra.Command { 44 | o := newPodsOptions(streams) 45 | 46 | cmd := &cobra.Command{ 47 | Use: "pods [ExtendedDaemonSet name]", 48 | Short: "print the list of active canary pods", 49 | Example: fmt.Sprintf(podsExample, "kubectl"), 50 | SilenceUsage: true, 51 | RunE: func(c *cobra.Command, args []string) error { 52 | if err := o.complete(c, args); err != nil { 53 | return err 54 | } 55 | if err := o.validate(); err != nil { 56 | return err 57 | } 58 | 59 | return o.run() 60 | }, 61 | } 62 | 63 | o.configFlags.AddFlags(cmd.Flags()) 64 | 65 | return cmd 66 | } 67 | 68 | // complete sets all information required for processing the command. 69 | func (o *podsOptions) complete(cmd *cobra.Command, args []string) error { 70 | o.args = args 71 | var err error 72 | 73 | clientConfig := o.configFlags.ToRawKubeConfigLoader() 74 | // Create the Client for Read/Write operations. 75 | o.client, err = common.NewClient(clientConfig) 76 | if err != nil { 77 | return fmt.Errorf("unable to instantiate client, err: %w", err) 78 | } 79 | 80 | o.userNamespace, _, err = clientConfig.Namespace() 81 | if err != nil { 82 | return err 83 | } 84 | 85 | ns, err2 := cmd.Flags().GetString("namespace") 86 | if err2 != nil { 87 | return err 88 | } 89 | if ns != "" { 90 | o.userNamespace = ns 91 | } 92 | 93 | if len(args) > 0 { 94 | o.userExtendedDaemonSetName = args[0] 95 | } 96 | 97 | return nil 98 | } 99 | 100 | // validate ensures that all required arguments and flag values are provided. 101 | func (o *podsOptions) validate() error { 102 | if len(o.args) < 1 { 103 | return errors.New("the extendeddaemonset name is required") 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // run runs the command. 110 | func (o *podsOptions) run() error { 111 | return common.PrintCanaryPods(o.client, o.userNamespace, o.userExtendedDaemonSetName, o.Out) 112 | } 113 | -------------------------------------------------------------------------------- /pkg/plugin/common/client.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package common 7 | 8 | import ( 9 | "fmt" 10 | 11 | "k8s.io/client-go/kubernetes/scheme" 12 | "k8s.io/client-go/rest" 13 | "k8s.io/client-go/tools/clientcmd" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 16 | 17 | "github.com/DataDog/extendeddaemonset/api/v1alpha1" 18 | ) 19 | 20 | // NewClient returns new client instance. 21 | func NewClient(clientConfig clientcmd.ClientConfig) (client.Client, error) { 22 | restConfig, err := clientConfig.ClientConfig() 23 | if err != nil { 24 | return nil, fmt.Errorf("unable to get rest client config, err: %w", err) 25 | } 26 | 27 | httpClient, err := rest.HTTPClientFor(restConfig) 28 | if err != nil { 29 | return nil, fmt.Errorf("unable to create http client from rest config, err: %w", err) 30 | } 31 | 32 | // Create the mapper provider. 33 | mapper, err := apiutil.NewDynamicRESTMapper(restConfig, httpClient) 34 | if err != nil { 35 | return nil, fmt.Errorf("unable to instantiate mapper, err: %w", err) 36 | } 37 | 38 | if err = v1alpha1.AddToScheme(scheme.Scheme); err != nil { 39 | return nil, fmt.Errorf("unable register ExtendedDaemonset apis, err: %w", err) 40 | } 41 | // Create the Client for Read/Write operations. 42 | var newClient client.Client 43 | newClient, err = client.New(restConfig, client.Options{Scheme: scheme.Scheme, Mapper: mapper}) 44 | if err != nil { 45 | return nil, fmt.Errorf("unable to instantiate client, err: %w", err) 46 | } 47 | 48 | return newClient, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/plugin/common/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | // Package common contains common plugin function helpers. 7 | package common 8 | -------------------------------------------------------------------------------- /pkg/plugin/common/pods.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | package common 7 | 8 | import ( 9 | "context" 10 | "errors" 11 | "fmt" 12 | "io" 13 | 14 | corev1 "k8s.io/api/core/v1" 15 | apierrors "k8s.io/apimachinery/pkg/api/errors" 16 | "k8s.io/apimachinery/pkg/labels" 17 | "k8s.io/apimachinery/pkg/selection" 18 | "sigs.k8s.io/controller-runtime/pkg/client" 19 | 20 | "github.com/DataDog/extendeddaemonset/api/v1alpha1" 21 | ) 22 | 23 | // PrintCanaryPods prints the list of canary pods. 24 | func PrintCanaryPods(c client.Client, ns, edsName string, out io.Writer) error { 25 | eds := &v1alpha1.ExtendedDaemonSet{} 26 | err := c.Get(context.TODO(), client.ObjectKey{Namespace: ns, Name: edsName}, eds) 27 | if err != nil && apierrors.IsNotFound(err) { 28 | return fmt.Errorf("ExtendedDaemonSet %s/%s not found", ns, edsName) 29 | } else if err != nil { 30 | return fmt.Errorf("unable to get ExtendedDaemonSet, err: %w", err) 31 | } 32 | 33 | if eds.Status.Canary == nil { 34 | return errors.New("the ExtendedDaemonset is not currently running a canary replicaset") 35 | } 36 | 37 | req, err := labels.NewRequirement(v1alpha1.ExtendedDaemonSetReplicaSetNameLabelKey, selection.Equals, []string{eds.Status.Canary.ReplicaSet}) 38 | if err != nil { 39 | return fmt.Errorf("couldn't query canary pods: %w", err) 40 | } 41 | 42 | rsSelector := labels.NewSelector().Add(*req) 43 | 44 | return printPods(c, rsSelector, out, false) 45 | } 46 | 47 | // PrintNotReadyPods prints the list of not ready pods. 48 | func PrintNotReadyPods(c client.Client, ns, edsName string, out io.Writer) error { 49 | eds := &v1alpha1.ExtendedDaemonSet{} 50 | err := c.Get(context.TODO(), client.ObjectKey{Namespace: ns, Name: edsName}, eds) 51 | if err != nil && apierrors.IsNotFound(err) { 52 | return fmt.Errorf("ExtendedDaemonSet %s/%s not found", ns, edsName) 53 | } else if err != nil { 54 | return fmt.Errorf("unable to get ExtendedDaemonSet, err: %w", err) 55 | } 56 | 57 | req, err := labels.NewRequirement(v1alpha1.ExtendedDaemonSetNameLabelKey, selection.Equals, []string{edsName}) 58 | if err != nil { 59 | return fmt.Errorf("couldn't query daemon pods: %w", err) 60 | } 61 | 62 | edsSelector := labels.NewSelector().Add(*req) 63 | 64 | return printPods(c, edsSelector, out, true) 65 | } 66 | 67 | func printPods(c client.Client, selector labels.Selector, out io.Writer, notReadyOnly bool) error { 68 | podList := &corev1.PodList{} 69 | err := c.List(context.TODO(), podList, &client.ListOptions{LabelSelector: selector}) 70 | if err != nil { 71 | return fmt.Errorf("couldn't get pods: %w", err) 72 | } 73 | 74 | table := newPodsTable(out) 75 | for _, pod := range podList.Items { 76 | podNotready, reason := isPodNotReady(&pod) 77 | if notReadyOnly && !podNotready { 78 | continue 79 | } 80 | ready, containers, restarts := containersInfo(&pod) 81 | table.Append([]string{pod.Name, ready, string(pod.Status.Phase), reason, containers, restarts, pod.Spec.NodeName, getNodeReadiness(c, pod.Spec.NodeName), GetDuration(&pod.ObjectMeta)}) 82 | } 83 | 84 | table.Render() 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /pkg/plugin/common/table.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | package common 7 | 8 | import ( 9 | "io" 10 | 11 | "github.com/olekukonko/tablewriter" 12 | ) 13 | 14 | // newPodsTable returns a table to print pods. 15 | func newPodsTable(out io.Writer) *tablewriter.Table { 16 | table := tablewriter.NewWriter(out) 17 | table.SetHeader([]string{"Pod", "Ready", "Phase", "Reason", "Not ready containers", "Restarts", "Node", "Node Ready", "Age"}) 18 | table.SetBorders(tablewriter.Border{Left: false, Top: false, Right: false, Bottom: false}) 19 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 20 | table.SetRowLine(false) 21 | table.SetCenterSeparator("") 22 | table.SetColumnSeparator("") 23 | table.SetRowSeparator("") 24 | table.SetAlignment(tablewriter.ALIGN_LEFT) 25 | table.SetHeaderLine(false) 26 | 27 | return table 28 | } 29 | -------------------------------------------------------------------------------- /pkg/plugin/common/utils.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2020 Datadog, Inc. 5 | 6 | package common 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/hako/durafmt" 16 | corev1 "k8s.io/api/core/v1" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | "sigs.k8s.io/controller-runtime/pkg/client" 19 | ) 20 | 21 | // IntToString converts int32 into string. 22 | func IntToString(i int32) string { 23 | return strconv.Itoa(int(i)) 24 | } 25 | 26 | // GetDuration gets uptime duration of a resource. 27 | func GetDuration(obj *metav1.ObjectMeta) string { 28 | return durafmt.ParseShort(time.Since(obj.CreationTimestamp.Time)).String() 29 | } 30 | 31 | // isPodNotReady returns whether the pod is ready, returns the reason if not ready. 32 | func isPodNotReady(pod *corev1.Pod) (bool, string) { 33 | if pod.Status.Phase != corev1.PodRunning { 34 | return true, pod.Status.Reason 35 | } 36 | 37 | for _, cond := range pod.Status.Conditions { 38 | if cond.Type == corev1.PodReady && cond.Status != corev1.ConditionTrue { 39 | return true, cond.Reason 40 | } 41 | } 42 | 43 | return false, "" 44 | } 45 | 46 | // containersInfo returns containers readiness and restart details. 47 | func containersInfo(pod *corev1.Pod) (string, string, string) { 48 | notreadyContainers := []string{} 49 | containersCount := len(pod.Status.ContainerStatuses) 50 | notreadyCount := 0 51 | restartCount := int32(0) 52 | for _, ctr := range pod.Status.ContainerStatuses { 53 | restartCount += ctr.RestartCount 54 | if !ctr.Ready { 55 | notreadyCount++ 56 | notreadyContainers = append(notreadyContainers, ctr.Name) 57 | } 58 | } 59 | 60 | return fmt.Sprintf("%d/%d", containersCount-notreadyCount, containersCount), strings.Join(notreadyContainers, ", "), IntToString(restartCount) 61 | } 62 | 63 | // getNodeReadiness returns whether a node is ready. 64 | func getNodeReadiness(c client.Client, nodename string) string { 65 | isNodeReady := func(node *corev1.Node) bool { 66 | for _, cond := range node.Status.Conditions { 67 | if cond.Type == corev1.NodeReady && cond.Status == corev1.ConditionTrue { 68 | return true 69 | } 70 | } 71 | 72 | return false 73 | } 74 | 75 | node := &corev1.Node{} 76 | readiness := "Unknown" 77 | if err := c.Get(context.TODO(), client.ObjectKey{Name: nodename}, node); err == nil { 78 | readiness = strconv.FormatBool(isNodeReady(node)) 79 | } 80 | 81 | return readiness 82 | } 83 | -------------------------------------------------------------------------------- /pkg/plugin/diff/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2023 Datadog, Inc. 5 | 6 | // Package diff contains diff plugin function. 7 | package diff 8 | -------------------------------------------------------------------------------- /pkg/plugin/extendeddaemonset.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package plugin contains kubectl plugin logic. 7 | package plugin 8 | 9 | import ( 10 | "github.com/spf13/cobra" 11 | "k8s.io/cli-runtime/pkg/genericclioptions" 12 | 13 | "github.com/DataDog/extendeddaemonset/pkg/plugin/canary" 14 | "github.com/DataDog/extendeddaemonset/pkg/plugin/diff" 15 | "github.com/DataDog/extendeddaemonset/pkg/plugin/freeze" 16 | "github.com/DataDog/extendeddaemonset/pkg/plugin/get" 17 | "github.com/DataDog/extendeddaemonset/pkg/plugin/pause" 18 | "github.com/DataDog/extendeddaemonset/pkg/plugin/pods" 19 | ) 20 | 21 | // ExtendedDaemonsetOptions provides information required to manage ExtendedDaemonset. 22 | type ExtendedDaemonsetOptions struct { 23 | configFlags *genericclioptions.ConfigFlags 24 | genericclioptions.IOStreams 25 | } 26 | 27 | // NewExtendedDaemonsetOptions provides an instance of ExtendedDaemonsetOptions with default values. 28 | func NewExtendedDaemonsetOptions(streams genericclioptions.IOStreams) *ExtendedDaemonsetOptions { 29 | return &ExtendedDaemonsetOptions{ 30 | configFlags: genericclioptions.NewConfigFlags(false), 31 | 32 | IOStreams: streams, 33 | } 34 | } 35 | 36 | // NewCmdExtendedDaemonset provides a cobra command wrapping ExtendedDaemonsetOptions. 37 | func NewCmdExtendedDaemonset(streams genericclioptions.IOStreams) *cobra.Command { 38 | o := NewExtendedDaemonsetOptions(streams) 39 | 40 | cmd := &cobra.Command{ 41 | Use: "kubectl eds [subcommand] [flags]", 42 | } 43 | 44 | cmd.AddCommand(canary.NewCmdCanary(streams)) 45 | cmd.AddCommand(get.NewCmdGet(streams)) 46 | cmd.AddCommand(get.NewCmdGetERS(streams)) 47 | cmd.AddCommand(pods.NewCmdPods(streams)) 48 | cmd.AddCommand(pause.NewCmdPause(streams)) 49 | cmd.AddCommand(pause.NewCmdUnpause(streams)) 50 | cmd.AddCommand(freeze.NewCmdFreeze(streams)) 51 | cmd.AddCommand(freeze.NewCmdUnfreeze(streams)) 52 | cmd.AddCommand(diff.NewCmdDiff(streams)) 53 | 54 | o.configFlags.AddFlags(cmd.Flags()) 55 | 56 | return cmd 57 | } 58 | 59 | // Complete sets all information required for processing the command. 60 | func (o *ExtendedDaemonsetOptions) Complete(cmd *cobra.Command, args []string) error { 61 | return nil 62 | } 63 | 64 | // Validate ensures that all required arguments and flag values are provided. 65 | func (o *ExtendedDaemonsetOptions) Validate() error { 66 | return nil 67 | } 68 | 69 | // Run use to run the command. 70 | func (o *ExtendedDaemonsetOptions) Run() error { 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/plugin/freeze/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-present Datadog, Inc. 5 | 6 | // Package freeze contains freeze plugin functions. 7 | package freeze 8 | -------------------------------------------------------------------------------- /pkg/plugin/get/common.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | package get 7 | 8 | import "github.com/DataDog/extendeddaemonset/api/v1alpha1" 9 | 10 | func getCanaryRS(eds *v1alpha1.ExtendedDaemonSet) string { 11 | if eds.Status.Canary != nil { 12 | return eds.Status.Canary.ReplicaSet 13 | } 14 | 15 | return "-" 16 | } 17 | -------------------------------------------------------------------------------- /pkg/plugin/get/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package get contains get plugin function. 7 | package get 8 | -------------------------------------------------------------------------------- /pkg/plugin/pause/doc.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-present Datadog, Inc. 5 | 6 | // Package pause contains pause plugin functions. 7 | package pause 8 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Unless explicitly stated otherwise all files in this repository are licensed 2 | // under the Apache License Version 2.0. 3 | // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 | // Copyright 2016-2019 Datadog, Inc. 5 | 6 | // Package version contains function to set and store controller version. 7 | package version 8 | 9 | import ( 10 | "fmt" 11 | "io" 12 | "runtime" 13 | 14 | "github.com/go-logr/logr" 15 | ) 16 | 17 | var ( 18 | // Version binary version. 19 | Version = "0.0.0" 20 | // BuildTime binary build time. 21 | BuildTime = "" 22 | // Commit current git commit. 23 | Commit = "" 24 | ) 25 | 26 | // PrintVersionWriter print versions information in to writer interface. 27 | func PrintVersionWriter(writer io.Writer) { 28 | fmt.Fprintf(writer, "Version:\n") 29 | for _, val := range printVersionSlice() { 30 | fmt.Fprintf(writer, "- %s\n", val) 31 | } 32 | } 33 | 34 | // PrintVersionLogs print versions information in logs. 35 | func PrintVersionLogs(logger logr.Logger) { 36 | for _, val := range printVersionSlice() { 37 | logger.Info(val) 38 | } 39 | } 40 | 41 | func printVersionSlice() []string { 42 | output := []string{ 43 | "Version: " + Version, 44 | "Build time: " + BuildTime, 45 | "Git Commit: " + Commit, 46 | "Go Version: " + runtime.Version(), 47 | "Go OS/Arch: " + runtime.GOOS + "/" + runtime.GOARCH, 48 | } 49 | 50 | return output 51 | } 52 | -------------------------------------------------------------------------------- /repository.datadog.yml: -------------------------------------------------------------------------------- 1 | --- 2 | schema-version: v1 3 | kind: mergequeue 4 | gitlab_check_enable: false 5 | github_teams_restrictions: 6 | - agent-all 7 | - container-ecosystems 8 | - container-integrations 9 | - container-platforms 10 | -------------------------------------------------------------------------------- /service.datadog.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | schema-version: v2 3 | dd-service: extendeddaemonset 4 | team: agent-onboarding 5 | contacts: 6 | - type: slack 7 | contact: https://dd.slack.com/archives/C037CDX0WJV 8 | - type: email 9 | contact: team-agentonboarding@datadoghq.com 10 | links: 11 | - name: Runbook 12 | type: runbook 13 | url: https://datadoghq.atlassian.net/wiki/spaces/ContEco/pages/2446754987/Runbooks 14 | - name: extendeddaemonset 15 | type: repo 16 | url: https://github.com/DataDog/extendeddaemonset 17 | - name: deployment 18 | type: repo 19 | url: https://github.com/DataDog/k8s-datadog-agent-ops 20 | - name: internal-chart 21 | type: repo 22 | url: https://github.com/DataDog/k8s-datadog-agent-ops/tree/main/charts/extendeddaemonset 23 | - name: internal-image 24 | type: repo 25 | url: https://github.com/DataDog/images/tree/master/extendeddaemonset 26 | - name: On Call documentation 27 | type: doc 28 | url: https://datadoghq.atlassian.net/wiki/spaces/ContEco/pages/2445645960/On+Call 29 | tags: 30 | - app:extendeddaemonset 31 | - service:extendeddaemonset 32 | - team:agent-onboarding 33 | integrations: 34 | pagerduty: https://datadog.pagerduty.com/service-directory/PFZC0QZ 35 | extensions: 36 | datadoghq.com/sdp: 37 | conductor: 38 | slack: "celene-test" 39 | options: 40 | rollout_strategy: "installation" 41 | targets: 42 | - name: "horizon-build-test" 43 | # For now, this config is mandatory, even if the target is not a ci_pipeline CNAB object. 44 | # Setting this to a placeholder value for now, the intended pipeline that will be triggered will 45 | # be the one that is defined in the main .gitlab-ci.yml file. 46 | ci_pipeline: "//fake_placeholder:fake_placeholder" 47 | parent_environments: 48 | - "staging" 49 | # Need automation to set this 50 | branch: "v0.10" 51 | # Run at 8am every 4 Mondays 52 | schedule: "0 8/672 * * 1" 53 | options: 54 | disable_failure_notifications: true 55 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## Unit-tests 4 | 5 | The command: ```make test``` with execute the unit-tests in every package and also generate the code coverage report. 6 | 7 | ## End to end testing 8 | 9 | End to end test suite can be executed with the comment: ```make e2e```. 10 | 11 | To test locally, you should use "[Kind](https://kind.sigs.k8s.io/)" for creating a multi nodes local cluster. 12 | And use the Kind cluster template: `test/cluster-kind.yaml` 13 | 14 | ```console 15 | kind create cluster --config test/cluster-kind.yaml 16 | ``` 17 | 18 | It will spinn up a 3 nodes cluster: 1 control plane + 2 worker nodes. 19 | -------------------------------------------------------------------------------- /test/cluster-kind.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: eds-e2e 4 | nodes: 5 | - role: control-plane 6 | - role: worker 7 | - role: worker 8 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | 8 | // Code generators built at runtime. 9 | _ "k8s.io/kube-openapi/cmd/openapi-gen" 10 | ) 11 | --------------------------------------------------------------------------------