├── .dockerignore
├── .github
└── workflows
│ ├── build-and-push-image.yml
│ ├── golangci-lint.yml
│ ├── lint-helm-test.yaml
│ └── releases.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.runners
├── LICENSE
├── Makefile
├── PROJECT
├── README.md
├── api
└── v1alpha1
│ ├── groupversion_info.go
│ ├── sleepcycle_types.go
│ └── zz_generated.deepcopy.go
├── charts
└── sleepcycles
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── leader-election-rbac.yaml
│ ├── manager-config.yaml
│ ├── manager-rbac.yaml
│ ├── metrics-reader-rbac.yaml
│ ├── metrics-service.yaml
│ ├── proxy-rbac.yaml
│ ├── serviceaccount.yaml
│ └── sleepcycle-crd.yaml
│ └── values.yaml
├── config
├── crd
│ ├── bases
│ │ └── core.rekuberate.io_sleepcycles.yaml
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── patches
│ │ ├── cainjection_in_sleepcycles.yaml
│ │ └── webhook_in_sleepcycles.yaml
├── default
│ ├── kustomization.yaml
│ ├── manager_auth_proxy_patch.yaml
│ └── manager_config_patch.yaml
├── helm
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ │ ├── _helpers.tpl
│ │ ├── deployment.yaml
│ │ ├── leader-election-rbac.yaml
│ │ ├── manager-config.yaml
│ │ ├── manager-rbac.yaml
│ │ ├── metrics-reader-rbac.yaml
│ │ ├── metrics-service.yaml
│ │ ├── proxy-rbac.yaml
│ │ ├── serviceaccount.yaml
│ │ └── sleepcycle-crd.yaml
│ └── values.yaml
├── manager
│ ├── controller_manager_config.yaml
│ ├── kustomization.yaml
│ └── manager.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
│ ├── kustomization.yaml
│ ├── leader_election_role.yaml
│ ├── leader_election_role_binding.yaml
│ ├── role.yaml
│ ├── role_binding.yaml
│ ├── service_account.yaml
│ ├── sleepcycle_editor_role.yaml
│ └── sleepcycle_viewer_role.yaml
└── samples
│ ├── apache-hpa.yaml
│ ├── busybox-cronjob.yaml
│ ├── core_v1alpha1_sleepcycle_app_1.yaml
│ ├── core_v1alpha1_sleepcycle_app_2.yaml
│ ├── core_v1alpha1_sleepcycle_app_3.yaml
│ ├── core_v1alpha1_sleepcycle_app_4.yaml
│ ├── core_v1alpha1_sleepcycle_app_5.yaml
│ ├── core_v1alpha1_sleepcycle_app_6.yaml
│ ├── nginx-statefulset.yaml
│ ├── stress
│ └── stress.go
│ ├── whoami-app-1_1-deployment.yaml
│ ├── whoami-app-1_2-deployment.yaml
│ └── whoami-app-2-deployment.yaml
├── controllers
├── sleepcycle_controller.go
├── sleepcycle_reconcilers.go
├── sleepcycle_runners_cronjobs.go
├── sleepcycle_utils.go
├── sleepcycles_rbac.go
└── suite_test.go
├── docs
└── images
│ ├── SCR-20220920-i6y-2.png
│ ├── SCR-20221222-hij.png
│ ├── SCR-20240527-q9y.png
│ ├── SCR-20240527-qei.png
│ ├── Screencast from 05-24-2024 11-13-28 AM.webm
│ ├── Screencast from 05-30-2024 11-12-15 AM.webm
│ ├── argocd.png
│ ├── rekuberate-io-sleepcycles.gif
│ ├── rekuberate-io-sleepcycles.mp4
│ ├── rekuberate-sleepcycle-banner.png
│ ├── rekuberate-sleepcycle-logo-2.png
│ ├── rekuberate-sleepcycle-logo-3.png
│ └── rekuberate-sleepcycle-logo.png
├── go.mod
├── go.sum
├── hack
└── boilerplate.go.txt
├── main.go
└── runners
└── runner.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
2 | # Ignore build and test binaries.
3 | bin/
4 | testbin/
5 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-push-image.yml:
--------------------------------------------------------------------------------
1 | name: Build Image and Publish to Dockerhub
2 |
3 | on:
4 | release:
5 | types: [ published ]
6 | workflow_dispatch:
7 | inputs:
8 | tag:
9 | description: 'Image tag'
10 | required: true
11 | default: 'test'
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | image:
17 | name: Build Image from Dockerfile and binaries
18 | runs-on: ubuntu-latest
19 | steps:
20 | # environment
21 | - name: Checkout
22 | uses: actions/checkout@v4
23 | with:
24 | fetch-depth: '0'
25 |
26 | - name: Set up Docker Buildx
27 | uses: docker/setup-buildx-action@v3
28 |
29 | - name: Get Image Tag Name
30 | run: |
31 | if [ x${{ github.event.inputs.tag }} == x"" ]; then
32 | echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
33 | else
34 | echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
35 | fi
36 |
37 | - name: Login to DockerHub
38 | uses: docker/login-action@v3
39 | with:
40 | username: ${{ secrets.DOCKERHUB_USERNAME }}
41 | password: ${{ secrets.DOCKERHUB_PASSWORD }}
42 |
43 | # prepare image tags
44 | - name: Prepare Image Tags
45 | run: |
46 | echo "TAG_MANAGER=akyriako78/rekuberate-io-sleepcycles:${{ env.TAG_NAME }}" >> $GITHUB_ENV
47 | echo "TAG_RUNNER=akyriako78/rekuberate-io-sleepcycles:${{ env.TAG_NAME }}" >> $GITHUB_ENV
48 |
49 | - name: Build and push manager
50 | uses: docker/build-push-action@v5
51 | with:
52 | context: .
53 | file: ./Dockerfile
54 | platforms: linux/amd64,linux/arm/v7,linux/arm64
55 | push: true
56 | tags: |
57 | ${{ env.TAG_MANAGER }}
58 |
59 | - name: Build and push runner
60 | uses: docker/build-push-action@v5
61 | with:
62 | context: .
63 | file: ./Dockerfile.runner
64 | platforms: linux/amd64,linux/arm/v7,linux/arm64
65 | push: true
66 | tags: |
67 | ${{ env.TAG_RUNNER }}
68 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - dev
7 | pull_request:
8 | permissions:
9 | contents: read
10 | # Optional: allow read access to pull request. Use with `only-new-issues` option.
11 | pull-requests: read
12 | jobs:
13 | golangci:
14 | name: lint
15 | runs-on: ubuntu-latest
16 | timeout-minutes: 5
17 | strategy:
18 | fail-fast: true
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - uses: actions/setup-go@v5
23 | with:
24 | go-version: '1.23'
25 | cache: false
26 |
27 | - name: golangci-lint
28 | uses: golangci/golangci-lint-action@v4
29 | with:
30 | version: v1.61
31 | args: --timeout 5m
32 |
33 | - name: Go Format
34 | run: gofmt -s -w . && git diff --exit-code
35 |
36 | - name: Go Vet
37 | run: go vet ./...
38 |
39 | - name: Go Tidy
40 | run: go mod tidy && git diff --exit-code
41 |
42 | - name: Go Mod
43 | run: go mod download
44 |
45 | - name: Go Mod Verify
46 | run: go mod verify
47 |
48 | # TODO: enable tests
49 | # - name: Go Test
50 | # run: go test -v -count=1 -race -shuffle=on -coverprofile=coverage.txt ./...
51 |
--------------------------------------------------------------------------------
/.github/workflows/lint-helm-test.yaml:
--------------------------------------------------------------------------------
1 | name: Lint and Test Charts
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | lint-test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v3
11 | with:
12 | fetch-depth: 0
13 |
14 | - name: Set up Helm
15 | uses: azure/setup-helm@v4.2.0
16 | with:
17 | version: v3.14.4
18 |
19 | - uses: actions/setup-python@v5
20 | with:
21 | python-version: '3.x'
22 | check-latest: true
23 |
24 | - name: Set up chart-testing
25 | uses: helm/chart-testing-action@v2.6.1
26 |
27 | - name: Run chart-testing (list-changed)
28 | id: list-changed
29 | run: |
30 | changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
31 | if [[ -n "$changed" ]]; then
32 | echo "changed=true" >> "$GITHUB_OUTPUT"
33 | fi
34 |
35 | # - name: Run chart-testing (lint)
36 | # if: steps.list-changed.outputs.changed == 'true'
37 | # run: ct lint --target-branch ${{ github.event.repository.default_branch }}
38 |
39 | # - name: Create kind cluster
40 | # if: steps.list-changed.outputs.changed == 'true'
41 | # uses: helm/kind-action@v1.10.0
42 | #
43 | # - name: Run chart-testing (install)
44 | # if: steps.list-changed.outputs.changed == 'true'
45 | # run: ct install --target-branch ${{ github.event.repository.default_branch }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/releases.yml:
--------------------------------------------------------------------------------
1 | name: Release Charts
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | chart-release:
9 | permissions:
10 | contents: write
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Configure Git
19 | run: |
20 | git config user.name "$GITHUB_ACTOR"
21 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
22 |
23 | - name: Run chart-releaser
24 | uses: helm/chart-releaser-action@v1.6.0
25 | env:
26 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Binaries for programs and plugins
3 | *.exe
4 | *.exe~
5 | *.dll
6 | *.so
7 | *.dylib
8 | bin
9 | testbin/*
10 |
11 | # Test binary, build with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Kubernetes Generated files - skip generated files, except for vendored files
18 |
19 | !vendor/**/zz_generated.*
20 |
21 | # editor and IDE paraphernalia
22 | .idea
23 | *.swp
24 | *.swo
25 | *~
26 |
27 | *.cross
28 | kind.yaml
29 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## New contributor guide
2 |
3 | To get an overview of the project, read the [README](README.md). Here are some resources to help you get started with open source contributions:
4 |
5 | - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
6 | - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
7 | - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
8 | - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
9 |
10 |
11 | ## Getting started
12 |
13 | To navigate our codebase with confidence, see [the introduction to working in the docs repository](/contributing/working-in-docs-repository.md) :confetti_ball:. For more information on how we write our markdown files, see [the GitHub Markdown reference](contributing/content-markup-reference.md).
14 |
15 | Check to see what [types of contributions](/contributing/types-of-contributions.md) we accept before making changes. Some of them don't even require writing a single line of code :sparkles:.
16 |
17 | ### Issues
18 |
19 | #### Create a new issue
20 |
21 | If you spot a problem with the docs, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/github/docs/issues/new/choose).
22 |
23 | #### Solve an issue
24 |
25 | Scan through our [existing issues](https://github.com/github/docs/issues) to find one that interests you. You can narrow down the search using `labels` as filters. See [Labels](/contributing/how-to-use-labels.md) for more information. As a general rule, we don’t assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
26 |
27 | ### Make Changes
28 |
29 | #### Make changes in the UI
30 |
31 | Click **Make a contribution** at the bottom of any docs page to make small changes such as a typo, sentence fix, or a broken link. This takes you to the `.md` file where you can make your changes and [create a pull request](#pull-request) for a review.
32 |
33 |
34 |
35 | #### Make changes in a codespace
36 |
37 | For more information about using a codespace for working on GitHub documentation, see "[Working in a codespace](https://github.com/github/docs/blob/main/contributing/codespace.md)."
38 |
39 | #### Make changes locally
40 |
41 | 1. [Install Git LFS](https://docs.github.com/en/github/managing-large-files/versioning-large-files/installing-git-large-file-storage).
42 |
43 | 2. Fork the repository.
44 | - Using GitHub Desktop:
45 | - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
46 | - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
47 |
48 | - Using the command line:
49 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
50 |
51 | 3. Install or update to **Node.js v16**. For more information, see [the development guide](contributing/development.md).
52 |
53 | 4. Create a working branch and start with your changes!
54 |
55 | ### Commit your update
56 |
57 | Commit the changes once you are happy with them. Don't forget to [self-review](/contributing/self-review.md) to speed up the review process:zap:.
58 |
59 | ### Pull Request
60 |
61 | When you're finished with the changes, create a pull request, also known as a PR.
62 | - Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request.
63 | - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
64 | - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
65 | Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request additional information.
66 | - We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
67 | - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
68 | - If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
69 |
70 | ### Your PR is merged!
71 |
72 | Congratulations :tada::tada: The GitHub team thanks you :sparkles:.
73 |
74 | Once your PR is merged, your contributions will be publicly visible on the [GitHub docs](https://docs.github.com/en).
75 |
76 | Now that you are part of the GitHub docs community, see how else you can [contribute to the docs](/contributing/types-of-contributions.md).
77 |
78 | ## Windows
79 |
80 | This site can be developed on Windows, however a few potential gotchas need to be kept in mind:
81 |
82 | 1. Regular Expressions: Windows uses `\r\n` for line endings, while Unix-based systems use `\n`. Therefore, when working on Regular Expressions, use `\r?\n` instead of `\n` in order to support both environments. The Node.js [`os.EOL`](https://nodejs.org/api/os.html#os_os_eol) property can be used to get an OS-specific end-of-line marker.
83 | 2. Paths: Windows systems use `\` for the path separator, which would be returned by `path.join` and others. You could use `path.posix`, `path.posix.join` etc and the [slash](https://ghub.io/slash) module, if you need forward slashes - like for constructing URLs - or ensure your code works with either.
84 | 3. Bash: Not every Windows developer has a terminal that fully supports Bash, so it's generally preferred to write [scripts](/script) in JavaScript instead of Bash.
85 | 4. Filename too long error: There is a 260 character limit for a filename when Git is compiled with `msys`. While the suggestions below are not guaranteed to work and could cause other issues, a few workarounds include:
86 | - Update Git configuration: `git config --system core.longpaths true`
87 | - Consider using a different Git client on Windows
88 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build the manager binary
2 | FROM golang:1.18 AS builder
3 | ARG TARGETOS
4 | ARG TARGETARCH
5 |
6 | WORKDIR /workspace
7 | # Copy the Go Modules manifests
8 | COPY go.mod go.mod
9 | COPY go.sum go.sum
10 | # cache deps before building and copying source so that we don't need to re-download as much
11 | # and so that source changes don't invalidate our downloaded layer
12 | RUN go mod download
13 |
14 | # Copy the go source
15 | COPY main.go main.go
16 | COPY api/ api/
17 | COPY controllers/ controllers/
18 |
19 | # Build
20 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go
21 |
22 | # Use distroless as minimal base image to package the manager binary
23 | # Refer to https://github.com/GoogleContainerTools/distroless for more details
24 | FROM gcr.io/distroless/static:nonroot
25 | WORKDIR /
26 | COPY --from=builder /workspace/manager .
27 | USER 65532:65532
28 |
29 | ENTRYPOINT ["/manager"]
30 |
--------------------------------------------------------------------------------
/Dockerfile.runners:
--------------------------------------------------------------------------------
1 | # Build the runner binary
2 | FROM golang:1.18 AS builder
3 | ARG TARGETOS
4 | ARG TARGETARCH
5 |
6 | WORKDIR /workspace
7 | # Copy the Go Modules manifests
8 | COPY go.mod go.mod
9 | COPY go.sum go.sum
10 | # cache deps before building and copying source so that we don't need to re-download as much
11 | # and so that source changes don't invalidate our downloaded layer
12 | RUN go mod download
13 |
14 | # Copy the go source
15 | COPY runners/runner.go runner.go
16 |
17 | # Build
18 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o runner runner.go
19 |
20 | # Use distroless as minimal base image to package the manager binary
21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details
22 | FROM gcr.io/distroless/static:nonroot
23 | WORKDIR /
24 | COPY --from=builder /workspace/runner .
25 | USER 65532:65532
26 |
27 | ENTRYPOINT ["/runner"]
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Image URL to use all building/pushing image targets
2 | DOCKER_HUB_NAME ?= akyriako78#$(shell docker info | sed '/Username:/!d;s/.* //')
3 | # sleepcycles
4 | IMG_TAG ?= 0.2.8-rc.0
5 | #IMG_TAG ?= $(shell git rev-parse --short HEAD)
6 | IMG_NAME ?= rekuberate-io-sleepcycles
7 | IMG ?= $(DOCKER_HUB_NAME)/$(IMG_NAME):$(IMG_TAG)
8 | # runners
9 | RUNNERS_IMAGE_TAG ?= 0.2.0-rc.0
10 | RUNNERS_IMG_NAME ?= rekuberate-io-sleepcycles-runners
11 | RUNNERS_IMG ?= $(DOCKER_HUB_NAME)/$(RUNNERS_IMG_NAME):$(RUNNERS_IMAGE_TAG)
12 | RUNNERS_IMG_LATEST ?= $(DOCKER_HUB_NAME)/$(RUNNERS_IMG_NAME):latest
13 | # targets
14 | PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
15 | # CONTAINER_TOOL defines the container tool to be used for building images.
16 | # Be aware that the target commands are only tested with Docker which is
17 | # scaffolded by default. However, you might want to replace it to use other
18 | # tools. (i.e. podman)
19 | CONTAINER_TOOL ?= docker
20 |
21 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
22 | ENVTEST_K8S_VERSION = 1.24.2
23 |
24 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
25 | ifeq (,$(shell go env GOBIN))
26 | GOBIN=$(shell go env GOPATH)/bin
27 | else
28 | GOBIN=$(shell go env GOBIN)
29 | endif
30 |
31 | # Setting SHELL to bash allows bash commands to be executed by recipes.
32 | # Options are set to exit when a recipe line exits non-zero or a piped command fails.
33 | SHELL = /usr/bin/env bash -o pipefail
34 | .SHELLFLAGS = -ec
35 |
36 | .PHONY: all
37 | all: build
38 |
39 | ##@ General
40 |
41 | # The help target prints out all targets with their descriptions organized
42 | # beneath their categories. The categories are represented by '##@' and the
43 | # target descriptions by '##'. The awk commands is responsible for reading the
44 | # entire set of makefiles included in this invocation, looking for lines of the
45 | # file as xyz: ## something, and then pretty-format the target and help. Then,
46 | # if there's a line with ##@ something, that gets pretty-printed as a category.
47 | # More info on the usage of ANSI control characters for terminal formatting:
48 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
49 | # More info on the awk command:
50 | # http://linuxcommand.org/lc3_adv_awk.php
51 |
52 | .PHONY: help
53 | help: ## Display this help.
54 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
55 |
56 | ##@ Development
57 |
58 | .PHONY: manifests
59 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
60 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
61 |
62 | .PHONY: generate
63 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
64 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
65 |
66 | .PHONY: fmt
67 | fmt: ## Run go fmt against code.
68 | go fmt ./...
69 |
70 | .PHONY: vet
71 | vet: ## Run go vet against code.
72 | go vet ./...
73 |
74 | .PHONY: test
75 | test: manifests generate fmt vet envtest ## Run tests.
76 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out
77 |
78 | ##@ Build
79 |
80 | .PHONY: build
81 | build: generate fmt vet ## Build manager binary.
82 | go build -o bin/manager main.go
83 |
84 | .PHONY: run
85 | run: manifests generate fmt vet ## Run a controller from your host.
86 | go run ./main.go
87 |
88 | .PHONY: docker-build
89 | docker-build: test ## Build docker image with the manager.
90 | docker build -t ${IMG} .
91 |
92 | .PHONY: docker-push
93 | docker-push: ## Push docker image with the manager.
94 | docker push ${IMG}
95 |
96 | .PHONY: docker-buildx
97 | docker-buildx: test ## Build and push docker image for the manager for cross-platform support
98 | # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
99 | sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
100 | - $(CONTAINER_TOOL) buildx create --name sleepcycles-builder
101 | $(CONTAINER_TOOL) buildx use sleepcycles-builder
102 | - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
103 | - $(CONTAINER_TOOL) buildx rm sleepcycles-builder
104 | rm Dockerfile.cross
105 |
106 | .PHONY: docker-buildx-runner
107 | docker-buildx-runner: fmt vet ## Build and push docker image for the runner for cross-platform support
108 | # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.runners.cross, and preserve the original Dockerfile.runners
109 | sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile.runners > Dockerfile.runners.cross
110 | - $(CONTAINER_TOOL) buildx create --name runners-builder
111 | $(CONTAINER_TOOL) buildx use runners-builder
112 | - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${RUNNERS_IMG} --tag ${RUNNERS_IMG_LATEST} -f Dockerfile.runners.cross .
113 | - $(CONTAINER_TOOL) buildx rm runners-builder
114 | rm Dockerfile.runners.cross
115 |
116 | ##@ Deployment
117 |
118 | ifndef ignore-not-found
119 | ignore-not-found = false
120 | endif
121 |
122 | .PHONY: install
123 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
124 | $(KUSTOMIZE) build config/crd | kubectl apply -f -
125 |
126 | .PHONY: uninstall
127 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
128 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
129 |
130 | .PHONY: deploy
131 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
132 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
133 | $(KUSTOMIZE) build config/default | kubectl apply -f -
134 |
135 | .PHONY: undeploy
136 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
137 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
138 |
139 | ##@ Build Dependencies
140 |
141 | ## Location to install dependencies to
142 | LOCALBIN ?= $(shell pwd)/bin
143 | $(LOCALBIN):
144 | mkdir -p $(LOCALBIN)
145 |
146 | ## Tool Binaries
147 | KUSTOMIZE ?= $(LOCALBIN)/kustomize
148 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
149 | ENVTEST ?= $(LOCALBIN)/setup-envtest
150 |
151 | ## Tool Versions
152 | KUSTOMIZE_VERSION ?= v3.8.7
153 | CONTROLLER_TOOLS_VERSION ?= v0.9.2
154 |
155 | KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
156 | .PHONY: kustomize
157 | kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
158 | $(KUSTOMIZE): $(LOCALBIN)
159 | test -s $(LOCALBIN)/kustomize || { curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); }
160 |
161 | .PHONY: controller-gen
162 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
163 | $(CONTROLLER_GEN): $(LOCALBIN)
164 | test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
165 |
166 | .PHONY: envtest
167 | envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
168 | $(ENVTEST): $(LOCALBIN)
169 | test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
170 |
171 |
172 | HELMIFY ?= $(LOCALBIN)/helmify
173 |
174 | .PHONY: helmify
175 | helmify: $(HELMIFY) ## Download helmify locally if necessary.
176 | $(HELMIFY): $(LOCALBIN)
177 | test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
178 |
179 | helm: manifests kustomize helmify
180 | $(KUSTOMIZE) build config/default | $(HELMIFY) charts/sleepcycles
--------------------------------------------------------------------------------
/PROJECT:
--------------------------------------------------------------------------------
1 | domain: rekuberate.io
2 | layout:
3 | - go.kubebuilder.io/v3
4 | projectName: sleepcycles
5 | repo: github.com/rekuberate-io/sleepcycles
6 | resources:
7 | - api:
8 | crdVersion: v1
9 | namespaced: true
10 | controller: true
11 | domain: rekuberate.io
12 | group: core
13 | kind: SleepCycle
14 | path: github.com/rekuberate-io/sleepcycles/api/v1alpha1
15 | version: v1alpha1
16 | version: "3"
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Define sleep & wake up cycles for your Kubernetes resources. Automatically schedule to shutdown **Deployments**, **CronJobs**,
4 | **StatefulSets** and **HorizontalPodAutoscalers** that occupy resources in your cluster and wake them up **only** when you need them;
5 | in that way you can:
6 |
7 | - _schedule_ resource-hungry workloads (migrations, synchronizations, replications) in hours that do not impact your daily business
8 | - _depressurize_ your cluster
9 | - _decrease_ your costs
10 | - _reduce_ your power consumption
11 | - _lower_ you carbon footprint
12 |
13 | [//]: # ()
14 |
15 | ## Getting Started
16 | You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) or [K3D](https://k3d.io) to get a local cluster for testing,
17 | or run against a remote cluster.
18 |
19 | ### Samples
20 |
21 | Under `config/samples` you will find a set manifests that you can use to test this sleepcycles on your cluster:
22 |
23 | #### SleepCycles
24 |
25 | * _core_v1alpha1_sleepcycle_app_x.yaml_, manifests to deploy 2 `SleepCycle` resources in namespaces `app-1` and `app-2`
26 |
27 | ```yaml
28 | apiVersion: core.rekuberate.io/v1alpha1
29 | kind: SleepCycle
30 | metadata:
31 | name: sleepcycle-app-1
32 | namespace: app-1
33 | spec:
34 | shutdown: "1/2 * * * *"
35 | shutdownTimeZone: "Europe/Athens"
36 | wakeup: "*/2 * * * *"
37 | wakeupTimeZone: "Europe/Dublin"
38 | enabled: true
39 | ```
40 |
41 | > [!NOTE]
42 | > The cron expressions of the samples are tailored so you perform a quick demo. The `shutdown` expression schedules
43 | > the deployment to scale down on _odd_ minutes and the `wakeup` schedule to scale up on _even_ minutes.
44 |
45 | Every `SleepCycle` has the following **mandatory** properties:
46 |
47 | - `shutdown`: cron expression for your shutdown schedule
48 | - `enabled`: whether this sleepcycle policy is enabled
49 |
50 | and the following **non-mandatory** properties:
51 |
52 | - `shutdownTimeZone`: the timezone for your shutdown schedule, defaults to `UTC`
53 | - `wakeup`: cron expression for your wake-up schedule
54 | - `wakeupTimeZone`: the timezone for your wake-up schedule, defaults to `UTC`
55 | - `successfulJobsHistoryLimit`: how many _completed_ CronJob Runner Pods to retain for debugging reasons, defaults to `1`
56 | - `failedJobsHistoryLimit`: how many _failed_ CronJob Runner Pods to retain for debugging reasons, defaults to `1`
57 | - `runnerImage`: the image to use when spawn CronJob Runner pods, defaults to `akyriako78/rekuberate-io-sleepcycles-runners`
58 |
59 | > [!IMPORTANT]
60 | > DO **NOT** ADD **seconds** or **timezone** information to you cron expressions.
61 |
62 | #### Demo workloads
63 |
64 | * _whoami-app-1_x-deployment.yaml_, manifests to deploy 2 `Deployment` that provisions _traefik/whoami_ in namespace `app-1`
65 | * _whoami-app-2_x-deployment.yaml_, manifests to deploy a `Deployment`that provisions _traefik/whoami_ in namespace `app-2`
66 | * _apache-hpa.yaml_, manifest to deploy an `HorizontalPodAutoscaler` for a PHP application in namespace `app-2`
67 | * _nginx-statefulset.yaml_, manifest to deploy a `Statefulset`in namespace `app-2`
68 | * _busybox-cronjob.yaml_, manifest to deploy a `Statefulset`in namespace `app-1`
69 |
70 | `SleepCycle` is a namespace-scoped custom resource; the controller will monitor all the resources in that namespace that
71 | are marked with a `Label` that has as key `rekuberate.io/sleepcycle:` and as value the `name` of the manifest you created:
72 |
73 | ```yaml
74 | apiVersion: apps/v1
75 | kind: Deployment
76 | metadata:
77 | name: app-2
78 | namespace: app-2
79 | labels:
80 | app: app-2
81 | rekuberate.io/sleepcycle: sleepcycle-app-2
82 | spec:
83 | replicas: 9
84 | selector:
85 | matchLabels:
86 | app: app-2
87 | template:
88 | metadata:
89 | name: app-2
90 | labels:
91 | app: app-2
92 | spec:
93 | containers:
94 | - name: app-2
95 | image: traefik/whoami
96 | imagePullPolicy: IfNotPresent
97 | ```
98 |
99 | > [!IMPORTANT]
100 | > Any workload in namespace `kube-system` marked with `rekuberate.io/sleepcycle` will be ignored by the controller **by design**.
101 |
102 | ## How it works
103 |
104 | The diagram below describes how `rekuberate.io/sleepcycles` are dealing with scheduling a `Deployment`:
105 |
106 | 1. The `sleepcycle-controller` **watches** periodically, every 1min, all the `SleepCycle` custom resources for changes (in **all** namespaces).
107 | 2. The controller, for **every** `SleepCycle` resource within the namespace `app-1`, collects all the resources that have been marked with the label `rekuberate.io/sleepcycle: sleepcycle-app1`.
108 | 3. It provisions, for **every** workload - in this case deployment `deployment-app1` a `CronJob` for the shutdown schedule and optionally a second `CronJob` if a wake-up schedule is provided.
109 | 4. It provisions a `ServiceAccount`, a `Role` and a `RoleBinding` **per namespace**, in order to make possible for runner-pods to update resources' specs.
110 | 5. The `Runner` pods will be created automatically by the cron jobs and are responsible for scaling the resources up or down.
111 |
112 | 
113 |
114 | > [!NOTE]
115 | > In the diagram it was depicted how `rekuberate.io/sleepcycles` scales `Deployment`. The same steps count for a
116 | > `StatefulSet` and a `HorizontalPodAutoscaler`. There are two exception though:
117 | > - a `HorizontalPodAutoscaler` will scale down to `1` replica and not to `0` as for a `Deployment` or a `Statefulset`.
118 | > - a `CronJob` has no replicas to scale up or down, it is going to be enabled or suspended respectively.
119 |
120 | ### Using it together with ArgoCD
121 |
122 | You can combine `rekuberate.io/sleepcycles` with applications provisioned with [ArgoCD](https://argoproj.github.io/cd/),
123 | **as long as** you disable [self-healing](https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/#automatic-self-healing)
124 | when an automatic sync policy is enabled, otherwise ArgoCD's sync mechanism will operate antagonistically towards the
125 | shutdown and wakeup cronjobs. In this case ArgoCD will always automatically revert to the state described in the
126 | git manifests and practically will cancel the effect of the sleepcycle schedule. An ArgoCD application with Manual or
127 | Automatic sync policy **without** self-healing will work as expected.
128 |
129 | 
130 |
131 | > [!TIP]
132 | > There is a git repository in place, with all the necessary artifacts to deploy via ArgoCD
133 | > an nginx application and a preconfigured sleepcycle. You can find the git repo [here](https://github.com/rekuberate-io/argocd-guide).
134 |
135 | ## Deploy
136 |
137 | ### From sources
138 |
139 | 1. Build and push your image to the location specified by `IMG` in `Makefile`:
140 |
141 | ```shell
142 | # Image URL to use all building/pushing image targets
143 | DOCKER_HUB_NAME ?= $(shell docker info | sed '/Username:/!d;s/.* //')
144 | # sleepcycles
145 | IMG_TAG ?= $(shell git rev-parse --short HEAD)
146 | IMG_NAME ?= rekuberate-io-sleepcycles
147 | IMG ?= $(DOCKER_HUB_NAME)/$(IMG_NAME):$(IMG_TAG)
148 | # runners
149 | RUNNERS_IMG_NAME ?= rekuberate-io-sleepcycles-runners
150 | RUNNERS_IMG ?= $(DOCKER_HUB_NAME)/$(RUNNERS_IMG_NAME)
151 | # targets
152 | PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
153 | # CONTAINER_TOOL defines the container tool to be used for building images.
154 | # Be aware that the target commands are only tested with Docker which is
155 | # scaffolded by default. However, you might want to replace it to use other
156 | # tools. (i.e. podman)
157 | CONTAINER_TOOL ?= docker
158 | ```
159 |
160 | ```sh
161 | make docker-buildx
162 | ```
163 |
164 | 2. Deploy the controller to the cluster using the image defined in `IMG`:
165 |
166 | ```sh
167 | make install && make deploy
168 | ```
169 |
170 | and then deploy the samples:
171 |
172 | ```sh
173 | kubectl create namespace app-1
174 | kubectl create namespace app-2
175 | kubectl apply -f config/samples
176 | ```
177 |
178 | #### Uninstall
179 |
180 | ```sh
181 | make undeploy
182 | ```
183 |
184 | ### Using Helm (from sources)
185 |
186 | If you are on a development environment, you can quickly test & deploy the controller to the cluster
187 | using a **Helm chart** directly from `config/helm`:
188 |
189 | ```sh
190 | helm install rekuberate-io-sleepcycles config/helm/ -n --create-namespace
191 | ```
192 |
193 | and then deploy the samples:
194 |
195 | ```sh
196 | kubectl create namespace app-1
197 | kubectl create namespace app-2
198 | kubectl apply -f config/samples
199 | ```
200 | #### Uninstall
201 |
202 | ```shell
203 | helm uninstall rekuberate-io-sleepcycles -n
204 | ```
205 |
206 | ### Using Helm (from repo)
207 |
208 | On the other hand if you are deploying on a production environment, it is **highly recommended** to deploy the
209 | controller to the cluster using a **Helm chart** from its repo:
210 |
211 |
212 | ```sh
213 | helm repo add sleepcycles https://rekuberate-io.github.io/sleepcycles/
214 | helm repo update
215 |
216 | helm upgrade --install sleepcycles sleepcycles/sleepcycles -n rekuberate-system --create-namespace
217 | ```
218 |
219 | and then deploy the samples:
220 |
221 | ```sh
222 | kubectl create namespace app-1
223 | kubectl create namespace app-2
224 | kubectl apply -f config/samples
225 | ```
226 | #### Uninstall
227 |
228 | ```shell
229 | helm uninstall rekuberate-io-sleepcycles -n
230 | ```
231 |
232 | ## Develop
233 |
234 | This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/). It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
235 | which provides a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster.
236 |
237 | ### Controller
238 |
239 | #### Modifying the API definitions
240 | If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
241 |
242 | ```sh
243 | make generate
244 | make manifests
245 | ```
246 |
247 | then install the CRDs in the cluster with:
248 |
249 | ```sh
250 | make install
251 | ```
252 |
253 | > [!TIP]
254 | > You can debug the controller in the IDE of your choice by hooking to the `main.go` **or** you can start
255 | > the controller _without_ debugging with:
256 |
257 | ```sh
258 | make run
259 | ```
260 |
261 | > [!TIP]
262 | > Run `make --help` for more information on all potential `make` targets
263 | > More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
264 |
265 | #### Build
266 |
267 | You always need to build a new docker container and push it to your repository:
268 |
269 | ```sh
270 | make docker-buildx
271 | ```
272 |
273 | > [!IMPORTANT]
274 | > In this case you will need to adjust your Helm chart values to use your repository and container image.
275 |
276 | ### Runner
277 |
278 | #### Build
279 |
280 | Adjust the image and container registry name in `Makefile`:
281 |
282 | ```shell
283 | # runners
284 | RUNNERS_IMG_NAME ?= rekuberate-io-sleepcycles-runners
285 | RUNNERS_IMG ?= $(DOCKER_HUB_NAME)/$(RUNNERS_IMG_NAME)
286 | ```
287 |
288 | and then build and push the new image to the registry:
289 |
290 | ```sh
291 | make docker-buildx-runner
292 | ```
293 |
294 | > [!IMPORTANT]
295 | > In this case you will need to adjust the `runnerImage` of your `SleepCycle` manifest to use your own Runner image,
296 | > otherwise it defaults always to `akyriako78/rekuberate-io-sleepcycles-runners`
297 |
298 | ### Uninstall CRDs
299 | To delete the CRDs from the cluster:
300 |
301 | ```sh
302 | make uninstall
303 | ```
304 |
--------------------------------------------------------------------------------
/api/v1alpha1/groupversion_info.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Package v1alpha1 contains API Schema definitions for the core v1alpha1 API group
18 | // +kubebuilder:object:generate=true
19 | // +groupName=core.rekuberate.io
20 | package v1alpha1
21 |
22 | import (
23 | "k8s.io/apimachinery/pkg/runtime/schema"
24 | "sigs.k8s.io/controller-runtime/pkg/scheme"
25 | )
26 |
27 | var (
28 | // GroupVersion is group version used to register these objects
29 | GroupVersion = schema.GroupVersion{Group: "core.rekuberate.io", Version: "v1alpha1"}
30 |
31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme
32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33 |
34 | // AddToScheme adds the types in this group-version to the given scheme.
35 | AddToScheme = SchemeBuilder.AddToScheme
36 | )
37 |
--------------------------------------------------------------------------------
/api/v1alpha1/sleepcycle_types.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package v1alpha1
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | )
22 |
23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
25 |
26 | // SleepCycleSpec defines the desired state of SleepCycle
27 | type SleepCycleSpec struct {
28 | // +kubebuilder:validation:Pattern:=`(^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)`
29 | // +kubebuilder:validation:Type=string
30 | Shutdown string `json:"shutdown"`
31 |
32 | // +kubebuilder:validation:Optional
33 | // +kubebuilder:default:="UTC"
34 | ShutdownTimeZone *string `json:"shutdownTimeZone,omitempty"`
35 |
36 | // +kubebuilder:validation:Pattern:=`(^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)`
37 | // +kubebuilder:validation:Type=string
38 | WakeUp *string `json:"wakeup,omitempty"`
39 |
40 | // +kubebuilder:validation:Optional
41 | // +kubebuilder:default:="UTC"
42 | WakeupTimeZone *string `json:"wakeupTimeZone,omitempty"`
43 |
44 | // +kubebuilder:validation:default:=true
45 | // +kubebuilder:validation:Type=boolean
46 | Enabled bool `json:"enabled"`
47 |
48 | // +optional
49 | // +kubebuilder:default=0
50 | // +kubebuilder:validation:Type=integer
51 | // +kubebuilder:validation:Minimum=0
52 | // +kubebuilder:validation:Maximum=3
53 | // +kubebuilder:validation:ExclusiveMinimum=false
54 | // +kubebuilder:validation:ExclusiveMaximum=false
55 | SuccessfulJobsHistoryLimit int32 `json:"successfulJobsHistoryLimit,omitempty"`
56 |
57 | // +optional
58 | // +kubebuilder:default=1
59 | // +kubebuilder:validation:Type=integer
60 | // +kubebuilder:validation:Minimum=1
61 | // +kubebuilder:validation:Maximum=3
62 | // +kubebuilder:validation:ExclusiveMinimum=false
63 | // +kubebuilder:validation:ExclusiveMaximum=false
64 | FailedJobsHistoryLimit int32 `json:"failedJobsHistoryLimit,omitempty"`
65 |
66 | // +kubebuilder:validation:Optional
67 | // +kubebuilder:default:="akyriako78/rekuberate-io-sleepcycles-runners"
68 | RunnerImage string `json:"runnerImage,omitempty"`
69 | }
70 |
71 | // SleepCycleStatus defines the observed state of SleepCycle
72 | type SleepCycleStatus struct {
73 | Enabled bool `json:"enabled,omitempty"`
74 | State string `json:"state,omitempty"`
75 | Targets string `json:"targets,omitempty"`
76 | }
77 |
78 | //+kubebuilder:object:root=true
79 | //+kubebuilder:subresource:status
80 |
81 | // SleepCycle is the Schema for the sleepcycles API
82 | // +kubebuilder:printcolumn:name="Enabled",type=boolean,JSONPath=`.spec.enabled`
83 | // +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state`
84 | // +kubebuilder:printcolumn:name="Targets",type=string,JSONPath=`.status.targets`
85 | // +kubebuilder:printcolumn:name="Shutdown Schedule",type=string,JSONPath=`.spec.shutdown`
86 | // +kubebuilder:printcolumn:name="Shutdown Timezone",type=string,JSONPath=`.spec.shutdownTimeZone`
87 | // +kubebuilder:printcolumn:name="Wakeup Schedule",type=string,JSONPath=`.spec.wakeup`
88 | // +kubebuilder:printcolumn:name="Wakeup Timezone",type=string,JSONPath=`.spec.wakeupTimeZone`
89 | type SleepCycle struct {
90 | metav1.TypeMeta `json:",inline"`
91 | metav1.ObjectMeta `json:"metadata,omitempty"`
92 |
93 | Spec SleepCycleSpec `json:"spec,omitempty"`
94 | Status SleepCycleStatus `json:"status,omitempty"`
95 | }
96 |
97 | //+kubebuilder:object:root=true
98 |
99 | // SleepCycleList contains a list of SleepCycle
100 | type SleepCycleList struct {
101 | metav1.TypeMeta `json:",inline"`
102 | metav1.ListMeta `json:"metadata,omitempty"`
103 | Items []SleepCycle `json:"items"`
104 | }
105 |
106 | func init() {
107 | SchemeBuilder.Register(&SleepCycle{}, &SleepCycleList{})
108 | }
109 |
--------------------------------------------------------------------------------
/api/v1alpha1/zz_generated.deepcopy.go:
--------------------------------------------------------------------------------
1 | //go:build !ignore_autogenerated
2 | // +build !ignore_autogenerated
3 |
4 | /*
5 | Copyright 2022.
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | */
19 |
20 | // Code generated by controller-gen. DO NOT EDIT.
21 |
22 | package v1alpha1
23 |
24 | import (
25 | runtime "k8s.io/apimachinery/pkg/runtime"
26 | )
27 |
28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
29 | func (in *SleepCycle) DeepCopyInto(out *SleepCycle) {
30 | *out = *in
31 | out.TypeMeta = in.TypeMeta
32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
33 | in.Spec.DeepCopyInto(&out.Spec)
34 | out.Status = in.Status
35 | }
36 |
37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SleepCycle.
38 | func (in *SleepCycle) DeepCopy() *SleepCycle {
39 | if in == nil {
40 | return nil
41 | }
42 | out := new(SleepCycle)
43 | in.DeepCopyInto(out)
44 | return out
45 | }
46 |
47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
48 | func (in *SleepCycle) DeepCopyObject() runtime.Object {
49 | if c := in.DeepCopy(); c != nil {
50 | return c
51 | }
52 | return nil
53 | }
54 |
55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
56 | func (in *SleepCycleList) DeepCopyInto(out *SleepCycleList) {
57 | *out = *in
58 | out.TypeMeta = in.TypeMeta
59 | in.ListMeta.DeepCopyInto(&out.ListMeta)
60 | if in.Items != nil {
61 | in, out := &in.Items, &out.Items
62 | *out = make([]SleepCycle, len(*in))
63 | for i := range *in {
64 | (*in)[i].DeepCopyInto(&(*out)[i])
65 | }
66 | }
67 | }
68 |
69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SleepCycleList.
70 | func (in *SleepCycleList) DeepCopy() *SleepCycleList {
71 | if in == nil {
72 | return nil
73 | }
74 | out := new(SleepCycleList)
75 | in.DeepCopyInto(out)
76 | return out
77 | }
78 |
79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
80 | func (in *SleepCycleList) DeepCopyObject() runtime.Object {
81 | if c := in.DeepCopy(); c != nil {
82 | return c
83 | }
84 | return nil
85 | }
86 |
87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
88 | func (in *SleepCycleSpec) DeepCopyInto(out *SleepCycleSpec) {
89 | *out = *in
90 | if in.ShutdownTimeZone != nil {
91 | in, out := &in.ShutdownTimeZone, &out.ShutdownTimeZone
92 | *out = new(string)
93 | **out = **in
94 | }
95 | if in.WakeUp != nil {
96 | in, out := &in.WakeUp, &out.WakeUp
97 | *out = new(string)
98 | **out = **in
99 | }
100 | if in.WakeupTimeZone != nil {
101 | in, out := &in.WakeupTimeZone, &out.WakeupTimeZone
102 | *out = new(string)
103 | **out = **in
104 | }
105 | }
106 |
107 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SleepCycleSpec.
108 | func (in *SleepCycleSpec) DeepCopy() *SleepCycleSpec {
109 | if in == nil {
110 | return nil
111 | }
112 | out := new(SleepCycleSpec)
113 | in.DeepCopyInto(out)
114 | return out
115 | }
116 |
117 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
118 | func (in *SleepCycleStatus) DeepCopyInto(out *SleepCycleStatus) {
119 | *out = *in
120 | }
121 |
122 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SleepCycleStatus.
123 | func (in *SleepCycleStatus) DeepCopy() *SleepCycleStatus {
124 | if in == nil {
125 | return nil
126 | }
127 | out := new(SleepCycleStatus)
128 | in.DeepCopyInto(out)
129 | return out
130 | }
131 |
--------------------------------------------------------------------------------
/charts/sleepcycles/.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 |
--------------------------------------------------------------------------------
/charts/sleepcycles/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: sleepcycles
3 | description: Define sleep and wake up cycles for your Kubernetes resources
4 | # A chart can be either an 'application' or a 'library' chart.
5 | #
6 | # Application charts are a collection of templates that can be packaged into versioned archives
7 | # to be deployed.
8 | #
9 | # Library charts provide useful utilities or functions for the chart developer. They're included as
10 | # a dependency of application charts to inject those utilities and functions into the rendering
11 | # pipeline. Library charts do not define any templates and therefore cannot be deployed.
12 | type: application
13 | # This is the chart version. This version number should be incremented each time you make changes
14 | # to the chart and its templates, including the app version.
15 | # Versions are expected to follow Semantic Versioning (https://semver.org/)
16 | version: 0.2.8
17 | # This is the version number of the application being deployed. This version number should be
18 | # incremented each time you make changes to the application. Versions are not expected to
19 | # follow Semantic Versioning. They should reflect the version the application is using.
20 | # It is recommended to use it with quotes.
21 | appVersion: "0.2.8-rc.0"
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/*
2 | Expand the name of the chart.
3 | */}}
4 | {{- define "sleepcycles.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 "sleepcycles.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 "sleepcycles.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "sleepcycles.labels" -}}
37 | helm.sh/chart: {{ include "sleepcycles.chart" . }}
38 | {{ include "sleepcycles.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 "sleepcycles.selectorLabels" -}}
49 | app.kubernetes.io/name: {{ include "sleepcycles.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 "sleepcycles.serviceAccountName" -}}
57 | {{- if .Values.serviceAccount.create }}
58 | {{- default (include "sleepcycles.fullname" .) .Values.serviceAccount.name }}
59 | {{- else }}
60 | {{- default "default" .Values.serviceAccount.name }}
61 | {{- end }}
62 | {{- end }}
63 |
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-controller-manager
5 | labels:
6 | control-plane: controller-manager
7 | {{- include "sleepcycles.labels" . | nindent 4 }}
8 | spec:
9 | replicas: {{ .Values.controllerManager.replicas }}
10 | selector:
11 | matchLabels:
12 | control-plane: controller-manager
13 | {{- include "sleepcycles.selectorLabels" . | nindent 6 }}
14 | template:
15 | metadata:
16 | labels:
17 | control-plane: controller-manager
18 | {{- include "sleepcycles.selectorLabels" . | nindent 8 }}
19 | annotations:
20 | kubectl.kubernetes.io/default-container: manager
21 | spec:
22 | containers:
23 | - args: {{- toYaml .Values.controllerManager.kubeRbacProxy.args | nindent 8 }}
24 | env:
25 | - name: KUBERNETES_CLUSTER_DOMAIN
26 | value: {{ quote .Values.kubernetesClusterDomain }}
27 | image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag
28 | | default .Chart.AppVersion }}
29 | name: kube-rbac-proxy
30 | ports:
31 | - containerPort: 8443
32 | name: https
33 | protocol: TCP
34 | resources: {{- toYaml .Values.controllerManager.kubeRbacProxy.resources | nindent
35 | 10 }}
36 | securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext
37 | | nindent 10 }}
38 | - args: {{- toYaml .Values.controllerManager.manager.args | nindent 8 }}
39 | command:
40 | - /manager
41 | env:
42 | - name: KUBERNETES_CLUSTER_DOMAIN
43 | value: {{ quote .Values.kubernetesClusterDomain }}
44 | image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
45 | | default .Chart.AppVersion }}
46 | livenessProbe:
47 | httpGet:
48 | path: /healthz
49 | port: 8081
50 | initialDelaySeconds: 15
51 | periodSeconds: 20
52 | name: manager
53 | readinessProbe:
54 | httpGet:
55 | path: /readyz
56 | port: 8081
57 | initialDelaySeconds: 5
58 | periodSeconds: 10
59 | resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10
60 | }}
61 | securityContext: {{- toYaml .Values.controllerManager.manager.containerSecurityContext
62 | | nindent 10 }}
63 | securityContext:
64 | runAsNonRoot: true
65 | serviceAccountName: {{ include "sleepcycles.fullname" . }}-controller-manager
66 | terminationGracePeriodSeconds: 10
67 | {{- if .Values.controllerManager.nodeSelector }}
68 | nodeSelector:
69 | {{ toYaml .Values.controllerManager.nodeSelector | indent 8 }}
70 | {{- end }}
71 | {{- if .Values.controllerManager.tolerations }}
72 | tolerations:
73 | {{ toYaml .Values.controllerManager.tolerations | indent 8 }}
74 | {{- end }}
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/leader-election-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: Role
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-leader-election-role
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - configmaps
12 | verbs:
13 | - get
14 | - list
15 | - watch
16 | - create
17 | - update
18 | - patch
19 | - delete
20 | - apiGroups:
21 | - coordination.k8s.io
22 | resources:
23 | - leases
24 | verbs:
25 | - get
26 | - list
27 | - watch
28 | - create
29 | - update
30 | - patch
31 | - delete
32 | - apiGroups:
33 | - ""
34 | resources:
35 | - events
36 | verbs:
37 | - create
38 | - patch
39 | ---
40 | apiVersion: rbac.authorization.k8s.io/v1
41 | kind: RoleBinding
42 | metadata:
43 | name: {{ include "sleepcycles.fullname" . }}-leader-election-rolebinding
44 | labels:
45 | {{- include "sleepcycles.labels" . | nindent 4 }}
46 | roleRef:
47 | apiGroup: rbac.authorization.k8s.io
48 | kind: Role
49 | name: '{{ include "sleepcycles.fullname" . }}-leader-election-role'
50 | subjects:
51 | - kind: ServiceAccount
52 | name: '{{ include "sleepcycles.fullname" . }}-controller-manager'
53 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/manager-config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-manager-config
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | data:
8 | controller_manager_config.yaml: {{ .Values.managerConfig.controllerManagerConfigYaml
9 | | toYaml | indent 1 }}
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/manager-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-manager-role
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - events
12 | verbs:
13 | - create
14 | - patch
15 | - apiGroups:
16 | - ""
17 | resources:
18 | - secrets
19 | verbs:
20 | - create
21 | - delete
22 | - get
23 | - list
24 | - patch
25 | - update
26 | - watch
27 | - apiGroups:
28 | - apps
29 | resources:
30 | - deployments
31 | verbs:
32 | - create
33 | - delete
34 | - get
35 | - list
36 | - patch
37 | - update
38 | - watch
39 | - apiGroups:
40 | - apps
41 | resources:
42 | - replicasets
43 | verbs:
44 | - create
45 | - delete
46 | - get
47 | - list
48 | - patch
49 | - update
50 | - watch
51 | - apiGroups:
52 | - apps
53 | resources:
54 | - statefulsets
55 | verbs:
56 | - create
57 | - delete
58 | - get
59 | - list
60 | - patch
61 | - update
62 | - watch
63 | - apiGroups:
64 | - autoscaling
65 | resources:
66 | - horizontalpodautoscalers
67 | verbs:
68 | - create
69 | - delete
70 | - get
71 | - list
72 | - patch
73 | - update
74 | - watch
75 | - apiGroups:
76 | - batch
77 | resources:
78 | - cronjobs
79 | verbs:
80 | - create
81 | - delete
82 | - get
83 | - list
84 | - patch
85 | - update
86 | - watch
87 | - apiGroups:
88 | - ""
89 | resources:
90 | - replicasets
91 | verbs:
92 | - create
93 | - delete
94 | - get
95 | - list
96 | - patch
97 | - update
98 | - watch
99 | - apiGroups:
100 | - ""
101 | resources:
102 | - serviceaccounts
103 | verbs:
104 | - create
105 | - delete
106 | - get
107 | - list
108 | - patch
109 | - update
110 | - watch
111 | - apiGroups:
112 | - core.rekuberate.io
113 | resources:
114 | - sleepcycles
115 | verbs:
116 | - create
117 | - delete
118 | - get
119 | - list
120 | - patch
121 | - update
122 | - watch
123 | - apiGroups:
124 | - core.rekuberate.io
125 | resources:
126 | - sleepcycles/finalizers
127 | verbs:
128 | - update
129 | - apiGroups:
130 | - core.rekuberate.io
131 | resources:
132 | - sleepcycles/status
133 | verbs:
134 | - get
135 | - patch
136 | - update
137 | - apiGroups:
138 | - rbac.authorization.k8s.io
139 | resources:
140 | - clusterrolebindings
141 | verbs:
142 | - create
143 | - delete
144 | - escalate
145 | - get
146 | - list
147 | - patch
148 | - update
149 | - watch
150 | - apiGroups:
151 | - rbac.authorization.k8s.io
152 | resources:
153 | - clusterroles
154 | verbs:
155 | - create
156 | - delete
157 | - escalate
158 | - get
159 | - list
160 | - patch
161 | - update
162 | - watch
163 | - apiGroups:
164 | - rbac.authorization.k8s.io
165 | resources:
166 | - rolebindings
167 | verbs:
168 | - create
169 | - delete
170 | - escalate
171 | - get
172 | - list
173 | - patch
174 | - update
175 | - watch
176 | - apiGroups:
177 | - rbac.authorization.k8s.io
178 | resources:
179 | - roles
180 | verbs:
181 | - create
182 | - delete
183 | - escalate
184 | - get
185 | - list
186 | - patch
187 | - update
188 | - watch
189 | ---
190 | apiVersion: rbac.authorization.k8s.io/v1
191 | kind: ClusterRoleBinding
192 | metadata:
193 | name: {{ include "sleepcycles.fullname" . }}-manager-rolebinding
194 | labels:
195 | {{- include "sleepcycles.labels" . | nindent 4 }}
196 | roleRef:
197 | apiGroup: rbac.authorization.k8s.io
198 | kind: ClusterRole
199 | name: '{{ include "sleepcycles.fullname" . }}-manager-role'
200 | subjects:
201 | - kind: ServiceAccount
202 | name: '{{ include "sleepcycles.fullname" . }}-controller-manager'
203 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/metrics-reader-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-metrics-reader
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | rules:
8 | - nonResourceURLs:
9 | - /metrics
10 | verbs:
11 | - get
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/metrics-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-controller-manager-metrics-service
5 | labels:
6 | control-plane: controller-manager
7 | {{- include "sleepcycles.labels" . | nindent 4 }}
8 | spec:
9 | type: {{ .Values.metricsService.type }}
10 | selector:
11 | control-plane: controller-manager
12 | {{- include "sleepcycles.selectorLabels" . | nindent 4 }}
13 | ports:
14 | {{- .Values.metricsService.ports | toYaml | nindent 2 -}}
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/proxy-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-proxy-role
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - authentication.k8s.io
10 | resources:
11 | - tokenreviews
12 | verbs:
13 | - create
14 | - apiGroups:
15 | - authorization.k8s.io
16 | resources:
17 | - subjectaccessreviews
18 | verbs:
19 | - create
20 | ---
21 | apiVersion: rbac.authorization.k8s.io/v1
22 | kind: ClusterRoleBinding
23 | metadata:
24 | name: {{ include "sleepcycles.fullname" . }}-proxy-rolebinding
25 | labels:
26 | {{- include "sleepcycles.labels" . | nindent 4 }}
27 | roleRef:
28 | apiGroup: rbac.authorization.k8s.io
29 | kind: ClusterRole
30 | name: '{{ include "sleepcycles.fullname" . }}-proxy-role'
31 | subjects:
32 | - kind: ServiceAccount
33 | name: '{{ include "sleepcycles.fullname" . }}-controller-manager'
34 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: {{ include "sleepcycles.fullname" . }}-controller-manager
5 | labels:
6 | {{- include "sleepcycles.labels" . | nindent 4 }}
7 | annotations:
8 | {{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }}
--------------------------------------------------------------------------------
/charts/sleepcycles/templates/sleepcycle-crd.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: sleepcycles.core.rekuberate.io
5 | annotations:
6 | controller-gen.kubebuilder.io/version: v0.9.2
7 | labels:
8 | {{- include "sleepcycles.labels" . | nindent 4 }}
9 | spec:
10 | group: core.rekuberate.io
11 | names:
12 | kind: SleepCycle
13 | listKind: SleepCycleList
14 | plural: sleepcycles
15 | singular: sleepcycle
16 | scope: Namespaced
17 | versions:
18 | - additionalPrinterColumns:
19 | - jsonPath: .spec.enabled
20 | name: Enabled
21 | type: boolean
22 | - jsonPath: .status.state
23 | name: State
24 | type: string
25 | - jsonPath: .status.targets
26 | name: Targets
27 | type: string
28 | - jsonPath: .spec.shutdown
29 | name: Shutdown Schedule
30 | type: string
31 | - jsonPath: .spec.shutdownTimeZone
32 | name: Shutdown Timezone
33 | type: string
34 | - jsonPath: .spec.wakeup
35 | name: Wakeup Schedule
36 | type: string
37 | - jsonPath: .spec.wakeupTimeZone
38 | name: Wakeup Timezone
39 | type: string
40 | name: v1alpha1
41 | schema:
42 | openAPIV3Schema:
43 | description: SleepCycle is the Schema for the sleepcycles API
44 | properties:
45 | apiVersion:
46 | description: 'APIVersion defines the versioned schema of this representation
47 | of an object. Servers should convert recognized schemas to the latest
48 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
49 | type: string
50 | kind:
51 | description: 'Kind is a string value representing the REST resource this
52 | object represents. Servers may infer this from the endpoint the client
53 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
54 | type: string
55 | metadata:
56 | type: object
57 | spec:
58 | description: SleepCycleSpec defines the desired state of SleepCycle
59 | properties:
60 | enabled:
61 | type: boolean
62 | failedJobsHistoryLimit:
63 | default: 1
64 | format: int32
65 | maximum: 3
66 | minimum: 1
67 | type: integer
68 | runnerImage:
69 | default: akyriako78/rekuberate-io-sleepcycles-runners
70 | type: string
71 | shutdown:
72 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
73 | type: string
74 | shutdownTimeZone:
75 | default: UTC
76 | type: string
77 | successfulJobsHistoryLimit:
78 | default: 1
79 | format: int32
80 | maximum: 3
81 | minimum: 1
82 | type: integer
83 | wakeup:
84 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
85 | type: string
86 | wakeupTimeZone:
87 | default: UTC
88 | type: string
89 | required:
90 | - enabled
91 | - shutdown
92 | type: object
93 | status:
94 | description: SleepCycleStatus defines the observed state of SleepCycle
95 | properties:
96 | enabled:
97 | type: boolean
98 | state:
99 | type: string
100 | targets:
101 | type: string
102 | type: object
103 | type: object
104 | served: true
105 | storage: true
106 | subresources:
107 | status: {}
108 | status:
109 | acceptedNames:
110 | kind: ""
111 | plural: ""
112 | conditions: []
113 | storedVersions: []
--------------------------------------------------------------------------------
/charts/sleepcycles/values.yaml:
--------------------------------------------------------------------------------
1 | controllerManager:
2 | kubeRbacProxy:
3 | args:
4 | - --secure-listen-address=0.0.0.0:8443
5 | - --upstream=http://127.0.0.1:8080/
6 | - --logtostderr=true
7 | - --v=0
8 | containerSecurityContext:
9 | allowPrivilegeEscalation: false
10 | capabilities:
11 | drop:
12 | - ALL
13 | image:
14 | repository: quay.io/brancz/kube-rbac-proxy
15 | tag: v0.13.0
16 | resources:
17 | limits:
18 | cpu: 500m
19 | memory: 128Mi
20 | requests:
21 | cpu: 5m
22 | memory: 64Mi
23 | manager:
24 | args:
25 | - --health-probe-bind-address=:8081
26 | - --metrics-bind-address=127.0.0.1:8080
27 | - --leader-elect
28 | containerSecurityContext:
29 | allowPrivilegeEscalation: false
30 | capabilities:
31 | drop:
32 | - ALL
33 | image:
34 | repository: akyriako78/rekuberate-io-sleepcycles
35 | tag: 0.2.8-rc.0
36 | resources:
37 | limits:
38 | cpu: 500m
39 | memory: 128Mi
40 | requests:
41 | cpu: 10m
42 | memory: 64Mi
43 | replicas: 1
44 | nodeSelector: {}
45 | tolerations: []
46 | serviceAccount:
47 | annotations: {}
48 | kubernetesClusterDomain: cluster.local
49 | managerConfig:
50 | controllerManagerConfigYaml: |-
51 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
52 | kind: ControllerManagerConfig
53 | health:
54 | healthProbeBindAddress: :8081
55 | metrics:
56 | bindAddress: 127.0.0.1:8080
57 | webhook:
58 | port: 9443
59 | leaderElection:
60 | leaderElect: true
61 | resourceName: 04cbe4c0.rekuberate.io
62 | # leaderElectionReleaseOnCancel defines if the leader should step down volume
63 | # when the Manager ends. This requires the binary to immediately end when the
64 | # Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
65 | # speeds up voluntary leader transitions as the new leader don't have to wait
66 | # LeaseDuration time first.
67 | # In the default scaffold provided, the program ends immediately after
68 | # the manager stops, so would be fine to enable this option. However,
69 | # if you are doing or is intended to do any operation such as perform cleanups
70 | # after the manager stops then its usage might be unsafe.
71 | # leaderElectionReleaseOnCancel: true
72 | metricsService:
73 | ports:
74 | - name: https
75 | port: 8443
76 | protocol: TCP
77 | targetPort: https
78 | type: ClusterIP
79 |
--------------------------------------------------------------------------------
/config/crd/bases/core.rekuberate.io_sleepcycles.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | annotations:
6 | controller-gen.kubebuilder.io/version: v0.9.2
7 | creationTimestamp: null
8 | name: sleepcycles.core.rekuberate.io
9 | spec:
10 | group: core.rekuberate.io
11 | names:
12 | kind: SleepCycle
13 | listKind: SleepCycleList
14 | plural: sleepcycles
15 | singular: sleepcycle
16 | scope: Namespaced
17 | versions:
18 | - additionalPrinterColumns:
19 | - jsonPath: .spec.enabled
20 | name: Enabled
21 | type: boolean
22 | - jsonPath: .status.state
23 | name: State
24 | type: string
25 | - jsonPath: .status.targets
26 | name: Targets
27 | type: string
28 | - jsonPath: .spec.shutdown
29 | name: Shutdown Schedule
30 | type: string
31 | - jsonPath: .spec.shutdownTimeZone
32 | name: Shutdown Timezone
33 | type: string
34 | - jsonPath: .spec.wakeup
35 | name: Wakeup Schedule
36 | type: string
37 | - jsonPath: .spec.wakeupTimeZone
38 | name: Wakeup Timezone
39 | type: string
40 | name: v1alpha1
41 | schema:
42 | openAPIV3Schema:
43 | description: SleepCycle is the Schema for the sleepcycles API
44 | properties:
45 | apiVersion:
46 | description: 'APIVersion defines the versioned schema of this representation
47 | of an object. Servers should convert recognized schemas to the latest
48 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
49 | type: string
50 | kind:
51 | description: 'Kind is a string value representing the REST resource this
52 | object represents. Servers may infer this from the endpoint the client
53 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
54 | type: string
55 | metadata:
56 | type: object
57 | spec:
58 | description: SleepCycleSpec defines the desired state of SleepCycle
59 | properties:
60 | enabled:
61 | type: boolean
62 | failedJobsHistoryLimit:
63 | default: 1
64 | format: int32
65 | maximum: 3
66 | minimum: 1
67 | type: integer
68 | runnerImage:
69 | default: akyriako78/rekuberate-io-sleepcycles-runners
70 | type: string
71 | shutdown:
72 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
73 | type: string
74 | shutdownTimeZone:
75 | default: UTC
76 | type: string
77 | successfulJobsHistoryLimit:
78 | default: 0
79 | format: int32
80 | maximum: 3
81 | minimum: 0
82 | type: integer
83 | wakeup:
84 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
85 | type: string
86 | wakeupTimeZone:
87 | default: UTC
88 | type: string
89 | required:
90 | - enabled
91 | - shutdown
92 | type: object
93 | status:
94 | description: SleepCycleStatus defines the observed state of SleepCycle
95 | properties:
96 | enabled:
97 | type: boolean
98 | state:
99 | type: string
100 | targets:
101 | type: string
102 | type: object
103 | type: object
104 | served: true
105 | storage: true
106 | subresources:
107 | status: {}
108 |
--------------------------------------------------------------------------------
/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/core.rekuberate.io_sleepcycles.yaml
6 | #+kubebuilder:scaffold:crdkustomizeresource
7 |
8 | patchesStrategicMerge:
9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
10 | # patches here are for enabling the conversion webhook for each CRD
11 | #- patches/webhook_in_sleepcycles.yaml
12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch
13 |
14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
15 | # patches here are for enabling the CA injection for each CRD
16 | #- patches/cainjection_in_sleepcycles.yaml
17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch
18 |
19 | # the following config is for teaching kustomize how to do kustomization for CRDs.
20 | configurations:
21 | - kustomizeconfig.yaml
22 |
--------------------------------------------------------------------------------
/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 | version: v1
8 | group: apiextensions.k8s.io
9 | path: spec/conversion/webhook/clientConfig/service/name
10 |
11 | namespace:
12 | - kind: CustomResourceDefinition
13 | version: v1
14 | group: apiextensions.k8s.io
15 | path: spec/conversion/webhook/clientConfig/service/namespace
16 | create: false
17 |
18 | varReference:
19 | - path: metadata/annotations
20 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_sleepcycles.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | annotations:
6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
7 | name: sleepcycles.core.rekuberate.io
8 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_sleepcycles.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables a conversion webhook for the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | name: sleepcycles.core.rekuberate.io
6 | spec:
7 | conversion:
8 | strategy: Webhook
9 | webhook:
10 | clientConfig:
11 | service:
12 | namespace: system
13 | name: webhook-service
14 | path: /convert
15 | conversionReviewVersions:
16 | - v1
17 |
--------------------------------------------------------------------------------
/config/default/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # Adds namespace to all resources.
2 | namespace: sleepcycles-system
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: sleepcycles-
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 | # Mount the controller config file for loading manager configurations
34 | # through a ComponentConfig type
35 | #- manager_config_patch.yaml
36 |
37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
38 | # crd/kustomization.yaml
39 | #- manager_webhook_patch.yaml
40 |
41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
43 | # 'CERTMANAGER' needs to be enabled to use ca injection
44 | #- webhookcainjection_patch.yaml
45 |
46 | # the following config is for teaching kustomize how to do var substitution
47 | vars:
48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
50 | # objref:
51 | # kind: Certificate
52 | # group: cert-manager.io
53 | # version: v1
54 | # name: serving-cert # this name should match the one in certificate.yaml
55 | # fieldref:
56 | # fieldpath: metadata.namespace
57 | #- name: CERTIFICATE_NAME
58 | # objref:
59 | # kind: Certificate
60 | # group: cert-manager.io
61 | # version: v1
62 | # name: serving-cert # this name should match the one in certificate.yaml
63 | #- name: SERVICE_NAMESPACE # namespace of the service
64 | # objref:
65 | # kind: Service
66 | # version: v1
67 | # name: webhook-service
68 | # fieldref:
69 | # fieldpath: metadata.namespace
70 | #- name: SERVICE_NAME
71 | # objref:
72 | # kind: Service
73 | # version: v1
74 | # name: webhook-service
75 |
--------------------------------------------------------------------------------
/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 | securityContext:
14 | allowPrivilegeEscalation: false
15 | capabilities:
16 | drop:
17 | - "ALL"
18 | image: quay.io/brancz/kube-rbac-proxy
19 | args:
20 | - "--secure-listen-address=0.0.0.0:8443"
21 | - "--upstream=http://127.0.0.1:8080/"
22 | - "--logtostderr=true"
23 | - "--v=0"
24 | ports:
25 | - containerPort: 8443
26 | protocol: TCP
27 | name: https
28 | resources:
29 | limits:
30 | cpu: 500m
31 | memory: 128Mi
32 | requests:
33 | cpu: 5m
34 | memory: 64Mi
35 | - name: manager
36 | args:
37 | - "--health-probe-bind-address=:8081"
38 | - "--metrics-bind-address=127.0.0.1:8080"
39 | - "--leader-elect"
40 |
--------------------------------------------------------------------------------
/config/default/manager_config_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 | args:
12 | - "--config=controller_manager_config.yaml"
13 | volumeMounts:
14 | - name: manager-config
15 | mountPath: /controller_manager_config.yaml
16 | subPath: controller_manager_config.yaml
17 | volumes:
18 | - name: manager-config
19 | configMap:
20 | name: manager-config
21 |
--------------------------------------------------------------------------------
/config/helm/.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 |
--------------------------------------------------------------------------------
/config/helm/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: rekuberate-io-sleepcycles
3 | description: A Helm chart for Kubernetes
4 | # A chart can be either an 'application' or a 'library' chart.
5 | #
6 | # Application charts are a collection of templates that can be packaged into versioned archives
7 | # to be deployed.
8 | #
9 | # Library charts provide useful utilities or functions for the chart developer. They're included as
10 | # a dependency of application charts to inject those utilities and functions into the rendering
11 | # pipeline. Library charts do not define any templates and therefore cannot be deployed.
12 | type: application
13 | # This is the chart version. This version number should be incremented each time you make changes
14 | # to the chart and its templates, including the app version.
15 | # Versions are expected to follow Semantic Versioning (https://semver.org/)
16 | version: 0.1.0
17 | # This is the version number of the application being deployed. This version number should be
18 | # incremented each time you make changes to the application. Versions are not expected to
19 | # follow Semantic Versioning. They should reflect the version the application is using.
20 | # It is recommended to use it with quotes.
21 | appVersion: "0.1.2"
22 |
--------------------------------------------------------------------------------
/config/helm/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/*
2 | Expand the name of the chart.
3 | */}}
4 | {{- define "helm.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 "helm.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 "helm.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "helm.labels" -}}
37 | helm.sh/chart: {{ include "helm.chart" . }}
38 | {{ include "helm.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 "helm.selectorLabels" -}}
49 | app.kubernetes.io/name: {{ include "helm.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 "helm.serviceAccountName" -}}
57 | {{- if .Values.serviceAccount.create }}
58 | {{- default (include "helm.fullname" .) .Values.serviceAccount.name }}
59 | {{- else }}
60 | {{- default "default" .Values.serviceAccount.name }}
61 | {{- end }}
62 | {{- end }}
63 |
--------------------------------------------------------------------------------
/config/helm/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-controller-manager
5 | labels:
6 | control-plane: controller-manager
7 | {{- include "helm.labels" . | nindent 4 }}
8 | spec:
9 | replicas: {{ .Values.controllerManager.replicas }}
10 | selector:
11 | matchLabels:
12 | control-plane: controller-manager
13 | {{- include "helm.selectorLabels" . | nindent 6 }}
14 | template:
15 | metadata:
16 | labels:
17 | control-plane: controller-manager
18 | {{- include "helm.selectorLabels" . | nindent 8 }}
19 | annotations:
20 | kubectl.kubernetes.io/default-container: manager
21 | spec:
22 | containers:
23 | - args: {{- toYaml .Values.controllerManager.kubeRbacProxy.args | nindent 8 }}
24 | env:
25 | - name: KUBERNETES_CLUSTER_DOMAIN
26 | value: {{ quote .Values.kubernetesClusterDomain }}
27 | image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag
28 | | default .Chart.AppVersion }}
29 | name: kube-rbac-proxy
30 | ports:
31 | - containerPort: 8443
32 | name: https
33 | protocol: TCP
34 | resources: {{- toYaml .Values.controllerManager.kubeRbacProxy.resources | nindent
35 | 10 }}
36 | securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext
37 | | nindent 10 }}
38 | - args: {{- toYaml .Values.controllerManager.manager.args | nindent 8 }}
39 | command:
40 | - /manager
41 | env:
42 | - name: KUBERNETES_CLUSTER_DOMAIN
43 | value: {{ quote .Values.kubernetesClusterDomain }}
44 | image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
45 | | default .Chart.AppVersion }}
46 | livenessProbe:
47 | httpGet:
48 | path: /healthz
49 | port: 8081
50 | initialDelaySeconds: 15
51 | periodSeconds: 20
52 | name: manager
53 | readinessProbe:
54 | httpGet:
55 | path: /readyz
56 | port: 8081
57 | initialDelaySeconds: 5
58 | periodSeconds: 10
59 | resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10
60 | }}
61 | securityContext: {{- toYaml .Values.controllerManager.manager.containerSecurityContext
62 | | nindent 10 }}
63 | securityContext:
64 | runAsNonRoot: true
65 | serviceAccountName: {{ include "helm.fullname" . }}-controller-manager
66 | terminationGracePeriodSeconds: 10
--------------------------------------------------------------------------------
/config/helm/templates/leader-election-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: Role
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-leader-election-role
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - configmaps
12 | verbs:
13 | - get
14 | - list
15 | - watch
16 | - create
17 | - update
18 | - patch
19 | - delete
20 | - apiGroups:
21 | - coordination.k8s.io
22 | resources:
23 | - leases
24 | verbs:
25 | - get
26 | - list
27 | - watch
28 | - create
29 | - update
30 | - patch
31 | - delete
32 | - apiGroups:
33 | - ""
34 | resources:
35 | - events
36 | verbs:
37 | - create
38 | - patch
39 | ---
40 | apiVersion: rbac.authorization.k8s.io/v1
41 | kind: RoleBinding
42 | metadata:
43 | name: {{ include "helm.fullname" . }}-leader-election-rolebinding
44 | labels:
45 | {{- include "helm.labels" . | nindent 4 }}
46 | roleRef:
47 | apiGroup: rbac.authorization.k8s.io
48 | kind: Role
49 | name: '{{ include "helm.fullname" . }}-leader-election-role'
50 | subjects:
51 | - kind: ServiceAccount
52 | name: '{{ include "helm.fullname" . }}-controller-manager'
53 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/config/helm/templates/manager-config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-manager-config
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | data:
8 | controller_manager_config.yaml: {{ .Values.managerConfig.controllerManagerConfigYaml
9 | | toYaml | indent 1 }}
--------------------------------------------------------------------------------
/config/helm/templates/manager-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-manager-role
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - events
12 | verbs:
13 | - create
14 | - patch
15 | - apiGroups:
16 | - apps
17 | resources:
18 | - deployments
19 | verbs:
20 | - create
21 | - delete
22 | - get
23 | - list
24 | - patch
25 | - update
26 | - watch
27 | - apiGroups:
28 | - apps
29 | resources:
30 | - replicasets
31 | verbs:
32 | - create
33 | - delete
34 | - get
35 | - list
36 | - patch
37 | - update
38 | - watch
39 | - apiGroups:
40 | - apps
41 | resources:
42 | - statefulsets
43 | verbs:
44 | - create
45 | - delete
46 | - get
47 | - list
48 | - patch
49 | - update
50 | - watch
51 | - apiGroups:
52 | - autoscaling
53 | resources:
54 | - horizontalpodautoscalers
55 | verbs:
56 | - create
57 | - delete
58 | - get
59 | - list
60 | - patch
61 | - update
62 | - watch
63 | - apiGroups:
64 | - batch
65 | resources:
66 | - cronjobs
67 | verbs:
68 | - create
69 | - delete
70 | - get
71 | - list
72 | - patch
73 | - update
74 | - watch
75 | - apiGroups:
76 | - ""
77 | resources:
78 | - replicasets
79 | verbs:
80 | - create
81 | - delete
82 | - get
83 | - list
84 | - patch
85 | - update
86 | - watch
87 | - apiGroups:
88 | - core.rekuberate.io
89 | resources:
90 | - sleepcycles
91 | verbs:
92 | - create
93 | - delete
94 | - get
95 | - list
96 | - patch
97 | - update
98 | - watch
99 | - apiGroups:
100 | - core.rekuberate.io
101 | resources:
102 | - sleepcycles/finalizers
103 | verbs:
104 | - update
105 | - apiGroups:
106 | - core.rekuberate.io
107 | resources:
108 | - sleepcycles/status
109 | verbs:
110 | - get
111 | - patch
112 | - update
113 | ---
114 | apiVersion: rbac.authorization.k8s.io/v1
115 | kind: ClusterRoleBinding
116 | metadata:
117 | name: {{ include "helm.fullname" . }}-manager-rolebinding
118 | labels:
119 | {{- include "helm.labels" . | nindent 4 }}
120 | roleRef:
121 | apiGroup: rbac.authorization.k8s.io
122 | kind: ClusterRole
123 | name: '{{ include "helm.fullname" . }}-manager-role'
124 | subjects:
125 | - kind: ServiceAccount
126 | name: '{{ include "helm.fullname" . }}-controller-manager'
127 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/config/helm/templates/metrics-reader-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-metrics-reader
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | rules:
8 | - nonResourceURLs:
9 | - /metrics
10 | verbs:
11 | - get
--------------------------------------------------------------------------------
/config/helm/templates/metrics-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-controller-manager-metrics-service
5 | labels:
6 | control-plane: controller-manager
7 | {{- include "helm.labels" . | nindent 4 }}
8 | spec:
9 | type: {{ .Values.metricsService.type }}
10 | selector:
11 | control-plane: controller-manager
12 | {{- include "helm.selectorLabels" . | nindent 4 }}
13 | ports:
14 | {{- .Values.metricsService.ports | toYaml | nindent 2 -}}
--------------------------------------------------------------------------------
/config/helm/templates/proxy-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-proxy-role
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | rules:
8 | - apiGroups:
9 | - authentication.k8s.io
10 | resources:
11 | - tokenreviews
12 | verbs:
13 | - create
14 | - apiGroups:
15 | - authorization.k8s.io
16 | resources:
17 | - subjectaccessreviews
18 | verbs:
19 | - create
20 | ---
21 | apiVersion: rbac.authorization.k8s.io/v1
22 | kind: ClusterRoleBinding
23 | metadata:
24 | name: {{ include "helm.fullname" . }}-proxy-rolebinding
25 | labels:
26 | {{- include "helm.labels" . | nindent 4 }}
27 | roleRef:
28 | apiGroup: rbac.authorization.k8s.io
29 | kind: ClusterRole
30 | name: '{{ include "helm.fullname" . }}-proxy-role'
31 | subjects:
32 | - kind: ServiceAccount
33 | name: '{{ include "helm.fullname" . }}-controller-manager'
34 | namespace: '{{ .Release.Namespace }}'
--------------------------------------------------------------------------------
/config/helm/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: {{ include "helm.fullname" . }}-controller-manager
5 | labels:
6 | {{- include "helm.labels" . | nindent 4 }}
7 | annotations:
8 | {{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }}
--------------------------------------------------------------------------------
/config/helm/templates/sleepcycle-crd.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: sleepcycles.core.rekuberate.io
5 | annotations:
6 | controller-gen.kubebuilder.io/version: v0.9.2
7 | labels:
8 | {{- include "helm.labels" . | nindent 4 }}
9 | spec:
10 | group: core.rekuberate.io
11 | names:
12 | kind: SleepCycle
13 | listKind: SleepCycleList
14 | plural: sleepcycles
15 | singular: sleepcycle
16 | scope: Namespaced
17 | versions:
18 | - additionalPrinterColumns:
19 | - jsonPath: .spec.shutdown
20 | name: Shutdown Schedule
21 | type: string
22 | - jsonPath: .spec.shutdownTimeZone
23 | name: Shutdown Timezone
24 | type: string
25 | - jsonPath: .spec.wakeup
26 | name: Wakeup Schedule
27 | type: string
28 | - jsonPath: .spec.wakeupTimeZone
29 | name: Wakeup Timezone
30 | type: string
31 | - jsonPath: .spec.enabled
32 | name: Enabled
33 | type: boolean
34 | - jsonPath: .status.lastRunWasSuccessful
35 | name: Success
36 | type: boolean
37 | - jsonPath: .status.lastRunOperation
38 | name: Last Op
39 | type: string
40 | name: v1alpha1
41 | schema:
42 | openAPIV3Schema:
43 | description: SleepCycle is the Schema for the sleepcycles API
44 | properties:
45 | apiVersion:
46 | description: 'APIVersion defines the versioned schema of this representation
47 | of an object. Servers should convert recognized schemas to the latest
48 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
49 | type: string
50 | kind:
51 | description: 'Kind is a string value representing the REST resource this
52 | object represents. Servers may infer this from the endpoint the client
53 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
54 | type: string
55 | metadata:
56 | type: object
57 | spec:
58 | description: SleepCycleSpec defines the desired state of SleepCycle
59 | properties:
60 | enabled:
61 | type: boolean
62 | shutdown:
63 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
64 | type: string
65 | shutdownTimeZone:
66 | default: UTC
67 | type: string
68 | wakeup:
69 | pattern: (^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)
70 | type: string
71 | wakeupTimeZone:
72 | default: UTC
73 | type: string
74 | required:
75 | - enabled
76 | - shutdown
77 | type: object
78 | status:
79 | description: SleepCycleStatus defines the observed state of SleepCycle
80 | properties:
81 | enabled:
82 | type: boolean
83 | lastRunOperation:
84 | type: string
85 | lastRunTime:
86 | format: date-time
87 | type: string
88 | lastRunWasSuccessful:
89 | type: boolean
90 | nextScheduledOp:
91 | type: string
92 | nextScheduledShutdown:
93 | format: date-time
94 | type: string
95 | nextScheduledWakeUp:
96 | format: date-time
97 | type: string
98 | usedBy:
99 | additionalProperties:
100 | type: integer
101 | type: object
102 | type: object
103 | type: object
104 | served: true
105 | storage: true
106 | subresources:
107 | status: {}
108 | status:
109 | acceptedNames:
110 | kind: ""
111 | plural: ""
112 | conditions: []
113 | storedVersions: []
--------------------------------------------------------------------------------
/config/helm/values.yaml:
--------------------------------------------------------------------------------
1 | controllerManager:
2 | kubeRbacProxy:
3 | args:
4 | - --secure-listen-address=0.0.0.0:8443
5 | - --upstream=http://127.0.0.1:8080/
6 | - --logtostderr=true
7 | - --v=0
8 | containerSecurityContext:
9 | allowPrivilegeEscalation: false
10 | capabilities:
11 | drop:
12 | - ALL
13 | image:
14 | repository: quay.io/brancz/kube-rbac-proxy
15 | tag: v0.13.0
16 | resources:
17 | limits:
18 | cpu: 500m
19 | memory: 128Mi
20 | requests:
21 | cpu: 5m
22 | memory: 64Mi
23 | manager:
24 | args:
25 | - --health-probe-bind-address=:8081
26 | - --metrics-bind-address=127.0.0.1:8080
27 | - --leader-elect
28 | containerSecurityContext:
29 | allowPrivilegeEscalation: false
30 | capabilities:
31 | drop:
32 | - ALL
33 | image:
34 | repository: akyriako78/rekuberate-io-sleepcycles
35 | tag: 0.1.2
36 | resources:
37 | limits:
38 | cpu: 500m
39 | memory: 128Mi
40 | requests:
41 | cpu: 10m
42 | memory: 64Mi
43 | replicas: 1
44 | serviceAccount:
45 | annotations: {}
46 | kubernetesClusterDomain: cluster.local
47 | managerConfig:
48 | controllerManagerConfigYaml: |-
49 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
50 | kind: ControllerManagerConfig
51 | health:
52 | healthProbeBindAddress: :8081
53 | metrics:
54 | bindAddress: 127.0.0.1:8080
55 | webhook:
56 | port: 9443
57 | leaderElection:
58 | leaderElect: true
59 | resourceName: 04cbe4c0.rekuberate.io
60 | # leaderElectionReleaseOnCancel defines if the leader should step down volume
61 | # when the Manager ends. This requires the binary to immediately end when the
62 | # Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
63 | # speeds up voluntary leader transitions as the new leader don't have to wait
64 | # LeaseDuration time first.
65 | # In the default scaffold provided, the program ends immediately after
66 | # the manager stops, so would be fine to enable this option. However,
67 | # if you are doing or is intended to do any operation such as perform cleanups
68 | # after the manager stops then its usage might be unsafe.
69 | # leaderElectionReleaseOnCancel: true
70 | metricsService:
71 | ports:
72 | - name: https
73 | port: 8443
74 | protocol: TCP
75 | targetPort: https
76 | type: ClusterIP
77 |
--------------------------------------------------------------------------------
/config/manager/controller_manager_config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
2 | kind: ControllerManagerConfig
3 | health:
4 | healthProbeBindAddress: :8081
5 | metrics:
6 | bindAddress: 127.0.0.1:8080
7 | webhook:
8 | port: 9443
9 | leaderElection:
10 | leaderElect: true
11 | resourceName: 04cbe4c0.rekuberate.io
12 | # leaderElectionReleaseOnCancel defines if the leader should step down volume
13 | # when the Manager ends. This requires the binary to immediately end when the
14 | # Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
15 | # speeds up voluntary leader transitions as the new leader don't have to wait
16 | # LeaseDuration time first.
17 | # In the default scaffold provided, the program ends immediately after
18 | # the manager stops, so would be fine to enable this option. However,
19 | # if you are doing or is intended to do any operation such as perform cleanups
20 | # after the manager stops then its usage might be unsafe.
21 | # leaderElectionReleaseOnCancel: true
22 |
--------------------------------------------------------------------------------
/config/manager/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - manager.yaml
3 |
4 | generatorOptions:
5 | disableNameSuffixHash: true
6 |
7 | configMapGenerator:
8 | - files:
9 | - controller_manager_config.yaml
10 | name: manager-config
11 | apiVersion: kustomize.config.k8s.io/v1beta1
12 | kind: Kustomization
13 | images:
14 | - name: controller
15 | newName: akyriako78/rekuberate-io-sleepcycles
16 | newTag: 0.2.8-dev.7
17 |
--------------------------------------------------------------------------------
/config/manager/manager.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | labels:
5 | control-plane: controller-manager
6 | name: system
7 | ---
8 | apiVersion: apps/v1
9 | kind: Deployment
10 | metadata:
11 | name: controller-manager
12 | namespace: system
13 | labels:
14 | control-plane: controller-manager
15 | spec:
16 | selector:
17 | matchLabels:
18 | control-plane: controller-manager
19 | replicas: 1
20 | template:
21 | metadata:
22 | annotations:
23 | kubectl.kubernetes.io/default-container: manager
24 | labels:
25 | control-plane: controller-manager
26 | spec:
27 | securityContext:
28 | runAsNonRoot: true
29 | # TODO(user): For common cases that do not require escalating privileges
30 | # it is recommended to ensure that all your Pods/Containers are restrictive.
31 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
32 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes
33 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
34 | # seccompProfile:
35 | # type: RuntimeDefault
36 | containers:
37 | - command:
38 | - /manager
39 | args:
40 | - --leader-elect
41 | image: controller:latest
42 | name: manager
43 | securityContext:
44 | allowPrivilegeEscalation: false
45 | capabilities:
46 | drop:
47 | - "ALL"
48 | livenessProbe:
49 | httpGet:
50 | path: /healthz
51 | port: 8081
52 | initialDelaySeconds: 15
53 | periodSeconds: 20
54 | readinessProbe:
55 | httpGet:
56 | path: /readyz
57 | port: 8081
58 | initialDelaySeconds: 5
59 | periodSeconds: 10
60 | # TODO(user): Configure the resources accordingly based on the project requirements.
61 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
62 | resources:
63 | limits:
64 | cpu: 500m
65 | memory: 128Mi
66 | requests:
67 | cpu: 10m
68 | memory: 64Mi
69 | serviceAccountName: controller-manager
70 | terminationGracePeriodSeconds: 10
71 |
--------------------------------------------------------------------------------
/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 | scheme: https
15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
16 | tlsConfig:
17 | insecureSkipVerify: true
18 | selector:
19 | matchLabels:
20 | control-plane: controller-manager
21 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_client_clusterrole.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: metrics-reader
5 | rules:
6 | - nonResourceURLs:
7 | - "/metrics"
8 | verbs:
9 | - get
10 |
--------------------------------------------------------------------------------
/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:
7 | - authentication.k8s.io
8 | resources:
9 | - tokenreviews
10 | verbs:
11 | - create
12 | - apiGroups:
13 | - authorization.k8s.io
14 | resources:
15 | - subjectaccessreviews
16 | verbs:
17 | - create
18 |
--------------------------------------------------------------------------------
/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: controller-manager
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 | protocol: TCP
13 | targetPort: https
14 | selector:
15 | control-plane: controller-manager
16 |
--------------------------------------------------------------------------------
/config/rbac/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | # All RBAC will be applied under this service account in
3 | # the deployment namespace. You may comment out this resource
4 | # if your manager will use a service account that exists at
5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding
6 | # subjects if changing service account names.
7 | - service_account.yaml
8 | - role.yaml
9 | - role_binding.yaml
10 | - leader_election_role.yaml
11 | - leader_election_role_binding.yaml
12 | # Comment the following 4 lines if you want to disable
13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy)
14 | # which protects your /metrics endpoint.
15 | - auth_proxy_service.yaml
16 | - auth_proxy_role.yaml
17 | - auth_proxy_role_binding.yaml
18 | - auth_proxy_client_clusterrole.yaml
19 |
--------------------------------------------------------------------------------
/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 | - coordination.k8s.io
21 | resources:
22 | - leases
23 | verbs:
24 | - get
25 | - list
26 | - watch
27 | - create
28 | - update
29 | - patch
30 | - delete
31 | - apiGroups:
32 | - ""
33 | resources:
34 | - events
35 | verbs:
36 | - create
37 | - patch
38 |
--------------------------------------------------------------------------------
/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: controller-manager
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/rbac/role.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | creationTimestamp: null
6 | name: manager-role
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - events
12 | verbs:
13 | - create
14 | - patch
15 | - apiGroups:
16 | - ""
17 | resources:
18 | - secrets
19 | verbs:
20 | - create
21 | - delete
22 | - get
23 | - list
24 | - patch
25 | - update
26 | - watch
27 | - apiGroups:
28 | - apps
29 | resources:
30 | - deployments
31 | verbs:
32 | - create
33 | - delete
34 | - get
35 | - list
36 | - patch
37 | - update
38 | - watch
39 | - apiGroups:
40 | - apps
41 | resources:
42 | - replicasets
43 | verbs:
44 | - create
45 | - delete
46 | - get
47 | - list
48 | - patch
49 | - update
50 | - watch
51 | - apiGroups:
52 | - apps
53 | resources:
54 | - statefulsets
55 | verbs:
56 | - create
57 | - delete
58 | - get
59 | - list
60 | - patch
61 | - update
62 | - watch
63 | - apiGroups:
64 | - autoscaling
65 | resources:
66 | - horizontalpodautoscalers
67 | verbs:
68 | - create
69 | - delete
70 | - get
71 | - list
72 | - patch
73 | - update
74 | - watch
75 | - apiGroups:
76 | - batch
77 | resources:
78 | - cronjobs
79 | verbs:
80 | - create
81 | - delete
82 | - get
83 | - list
84 | - patch
85 | - update
86 | - watch
87 | - apiGroups:
88 | - ""
89 | resources:
90 | - replicasets
91 | verbs:
92 | - create
93 | - delete
94 | - get
95 | - list
96 | - patch
97 | - update
98 | - watch
99 | - apiGroups:
100 | - ""
101 | resources:
102 | - serviceaccounts
103 | verbs:
104 | - create
105 | - delete
106 | - get
107 | - list
108 | - patch
109 | - update
110 | - watch
111 | - apiGroups:
112 | - core.rekuberate.io
113 | resources:
114 | - sleepcycles
115 | verbs:
116 | - create
117 | - delete
118 | - get
119 | - list
120 | - patch
121 | - update
122 | - watch
123 | - apiGroups:
124 | - core.rekuberate.io
125 | resources:
126 | - sleepcycles/finalizers
127 | verbs:
128 | - update
129 | - apiGroups:
130 | - core.rekuberate.io
131 | resources:
132 | - sleepcycles/status
133 | verbs:
134 | - get
135 | - patch
136 | - update
137 | - apiGroups:
138 | - rbac.authorization.k8s.io
139 | resources:
140 | - clusterrolebindings
141 | verbs:
142 | - create
143 | - delete
144 | - escalate
145 | - get
146 | - list
147 | - patch
148 | - update
149 | - watch
150 | - apiGroups:
151 | - rbac.authorization.k8s.io
152 | resources:
153 | - clusterroles
154 | verbs:
155 | - create
156 | - delete
157 | - escalate
158 | - get
159 | - list
160 | - patch
161 | - update
162 | - watch
163 | - apiGroups:
164 | - rbac.authorization.k8s.io
165 | resources:
166 | - rolebindings
167 | verbs:
168 | - create
169 | - delete
170 | - escalate
171 | - get
172 | - list
173 | - patch
174 | - update
175 | - watch
176 | - apiGroups:
177 | - rbac.authorization.k8s.io
178 | resources:
179 | - roles
180 | verbs:
181 | - create
182 | - delete
183 | - escalate
184 | - get
185 | - list
186 | - patch
187 | - update
188 | - watch
189 |
--------------------------------------------------------------------------------
/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: controller-manager
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/rbac/service_account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: controller-manager
5 | namespace: system
6 |
--------------------------------------------------------------------------------
/config/rbac/sleepcycle_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit sleepcycles.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: sleepcycle-editor-role
6 | rules:
7 | - apiGroups:
8 | - core.rekuberate.io
9 | resources:
10 | - sleepcycles
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - core.rekuberate.io
21 | resources:
22 | - sleepcycles/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/rbac/sleepcycle_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view sleepcycles.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: sleepcycle-viewer-role
6 | rules:
7 | - apiGroups:
8 | - core.rekuberate.io
9 | resources:
10 | - sleepcycles
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - core.rekuberate.io
17 | resources:
18 | - sleepcycles/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/samples/apache-hpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: php-apache
5 | namespace: app-2
6 | spec:
7 | selector:
8 | matchLabels:
9 | run: php-apache
10 | template:
11 | metadata:
12 | labels:
13 | run: php-apache
14 | spec:
15 | containers:
16 | - name: php-apache
17 | image: registry.k8s.io/hpa-example
18 | ports:
19 | - containerPort: 80
20 | resources:
21 | limits:
22 | cpu: 500m
23 | requests:
24 | cpu: 200m
25 | ---
26 | apiVersion: v1
27 | kind: Service
28 | metadata:
29 | name: php-apache
30 | namespace: app-2
31 | labels:
32 | run: php-apache
33 | spec:
34 | ports:
35 | - port: 80
36 | selector:
37 | run: php-apache
38 | ---
39 | apiVersion: autoscaling/v2
40 | kind: HorizontalPodAutoscaler
41 | metadata:
42 | name: php-apache
43 | namespace: app-2
44 | labels:
45 | rekuberate.io/sleepcycle: sleepcycle-app-2
46 | spec:
47 | scaleTargetRef:
48 | apiVersion: apps/v1
49 | kind: Deployment
50 | name: php-apache
51 | minReplicas: 1
52 | maxReplicas: 10
53 | metrics:
54 | - type: Resource
55 | resource:
56 | name: cpu
57 | target:
58 | type: Utilization
59 | averageUtilization: 50
60 |
--------------------------------------------------------------------------------
/config/samples/busybox-cronjob.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: hello-busybox
5 | namespace: app-1
6 | labels:
7 | app: hello-busybox
8 | rekuberate.io/sleepcycle: sleepcycle-app-1
9 | spec:
10 | schedule: "* * * * *" # Run every minute
11 | jobTemplate:
12 | spec:
13 | template:
14 | spec:
15 | containers:
16 | - name: hello-busybox
17 | image: busybox:latest
18 | imagePullPolicy: IfNotPresent
19 | command:
20 | - /bin/sh
21 | - -c
22 | - date; echo Hello!
23 | restartPolicy: OnFailure
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_1.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-1
5 | namespace: app-1
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-2
5 | namespace: default
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
12 | runnerImage: akyriako78/rekuberate-io-sleepcycles-runners:0.2.0-dev-4
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_3.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-3
5 | namespace: default
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
12 | runnerImage: akyriako78/rekuberate-io-sleepcycles-runners:0.2.0-dev-4
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_4.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-4
5 | namespace: default
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
12 | runnerImage: akyriako78/rekuberate-io-sleepcycles-runners:0.2.0-dev-4
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_5.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-5
5 | namespace: default
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
12 | runnerImage: akyriako78/rekuberate-io-sleepcycles-runners:0.2.0-dev-4
--------------------------------------------------------------------------------
/config/samples/core_v1alpha1_sleepcycle_app_6.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: core.rekuberate.io/v1alpha1
2 | kind: SleepCycle
3 | metadata:
4 | name: sleepcycle-app-6
5 | namespace: default
6 | spec:
7 | shutdown: "1/2 * * * *"
8 | shutdownTimeZone: "Europe/Athens"
9 | wakeup: "*/2 * * * *"
10 | wakeupTimeZone: "Europe/Dublin"
11 | enabled: true
12 | runnerImage: akyriako78/rekuberate-io-sleepcycles-runners:0.2.0-dev-4
--------------------------------------------------------------------------------
/config/samples/nginx-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: nginx
5 | namespace: app-2
6 | labels:
7 | app: nginx
8 | spec:
9 | ports:
10 | - port: 80
11 | name: web
12 | clusterIP: None
13 | selector:
14 | app: nginx
15 | ---
16 | apiVersion: apps/v1
17 | kind: StatefulSet
18 | metadata:
19 | name: web
20 | namespace: app-2
21 | labels:
22 | rekuberate.io/sleepcycle: sleepcycle-app-2
23 | spec:
24 | serviceName: "nginx"
25 | replicas: 2
26 | selector:
27 | matchLabels:
28 | app: nginx
29 | template:
30 | metadata:
31 | labels:
32 | app: nginx
33 | spec:
34 | containers:
35 | - name: nginx
36 | image: registry.k8s.io/nginx-slim:0.8
37 | ports:
38 | - containerPort: 80
39 | name: web
40 | volumeMounts:
41 | - name: www
42 | mountPath: /usr/share/nginx/html
43 | volumeClaimTemplates:
44 | - metadata:
45 | name: www
46 | spec:
47 | accessModes: [ "ReadWriteOnce" ]
48 | resources:
49 | requests:
50 | storage: 1Gi
51 |
--------------------------------------------------------------------------------
/config/samples/stress/stress.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "crypto/rand"
6 | "flag"
7 | "fmt"
8 | "go.uber.org/zap"
9 | appsv1 "k8s.io/api/apps/v1"
10 | apiv1 "k8s.io/api/core/v1"
11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 | "k8s.io/client-go/kubernetes"
13 | v1 "k8s.io/client-go/kubernetes/typed/apps/v1"
14 | "k8s.io/client-go/tools/clientcmd"
15 | "k8s.io/client-go/util/homedir"
16 | "k8s.io/utils/pointer"
17 | "os"
18 | "path/filepath"
19 | "strings"
20 | )
21 |
22 | var logger *zap.Logger
23 |
24 | const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
25 |
26 | func main() {
27 | var kubeconfig *string
28 | home := homedir.HomeDir()
29 |
30 | if home != "" {
31 | kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
32 | } else {
33 | kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
34 | }
35 |
36 | purge := flag.Bool("purge", false, "(optional) false: remove all deployments , true: provision deployments")
37 | deployments := flag.Int("deployments", 10, "(optional) number of deployments")
38 | sleepcycle := flag.String("sleepcycle", "", "target sleepcycle")
39 | namespace := flag.String("namespace", apiv1.NamespaceDefault, "(optional) target kubernetes namespace")
40 |
41 | flag.Parse()
42 |
43 | if sleepcycle == nil || *sleepcycle == "" {
44 | flag.Usage()
45 | os.Exit(1)
46 | }
47 |
48 | logger, _ = zap.NewProduction()
49 | defer func(logger *zap.Logger) {
50 | err := logger.Sync()
51 | if err != nil {
52 | _ = fmt.Errorf("failed to sync zap logger: %v", err)
53 | }
54 | }(logger)
55 |
56 | config, err := clientcmd.BuildConfigFromFlags("", filepath.Join(home, ".kube", *kubeconfig))
57 | if err != nil {
58 | logger.Fatal(err.Error())
59 | }
60 |
61 | clientset, err := kubernetes.NewForConfig(config)
62 | if err != nil {
63 | logger.Fatal(err.Error())
64 | }
65 |
66 | deploymentsClient := clientset.AppsV1().Deployments(*namespace)
67 | gracefulExitCode := 0
68 |
69 | if *purge {
70 | err := deleteDeployments(
71 | deploymentsClient,
72 | *deployments,
73 | *sleepcycle,
74 | )
75 | if err != nil {
76 | gracefulExitCode = 1
77 | logger.Error(err.Error())
78 | }
79 |
80 | os.Exit(gracefulExitCode)
81 | }
82 |
83 | for i := 0; i < *deployments; i++ {
84 | deploymentSpec := getManifest(*sleepcycle)
85 |
86 | err := createDeployment(deploymentsClient, deploymentSpec)
87 | if err != nil {
88 | gracefulExitCode = 1
89 | logger.Error(err.Error())
90 | }
91 | }
92 |
93 | os.Exit(gracefulExitCode)
94 | }
95 |
96 | func createDeployment(deploymentsClient v1.DeploymentInterface, deploymentSpec *appsv1.Deployment) error {
97 | logger.Info(fmt.Sprintf("creating deployment %q", deploymentSpec.GetObjectMeta().GetName()))
98 |
99 | _, err := deploymentsClient.Create(context.TODO(), deploymentSpec, metav1.CreateOptions{})
100 | if err != nil {
101 | return err
102 | }
103 |
104 | return nil
105 | }
106 |
107 | func deleteDeployments(deploymentsClient v1.DeploymentInterface, count int, sleepcycle string) error {
108 | logger.Info(fmt.Sprintf("attempting to delete %d deployments", count))
109 |
110 | listOptions := metav1.ListOptions{
111 | LabelSelector: "rekuberate.io/sleepcycle=" + sleepcycle,
112 | }
113 | deploymentsList, err := deploymentsClient.List(context.TODO(), listOptions)
114 | if err != nil {
115 | logger.Error("getting deployments list failed")
116 | }
117 |
118 | if len(deploymentsList.Items) < count {
119 | count = len(deploymentsList.Items)
120 | }
121 |
122 | for i := 0; i < count; i++ {
123 | deletePolicy := metav1.DeletePropagationForeground
124 | if err := deploymentsClient.Delete(context.TODO(), deploymentsList.Items[i].Name, metav1.DeleteOptions{
125 | PropagationPolicy: &deletePolicy,
126 | }); err != nil {
127 | return err
128 | }
129 |
130 | logger.Info(fmt.Sprintf("deleted deployment: %s", deploymentsList.Items[i].Name))
131 | }
132 |
133 | return nil
134 | }
135 |
136 | func getManifest(sleepcycle string) *appsv1.Deployment {
137 | appName := fmt.Sprintf("whoami-%s", strings.ToLower(generateSecureRandomString(5)))
138 |
139 | deploymentSpec := &appsv1.Deployment{
140 | ObjectMeta: metav1.ObjectMeta{
141 | Name: appName,
142 | Labels: map[string]string{
143 | "app": appName,
144 | "rekuberate.io/sleepcycle": sleepcycle,
145 | },
146 | },
147 | Spec: appsv1.DeploymentSpec{
148 | Replicas: pointer.Int32Ptr(2),
149 | Selector: &metav1.LabelSelector{
150 | MatchLabels: map[string]string{
151 | "app": appName,
152 | },
153 | },
154 | Template: apiv1.PodTemplateSpec{
155 | ObjectMeta: metav1.ObjectMeta{
156 | Labels: map[string]string{
157 | "app": appName,
158 | },
159 | },
160 | Spec: apiv1.PodSpec{
161 | Containers: []apiv1.Container{
162 | {
163 | Name: "whoami",
164 | Image: "traefik/whoami",
165 | },
166 | },
167 | },
168 | },
169 | },
170 | }
171 |
172 | return deploymentSpec
173 | }
174 |
175 | func generateSecureRandomString(length int) string {
176 | result := make([]byte, length)
177 | _, err := rand.Read(result)
178 | if err != nil {
179 | os.Exit(1)
180 | }
181 |
182 | for i := range result {
183 | result[i] = letters[int(result[i])%len(letters)]
184 | }
185 | return string(result)
186 | }
187 |
--------------------------------------------------------------------------------
/config/samples/whoami-app-1_1-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: app-1-1
5 | namespace: app-1
6 | labels:
7 | app: app-1-1
8 | rekuberate.io/sleepcycle: sleepcycle-app-1
9 | spec:
10 | replicas: 3
11 | selector:
12 | matchLabels:
13 | app: app-1-1
14 | template:
15 | metadata:
16 | name: app-1-1
17 | labels:
18 | app: app-1-1
19 | spec:
20 | containers:
21 | - name: app-1-1
22 | image: traefik/whoami
23 | imagePullPolicy: IfNotPresent
24 |
--------------------------------------------------------------------------------
/config/samples/whoami-app-1_2-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: app-1-2
5 | namespace: app-1
6 | labels:
7 | app: app-1-2
8 | rekuberate.io/sleepcycle: sleepcycle-app-1
9 | spec:
10 | replicas: 3
11 | selector:
12 | matchLabels:
13 | app: app-1-2
14 | template:
15 | metadata:
16 | name: app-1-2
17 | labels:
18 | app: app-1-2
19 | spec:
20 | containers:
21 | - name: app-1-2
22 | image: traefik/whoami
23 | imagePullPolicy: IfNotPresent
24 |
--------------------------------------------------------------------------------
/config/samples/whoami-app-2-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: app-2
5 | namespace: default
6 | labels:
7 | app: app-2
8 | rekuberate.io/sleepcycle: sleepcycle-app-2
9 | spec:
10 | replicas: 9
11 | selector:
12 | matchLabels:
13 | app: app-2
14 | template:
15 | metadata:
16 | name: app-2
17 | labels:
18 | app: app-2
19 | spec:
20 | containers:
21 | - name: app-2
22 | image: traefik/whoami
23 | imagePullPolicy: IfNotPresent
24 |
--------------------------------------------------------------------------------
/controllers/sleepcycle_controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "time"
23 |
24 | "github.com/go-logr/logr"
25 | "github.com/hashicorp/go-multierror"
26 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
27 | apierrors "k8s.io/apimachinery/pkg/api/errors"
28 | "k8s.io/apimachinery/pkg/runtime"
29 | "k8s.io/client-go/tools/record"
30 | ctrl "sigs.k8s.io/controller-runtime"
31 | "sigs.k8s.io/controller-runtime/pkg/builder"
32 | "sigs.k8s.io/controller-runtime/pkg/client"
33 | "sigs.k8s.io/controller-runtime/pkg/event"
34 | "sigs.k8s.io/controller-runtime/pkg/log"
35 | "sigs.k8s.io/controller-runtime/pkg/predicate"
36 | )
37 |
38 | const (
39 | SleepCycleLabel = "rekuberate.io/sleepcycle"
40 | TimeWindowToleranceInSeconds int = 30
41 | requeueAfter time.Duration = 60 * time.Second
42 | )
43 |
44 | // SleepCycleReconciler reconciles a SleepCycle object
45 | type SleepCycleReconciler struct {
46 | client.Client
47 | Scheme *runtime.Scheme
48 | logger logr.Logger
49 | Recorder record.EventRecorder
50 | }
51 |
52 | type runtimeObjectReconciler func(
53 | ctx context.Context,
54 | sleepcycle *corev1alpha1.SleepCycle,
55 | ) (int, int, error)
56 |
57 | var (
58 | eventFilters = builder.WithPredicates(predicate.Funcs{
59 | UpdateFunc: func(e event.UpdateEvent) bool {
60 | // We only need to check generation changes here, because it is only
61 | // updated on spec changes. On the other hand RevisionVersion
62 | // changes also on status changes. We want to omit reconciliation
63 | // for status updates.
64 | return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
65 | },
66 | DeleteFunc: func(e event.DeleteEvent) bool {
67 | // DeleteStateUnknown evaluates to false only if the object
68 | // has been confirmed as deleted by the api server.
69 | return !e.DeleteStateUnknown
70 | },
71 | })
72 | )
73 |
74 | //+kubebuilder:rbac:groups=core.rekuberate.io,resources=sleepcycles,verbs=get;list;watch;create;update;patch;delete
75 | //+kubebuilder:rbac:groups=core.rekuberate.io,resources=sleepcycles/status,verbs=get;update;patch
76 | //+kubebuilder:rbac:groups=core.rekuberate.io,resources=sleepcycles/finalizers,verbs=update
77 | //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
78 | //+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete
79 | //+kubebuilder:rbac:groups=core,resources=replicasets,verbs=get;list;watch;create;update;patch;delete
80 | //+kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
81 | //+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
82 | //+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
83 | //+kubebuilder:rbac:groups="",resources=events,verbs=create;patch
84 | //+kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
85 | //+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
86 | //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete;escalate
87 | //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;create;update;patch;delete;escalate
88 | //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete;escalate
89 | //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;watch;create;update;patch;delete;escalate
90 |
91 | // Reconcile is part of the main kubernetes reconciliation loop which aims to
92 | // move the current state of the cluster closer to the desired state.
93 | // Modify the Reconcile function to compare the state specified by
94 | // the SleepCycle object against the actual cluster state, and then
95 | // perform operations to make the cluster state reflect the state specified by
96 | // the user.
97 | //
98 | // For more details, check Reconcile and its Result here:
99 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
100 | func (r *SleepCycleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
101 | r.logger = log.Log.WithValues("namespace", req.Namespace, "sleepcycle", req.Name)
102 | r.logger.Info("reconciling sleepcycle")
103 |
104 | nsks := "kube-system"
105 | if req.Namespace == nsks {
106 | r.logger.Info(fmt.Sprintf("setting sleepcycle schedule on resources in namespace %s is not supported", nsks))
107 | return ctrl.Result{}, nil
108 | }
109 |
110 | var original corev1alpha1.SleepCycle
111 | if err := r.Get(ctx, req.NamespacedName, &original); err != nil {
112 | if apierrors.IsNotFound(err) {
113 | return ctrl.Result{}, nil
114 | }
115 |
116 | r.logger.Error(err, "unable to fetch sleepcycle")
117 | return ctrl.Result{}, err
118 | }
119 |
120 | err := r.reconcileRbac(ctx, &original)
121 | if err != nil {
122 | r.recordEvent(&original, fmt.Sprintf("unable to create rbac resources in %s", req.Namespace), false)
123 | return ctrl.Result{}, err
124 | }
125 |
126 | reconcilers := []runtimeObjectReconciler{r.ReconcileDeployments, r.ReconcileCronJobs, r.ReconcileStatefulSets, r.ReconcileHorizontalPodAutoscalers}
127 | var errors error
128 | provisioned := 0
129 | total := 0
130 |
131 | for _, reconciler := range reconcilers {
132 | p, t, err := reconciler(ctx, &original)
133 | if err != nil {
134 | errors = multierror.Append(errors, err)
135 | }
136 |
137 | provisioned += p
138 | total += t
139 | }
140 |
141 | if errors != nil {
142 | if merr, ok := errors.(*multierror.Error); ok {
143 | for _, rerr := range merr.Errors {
144 | r.logger.Error(rerr, "failed to reconcile")
145 | }
146 | }
147 | }
148 |
149 | err = r.UpdateStatus(ctx, &original, r.getStatusState(provisioned, total), []int{provisioned, total})
150 | if err != nil {
151 | return ctrl.Result{}, err
152 | }
153 |
154 | return ctrl.Result{RequeueAfter: requeueAfter}, errors
155 | }
156 |
157 | func (r *SleepCycleReconciler) UpdateStatus(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle, state string, targets []int) error {
158 | patch := client.MergeFrom(sleepcycle.DeepCopy())
159 | sleepcycle.Status.State = state
160 | sleepcycle.Status.Targets = fmt.Sprintf("%d/%d", targets[0], targets[1])
161 |
162 | err := r.Status().Patch(ctx, sleepcycle, patch)
163 | if err != nil {
164 | r.logger.Error(err, "unable to patch sleepcycle status")
165 | return err
166 | }
167 |
168 | return nil
169 | }
170 |
171 | // SetupWithManager sets up the controller with the Manager.
172 | func (r *SleepCycleReconciler) SetupWithManager(mgr ctrl.Manager) error {
173 | return ctrl.NewControllerManagedBy(mgr).
174 | For(&corev1alpha1.SleepCycle{}, eventFilters).
175 | Complete(r)
176 | }
177 |
--------------------------------------------------------------------------------
/controllers/sleepcycle_reconcilers.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "context"
5 | "github.com/hashicorp/go-multierror"
6 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
7 | appsv1 "k8s.io/api/apps/v1"
8 | autoscalingv1 "k8s.io/api/autoscaling/v1"
9 | batchv1 "k8s.io/api/batch/v1"
10 | )
11 |
12 | func (r *SleepCycleReconciler) ReconcileDeployments(
13 | ctx context.Context,
14 | sleepcycle *corev1alpha1.SleepCycle,
15 | ) (int, int, error) {
16 | deploymentList := appsv1.DeploymentList{}
17 | if err := r.List(
18 | ctx,
19 | &deploymentList,
20 | r.getListOptions(sleepcycle.Namespace, sleepcycle.Name)...,
21 | ); err != nil {
22 | return 0, 0, err
23 | }
24 |
25 | var total, provisioned int
26 | if total = len(deploymentList.Items); total == 0 {
27 | return 0, 0, nil
28 | }
29 | provisioned = total
30 |
31 | var errors error
32 | for _, deployment := range deploymentList.Items {
33 | logger := r.logger.WithValues("deployment", deployment.Name)
34 |
35 | kind := deployment.TypeMeta.Kind
36 | meta := deployment.ObjectMeta
37 | replicas := *deployment.Spec.Replicas
38 |
39 | err := r.reconcile(ctx, logger, sleepcycle, kind, meta, replicas)
40 | if err != nil {
41 | provisioned -= 1
42 | errors = multierror.Append(errors, err)
43 | }
44 | }
45 |
46 | return provisioned, total, errors
47 | }
48 |
49 | func (r *SleepCycleReconciler) ReconcileCronJobs(
50 | ctx context.Context,
51 | sleepcycle *corev1alpha1.SleepCycle,
52 | ) (int, int, error) {
53 | cronJobList := batchv1.CronJobList{}
54 | if err := r.List(
55 | ctx,
56 | &cronJobList,
57 | r.getListOptions(sleepcycle.Namespace, sleepcycle.Name)...,
58 | ); err != nil {
59 | return 0, 0, err
60 | }
61 |
62 | var total, provisioned int
63 | if total = len(cronJobList.Items); total == 0 {
64 | return 0, 0, nil
65 | }
66 | provisioned = total
67 |
68 | var errors error
69 | for _, cronjob := range cronJobList.Items {
70 | logger := r.logger.WithValues("cronjob", cronjob.Name)
71 |
72 | kind := cronjob.TypeMeta.Kind
73 | meta := cronjob.ObjectMeta
74 |
75 | replicas := int32(1)
76 | if *cronjob.Spec.Suspend {
77 | replicas = int32(0)
78 | }
79 |
80 | err := r.reconcile(ctx, logger, sleepcycle, kind, meta, replicas)
81 | if err != nil {
82 | provisioned -= 1
83 | errors = multierror.Append(errors, err)
84 | }
85 | }
86 |
87 | return provisioned, total, errors
88 | }
89 |
90 | func (r *SleepCycleReconciler) ReconcileStatefulSets(
91 | ctx context.Context,
92 | sleepcycle *corev1alpha1.SleepCycle,
93 | ) (int, int, error) {
94 | statefulSetList := appsv1.StatefulSetList{}
95 | if err := r.List(
96 | ctx,
97 | &statefulSetList,
98 | r.getListOptions(sleepcycle.Namespace, sleepcycle.Name)...,
99 | ); err != nil {
100 | return 0, 0, err
101 | }
102 |
103 | var total, provisioned int
104 | if total = len(statefulSetList.Items); total == 0 {
105 | return 0, 0, nil
106 | }
107 | provisioned = total
108 |
109 | var errors error
110 | for _, statefulSet := range statefulSetList.Items {
111 | logger := r.logger.WithValues("statefulset", statefulSet.Name)
112 |
113 | kind := statefulSet.TypeMeta.Kind
114 | meta := statefulSet.ObjectMeta
115 | replicas := *statefulSet.Spec.Replicas
116 |
117 | err := r.reconcile(ctx, logger, sleepcycle, kind, meta, replicas)
118 | if err != nil {
119 | provisioned -= 1
120 | errors = multierror.Append(errors, err)
121 | }
122 | }
123 |
124 | return provisioned, total, errors
125 | }
126 |
127 | func (r *SleepCycleReconciler) ReconcileHorizontalPodAutoscalers(
128 | ctx context.Context,
129 | sleepcycle *corev1alpha1.SleepCycle,
130 | ) (int, int, error) {
131 | hpaList := autoscalingv1.HorizontalPodAutoscalerList{}
132 | if err := r.List(
133 | ctx,
134 | &hpaList,
135 | r.getListOptions(sleepcycle.Namespace, sleepcycle.Name)...,
136 | ); err != nil {
137 | return 0, 0, err
138 | }
139 |
140 | var total, provisioned int
141 | if total = len(hpaList.Items); total == 0 {
142 | return 0, 0, nil
143 | }
144 | provisioned = total
145 |
146 | var errors error
147 | for _, hpa := range hpaList.Items {
148 | logger := r.logger.WithValues("hpa", hpa.Name)
149 |
150 | kind := hpa.TypeMeta.Kind
151 | meta := hpa.ObjectMeta
152 | replicas := hpa.Spec.MaxReplicas
153 |
154 | err := r.reconcile(ctx, logger, sleepcycle, kind, meta, replicas)
155 | if err != nil {
156 | provisioned -= 1
157 | errors = multierror.Append(errors, err)
158 | }
159 | }
160 |
161 | return provisioned, total, errors
162 | }
163 |
--------------------------------------------------------------------------------
/controllers/sleepcycle_runners_cronjobs.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/go-logr/logr"
10 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
11 | batchv1 "k8s.io/api/batch/v1"
12 | v1 "k8s.io/api/core/v1"
13 | "k8s.io/apimachinery/pkg/api/resource"
14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 | ctrl "sigs.k8s.io/controller-runtime"
16 | "sigs.k8s.io/controller-runtime/pkg/client"
17 | )
18 |
19 | var (
20 | startingDeadlineSeconds int64 = 15
21 | )
22 |
23 | const (
24 | OwnedBy = "rekuberate.io/owned-by"
25 | Target = "rekuberate.io/target"
26 | TargetKind = "rekuberate.io/target-kind"
27 | TargetTimezone = "rekuberate.io/target-tz"
28 | Replicas = "rekuberate.io/replicas"
29 | )
30 |
31 | func (r *SleepCycleReconciler) getCronJob(ctx context.Context, ownedBy, target, namespace, suffix string) (*batchv1.CronJob, error) {
32 | var jobs batchv1.CronJobList
33 | labelSelector := map[string]string{
34 | OwnedBy: ownedBy,
35 | Target: target,
36 | }
37 | listOptions := []client.ListOption{
38 | client.InNamespace(namespace),
39 | client.MatchingLabels(labelSelector),
40 | }
41 |
42 | if err := r.List(ctx, &jobs, listOptions...); err != nil {
43 | return nil, err
44 | }
45 |
46 | for _, job := range jobs.Items {
47 | if strings.Contains(job.Name, suffix) {
48 | return &job, nil
49 | }
50 | }
51 |
52 | return nil, nil
53 | }
54 |
55 | func (r *SleepCycleReconciler) createCronJob(
56 | ctx context.Context,
57 | logger logr.Logger,
58 | sleepcycle *corev1alpha1.SleepCycle,
59 | cronObjectKey client.ObjectKey,
60 | targetKind string,
61 | targetMeta metav1.ObjectMeta,
62 | targetReplicas int32,
63 | isShutdownOp bool,
64 | ) (*batchv1.CronJob, error) {
65 |
66 | logger.Info("creating runner", "cronjob", cronObjectKey)
67 | backOffLimit := int32(0)
68 |
69 | schedule := sleepcycle.Spec.Shutdown
70 | tz := sleepcycle.Spec.ShutdownTimeZone
71 | suspend := !sleepcycle.Spec.Enabled
72 |
73 | if !isShutdownOp {
74 | schedule = *sleepcycle.Spec.WakeUp
75 | tz = sleepcycle.Spec.WakeupTimeZone
76 | }
77 |
78 | labels := make(map[string]string)
79 | labels[OwnedBy] = sleepcycle.Name
80 | labels[Target] = targetMeta.Name
81 | labels[TargetKind] = targetKind
82 |
83 | annotations := make(map[string]string)
84 | annotations[TargetTimezone] = *tz
85 |
86 | if targetKind != "CronJob" {
87 | annotations[Replicas] = fmt.Sprint(targetReplicas)
88 |
89 | if targetReplicas == 0 {
90 | annotations[Replicas] = strconv.FormatInt(1, 10)
91 | }
92 | }
93 |
94 | job := &batchv1.CronJob{
95 | ObjectMeta: metav1.ObjectMeta{
96 | Name: cronObjectKey.Name,
97 | Namespace: cronObjectKey.Namespace,
98 | Labels: labels,
99 | Annotations: annotations,
100 | },
101 | Spec: batchv1.CronJobSpec{
102 | SuccessfulJobsHistoryLimit: &sleepcycle.Spec.SuccessfulJobsHistoryLimit,
103 | FailedJobsHistoryLimit: &sleepcycle.Spec.FailedJobsHistoryLimit,
104 | Schedule: schedule,
105 | TimeZone: tz,
106 | StartingDeadlineSeconds: &startingDeadlineSeconds,
107 | ConcurrencyPolicy: batchv1.ForbidConcurrent,
108 | Suspend: &suspend,
109 | JobTemplate: batchv1.JobTemplateSpec{
110 | Spec: batchv1.JobSpec{
111 | Template: v1.PodTemplateSpec{
112 | Spec: v1.PodSpec{
113 | Containers: []v1.Container{
114 | {
115 | Name: cronObjectKey.Name,
116 | Image: sleepcycle.Spec.RunnerImage,
117 | Env: []v1.EnvVar{
118 | {
119 | Name: "MY_POD_NAME",
120 | ValueFrom: &v1.EnvVarSource{
121 | FieldRef: &v1.ObjectFieldSelector{
122 | FieldPath: "metadata.name",
123 | }},
124 | },
125 | {
126 | Name: "MY_POD_NAMESPACE",
127 | ValueFrom: &v1.EnvVarSource{
128 | FieldRef: &v1.ObjectFieldSelector{
129 | FieldPath: "metadata.namespace",
130 | }},
131 | },
132 | {
133 | Name: "MY_CRONJOB_NAME",
134 | Value: cronObjectKey.Name,
135 | },
136 | },
137 | Resources: v1.ResourceRequirements{
138 | Limits: v1.ResourceList{
139 | v1.ResourceCPU: resource.MustParse("150m"),
140 | v1.ResourceMemory: resource.MustParse("24Mi"),
141 | },
142 | Requests: v1.ResourceList{
143 | v1.ResourceCPU: resource.MustParse("5m"),
144 | v1.ResourceMemory: resource.MustParse("12Mi"),
145 | },
146 | },
147 | },
148 | },
149 | RestartPolicy: v1.RestartPolicyOnFailure,
150 | ServiceAccountName: serviceAccountName,
151 | },
152 | },
153 | BackoffLimit: &backOffLimit,
154 | },
155 | },
156 | },
157 | }
158 |
159 | err := ctrl.SetControllerReference(sleepcycle, job, r.Scheme)
160 | if err != nil {
161 | logger.Error(err, "unable to set controller reference for runner", "cronjob", cronObjectKey.Name)
162 | return nil, err
163 | }
164 |
165 | err = r.Create(ctx, job)
166 | if err != nil {
167 | r.recordEvent(sleepcycle, fmt.Sprintf("unable to create runner %s/%s", cronObjectKey.Namespace, cronObjectKey.Name), true)
168 | logger.Error(err, "unable to create runner", "cronjob", cronObjectKey.Name)
169 | return nil, err
170 | }
171 |
172 | r.recordEvent(sleepcycle, fmt.Sprintf("created runner %s/%s", cronObjectKey.Namespace, cronObjectKey.Name), false)
173 | return job, nil
174 | }
175 |
176 | func (r *SleepCycleReconciler) updateCronJob(
177 | ctx context.Context,
178 | logger logr.Logger,
179 | sleepcycle *corev1alpha1.SleepCycle,
180 | cronJob *batchv1.CronJob,
181 | kind string,
182 | schedule string,
183 | timezone string,
184 | suspend bool,
185 | replicas int32,
186 | ) error {
187 | deepCopy := cronJob.DeepCopy()
188 | deepCopy.Spec.Schedule = schedule
189 | *deepCopy.Spec.TimeZone = timezone
190 | *deepCopy.Spec.Suspend = suspend
191 |
192 | if kind != "CronJob" {
193 | if replicas != 0 {
194 | deepCopy.Annotations[Replicas] = fmt.Sprint(replicas)
195 | }
196 | }
197 |
198 | if err := r.Update(ctx, deepCopy); err != nil {
199 | r.recordEvent(sleepcycle, fmt.Sprintf("unable to update runner %s/%s", cronJob.Namespace, cronJob.Name), true)
200 | logger.Error(err, "unable to update runner", "cronjob", cronJob.Name)
201 | return err
202 | }
203 |
204 | //r.recordEvent(sleepcycle, fmt.Sprintf("updated runner %s/%s", cronJob.Namespace, cronJob.Name), false)
205 | return nil
206 | }
207 |
208 | func (r *SleepCycleReconciler) deleteCronJob(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle, cronJob *batchv1.CronJob) error {
209 | if err := r.Delete(ctx, cronJob); err != nil {
210 | r.recordEvent(sleepcycle, fmt.Sprintf("unable to delete runner %s/%s", cronJob.Namespace, cronJob.Name), true)
211 | return err
212 | }
213 |
214 | r.recordEvent(sleepcycle, fmt.Sprintf("deleted runner %s/%s", cronJob.Namespace, cronJob.Name), false)
215 | return nil
216 | }
217 |
218 | func (r *SleepCycleReconciler) reconcileCronJob(
219 | ctx context.Context,
220 | logger logr.Logger,
221 | sleepcycle *corev1alpha1.SleepCycle,
222 | targetKind string,
223 | targetMeta metav1.ObjectMeta,
224 | targetReplicas int32,
225 | isShutdownOp bool,
226 | ) error {
227 | suffix := "shutdown"
228 | if !isShutdownOp {
229 | suffix = "wakeup"
230 | }
231 |
232 | cronjob, err := r.getCronJob(ctx, sleepcycle.Name, targetMeta.Name, sleepcycle.Namespace, suffix)
233 | if err != nil {
234 | logger.Error(err, "unable to fetch runner", "sleepcycle", sleepcycle.Name, "target", targetMeta.Namespace, "op", suffix)
235 | return err
236 | }
237 |
238 | if cronjob == nil {
239 | cronJobRandomUID, err := r.generateSecureRandomString(7)
240 | if err != nil {
241 | return err
242 | }
243 |
244 | cronObjectKey := client.ObjectKey{
245 | Name: fmt.Sprintf("sleepcycle-runner-%s-%s-%s-%s", sleepcycle.ObjectMeta.UID[:4], targetMeta.UID[:4], strings.ToLower(cronJobRandomUID), suffix),
246 | Namespace: sleepcycle.Namespace,
247 | }
248 |
249 | _, err = r.createCronJob(ctx, logger, sleepcycle, cronObjectKey, targetKind, targetMeta, targetReplicas, isShutdownOp)
250 | if err != nil {
251 | return err
252 | }
253 | }
254 |
255 | if cronjob != nil {
256 | if cronjob.Status.Active != nil {
257 | return nil
258 | }
259 |
260 | if !isShutdownOp && sleepcycle.Spec.WakeUp == nil {
261 | err := r.deleteCronJob(ctx, sleepcycle, cronjob)
262 | if err != nil {
263 | return err
264 | }
265 | }
266 |
267 | schedule := sleepcycle.Spec.Shutdown
268 | tz := sleepcycle.Spec.ShutdownTimeZone
269 | suspend := !sleepcycle.Spec.Enabled
270 | if !isShutdownOp {
271 | schedule = *sleepcycle.Spec.WakeUp
272 | tz = sleepcycle.Spec.WakeupTimeZone
273 | }
274 |
275 | err := r.updateCronJob(ctx, logger, sleepcycle, cronjob, targetKind, schedule, *tz, suspend, targetReplicas)
276 | if err != nil {
277 | logger.Error(err, "failed to update runner", "sleepcycle", sleepcycle.Name, "target", targetMeta.Namespace, "op", suffix)
278 | return err
279 | }
280 | }
281 |
282 | return nil
283 | }
284 |
285 | func (r *SleepCycleReconciler) reconcile(
286 | ctx context.Context,
287 | logger logr.Logger,
288 | sleepcycle *corev1alpha1.SleepCycle,
289 | targetKind string,
290 | targetMeta metav1.ObjectMeta,
291 | targetReplicas int32,
292 | ) error {
293 | err := r.reconcileCronJob(ctx, logger, sleepcycle, targetKind, targetMeta, targetReplicas, true)
294 | if err != nil {
295 | return err
296 | }
297 |
298 | if sleepcycle.Spec.WakeUp != nil {
299 | err := r.reconcileCronJob(ctx, logger, sleepcycle, targetKind, targetMeta, targetReplicas, false)
300 | if err != nil {
301 | return err
302 | }
303 | }
304 |
305 | return nil
306 | }
307 |
--------------------------------------------------------------------------------
/controllers/sleepcycle_utils.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base64"
6 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
7 | corev1 "k8s.io/api/core/v1"
8 | "sigs.k8s.io/controller-runtime/pkg/client"
9 | "strings"
10 | )
11 |
12 | const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
13 |
14 | func (r *SleepCycleReconciler) getListOptions(namespace, name string) []client.ListOption {
15 | labelSelector := map[string]string{
16 | SleepCycleLabel: name,
17 | }
18 | listOptions := []client.ListOption{
19 | client.InNamespace(namespace),
20 | client.MatchingLabels(labelSelector),
21 | }
22 |
23 | return listOptions
24 | }
25 |
26 | func (r *SleepCycleReconciler) generateToken() (string, error) {
27 | token := make([]byte, 256)
28 | _, err := rand.Read(token)
29 | if err != nil {
30 | r.logger.Error(err, "error while generating the secret token")
31 | return "", err
32 | }
33 |
34 | base64EncodedToken := base64.StdEncoding.EncodeToString(token)
35 | return base64EncodedToken, nil
36 | }
37 |
38 | func (r *SleepCycleReconciler) generateSecureRandomString(length int) (string, error) {
39 | result := make([]byte, length)
40 | _, err := rand.Read(result)
41 | if err != nil {
42 | r.logger.Error(err, "error while generating a random string")
43 | return "", err
44 | }
45 |
46 | for i := range result {
47 | result[i] = letters[int(result[i])%len(letters)]
48 | }
49 | return string(result), nil
50 | }
51 |
52 | func (r *SleepCycleReconciler) getStatusState(provisioned, total int) (state string) {
53 | state = "Ready"
54 | if provisioned != 0 && provisioned < total {
55 | state = "Warning"
56 | } else if provisioned == 0 && total != 0 {
57 | state = "NotReady"
58 | }
59 |
60 | return state
61 | }
62 |
63 | func (r *SleepCycleReconciler) recordEvent(sleepCycle *corev1alpha1.SleepCycle, message string, isError bool) {
64 | eventType := corev1.EventTypeNormal
65 | reason := "SuccessfulSleepCycleReconcile"
66 |
67 | if isError {
68 | eventType = corev1.EventTypeWarning
69 | reason = "FailedSleepCycleReconcile"
70 | }
71 |
72 | r.Recorder.Event(sleepCycle, eventType, reason, strings.ToLower(message))
73 | }
74 |
--------------------------------------------------------------------------------
/controllers/sleepcycles_rbac.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/pkg/errors"
7 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
8 | v1 "k8s.io/api/core/v1"
9 | rbacv1 "k8s.io/api/rbac/v1"
10 | apierrors "k8s.io/apimachinery/pkg/api/errors"
11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 | ctrl "sigs.k8s.io/controller-runtime"
13 | "sigs.k8s.io/controller-runtime/pkg/client"
14 | )
15 |
16 | const (
17 | serviceAccountName = "sleecycles-runner"
18 | )
19 |
20 | func (r *SleepCycleReconciler) reconcileRbac(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle) error {
21 | perr := fmt.Errorf("unable to create rbac resources")
22 | ready, err := r.checkRbac(ctx, sleepcycle)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | if ready {
28 | return nil
29 | }
30 |
31 | err = r.deleteRbac(ctx, sleepcycle)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | account, err := r.createServiceAccount(ctx, sleepcycle)
37 | if err != nil {
38 | return errors.Wrap(err, perr.Error())
39 | }
40 |
41 | _, err = r.createSecret(ctx, account)
42 | if err != nil {
43 | return errors.Wrap(err, perr.Error())
44 | }
45 |
46 | role, err := r.createRole(ctx, account)
47 | if err != nil {
48 | return errors.Wrap(err, perr.Error())
49 | }
50 |
51 | _, err = r.createRoleBinding(ctx, role)
52 | if err != nil {
53 | return errors.Wrap(err, perr.Error())
54 | }
55 |
56 | r.recordEvent(sleepcycle, fmt.Sprintf("created rbac resources in %s", sleepcycle.Namespace), false)
57 | return nil
58 | }
59 |
60 | func (r *SleepCycleReconciler) checkRbac(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle) (bool, error) {
61 | ready := true
62 |
63 | saok := client.ObjectKey{
64 | Namespace: sleepcycle.Namespace,
65 | Name: serviceAccountName,
66 | }
67 | var sa v1.ServiceAccount
68 | if err := r.Get(ctx, saok, &sa); err != nil {
69 | if apierrors.IsNotFound(err) {
70 | ready = false
71 | } else {
72 | r.logger.Error(err, "unable to fetch service account")
73 | return false, err
74 | }
75 | }
76 |
77 | var ro rbacv1.Role
78 | rook := client.ObjectKey{
79 | Namespace: sleepcycle.Namespace,
80 | Name: fmt.Sprintf("%s-role", serviceAccountName),
81 | }
82 | if err := r.Get(ctx, rook, &ro); err != nil {
83 | if apierrors.IsNotFound(err) {
84 | ready = false
85 | } else {
86 | r.logger.Error(err, "unable to fetch role")
87 | return false, err
88 | }
89 | }
90 |
91 | var rb rbacv1.RoleBinding
92 | rbok := client.ObjectKey{
93 | Namespace: sleepcycle.Namespace,
94 | Name: fmt.Sprintf("%s-rolebinding", serviceAccountName),
95 | }
96 | if err := r.Get(ctx, rbok, &rb); err != nil {
97 | if apierrors.IsNotFound(err) {
98 | ready = false
99 | } else {
100 | r.logger.Error(err, "unable to fetch role binding")
101 | return false, err
102 | }
103 | }
104 |
105 | return ready, nil
106 | }
107 |
108 | func (r *SleepCycleReconciler) deleteRbac(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle) error {
109 | saok := client.ObjectKey{
110 | Namespace: sleepcycle.Namespace,
111 | Name: serviceAccountName,
112 | }
113 | var sa v1.ServiceAccount
114 | if err := r.Get(ctx, saok, &sa); err != nil {
115 | if !apierrors.IsNotFound(err) {
116 | r.logger.Error(err, "unable to fetch service account")
117 | return err
118 | }
119 |
120 | return nil
121 | }
122 |
123 | err := r.Delete(ctx, &sa)
124 | if err != nil {
125 | return err
126 | }
127 |
128 | return nil
129 | }
130 |
131 | func (r *SleepCycleReconciler) createServiceAccount(ctx context.Context, sleepcycle *corev1alpha1.SleepCycle) (*v1.ServiceAccount, error) {
132 | objectKey := client.ObjectKey{
133 | Namespace: sleepcycle.Namespace,
134 | Name: serviceAccountName,
135 | }
136 |
137 | r.logger.Info("creating service account", "account", serviceAccountName)
138 | sa := &v1.ServiceAccount{
139 | ObjectMeta: metav1.ObjectMeta{
140 | Name: objectKey.Name,
141 | Namespace: objectKey.Namespace,
142 | },
143 | }
144 |
145 | err := ctrl.SetControllerReference(sleepcycle, sa, r.Scheme)
146 | if err != nil {
147 | return nil, err
148 | }
149 |
150 | err = r.Create(ctx, sa)
151 | if err != nil {
152 | return nil, err
153 | }
154 |
155 | return sa, nil
156 | }
157 |
158 | func (r *SleepCycleReconciler) createSecret(ctx context.Context, serviceAccount *v1.ServiceAccount) (*v1.Secret, error) {
159 | r.logger.Info("creating secret", "secret", fmt.Sprintf("%s-secret", serviceAccountName))
160 |
161 | token, err := r.generateToken()
162 | if err != nil {
163 | return nil, err
164 | }
165 |
166 | secret := &v1.Secret{
167 | ObjectMeta: metav1.ObjectMeta{
168 | Name: fmt.Sprintf("%s-secret", serviceAccountName),
169 | Namespace: serviceAccount.Namespace,
170 | Annotations: map[string]string{
171 | "kubernetes.io/service-account.name": serviceAccountName,
172 | },
173 | },
174 | Type: v1.SecretTypeServiceAccountToken,
175 | Data: map[string][]byte{
176 | "token": []byte(token),
177 | },
178 | }
179 |
180 | err = ctrl.SetControllerReference(serviceAccount, secret, r.Scheme)
181 | if err != nil {
182 | return nil, err
183 | }
184 |
185 | err = r.Create(ctx, secret)
186 | if err != nil {
187 | return nil, err
188 | }
189 |
190 | return secret, nil
191 | }
192 |
193 | func (r *SleepCycleReconciler) createRole(ctx context.Context, serviceAccount *v1.ServiceAccount) (*rbacv1.Role, error) {
194 | r.logger.Info("creating role", "role", fmt.Sprintf("%s-role", serviceAccountName))
195 |
196 | roleName := fmt.Sprintf("%s-role", serviceAccountName)
197 | role := &rbacv1.Role{
198 | ObjectMeta: metav1.ObjectMeta{
199 | Name: roleName,
200 | Namespace: serviceAccount.Namespace,
201 | },
202 | Rules: []rbacv1.PolicyRule{
203 | {
204 | APIGroups: []string{""},
205 | Resources: []string{"events"},
206 | Verbs: []string{"create", "patch"},
207 | },
208 | {
209 | APIGroups: []string{"apps"},
210 | Resources: []string{"deployments", "replicasets", "statefulsets"},
211 | Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
212 | },
213 | {
214 | APIGroups: []string{"batch"},
215 | Resources: []string{"cronjobs"},
216 | Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
217 | },
218 | {
219 | APIGroups: []string{"autoscaling"},
220 | Resources: []string{"horizontalpodautoscalers"},
221 | Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
222 | },
223 | },
224 | }
225 |
226 | err := ctrl.SetControllerReference(serviceAccount, role, r.Scheme)
227 | if err != nil {
228 | return nil, err
229 | }
230 |
231 | err = r.Create(ctx, role)
232 | if err != nil {
233 | return nil, err
234 | }
235 |
236 | return role, nil
237 | }
238 |
239 | func (r *SleepCycleReconciler) createRoleBinding(ctx context.Context, role *rbacv1.Role) (*rbacv1.RoleBinding, error) {
240 | r.logger.Info("creating role binding", "role", fmt.Sprintf("%s-rolebinding", serviceAccountName))
241 | roleBinding := &rbacv1.RoleBinding{
242 | ObjectMeta: metav1.ObjectMeta{
243 | Name: fmt.Sprintf("%s-rolebinding", serviceAccountName),
244 | Namespace: role.Namespace,
245 | },
246 | RoleRef: rbacv1.RoleRef{
247 | APIGroup: rbacv1.GroupName,
248 | Kind: "Role",
249 | Name: role.Name,
250 | },
251 | Subjects: []rbacv1.Subject{
252 | {
253 | Kind: "ServiceAccount",
254 | Name: serviceAccountName,
255 | Namespace: role.Namespace,
256 | },
257 | },
258 | }
259 |
260 | err := ctrl.SetControllerReference(role, roleBinding, r.Scheme)
261 | if err != nil {
262 | return nil, err
263 | }
264 |
265 | err = r.Create(ctx, roleBinding)
266 | if err != nil {
267 | return nil, err
268 | }
269 |
270 | return roleBinding, nil
271 | }
272 |
--------------------------------------------------------------------------------
/controllers/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "path/filepath"
21 | "testing"
22 |
23 | . "github.com/onsi/ginkgo"
24 | . "github.com/onsi/gomega"
25 |
26 | "k8s.io/client-go/kubernetes/scheme"
27 | "k8s.io/client-go/rest"
28 | "sigs.k8s.io/controller-runtime/pkg/client"
29 | "sigs.k8s.io/controller-runtime/pkg/envtest"
30 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
31 | logf "sigs.k8s.io/controller-runtime/pkg/log"
32 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
33 |
34 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
35 | //+kubebuilder:scaffold:imports
36 | )
37 |
38 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to
39 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
40 |
41 | var cfg *rest.Config
42 | var k8sClient client.Client
43 | var testEnv *envtest.Environment
44 |
45 | func TestAPIs(t *testing.T) {
46 | RegisterFailHandler(Fail)
47 |
48 | RunSpecsWithDefaultAndCustomReporters(t,
49 | "Controller Suite",
50 | []Reporter{printer.NewlineReporter{}})
51 | }
52 |
53 | var _ = BeforeSuite(func() {
54 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
55 |
56 | By("bootstrapping test environment")
57 | testEnv = &envtest.Environment{
58 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
59 | ErrorIfCRDPathMissing: true,
60 | }
61 |
62 | var err error
63 | // cfg is defined in this file globally.
64 | cfg, err = testEnv.Start()
65 | Expect(err).NotTo(HaveOccurred())
66 | Expect(cfg).NotTo(BeNil())
67 |
68 | err = corev1alpha1.AddToScheme(scheme.Scheme)
69 | Expect(err).NotTo(HaveOccurred())
70 |
71 | //+kubebuilder:scaffold:scheme
72 |
73 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
74 | Expect(err).NotTo(HaveOccurred())
75 | Expect(k8sClient).NotTo(BeNil())
76 |
77 | }, 60)
78 |
79 | var _ = AfterSuite(func() {
80 | By("tearing down the test environment")
81 | err := testEnv.Stop()
82 | Expect(err).NotTo(HaveOccurred())
83 | })
84 |
--------------------------------------------------------------------------------
/docs/images/SCR-20220920-i6y-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/SCR-20220920-i6y-2.png
--------------------------------------------------------------------------------
/docs/images/SCR-20221222-hij.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/SCR-20221222-hij.png
--------------------------------------------------------------------------------
/docs/images/SCR-20240527-q9y.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/SCR-20240527-q9y.png
--------------------------------------------------------------------------------
/docs/images/SCR-20240527-qei.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/SCR-20240527-qei.png
--------------------------------------------------------------------------------
/docs/images/Screencast from 05-24-2024 11-13-28 AM.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/Screencast from 05-24-2024 11-13-28 AM.webm
--------------------------------------------------------------------------------
/docs/images/Screencast from 05-30-2024 11-12-15 AM.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/Screencast from 05-30-2024 11-12-15 AM.webm
--------------------------------------------------------------------------------
/docs/images/argocd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/argocd.png
--------------------------------------------------------------------------------
/docs/images/rekuberate-io-sleepcycles.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-io-sleepcycles.gif
--------------------------------------------------------------------------------
/docs/images/rekuberate-io-sleepcycles.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-io-sleepcycles.mp4
--------------------------------------------------------------------------------
/docs/images/rekuberate-sleepcycle-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-sleepcycle-banner.png
--------------------------------------------------------------------------------
/docs/images/rekuberate-sleepcycle-logo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-sleepcycle-logo-2.png
--------------------------------------------------------------------------------
/docs/images/rekuberate-sleepcycle-logo-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-sleepcycle-logo-3.png
--------------------------------------------------------------------------------
/docs/images/rekuberate-sleepcycle-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rekuberate-io/sleepcycles/155fd2442ebe6ad91197bf21603a20bbe0d8676e/docs/images/rekuberate-sleepcycle-logo.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/rekuberate-io/sleepcycles
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/go-logr/logr v1.2.0
7 | github.com/hashicorp/go-multierror v1.0.0
8 | github.com/onsi/ginkgo v1.16.5
9 | github.com/onsi/gomega v1.18.1
10 | github.com/pkg/errors v0.9.1
11 | go.uber.org/zap v1.19.1
12 | k8s.io/api v0.24.2
13 | k8s.io/apimachinery v0.24.2
14 | k8s.io/client-go v0.24.2
15 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
16 | sigs.k8s.io/controller-runtime v0.12.2
17 | )
18 |
19 | require (
20 | cloud.google.com/go v0.81.0 // indirect
21 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect
22 | github.com/Azure/go-autorest/autorest v0.11.18 // indirect
23 | github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
24 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
25 | github.com/Azure/go-autorest/logger v0.2.1 // indirect
26 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect
27 | github.com/PuerkitoBio/purell v1.1.1 // indirect
28 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
29 | github.com/beorn7/perks v1.0.1 // indirect
30 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
31 | github.com/davecgh/go-spew v1.1.1 // indirect
32 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect
33 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect
34 | github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
35 | github.com/fsnotify/fsnotify v1.5.1 // indirect
36 | github.com/go-logr/zapr v1.2.0 // indirect
37 | github.com/go-openapi/jsonpointer v0.19.5 // indirect
38 | github.com/go-openapi/jsonreference v0.19.5 // indirect
39 | github.com/go-openapi/swag v0.19.14 // indirect
40 | github.com/gogo/protobuf v1.3.2 // indirect
41 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
42 | github.com/golang/protobuf v1.5.2 // indirect
43 | github.com/google/gnostic v0.5.7-v3refs // indirect
44 | github.com/google/go-cmp v0.5.5 // indirect
45 | github.com/google/gofuzz v1.1.0 // indirect
46 | github.com/google/uuid v1.1.2 // indirect
47 | github.com/hashicorp/errwrap v1.0.0 // indirect
48 | github.com/imdario/mergo v0.3.12 // indirect
49 | github.com/josharian/intern v1.0.0 // indirect
50 | github.com/json-iterator/go v1.1.12 // indirect
51 | github.com/mailru/easyjson v0.7.6 // indirect
52 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
54 | github.com/modern-go/reflect2 v1.0.2 // indirect
55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
56 | github.com/nxadm/tail v1.4.8 // indirect
57 | github.com/prometheus/client_golang v1.12.1 // indirect
58 | github.com/prometheus/client_model v0.2.0 // indirect
59 | github.com/prometheus/common v0.32.1 // indirect
60 | github.com/prometheus/procfs v0.7.3 // indirect
61 | github.com/spf13/pflag v1.0.5 // indirect
62 | go.uber.org/atomic v1.7.0 // indirect
63 | go.uber.org/multierr v1.6.0 // indirect
64 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
65 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
66 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
67 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
68 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
69 | golang.org/x/text v0.3.7 // indirect
70 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
71 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
72 | google.golang.org/appengine v1.6.7 // indirect
73 | google.golang.org/protobuf v1.27.1 // indirect
74 | gopkg.in/inf.v0 v0.9.1 // indirect
75 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
76 | gopkg.in/yaml.v2 v2.4.0 // indirect
77 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
78 | k8s.io/apiextensions-apiserver v0.24.2 // indirect
79 | k8s.io/component-base v0.24.2 // indirect
80 | k8s.io/klog/v2 v2.60.1 // indirect
81 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
82 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
83 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
84 | sigs.k8s.io/yaml v1.3.0 // indirect
85 | )
86 |
--------------------------------------------------------------------------------
/hack/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "flag"
21 | "go.uber.org/zap/zapcore"
22 | "os"
23 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
24 | // to ensure that exec-entrypoint and run can make use of them.
25 | _ "k8s.io/client-go/plugin/pkg/client/auth"
26 |
27 | "k8s.io/apimachinery/pkg/runtime"
28 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
29 | clientgoscheme "k8s.io/client-go/kubernetes/scheme"
30 | ctrl "sigs.k8s.io/controller-runtime"
31 | "sigs.k8s.io/controller-runtime/pkg/healthz"
32 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
33 |
34 | corev1alpha1 "github.com/rekuberate-io/sleepcycles/api/v1alpha1"
35 | "github.com/rekuberate-io/sleepcycles/controllers"
36 | //+kubebuilder:scaffold:imports
37 | )
38 |
39 | var (
40 | scheme = runtime.NewScheme()
41 | setupLog = ctrl.Log.WithName("setup")
42 | )
43 |
44 | func init() {
45 | utilruntime.Must(clientgoscheme.AddToScheme(scheme))
46 |
47 | utilruntime.Must(corev1alpha1.AddToScheme(scheme))
48 | //+kubebuilder:scaffold:scheme
49 | }
50 |
51 | func main() {
52 | var metricsAddr string
53 | var enableLeaderElection bool
54 | var probeAddr string
55 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
56 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
57 | flag.BoolVar(&enableLeaderElection, "leader-elect", false,
58 | "Enable leader election for controller manager. "+
59 | "Enabling this will ensure there is only one active controller manager.")
60 | opts := zap.Options{
61 | Development: true,
62 | TimeEncoder: zapcore.ISO8601TimeEncoder,
63 | StacktraceLevel: zapcore.DPanicLevel,
64 | }
65 | opts.BindFlags(flag.CommandLine)
66 | flag.Parse()
67 |
68 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
69 |
70 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
71 | Scheme: scheme,
72 | MetricsBindAddress: metricsAddr,
73 | Port: 9443,
74 | HealthProbeBindAddress: probeAddr,
75 | LeaderElection: enableLeaderElection,
76 | LeaderElectionID: "04cbe4c0.rekuberate.io",
77 | // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
78 | // when the Manager ends. This requires the binary to immediately end when the
79 | // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
80 | // speeds up voluntary leader transitions as the new leader don't have to wait
81 | // LeaseDuration time first.
82 | //
83 | // In the default scaffold provided, the program ends immediately after
84 | // the manager stops, so would be fine to enable this option. However,
85 | // if you are doing or is intended to do any operation such as perform cleanups
86 | // after the manager stops then its usage might be unsafe.
87 | // LeaderElectionReleaseOnCancel: true,
88 | })
89 | if err != nil {
90 | setupLog.Error(err, "unable to start manager")
91 | os.Exit(1)
92 | }
93 |
94 | if err = (&controllers.SleepCycleReconciler{
95 | Client: mgr.GetClient(),
96 | Scheme: mgr.GetScheme(),
97 | Recorder: mgr.GetEventRecorderFor("rekuberate-io/sleepcycles"),
98 | }).SetupWithManager(mgr); err != nil {
99 | setupLog.Error(err, "unable to create controller", "controller", "SleepCycle")
100 | os.Exit(1)
101 | }
102 | //+kubebuilder:scaffold:builder
103 |
104 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
105 | setupLog.Error(err, "unable to set up health check")
106 | os.Exit(1)
107 | }
108 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
109 | setupLog.Error(err, "unable to set up ready check")
110 | os.Exit(1)
111 | }
112 |
113 | setupLog.Info("starting manager")
114 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
115 | setupLog.Error(err, "problem running manager")
116 | os.Exit(1)
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/runners/runner.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "os"
8 | "strconv"
9 | "strings"
10 | "time"
11 |
12 | "github.com/go-logr/logr"
13 | "github.com/pkg/errors"
14 | "go.uber.org/zap/zapcore"
15 | batchv1 "k8s.io/api/batch/v1"
16 | corev1 "k8s.io/api/core/v1"
17 | apierrors "k8s.io/apimachinery/pkg/api/errors"
18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 | "k8s.io/apimachinery/pkg/runtime"
20 | "k8s.io/client-go/kubernetes"
21 | typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
22 | "k8s.io/client-go/rest"
23 | "k8s.io/client-go/tools/record"
24 | "k8s.io/utils/pointer"
25 |
26 | ctrl "sigs.k8s.io/controller-runtime"
27 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
28 | )
29 |
30 | var (
31 | logger logr.Logger
32 | config *rest.Config
33 | clientSet *kubernetes.Clientset
34 | ctx context.Context
35 |
36 | envVarErr = "environment variable %s not found"
37 | podEnvVar = "MY_POD_NAME"
38 | namespaceEnvVar = "MY_POD_NAMESPACE"
39 | cronjobEnvVar = "MY_CRONJOB_NAME"
40 |
41 | eventRecorder record.EventRecorder
42 | )
43 |
44 | func init() {
45 | opts := zap.Options{
46 | Development: true,
47 | TimeEncoder: zapcore.ISO8601TimeEncoder,
48 | StacktraceLevel: zapcore.DPanicLevel,
49 | }
50 | opts.BindFlags(flag.CommandLine)
51 |
52 | logger = zap.New(zap.UseFlagOptions(&opts))
53 | config = ctrl.GetConfigOrDie()
54 | ctx = context.Background()
55 | }
56 |
57 | func main() {
58 | pd, ok := os.LookupEnv(podEnvVar)
59 | if !ok {
60 | logger.Error(fmt.Errorf(envVarErr, podEnvVar), "failed to load environment variable")
61 | //os.Exit(78)
62 | }
63 |
64 | ns, ok := os.LookupEnv(namespaceEnvVar)
65 | if !ok {
66 | logger.Error(fmt.Errorf(envVarErr, namespaceEnvVar), "failed to load environment variable")
67 | //os.Exit(78)
68 | }
69 |
70 | cj, ok := os.LookupEnv(cronjobEnvVar)
71 | if !ok {
72 | logger.Error(fmt.Errorf(envVarErr, cronjobEnvVar), "failed to load environment variable")
73 | //os.Exit(78)
74 | }
75 |
76 | logger.Info("starting runner", "namespace", ns, "cronjob", cj, "pod", pd)
77 | cs, err := kubernetes.NewForConfig(config)
78 | if err != nil {
79 | logger.Error(err, "failed to create clientset")
80 | //os.Exit(1)
81 | }
82 | clientSet = cs
83 |
84 | scheme := runtime.NewScheme()
85 | _ = batchv1.AddToScheme(scheme)
86 |
87 | eventBroadcaster := record.NewBroadcaster()
88 | defer eventBroadcaster.Shutdown()
89 |
90 | eventBroadcaster.StartStructuredLogging(4)
91 | eventBroadcaster.StartRecordingToSink(&typedv1core.EventSinkImpl{Interface: clientSet.CoreV1().Events("")})
92 | eventRecorder = eventBroadcaster.NewRecorder(scheme, corev1.EventSource{Component: "rekuberate-io/sleepcycles-runner"})
93 |
94 | cronjob, err := clientSet.BatchV1().CronJobs(ns).Get(ctx, cj, metav1.GetOptions{})
95 | if err != nil {
96 | logger.Error(err, "failed to get runner cronjob")
97 | os.Exit(1)
98 | }
99 |
100 | isShutdownOp := true
101 | if !strings.HasSuffix(cronjob.Name, "shutdown") {
102 | isShutdownOp = false
103 | }
104 |
105 | target := cronjob.Labels["rekuberate.io/target"]
106 | kind := cronjob.Labels["rekuberate.io/target-kind"]
107 |
108 | replicas := int64(1)
109 | if kind != "CronJob" {
110 | replicas, err = strconv.ParseInt(cronjob.Annotations["rekuberate.io/replicas"], 10, 32)
111 | if err != nil {
112 | logger.Error(err, "failed to get rekuberate.io/replicas value")
113 | }
114 | }
115 |
116 | if err == nil {
117 | err := run(ns, cronjob, target, kind, replicas, isShutdownOp)
118 | if err != nil {
119 | recordEvent(cronjob, err.Error(), true)
120 | logger.Error(err, "runner failed", "target", target, "kind", kind)
121 | } else {
122 | action := "up"
123 | if isShutdownOp {
124 | action = "down"
125 | }
126 | recordEvent(cronjob, fmt.Sprintf("runner scaled %s %s", action, target), false)
127 | }
128 | }
129 | }
130 |
131 | func run(ns string, cronjob *batchv1.CronJob, target string, kind string, targetReplicas int64, shutdown bool) error {
132 | smsg := "scaling failed"
133 | var serr error
134 |
135 | switch kind {
136 | case "Deployment":
137 | if shutdown {
138 | targetReplicas = 0
139 | }
140 | err := scaleDeployment(ctx, ns, cronjob, target, int32(targetReplicas))
141 | if err != nil {
142 | serr = errors.Wrap(err, smsg)
143 | }
144 | case "StatefulSet":
145 | if shutdown {
146 | targetReplicas = 0
147 | }
148 | err := scaleStatefulSets(ctx, ns, cronjob, target, int32(targetReplicas))
149 | if err != nil {
150 | serr = errors.Wrap(err, smsg)
151 | }
152 | case "CronJob":
153 | if shutdown {
154 | targetReplicas = 0
155 | }
156 | err := scaleCronJob(ctx, ns, cronjob, target, int32(targetReplicas))
157 | if err != nil {
158 | serr = errors.Wrap(err, smsg)
159 | }
160 | case "HorizontalPodAutoscaler":
161 | if shutdown {
162 | targetReplicas = 1
163 | }
164 | err := scaleHorizontalPodAutoscalers(ctx, ns, cronjob, target, int32(targetReplicas))
165 | if err != nil {
166 | serr = errors.Wrap(err, smsg)
167 | }
168 | default:
169 | err := fmt.Errorf("not supported kind: %s", kind)
170 | serr = errors.Wrap(err, smsg)
171 | }
172 |
173 | return serr
174 | }
175 |
176 | func syncReplicas(ctx context.Context, namespace string, cronjob *batchv1.CronJob, currentReplicas int32, targetReplicas int32) error {
177 | if currentReplicas != targetReplicas && currentReplicas > 0 {
178 | cronjob.Annotations["rekuberate.io/replicas"] = fmt.Sprint(currentReplicas)
179 | _, err := clientSet.BatchV1().CronJobs(namespace).Update(ctx, cronjob, metav1.UpdateOptions{})
180 | if err != nil {
181 | return err
182 | }
183 | }
184 |
185 | return nil
186 | }
187 |
188 | func scaleDeployment(ctx context.Context, namespace string, cronjob *batchv1.CronJob, target string, targetReplicas int32) error {
189 | deployment, err := clientSet.AppsV1().Deployments(namespace).Get(ctx, target, metav1.GetOptions{})
190 | if err != nil {
191 | if apierrors.IsNotFound(err) {
192 | err := markParentCronJobForDeletion(ctx, cronjob)
193 | if err != nil {
194 | return err
195 | }
196 | }
197 |
198 | return err
199 | }
200 |
201 | currentReplicas := *deployment.Spec.Replicas
202 | err = syncReplicas(ctx, namespace, cronjob, currentReplicas, targetReplicas)
203 | if err != nil {
204 | return err
205 | }
206 |
207 | if currentReplicas != targetReplicas {
208 | deployment.Spec.Replicas = &targetReplicas
209 | _, err = clientSet.AppsV1().Deployments(namespace).Update(ctx, deployment, metav1.UpdateOptions{})
210 | if err != nil {
211 | return err
212 | }
213 |
214 | action := "down"
215 | if targetReplicas > 0 {
216 | action = "up"
217 | }
218 |
219 | logger.Info(fmt.Sprintf("scaled %s deployment", action), "namespace", namespace, "deployment", target, "replicas", targetReplicas)
220 | return nil
221 | }
222 |
223 | logger.Info("deployment already in desired state", "namespace", namespace, "deployment", target, "replicas", targetReplicas)
224 |
225 | return nil
226 | }
227 |
228 | func scaleCronJob(ctx context.Context, namespace string, cronjob *batchv1.CronJob, target string, targetReplicas int32) error {
229 | cj, err := clientSet.BatchV1().CronJobs(namespace).Get(ctx, target, metav1.GetOptions{})
230 | if err != nil {
231 | if apierrors.IsNotFound(err) {
232 | err := markParentCronJobForDeletion(ctx, cronjob)
233 | if err != nil {
234 | return err
235 | }
236 | }
237 |
238 | return err
239 | }
240 |
241 | suspend := targetReplicas <= 0
242 | if suspend != *cj.Spec.Suspend {
243 | cj.Spec.Suspend = &suspend
244 | _, err = clientSet.BatchV1().CronJobs(namespace).Update(ctx, cj, metav1.UpdateOptions{})
245 | if err != nil {
246 | return err
247 | }
248 |
249 | action := "resumed"
250 | if suspend {
251 | action = "suspended"
252 | }
253 |
254 | logger.Info(fmt.Sprintf("cronjob %s", action), "namespace", namespace, "cronjob", target)
255 | return nil
256 | }
257 |
258 | logger.Info("cronjob already in desired state", "namespace", namespace, "cronjob", target, "suspended", suspend)
259 | return nil
260 | }
261 |
262 | func scaleStatefulSets(ctx context.Context, namespace string, cronjob *batchv1.CronJob, target string, targetReplicas int32) error {
263 | statefulSet, err := clientSet.AppsV1().StatefulSets(namespace).Get(ctx, target, metav1.GetOptions{})
264 | if err != nil {
265 | if apierrors.IsNotFound(err) {
266 | err := markParentCronJobForDeletion(ctx, cronjob)
267 | if err != nil {
268 | return err
269 | }
270 | }
271 |
272 | return err
273 | }
274 |
275 | currentReplicas := *statefulSet.Spec.Replicas
276 | err = syncReplicas(ctx, namespace, cronjob, currentReplicas, targetReplicas)
277 | if err != nil {
278 | return err
279 | }
280 |
281 | if currentReplicas != targetReplicas {
282 | statefulSet.Spec.Replicas = &targetReplicas
283 | _, err = clientSet.AppsV1().StatefulSets(namespace).Update(ctx, statefulSet, metav1.UpdateOptions{})
284 | if err != nil {
285 | return err
286 | }
287 |
288 | action := "down"
289 | if targetReplicas > 0 {
290 | action = "up"
291 | }
292 |
293 | logger.Info(fmt.Sprintf("scaled %s statefulset", action), "namespace", namespace, "statefulset", target, "replicas", targetReplicas)
294 | return nil
295 | }
296 |
297 | logger.Info("statefulset already in desired state", "namespace", namespace, "statefulset", target, "replicas", targetReplicas)
298 |
299 | return nil
300 | }
301 |
302 | func scaleHorizontalPodAutoscalers(ctx context.Context, namespace string, cronjob *batchv1.CronJob, target string, targetReplicas int32) error {
303 | hpa, err := clientSet.AutoscalingV1().HorizontalPodAutoscalers(namespace).Get(ctx, target, metav1.GetOptions{})
304 | if err != nil {
305 | if apierrors.IsNotFound(err) {
306 | err := markParentCronJobForDeletion(ctx, cronjob)
307 | if err != nil {
308 | return err
309 | }
310 | }
311 |
312 | return err
313 | }
314 |
315 | currentReplicas := hpa.Spec.MaxReplicas
316 | err = syncReplicas(ctx, namespace, cronjob, currentReplicas, targetReplicas)
317 | if err != nil {
318 | return err
319 | }
320 |
321 | if currentReplicas != targetReplicas {
322 | hpa.Spec.MaxReplicas = targetReplicas
323 | _, err = clientSet.AutoscalingV1().HorizontalPodAutoscalers(namespace).Update(ctx, hpa, metav1.UpdateOptions{})
324 | if err != nil {
325 | return err
326 | }
327 |
328 | action := "down"
329 | if targetReplicas > 0 {
330 | action = "up"
331 | }
332 |
333 | logger.Info(fmt.Sprintf("scaled max replicas %s", action), "namespace", namespace, "hpa", target, "replicas", targetReplicas)
334 | return nil
335 | }
336 |
337 | logger.Info("horizontal pod autoscaler already in desired state", "namespace", namespace, "hpa", target, "replicas", targetReplicas)
338 |
339 | return nil
340 | }
341 |
342 | func markParentCronJobForDeletion(ctx context.Context, cronjob *batchv1.CronJob) error {
343 | if cronjob.Status.Active != nil {
344 | return nil
345 | }
346 |
347 | err := clientSet.BatchV1().CronJobs(cronjob.Namespace).Delete(
348 | ctx,
349 | cronjob.Name,
350 | metav1.DeleteOptions{
351 | GracePeriodSeconds: pointer.Int64Ptr(15),
352 | })
353 | if err != nil {
354 | return err
355 | }
356 |
357 | message := "runner marked cronjob for self-destruction. target workload not found"
358 | logger.Info(message, "namespace", cronjob.Namespace, "cronjob", cronjob.Name)
359 | recordEvent(cronjob, message, true)
360 |
361 | return nil
362 | }
363 |
364 | func recordEvent(cronjob *batchv1.CronJob, message string, isError bool) {
365 | eventType := corev1.EventTypeNormal
366 | reason := "SuccessfulSleepCycleScale"
367 |
368 | if isError {
369 | eventType = corev1.EventTypeWarning
370 | reason = "FailedSleepCycleScale"
371 | }
372 |
373 | eventRecorder.Event(cronjob, eventType, reason, strings.ToLower(message))
374 | time.Sleep(2 * time.Second)
375 | }
376 |
--------------------------------------------------------------------------------