├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation_issue.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── push.yml │ └── unit-test.yml ├── .gitignore ├── Dockerfile ├── Dockerfile-local ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── RELEASE.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── healthcheck_types.go │ ├── healthcheck_types_test.go │ ├── suite_test.go │ └── zz_generated.deepcopy.go ├── cmd └── main.go ├── codecov.yml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── activemonitor.keikoproj.io_healthchecks.yaml │ │ └── argoproj_v1alpha1_workflows.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_healthchecks.yaml │ │ └── webhook_in_healthchecks.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_image_patch.yaml │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── rbac │ ├── 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 ├── samples │ └── activemonitor_v1alpha1_healthcheck.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── deploy ├── deploy-active-monitor.yaml └── deploy-argo.yaml ├── examples ├── Remedy_Examples │ ├── Readme.md │ ├── inlineMemoryRemedy.yaml │ ├── inlineMemoryRemedy_limit.yaml │ ├── inlineMemoryRemedy_samesa.yaml │ └── stress.yaml ├── bdd │ ├── inlineCustomBackoffTest.yaml │ ├── inlineHelloTest.yaml │ ├── inlineMemoryRemedyUnitTest.yaml │ └── inlineMemoryRemedyUnitTest_Namespace.yaml ├── inlineDns.yaml ├── inlineFail.yaml ├── inlineFailrandom.yaml ├── inlineHello.yaml ├── inlineHello_cluster.yaml ├── inlineHello_cluster_cron.yaml ├── inlineHello_cluster_cron_repeat.yaml ├── inlineHello_ns.yaml ├── inlineLoops.yaml ├── url.yaml └── workflows │ └── deployment_workflow.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── kind.cluster.yaml ├── images └── monitoring-example.png └── internal ├── controllers ├── healthcheck_controller.go ├── healthcheck_controller_test.go └── suite_test.go ├── metrics ├── collector.go └── collector_test.go └── store ├── inline.go ├── store.go └── url.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line of this file represents a file pattern followed by one or more owners. 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo. Unless a later match takes precedence, 5 | # The following owners will be requested for 6 | # review when someone opens a pull request. 7 | * @keikoproj/authorized-approvers 8 | 9 | # Admins own root and CI. 10 | .github/** @keikoproj/keiko-admins @keikoproj/keiko-maintainers 11 | /* @keikoproj/keiko-admins @keikoproj/keiko-maintainers 12 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ### Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ### Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ### Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ### Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ### Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ### Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug in the project 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Bug Description 10 | 11 | 12 | 13 | ## Steps To Reproduce 14 | 15 | 16 | 17 | 1. Step one 18 | 2. Step two 19 | 3. Step three 20 | 21 | ## Expected Behavior 22 | 23 | 24 | 25 | ## Actual Behavior 26 | 27 | 28 | 29 | ## Screenshots/Logs 30 | 31 | 32 | 33 | ## Environment 34 | 35 | 36 | 37 | - Version: 38 | - Kubernetes version: 39 | - Cloud Provider: 40 | - Installation method: 41 | - OS: 42 | - Browser (if applicable): 43 | 44 | ## Additional Context 45 | 46 | 47 | 48 | 49 | ## Impact 50 | 51 | 52 | 53 | - [ ] Blocking (cannot proceed with work) 54 | - [ ] High (significant workaround needed) 55 | - [ ] Medium (minor workaround needed) 56 | - [ ] Low (inconvenience) 57 | 58 | ## Possible Solution 59 | 60 | 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Issue 3 | about: Report issues with documentation or suggest improvements 4 | title: '[DOCS] ' 5 | labels: documentation 6 | assignees: '' 7 | --- 8 | 9 | ## Documentation Issue 10 | 11 | 12 | 13 | 14 | ## Page/Location 15 | 16 | 17 | 18 | 19 | ## Suggested Changes 20 | 21 | 22 | 23 | 24 | ## Additional Information 25 | 26 | 27 | 28 | 29 | ## Would you be willing to contribute this documentation improvement? 30 | 31 | 32 | - [ ] Yes, I can submit a PR with the changes 33 | - [ ] No, I'm not able to contribute documentation for this 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an enhancement or new feature 4 | title: '[FEATURE] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Problem Statement 10 | 11 | 12 | 13 | 14 | ## Proposed Solution 15 | 16 | 17 | 18 | 19 | ## Alternatives Considered 20 | 21 | 22 | 23 | 24 | ## User Value 25 | 26 | 27 | 28 | 29 | ## Implementation Ideas 30 | 31 | 32 | 33 | 34 | ## Additional Context 35 | 36 | 37 | 38 | ## Would you be willing to contribute this feature? 39 | 40 | 41 | - [ ] Yes, I'd like to implement this feature 42 | - [ ] I could contribute partially 43 | - [ ] No, I'm not able to contribute code for this 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question or Support Request 3 | about: Ask a question or request support 4 | title: '[QUESTION] ' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | ## Question 10 | 11 | 12 | 13 | ## Context 14 | 15 | 16 | 17 | 18 | ## Environment (if relevant) 19 | 20 | 21 | - Version: 22 | - Kubernetes version: 23 | - Cloud Provider: 24 | - OS: 25 | 26 | ## Screenshots/Logs (if applicable) 27 | 28 | 29 | 30 | ## Related Documentation 31 | 32 | 33 | 34 | ## Search Terms 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## What type of PR is this? 10 | 11 | 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] Feature/Enhancement (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 15 | - [ ] Documentation update 16 | - [ ] Refactoring (no functional changes) 17 | - [ ] Performance improvement 18 | - [ ] Test updates 19 | - [ ] CI/CD related changes 20 | - [ ] Dependency upgrade 21 | 22 | ## Description 23 | 24 | 25 | ## Related issue(s) 26 | 27 | 28 | ## High-level overview of changes 29 | 33 | 34 | ## Testing performed 35 | 42 | 43 | ## Checklist 44 | 45 | 46 | - [ ] I've read the [CONTRIBUTING](/CONTRIBUTING.md) doc 47 | - [ ] I've added/updated tests that prove my fix is effective or that my feature works 48 | - [ ] I've added necessary documentation (if appropriate) 49 | - [ ] I've run `make test` locally and all tests pass 50 | - [ ] I've signed-off my commits with `git commit -s` for DCO verification 51 | - [ ] I've updated any relevant documentation 52 | - [ ] Code follows the style guidelines of this project 53 | 54 | ## Additional information 55 | 59 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | ignore: 13 | - dependency-name: "k8s.io*" ## K8s module version updates should be done explicitly 14 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 15 | - dependency-name: "sigs.k8s.io*" ## K8s module version updates should be done explicitly 16 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 17 | - dependency-name: "*" ## Major version updates should be done explicitly 18 | update-types: ["version-update:semver-major"] 19 | 20 | - package-ecosystem: "github-actions" 21 | directory: "/" 22 | schedule: 23 | interval: "monthly" 24 | 25 | - package-ecosystem: "docker" 26 | directory: "/" 27 | schedule: 28 | interval: "monthly" 29 | ignore: 30 | - dependency-name: "golang" ## Golang version should be done explicitly -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: push 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | push: 11 | name: push 12 | if: github.repository_owner == 'keikoproj' 13 | runs-on: ubuntu-22.04 14 | steps: 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Docker Buildx 20 | id: buildx 21 | uses: docker/setup-buildx-action@v3 22 | with: 23 | install: true 24 | version: latest 25 | 26 | - name: Set up QEMU 27 | id: qemu 28 | uses: docker/setup-qemu-action@v3 29 | with: 30 | image: tonistiigi/binfmt:latest 31 | platforms: all 32 | 33 | - name: Login to DockerHub 34 | uses: docker/login-action@v3 35 | with: 36 | username: ${{ secrets.DOCKERHUB_USERNAME }} 37 | password: ${{ secrets.DOCKERHUB_TOKEN }} 38 | 39 | - 40 | name: Docker meta 41 | id: docker_meta 42 | uses: docker/metadata-action@v5 43 | with: 44 | images: ${{ github.repository_owner }}/active-monitor 45 | - 46 | name: Build and push 47 | uses: docker/build-push-action@v6 48 | with: 49 | context: . 50 | file: ./Dockerfile 51 | platforms: linux/amd64,linux/arm/v7,linux/arm64 52 | push: true 53 | tags: ${{ steps.docker_meta.outputs.tags }} -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: unit-test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | unit-test: 11 | if: github.repository_owner == 'keikoproj' 12 | name: unit-test 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: 1.24 19 | 20 | - name: Check out code into the Go module directory 21 | uses: actions/checkout@v4 22 | 23 | - name: Build 24 | run: | 25 | make all 26 | 27 | - name: Test 28 | run: | 29 | make test 30 | 31 | - name: Upload coverage reports to Codecov 32 | uses: codecov/codecov-action@v5 33 | with: 34 | files: ./cover.out 35 | token: ${{ secrets.CODECOV_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | /bin/ 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | testbin/* 9 | active-monitor-controller 10 | 11 | # Temporary or metadata files 12 | *.yaml-e 13 | 14 | # Test binary, build with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | coverage.txt 20 | 21 | # IDE related 22 | .idea 23 | .vscode 24 | 25 | # OSX metadata files 26 | .DS_Store 27 | .windsurfrules 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.24 as builder 3 | 4 | ARG TARGETOS 5 | ARG TARGETARCH 6 | 7 | WORKDIR /workspace 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | # cache deps before building and copying source so that we don't need to re-download as much 12 | # and so that source changes don't invalidate our downloaded layer 13 | RUN go mod download 14 | 15 | # Copy the go source 16 | COPY cmd/ cmd/ 17 | COPY api/ api/ 18 | COPY internal/ internal/ 19 | 20 | # Build 21 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GO111MODULE=on go build -a -o active-monitor-controller cmd/main.go 22 | 23 | # Use distroless as minimal base image to package the manager binary 24 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 25 | FROM gcr.io/distroless/static:latest 26 | WORKDIR / 27 | COPY --from=builder /workspace/active-monitor-controller . 28 | ENTRYPOINT [ "/active-monitor-controller" ] 29 | -------------------------------------------------------------------------------- /Dockerfile-local: -------------------------------------------------------------------------------- 1 | # Use distroless as minimal base image to package the manager binary 2 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 3 | FROM gcr.io/distroless/static:latest 4 | WORKDIR / 5 | COPY active-monitor-controller . 6 | ENTRYPOINT [ "/active-monitor-controller" ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 The Keiko Authors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Image URL to use all building/pushing image targets 2 | IMG ?= controller:latest 3 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 4 | ENVTEST_K8S_VERSION = 1.32.0 5 | 6 | # Tool Versions 7 | CONTROLLER_TOOLS_VERSION ?= v0.17.2 8 | KUSTOMIZE_VERSION ?= v3.8.7 9 | 10 | LOCALBIN ?= $(shell pwd)/bin 11 | 12 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 13 | ifeq (,$(shell go env GOBIN)) 14 | GOBIN=$(shell go env GOPATH)/bin 15 | else 16 | GOBIN=$(shell go env GOBIN) 17 | endif 18 | 19 | # CONTAINER_TOOL defines the container tool to be used for building images. 20 | # Be aware that the target commands are only tested with Docker which is 21 | # scaffolded by default. However, you might want to replace it to use other 22 | # tools. (i.e. podman) 23 | CONTAINER_TOOL ?= docker 24 | 25 | # Setting SHELL to bash allows bash commands to be executed by recipes. 26 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 27 | SHELL = /usr/bin/env bash -o pipefail 28 | .SHELLFLAGS = -ec 29 | 30 | .PHONY: all 31 | all: build 32 | 33 | ##@ General 34 | 35 | # The help target prints out all targets with their descriptions organized 36 | # beneath their categories. The categories are represented by '##@' and the 37 | # target descriptions by '##'. The awk command is responsible for reading the 38 | # entire set of makefiles included in this invocation, looking for lines of the 39 | # file as xyz: ## something, and then pretty-format the target and help. Then, 40 | # if there's a line with ##@ something, that gets pretty-printed as a category. 41 | # More info on the usage of ANSI control characters for terminal formatting: 42 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 43 | # More info on the awk command: 44 | # http://linuxcommand.org/lc3_adv_awk.php 45 | 46 | .PHONY: help 47 | help: ## Display this help. 48 | @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) 49 | 50 | ##@ Development 51 | 52 | .PHONY: manifests 53 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 54 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd paths="./..." output:crd:artifacts:config=config/crd/bases 55 | 56 | # $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 57 | 58 | .PHONY: generate 59 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 60 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 61 | 62 | .PHONY: fmt 63 | fmt: ## Run go fmt against code. 64 | go fmt ./... 65 | 66 | .PHONY: vet 67 | vet: ## Run go vet against code. 68 | go vet ./... 69 | 70 | .PHONY: test 71 | test: manifests generate fmt vet envtest ## Run tests. 72 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out 73 | 74 | ##@ Build 75 | 76 | .PHONY: build 77 | build: manifests generate fmt vet ## Build manager binary. 78 | go build -o bin/manager cmd/main.go 79 | 80 | .PHONY: run 81 | run: manifests generate fmt vet ## Run a controller from your host. 82 | go run ./cmd/main.go 83 | 84 | # If you wish to build the manager image targeting other platforms you can use the --platform flag. 85 | # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. 86 | # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 87 | .PHONY: docker-build 88 | docker-build: ## Build docker image with the manager. 89 | $(CONTAINER_TOOL) build -t ${IMG} . 90 | 91 | .PHONY: docker-push 92 | docker-push: ## Push docker image with the manager. 93 | $(CONTAINER_TOOL) push ${IMG} 94 | 95 | # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple 96 | # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: 97 | # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ 98 | # - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 99 | # - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) 100 | # To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. 101 | PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le 102 | .PHONY: docker-buildx 103 | docker-buildx: ## Build and push docker image for the manager for cross-platform support 104 | # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile 105 | sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross 106 | - $(CONTAINER_TOOL) buildx create --name project-v3-builder 107 | $(CONTAINER_TOOL) buildx use project-v3-builder 108 | - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . 109 | - $(CONTAINER_TOOL) buildx rm project-v3-builder 110 | rm Dockerfile.cross 111 | 112 | ##@ Deployment 113 | 114 | ifndef ignore-not-found 115 | ignore-not-found = false 116 | endif 117 | 118 | .PHONY: install 119 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 120 | $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - 121 | 122 | .PHONY: uninstall 123 | 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. 124 | $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 125 | 126 | .PHONY: deploy 127 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 128 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 129 | $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - 130 | 131 | .PHONY: undeploy 132 | 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. 133 | $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 134 | 135 | ##@ Build Dependencies 136 | 137 | ## Tool Binaries 138 | KUBECTL ?= kubectl 139 | KUSTOMIZE ?= $(LOCALBIN)/kustomize 140 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 141 | ENVTEST ?= $(LOCALBIN)/setup-envtest 142 | 143 | # Update controller-gen installation to better support ARM architectures 144 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 145 | .PHONY: controller-gen 146 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. 147 | $(CONTROLLER_GEN): $(LOCALBIN) 148 | test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ 149 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 150 | 151 | KUSTOMIZE = $(shell pwd)/bin/kustomize 152 | .PHONY: kustomize 153 | kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. 154 | $(KUSTOMIZE): $(LOCALBIN) 155 | @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ 156 | echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ 157 | rm -rf $(LOCALBIN)/kustomize; \ 158 | fi 159 | test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v3@$(KUSTOMIZE_VERSION) 160 | 161 | .PHONY: envtest 162 | envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. 163 | $(ENVTEST): $(LOCALBIN) 164 | test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 165 | 166 | $(LOCALBIN): 167 | mkdir -p $(LOCALBIN) 168 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "2" 2 | domain: keikoproj.io 3 | repo: github.com/keikoproj/active-monitor 4 | resources: 5 | - group: activemonitor 6 | version: v1alpha1 7 | kind: HealthCheck 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Active-Monitor 2 | 3 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)][GithubMaintainedUrl] 4 | [![PR](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)][GithubPrsUrl] 5 | [![slack](https://img.shields.io/badge/slack-join%20the%20conversation-ff69b4.svg)][SlackUrl] 6 | 7 | [![Go Report Card][GoReportImg]][GoReportUrl] 8 | [![Build Status][BuildStatusImg]][BuildMasterUrl] 9 | [![Code Coverage][CodecovImg]][CodecovUrl] 10 | [![Latest Version][VersionImg]][VersionUrl] 11 | 12 | ## Motivation 13 | Active-Monitor is a Kubernetes custom resource controller which enables deep cluster monitoring and self-healing using [Argo workflows](https://github.com/argoproj/argo-workflows). 14 | 15 | While it is not too difficult to know that all entities in a cluster are running individually, it is often quite challenging to know that they can all coordinate with each other as required for successful cluster operation (network connectivity, volume access, etc). 16 | 17 | ## Overview 18 | Active-Monitor will create a new `health` namespace when installed in the cluster. Users can then create and submit HealthCheck object to the Kubernetes server. A HealthCheck / Remedy is essentially an instrumented wrapper around an Argo workflow. 19 | 20 | The HealthCheck workflow is run periodically, as defined by `repeatAfterSec` or a `schedule: cron` property in its spec, and watched by the Active-Monitor controller. 21 | 22 | Active-Monitor sets the status of the HealthCheck CR to indicate whether the monitoring check succeeded or failed. If in case the monitoring check failed then the Remedy workflow will execute to fix the issue. Status of Remedy will be updated in the CR. External systems can query these CRs and take appropriate action if they failed. 23 | 24 | RemedyRunsLimit parameter allows to configure how many times a remedy should be run. If Remedy action fails for any reason it will stop on further retries. It is an optional parameter. If it is not set Remedyworkflow is triggered whenever HealthCheck workflow fails. 25 | 26 | RemedyResetInterval parameter allows resetting remedy after the reset interval time and RemedyWorkflow can be retried again in case monitor workflow fails. If remedy reaches a RemedyRunsLimit it will be reset when HealthCheck passes in any subsequent run before RemedyResetInterval. 27 | 28 | Typical examples of such workflows include tests for basic Kubernetes object creation/deletion, tests for cluster-wide services such as policy engines checks, authentication and authorization checks, etc. 29 | 30 | The sort of HealthChecks one could run with Active-Monitor are: 31 | - verify namespace and deployment creation 32 | - verify AWS resources are using < 80% of their instance limits 33 | - verify kube-dns by running DNS lookups on the network 34 | - verify kube-dns by running DNS lookups on localhost 35 | - verify KIAM agent by running aws sts get-caller-identity on all available nodes 36 | - verify if pod max threads has reached 37 | - verify if storage volume for a pod (e.g: prometheus) has reached its capacity. 38 | - verify if critical pods e.g: calico, kube-dns/core-dns pods are in a failed or crashloopbackoff state 39 | 40 | With the Cluster/Namespace level, healthchecks can be run in any namespace provided namespace is already created. 41 | The `level` in the `HealthCheck` spec defines at which level it runs; it can be either `Namespace` or `Cluster`. 42 | 43 | When `level` is set to `Namespace`, Active-Monitor will create a `ServiceAccount` in the namespace as defined in the workflow spec, it will also create the `Role` and `RoleBinding` with namespace level permissions so that the `HealthChecks` in a namespace can be performed. 44 | 45 | When the `level` is set to be `Cluster` the Active-Monitor will create a `ServiceAccount` in the namespace as defined in the workflow spec, it will also create the `ClusterRole` and `ClusterRoleBinding` with cluster level permissions so that the `HealthChecks` in a cluster scope can be performed. 46 | 47 | ## Dependencies 48 | * [Go Language tools](golang.org) 49 | * Kubernetes command line tool (kubectl) 50 | * Access to Kubernetes Cluster as specified in `~/.kube/config` 51 | * Easiest option is to install [minikube](https://minikube.sigs.k8s.io/docs/start/) and ensure that `kubectl version` returns client and server info 52 | * [Argo Workflows Controller](https://github.com/argoproj/argo-workflows) 53 | 54 | ## Installation Guide 55 | ``` 56 | # step 0: ensure that all dependencies listed above are installed or present 57 | 58 | # step 1: install argo workflow controller 59 | kubectl apply -f https://raw.githubusercontent.com/keikoproj/active-monitor/master/deploy/deploy-argo.yaml 60 | 61 | # step 2: install active-monitor CRD and start controller 62 | kubectl apply -f https://raw.githubusercontent.com/keikoproj/active-monitor/master/config/crd/bases/activemonitor.keikoproj.io_healthchecks.yaml 63 | kubectl apply -f https://raw.githubusercontent.com/keikoproj/active-monitor/master/deploy/deploy-active-monitor.yaml 64 | ``` 65 | 66 | ### Alternate Install - using locally cloned code 67 | ``` 68 | # step 0: ensure that all dependencies listed above are installed or present 69 | 70 | # step 1: install argo workflow-controller 71 | kubectl apply -f deploy/deploy-argo.yaml 72 | 73 | # step 2: install active-monitor controller 74 | make install 75 | kubectl apply -f deploy/deploy-active-monitor.yaml 76 | 77 | # step 3: run the controller via Makefile target 78 | make run 79 | ``` 80 | 81 | ## Usage and Examples 82 | Create a new healthcheck: 83 | 84 | ## Example 1: 85 | 86 | Create a new healthcheck with cluster level bindings to specified serviceaccount and in `health` namespace: 87 | 88 | `kubectl create -f https://raw.githubusercontent.com/keikoproj/active-monitor/master/examples/inlineHello.yaml` 89 | 90 | OR with local source code: 91 | 92 | `kubectl create -f examples/inlineHello.yaml` 93 | 94 | Then, list all healthchecks: 95 | 96 | `kubectl get healthcheck -n health` OR `kubectl get hc -n health` 97 | 98 | ``` 99 | NAME LATEST STATUS SUCCESS CNT FAIL CNT AGE 100 | inline-hello-7nmzk Succeeded 7 0 7m53s 101 | ``` 102 | 103 | View additional details/status of a healthcheck: 104 | 105 | `kubectl describe healthcheck inline-hello-zz5vm -n health` 106 | 107 | ``` 108 | ... 109 | Status: 110 | Failed Count: 0 111 | Finished At: 2019-08-09T22:50:57Z 112 | Last Successful Workflow: inline-hello-4mwxf 113 | Status: Succeeded 114 | Success Count: 13 115 | Events: 116 | ``` 117 | 118 | ## Example 2: 119 | 120 | Create a new healthcheck with namespace level bindings to specified serviceaccount and in a specified namespace: 121 | 122 | `kubectl create ns test` 123 | 124 | `kubectl create -f https://raw.githubusercontent.com/keikoproj/active-monitor/master/examples/inlineHello_ns.yaml` 125 | 126 | OR with local source code: 127 | 128 | `kubectl create -f examples/inlineHello_ns.yaml` 129 | 130 | Then, list all healthchecks: 131 | 132 | `kubectl get healthcheck -n test` OR `kubectl get hc -n test` 133 | 134 | ``` 135 | NAME LATEST STATUS SUCCESS CNT FAIL CNT AGE 136 | inline-hello-zz5vm Succeeded 7 0 7m53s 137 | ``` 138 | 139 | View additional details/status of a healthcheck: 140 | 141 | `kubectl describe healthcheck inline-hello-zz5vm -n test` 142 | 143 | ``` 144 | ... 145 | Status: 146 | Failed Count: 0 147 | Finished At: 2019-08-09T22:50:57Z 148 | Last Successful Workflow: inline-hello-4mwxf 149 | Status: Succeeded 150 | Success Count: 13 151 | Events: 152 | ``` 153 | 154 | `argo list -n test` 155 | 156 | ``` 157 | NAME STATUS AGE DURATION PRIORITY 158 | inline-hello-88rh2 Succeeded 29s 7s 0 159 | inline-hello-xpsf5 Succeeded 1m 8s 0 160 | inline-hello-z8llk Succeeded 2m 7s 0 161 | ``` 162 | ## Generates Resources 163 | * `activemonitor.keikoproj.io/v1alpha1/HealthCheck` 164 | * `argoproj.io/v1alpha1/Workflow` 165 | 166 | #### Sample HealthCheck CR: 167 | ```yaml 168 | apiVersion: activemonitor.keikoproj.io/v1alpha1 169 | kind: HealthCheck 170 | metadata: 171 | generateName: dns-healthcheck- 172 | namespace: health 173 | spec: 174 | repeatAfterSec: 60 175 | description: "Monitor pod dns connections" 176 | workflow: 177 | generateName: dns-workflow- 178 | resource: 179 | namespace: health 180 | serviceAccount: activemonitor-controller-sa 181 | source: 182 | inline: | 183 | apiVersion: argoproj.io/v1alpha1 184 | kind: Workflow 185 | spec: 186 | ttlSecondsAfterFinished: 60 187 | entrypoint: start 188 | templates: 189 | - name: start 190 | retryStrategy: 191 | limit: 3 192 | container: 193 | image: tutum/dnsutils 194 | command: [sh, -c] 195 | args: ["nslookup www.google.com"] 196 | ``` 197 | #### Sample RemedyWorkflow CR: 198 | ``` 199 | apiVersion: activemonitor.keikoproj.io/v1alpha1 200 | kind: HealthCheck 201 | metadata: 202 | generateName: fail-healthcheck- 203 | namespace: health 204 | spec: 205 | repeatAfterSec: 60 # duration in seconds 206 | level: cluster 207 | workflow: 208 | generateName: fail-workflow- 209 | resource: 210 | namespace: health # workflow will be submitted in this ns 211 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this 212 | source: 213 | inline: | 214 | apiVersion: argoproj.io/v1alpha1 215 | kind: Workflow 216 | metadata: 217 | labels: 218 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 219 | spec: 220 | ttlSecondsAfterFinished: 60 221 | entrypoint: start 222 | templates: 223 | - name: start 224 | retryStrategy: 225 | limit: 1 226 | container: 227 | image: ravihari/ctrmemory:v2 228 | command: ["python"] 229 | args: ["promanalysis.py", "http://prometheus.system.svc.cluster.local:9090", "health", "memory-demo", "memory-demo-ctr", "95"] 230 | remedyworkflow: 231 | generateName: remedy-test- 232 | resource: 233 | namespace: health # workflow will be submitted in this ns 234 | serviceAccount: activemonitor-remedy-sa # workflow will be submitted using this acct 235 | source: 236 | inline: | 237 | apiVersion: argoproj.io/v1alpha1 238 | kind: Workflow 239 | spec: 240 | ttlSecondsAfterFinished: 60 241 | entrypoint: kubectl 242 | templates: 243 | - 244 | container: 245 | args: ["kubectl delete po/memory-demo"] 246 | command: ["/bin/bash", "-c"] 247 | image: "ravihari/kubectl:v1" 248 | name: kubectl 249 | ``` 250 | ![Active-Monitor Architecture](./images/monitoring-example.png) 251 | 252 | #### Access Workflows on Argo UI 253 | ``` 254 | kubectl -n health port-forward deployment/argo-ui 8001:8001 255 | ``` 256 | 257 | Then visit: [http://127.0.0.1:8001](http://127.0.0.1:8001) 258 | 259 | ## Prometheus Metrics 260 | 261 | Active-Monitor controller also exports metrics in Prometheus format which can be further used for notifications and alerting. 262 | 263 | Prometheus metrics are available on `:8080/metrics` 264 | ``` 265 | kubectl -n health port-forward deployment/activemonitor-controller 8080:8080 266 | ``` 267 | Then visit: [http://localhost:8080/metrics](http://localhost:8080/metrics) 268 | 269 | Active-Monitor, by default, exports following Promethus metrics: 270 | 271 | - `healthcheck_success_count` - The total number of successful healthcheck resources 272 | - `healthcheck_error_count` - The total number of erred healthcheck resources 273 | - `healthcheck_runtime_seconds` - Time taken for the healthcheck's workflow to complete 274 | 275 | Active-Monitor also supports custom metrics. For this to work, your workflow should export a global parameter. The parameter will be programmatically available in the completed workflow object under: `workflow.status.outputs.parameters`. 276 | 277 | The global output parameters should look like below: 278 | ``` 279 | "{\"metrics\": 280 | [ 281 | {\"name\": \"custom_total\", \"value\": 123, \"metrictype\": \"gauge\", \"help\": \"custom total\"}, 282 | {\"name\": \"custom_metric\", \"value\": 12.3, \"metrictype\": \"gauge\", \"help\": \"custom metric\"} 283 | ] 284 | }" 285 | ``` 286 | 287 | 288 | 289 | ## ❤ Contributing ❤ 290 | 291 | Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). 292 | 293 | To add a new example of a healthcheck and/or workflow: 294 | 295 | * Healthcheck: place it in [`./examples`](./examples) 296 | * Workflow: place it in [`./examples/workflows`](./examples/workflows) 297 | 298 | ## Release Process 299 | 300 | Please see [RELEASE](./RELEASE.md). 301 | 302 | ## License 303 | The Apache 2 license is used in this project. Details can be found in the [LICENSE](./LICENSE) file. 304 | 305 | ## Other Keiko Projects 306 | [Instance Manager][InstanceManagerUrl] - 307 | [Kube Forensics][KubeForensicsUrl] - 308 | [Addon Manager][AddonManagerUrl] - 309 | [Upgrade Manager][UpgradeManagerUrl] - 310 | [Minion Manager][MinionManagerUrl] - 311 | [Governor][GovernorUrl] 312 | 313 | 314 | 315 | [InstanceManagerUrl]: https://github.com/keikoproj/instance-manager 316 | [KubeForensicsUrl]: https://github.com/keikoproj/kube-forensics 317 | [AddonManagerUrl]: https://github.com/keikoproj/addon-manager 318 | [UpgradeManagerUrl]: https://github.com/keikoproj/upgrade-manager 319 | [MinionManagerUrl]: https://github.com/keikoproj/minion-manager 320 | [GovernorUrl]: https://github.com/keikoproj/governor 321 | 322 | [GithubMaintainedUrl]: https://github.com/keikoproj/active-monitor/graphs/commit-activity 323 | [GithubPrsUrl]: https://github.com/keikoproj/active-monitor/pulls 324 | [SlackUrl]: https://keikoproj.slack.com/app_redirect?channel=active-monitor 325 | 326 | [GoReportImg]: https://goreportcard.com/badge/github.com/keikoproj/active-monitor 327 | [GoReportUrl]: https://goreportcard.com/report/github.com/keikoproj/active-monitor 328 | 329 | [BuildStatusImg]: https://github.com/keikoproj/active-monitor/actions/workflows/unit-test.yml/badge.svg?branch=master 330 | [BuildMasterUrl]: https://github.com/keikoproj/active-monitor/actions/workflows/unit-test.yml 331 | 332 | [CodecovImg]: https://codecov.io/gh/keikoproj/active-monitor/branch/master/graph/badge.svg 333 | [CodecovUrl]: https://codecov.io/gh/keikoproj/active-monitor 334 | 335 | [VersionImg]: https://img.shields.io/github/v/release/keikoproj/active-monitor 336 | [VersionUrl]: https://github.com/keikoproj/active-monitor/releases 337 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | The Active-Monitor Project is released on an as-needed basis. The process is as follows: 4 | 5 | 1. An issue is created which proposes a new release with a changelog since the last release 6 | 1. All [OWNERS](.github/CODEOWNERS) are suggested to look at and sign off (ex: commenting with "LGTM") on this release 7 | 1. An [OWNER](.github/CODEOWNERS) updates [CHANGELOG](./CHANGELOG.md) with release details and updates badge at top of [README](./README.md) 8 | 1. A PR is created with these changes. Upon approval, it is merged to `master` branch. 9 | 1. Now, at `HEAD` on `master` branch, an [OWNER](.github/CODEOWNERS) runs `git tag -a $VERSION` and pushes the tag with `git push --tags` 10 | 1. The release is complete! 11 | 1. Consumers can now pull docker image from [DockerHub](https://hub.docker.com/r/keikoproj/active-monitor/tags) 12 | 13 | Note: This process does not apply to alpha/dev/latest (pre-)releases which may be cut at any time for development 14 | and testing. 15 | 16 | Note: This process adapted from that used in Kubebuilder project - https://raw.githubusercontent.com/kubernetes-sigs/kubebuilder/v2.2.0/RELEASE.md -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | // Package v1alpha1 contains API Schema definitions for the activemonitor v1alpha1 API group 17 | // +kubebuilder:object:generate=true 18 | // +groupName=activemonitor.keikoproj.io 19 | package v1alpha1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects 28 | GroupVersion = schema.GroupVersion{Group: "activemonitor.keikoproj.io", Version: "v1alpha1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /api/v1alpha1/healthcheck_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | "reflect" 20 | 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // Important: Run "make" to regenerate code after modifying this file 28 | 29 | // HealthCheckSpec defines the desired state of HealthCheck 30 | // Either RepeatAfterSec or Schedule must be defined for the health check to run 31 | type HealthCheckSpec struct { 32 | RepeatAfterSec int `json:"repeatAfterSec,omitempty"` 33 | Description string `json:"description,omitempty"` 34 | Workflow Workflow `json:"workflow"` 35 | Level string `json:"level,omitempty"` // defines if a workflow runs in a Namespace or Cluster level 36 | Schedule ScheduleSpec `json:"schedule,omitempty"` // Schedule defines schedule rules to run HealthCheck 37 | RemedyWorkflow RemedyWorkflow `json:"remedyworkflow,omitempty"` 38 | BackoffFactor string `json:"backoffFactor,omitempty"` 39 | BackoffMax int `json:"backoffMax,omitempty"` 40 | BackoffMin int `json:"backoffMin,omitempty"` 41 | RemedyRunsLimit int `json:"remedyRunsLimit,omitempty"` 42 | RemedyResetInterval int `json:"remedyResetInterval,omitempty"` 43 | } 44 | 45 | // HealthCheckStatus defines the observed state of HealthCheck 46 | type HealthCheckStatus struct { 47 | ErrorMessage string `json:"errorMessage,omitempty"` 48 | RemedyErrorMessage string `json:"remedyErrorMessage,omitempty"` 49 | StartedAt *metav1.Time `json:"startedAt,omitempty"` 50 | FinishedAt *metav1.Time `json:"finishedAt,omitempty"` 51 | LastFailedAt *metav1.Time `json:"lastFailedAt,omitempty"` 52 | RemedyStartedAt *metav1.Time `json:"remedyTriggeredAt,omitempty"` 53 | RemedyFinishedAt *metav1.Time `json:"remedyFinishedAt,omitempty"` 54 | RemedyLastFailedAt *metav1.Time `json:"remedyLastFailedAt,omitempty"` 55 | LastFailedWorkflow string `json:"lastFailedWorkflow,omitempty"` 56 | LastSuccessfulWorkflow string `json:"lastSuccessfulWorkflow,omitempty"` 57 | SuccessCount int `json:"successCount,omitempty"` 58 | FailedCount int `json:"failedCount,omitempty"` 59 | RemedySuccessCount int `json:"remedySuccessCount,omitempty"` 60 | RemedyFailedCount int `json:"remedyFailedCount,omitempty"` 61 | RemedyTotalRuns int `json:"remedyTotalRuns,omitempty"` 62 | TotalHealthCheckRuns int `json:"totalHealthCheckRuns,omitempty"` 63 | Status string `json:"status,omitempty"` 64 | RemedyStatus string `json:"remedyStatus,omitempty"` 65 | } 66 | 67 | // +kubebuilder:object:root=true 68 | // +kubebuilder:subresource:status 69 | // +kubebuilder:resource:path=healthchecks,scope=Namespaced,shortName=hc;hcs 70 | // +kubebuilder:printcolumn:name="LATEST STATUS",type=string,JSONPath=`.status.status` 71 | // +kubebuilder:printcolumn:name="SUCCESS CNT ",type=string,JSONPath=`.status.successCount` 72 | // +kubebuilder:printcolumn:name="FAIL CNT",type=string,JSONPath=`.status.failedCount` 73 | // +kubebuilder:printcolumn:name="REMEDY SUCCESS CNT ",type=string,JSONPath=`.status.remedySuccessCount` 74 | // +kubebuilder:printcolumn:name="REMEDY FAIL CNT",type=string,JSONPath=`.status.remedyFailedCount` 75 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 76 | 77 | // HealthCheck is the Schema for the healthchecks API 78 | type HealthCheck struct { 79 | metav1.TypeMeta `json:",inline"` 80 | metav1.ObjectMeta `json:"metadata,omitempty"` 81 | 82 | Spec HealthCheckSpec `json:"spec,omitempty"` 83 | Status HealthCheckStatus `json:"status,omitempty"` 84 | } 85 | 86 | // +kubebuilder:object:root=true 87 | 88 | // HealthCheckList contains a list of HealthCheck 89 | type HealthCheckList struct { 90 | metav1.TypeMeta `json:",inline"` 91 | metav1.ListMeta `json:"metadata,omitempty"` 92 | Items []HealthCheck `json:"items"` 93 | } 94 | 95 | // Workflow struct describes a Remedy workflow 96 | type RemedyWorkflow struct { 97 | GenerateName string `json:"generateName,omitempty"` 98 | Resource *ResourceObject `json:"resource,omitempty"` 99 | Timeout int `json:"workflowtimeout,omitempty"` 100 | } 101 | 102 | func (w RemedyWorkflow) IsEmpty() bool { 103 | return reflect.DeepEqual(w, RemedyWorkflow{}) 104 | } 105 | 106 | // Workflow struct describes an Argo workflow 107 | type Workflow struct { 108 | GenerateName string `json:"generateName,omitempty"` 109 | Resource *ResourceObject `json:"resource,omitempty"` 110 | Timeout int `json:"workflowtimeout,omitempty"` 111 | } 112 | 113 | // ResourceObject is the resource object to create on kubernetes 114 | type ResourceObject struct { 115 | // Namespace in which to create this object 116 | // defaults to the service account namespace 117 | Namespace string `json:"namespace"` 118 | ServiceAccount string `json:"serviceAccount,omitempty"` 119 | // Source of the K8 resource file(s) 120 | Source ArtifactLocation `json:"source"` 121 | } 122 | 123 | // ArtifactLocation describes the source location for an external artifact 124 | type ArtifactLocation struct { 125 | Inline *string `json:"inline,omitempty"` 126 | File *FileArtifact `json:"file,omitempty"` 127 | URL *URLArtifact `json:"url,omitempty"` 128 | } 129 | 130 | // FileArtifact contains information about an artifact in a filesystem 131 | type FileArtifact struct { 132 | Path string `json:"path,omitempty"` 133 | } 134 | 135 | // URLArtifact contains information about an artifact at an http endpoint. 136 | type URLArtifact struct { 137 | Path string `json:"path,omitempty"` 138 | VerifyCert bool `json:"verifyCert,omitempty"` 139 | } 140 | 141 | // ScheduleSpec contains the cron expression 142 | type ScheduleSpec struct { 143 | // cron expressions can be found here: https://godoc.org/github.com/robfig/cron 144 | Cron string `json:"cron,omitempty"` 145 | } 146 | 147 | func init() { 148 | SchemeBuilder.Register(&HealthCheck{}, &HealthCheckList{}) 149 | } 150 | -------------------------------------------------------------------------------- /api/v1alpha1/healthcheck_types_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | . "github.com/onsi/ginkgo/v2" 20 | . "github.com/onsi/gomega" 21 | 22 | "golang.org/x/net/context" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/types" 25 | ) 26 | 27 | // These tests are written in BDD-style using Ginkgo framework. Refer to 28 | // http://onsi.github.io/ginkgo to learn more. 29 | var wfSpecTemplate = ` 30 | apiVersion: argoproj.io/v1alpha1 31 | kind: Workflow 32 | metadata: 33 | generateName: scripts-python- 34 | spec: 35 | entrypoint: python-script-example 36 | templates: 37 | - name: python-script-example 38 | steps: 39 | - - name: generate 40 | template: gen-random-int 41 | - - name: print 42 | template: print-message 43 | arguments: 44 | parameters: 45 | - name: message 46 | value: "{{steps.generate.outputs.result}}"] 47 | ` 48 | 49 | var _ = Describe("HealthCheck", func() { 50 | var ( 51 | key types.NamespacedName 52 | created, fetched *HealthCheck 53 | ) 54 | 55 | BeforeEach(func() { 56 | // Add any setup steps that needs to be executed before each test 57 | }) 58 | 59 | AfterEach(func() { 60 | // Add any teardown steps that needs to be executed after each test 61 | }) 62 | 63 | // Add Tests for OpenAPI validation (or additional CRD features) specified in 64 | // your API definition. 65 | // Avoid adding tests for vanilla CRUD operations because they would 66 | // test Kubernetes API server, which isn't the goal here. 67 | Context("Create and Delete APIs", func() { 68 | It("should create an object successfully", func() { 69 | 70 | key = types.NamespacedName{ 71 | Name: "foo", 72 | Namespace: "default", 73 | } 74 | created = &HealthCheck{ 75 | ObjectMeta: metav1.ObjectMeta{ 76 | Name: "foo", 77 | Namespace: "default", 78 | }, 79 | Spec: HealthCheckSpec{ 80 | RepeatAfterSec: 60, 81 | Workflow: Workflow{ 82 | GenerateName: "my-sample-workflow-", 83 | Resource: &ResourceObject{ 84 | Namespace: "health", 85 | ServiceAccount: "activemonitor-remedy-sa", 86 | Source: ArtifactLocation{ 87 | Inline: &wfSpecTemplate}, 88 | }, 89 | }, 90 | }, 91 | } 92 | 93 | By("creating an API obj") 94 | Expect(k8sClient.Create(context.TODO(), created)).To(Succeed()) 95 | 96 | fetched = &HealthCheck{} 97 | Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed()) 98 | Expect(fetched).To(Equal(created)) 99 | 100 | By("deleting the created object") 101 | Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed()) 102 | Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed()) 103 | }) 104 | 105 | It("should ignore a healthcheck without a workflow resource", func() { 106 | 107 | key = types.NamespacedName{ 108 | Name: "foo", 109 | Namespace: "default", 110 | } 111 | created = &HealthCheck{ 112 | ObjectMeta: metav1.ObjectMeta{ 113 | Name: "foo", 114 | Namespace: "default", 115 | }, 116 | Spec: HealthCheckSpec{ 117 | Level: "cluster", 118 | RepeatAfterSec: 60, 119 | Workflow: Workflow{ 120 | GenerateName: "my-sample-workflow-", 121 | Resource: &ResourceObject{ 122 | Namespace: "health", 123 | ServiceAccount: "activemonitor-remedy-sa", 124 | Source: ArtifactLocation{ 125 | Inline: &wfSpecTemplate}, 126 | }, 127 | }, 128 | RemedyWorkflow: RemedyWorkflow{ 129 | GenerateName: "my-sample-workflow-", 130 | Resource: &ResourceObject{ 131 | Namespace: "health", 132 | ServiceAccount: "activemonitor-remedy-sa", 133 | Source: ArtifactLocation{ 134 | Inline: &wfSpecTemplate}, 135 | }, 136 | }, 137 | }, 138 | } 139 | 140 | By("creating a healthcheck obj") 141 | Expect(k8sClient.Create(context.TODO(), created)).To(Succeed()) 142 | 143 | fetched = &HealthCheck{} 144 | Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed()) 145 | Expect(fetched).To(Equal(created)) 146 | Expect(fetched.Status.SuccessCount).To(Equal(0)) 147 | Expect(fetched.Status.Status).To(Equal("")) 148 | 149 | By("deleting the created object") 150 | Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed()) 151 | Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed()) 152 | }) 153 | 154 | }) 155 | 156 | }) 157 | -------------------------------------------------------------------------------- /api/v1alpha1/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | "fmt" 20 | "path/filepath" 21 | "runtime" 22 | "testing" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | 27 | "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/rest" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/envtest" 31 | logf "sigs.k8s.io/controller-runtime/pkg/log" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | ) 34 | 35 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 36 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 37 | 38 | var cfg *rest.Config 39 | var k8sClient client.Client 40 | var testEnv *envtest.Environment 41 | 42 | func TestAPIs(t *testing.T) { 43 | RegisterFailHandler(Fail) 44 | 45 | RunSpecs(t, "Controller API Suite") 46 | } 47 | 48 | var _ = BeforeSuite(func(done Done) { 49 | logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))) 50 | 51 | By("bootstrapping test environment") 52 | testEnv = &envtest.Environment{ 53 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 54 | ErrorIfCRDPathMissing: true, 55 | 56 | // The BinaryAssetsDirectory is only required if you want to run the tests directly 57 | // without call the makefile target test. If not informed it will look for the 58 | // default path defined in controller-runtime which is /usr/local/kubebuilder/. 59 | // Note that you must have the required binaries setup under the bin directory to perform 60 | // the tests directly. When we run make test it will be setup and used automatically. 61 | BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", 62 | fmt.Sprintf("1.28.0-%s-%s", runtime.GOOS, runtime.GOARCH)), 63 | } 64 | 65 | err := SchemeBuilder.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | cfg, err = testEnv.Start() 69 | Expect(err).ToNot(HaveOccurred()) 70 | Expect(cfg).ToNot(BeNil()) 71 | 72 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 73 | Expect(err).ToNot(HaveOccurred()) 74 | Expect(k8sClient).ToNot(BeNil()) 75 | 76 | close(done) 77 | }) 78 | 79 | var _ = AfterSuite(func() { 80 | By("tearing down the test environment") 81 | err := testEnv.Stop() 82 | Expect(err).ToNot(HaveOccurred()) 83 | }) 84 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | // Code generated by controller-gen. DO NOT EDIT. 19 | 20 | package v1alpha1 21 | 22 | import ( 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 27 | func (in *ArtifactLocation) DeepCopyInto(out *ArtifactLocation) { 28 | *out = *in 29 | if in.Inline != nil { 30 | in, out := &in.Inline, &out.Inline 31 | *out = new(string) 32 | **out = **in 33 | } 34 | if in.File != nil { 35 | in, out := &in.File, &out.File 36 | *out = new(FileArtifact) 37 | **out = **in 38 | } 39 | if in.URL != nil { 40 | in, out := &in.URL, &out.URL 41 | *out = new(URLArtifact) 42 | **out = **in 43 | } 44 | } 45 | 46 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactLocation. 47 | func (in *ArtifactLocation) DeepCopy() *ArtifactLocation { 48 | if in == nil { 49 | return nil 50 | } 51 | out := new(ArtifactLocation) 52 | in.DeepCopyInto(out) 53 | return out 54 | } 55 | 56 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 57 | func (in *FileArtifact) DeepCopyInto(out *FileArtifact) { 58 | *out = *in 59 | } 60 | 61 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileArtifact. 62 | func (in *FileArtifact) DeepCopy() *FileArtifact { 63 | if in == nil { 64 | return nil 65 | } 66 | out := new(FileArtifact) 67 | in.DeepCopyInto(out) 68 | return out 69 | } 70 | 71 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 72 | func (in *HealthCheck) DeepCopyInto(out *HealthCheck) { 73 | *out = *in 74 | out.TypeMeta = in.TypeMeta 75 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 76 | in.Spec.DeepCopyInto(&out.Spec) 77 | in.Status.DeepCopyInto(&out.Status) 78 | } 79 | 80 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheck. 81 | func (in *HealthCheck) DeepCopy() *HealthCheck { 82 | if in == nil { 83 | return nil 84 | } 85 | out := new(HealthCheck) 86 | in.DeepCopyInto(out) 87 | return out 88 | } 89 | 90 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 91 | func (in *HealthCheck) DeepCopyObject() runtime.Object { 92 | if c := in.DeepCopy(); c != nil { 93 | return c 94 | } 95 | return nil 96 | } 97 | 98 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 99 | func (in *HealthCheckList) DeepCopyInto(out *HealthCheckList) { 100 | *out = *in 101 | out.TypeMeta = in.TypeMeta 102 | in.ListMeta.DeepCopyInto(&out.ListMeta) 103 | if in.Items != nil { 104 | in, out := &in.Items, &out.Items 105 | *out = make([]HealthCheck, len(*in)) 106 | for i := range *in { 107 | (*in)[i].DeepCopyInto(&(*out)[i]) 108 | } 109 | } 110 | } 111 | 112 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckList. 113 | func (in *HealthCheckList) DeepCopy() *HealthCheckList { 114 | if in == nil { 115 | return nil 116 | } 117 | out := new(HealthCheckList) 118 | in.DeepCopyInto(out) 119 | return out 120 | } 121 | 122 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 123 | func (in *HealthCheckList) DeepCopyObject() runtime.Object { 124 | if c := in.DeepCopy(); c != nil { 125 | return c 126 | } 127 | return nil 128 | } 129 | 130 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 131 | func (in *HealthCheckSpec) DeepCopyInto(out *HealthCheckSpec) { 132 | *out = *in 133 | in.Workflow.DeepCopyInto(&out.Workflow) 134 | out.Schedule = in.Schedule 135 | in.RemedyWorkflow.DeepCopyInto(&out.RemedyWorkflow) 136 | } 137 | 138 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckSpec. 139 | func (in *HealthCheckSpec) DeepCopy() *HealthCheckSpec { 140 | if in == nil { 141 | return nil 142 | } 143 | out := new(HealthCheckSpec) 144 | in.DeepCopyInto(out) 145 | return out 146 | } 147 | 148 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 149 | func (in *HealthCheckStatus) DeepCopyInto(out *HealthCheckStatus) { 150 | *out = *in 151 | if in.StartedAt != nil { 152 | in, out := &in.StartedAt, &out.StartedAt 153 | *out = (*in).DeepCopy() 154 | } 155 | if in.FinishedAt != nil { 156 | in, out := &in.FinishedAt, &out.FinishedAt 157 | *out = (*in).DeepCopy() 158 | } 159 | if in.LastFailedAt != nil { 160 | in, out := &in.LastFailedAt, &out.LastFailedAt 161 | *out = (*in).DeepCopy() 162 | } 163 | if in.RemedyStartedAt != nil { 164 | in, out := &in.RemedyStartedAt, &out.RemedyStartedAt 165 | *out = (*in).DeepCopy() 166 | } 167 | if in.RemedyFinishedAt != nil { 168 | in, out := &in.RemedyFinishedAt, &out.RemedyFinishedAt 169 | *out = (*in).DeepCopy() 170 | } 171 | if in.RemedyLastFailedAt != nil { 172 | in, out := &in.RemedyLastFailedAt, &out.RemedyLastFailedAt 173 | *out = (*in).DeepCopy() 174 | } 175 | } 176 | 177 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckStatus. 178 | func (in *HealthCheckStatus) DeepCopy() *HealthCheckStatus { 179 | if in == nil { 180 | return nil 181 | } 182 | out := new(HealthCheckStatus) 183 | in.DeepCopyInto(out) 184 | return out 185 | } 186 | 187 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 188 | func (in *RemedyWorkflow) DeepCopyInto(out *RemedyWorkflow) { 189 | *out = *in 190 | if in.Resource != nil { 191 | in, out := &in.Resource, &out.Resource 192 | *out = new(ResourceObject) 193 | (*in).DeepCopyInto(*out) 194 | } 195 | } 196 | 197 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemedyWorkflow. 198 | func (in *RemedyWorkflow) DeepCopy() *RemedyWorkflow { 199 | if in == nil { 200 | return nil 201 | } 202 | out := new(RemedyWorkflow) 203 | in.DeepCopyInto(out) 204 | return out 205 | } 206 | 207 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 208 | func (in *ResourceObject) DeepCopyInto(out *ResourceObject) { 209 | *out = *in 210 | in.Source.DeepCopyInto(&out.Source) 211 | } 212 | 213 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceObject. 214 | func (in *ResourceObject) DeepCopy() *ResourceObject { 215 | if in == nil { 216 | return nil 217 | } 218 | out := new(ResourceObject) 219 | in.DeepCopyInto(out) 220 | return out 221 | } 222 | 223 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 224 | func (in *ScheduleSpec) DeepCopyInto(out *ScheduleSpec) { 225 | *out = *in 226 | } 227 | 228 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduleSpec. 229 | func (in *ScheduleSpec) DeepCopy() *ScheduleSpec { 230 | if in == nil { 231 | return nil 232 | } 233 | out := new(ScheduleSpec) 234 | in.DeepCopyInto(out) 235 | return out 236 | } 237 | 238 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 239 | func (in *URLArtifact) DeepCopyInto(out *URLArtifact) { 240 | *out = *in 241 | } 242 | 243 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URLArtifact. 244 | func (in *URLArtifact) DeepCopy() *URLArtifact { 245 | if in == nil { 246 | return nil 247 | } 248 | out := new(URLArtifact) 249 | in.DeepCopyInto(out) 250 | return out 251 | } 252 | 253 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 254 | func (in *Workflow) DeepCopyInto(out *Workflow) { 255 | *out = *in 256 | if in.Resource != nil { 257 | in, out := &in.Resource, &out.Resource 258 | *out = new(ResourceObject) 259 | (*in).DeepCopyInto(*out) 260 | } 261 | } 262 | 263 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workflow. 264 | func (in *Workflow) DeepCopy() *Workflow { 265 | if in == nil { 266 | return nil 267 | } 268 | out := new(Workflow) 269 | in.DeepCopyInto(out) 270 | return out 271 | } 272 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "os" 21 | 22 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 23 | // to ensure that exec-entrypoint and run can make use of them. 24 | _ "k8s.io/client-go/plugin/pkg/client/auth" 25 | 26 | "k8s.io/apimachinery/pkg/runtime" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | "k8s.io/client-go/dynamic" 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 | metricsfilters "sigs.k8s.io/controller-runtime/pkg/metrics/filters" 34 | "sigs.k8s.io/controller-runtime/pkg/metrics/server" 35 | 36 | activemonitorv1alpha1 "github.com/keikoproj/active-monitor/api/v1alpha1" 37 | "github.com/keikoproj/active-monitor/internal/controllers" 38 | // +kubebuilder:scaffold:imports 39 | ) 40 | 41 | var ( 42 | scheme = runtime.NewScheme() 43 | setupLog = ctrl.Log.WithName("setup") 44 | ) 45 | 46 | func init() { 47 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 48 | 49 | utilruntime.Must(activemonitorv1alpha1.AddToScheme(scheme)) 50 | //+kubebuilder:scaffold:scheme 51 | } 52 | 53 | func main() { 54 | var metricsAddr string 55 | var enableLeaderElection bool 56 | var probeAddr string 57 | var maxParallel int 58 | var secureMetrics bool 59 | 60 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8443", "The address the metric endpoint binds to.") 61 | flag.BoolVar(&secureMetrics, "metrics-secure", true, "Enable authentication and authorization for the metrics endpoint.") 62 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 63 | "Enable leader election for controller manager. "+ 64 | "Enabling this will ensure there is only one active controller manager.") 65 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 66 | flag.IntVar(&maxParallel, "max-workers", 10, "The number of maximum parallel reconciles") 67 | 68 | opts := zap.Options{ 69 | Development: true, 70 | } 71 | opts.BindFlags(flag.CommandLine) 72 | flag.Parse() 73 | 74 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 75 | 76 | // Configure metrics options 77 | metricsServerOptions := server.Options{ 78 | BindAddress: metricsAddr, 79 | } 80 | 81 | // Enable authentication and authorization for metrics when secure metrics is enabled 82 | if secureMetrics { 83 | metricsServerOptions.FilterProvider = metricsfilters.WithAuthenticationAndAuthorization 84 | } 85 | 86 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 87 | Scheme: scheme, 88 | Metrics: metricsServerOptions, 89 | HealthProbeBindAddress: probeAddr, 90 | LeaderElection: enableLeaderElection, 91 | LeaderElectionID: "689451f8.keikoproj.io", 92 | // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 93 | // when the Manager ends. This requires the binary to immediately end when the 94 | // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 95 | // speeds up voluntary leader transitions as the new leader don't have to wait 96 | // LeaseDuration time first. 97 | // 98 | // In the default scaffold provided, the program ends immediately after 99 | // the manager stops, so would be fine to enable this option. However, 100 | // if you are doing or is intended to do any operation such as perform cleanups 101 | // after the manager stops then its usage might be unsafe. 102 | // LeaderElectionReleaseOnCancel: true, 103 | }) 104 | if err != nil { 105 | setupLog.Error(err, "unable to start manager") 106 | os.Exit(1) 107 | } 108 | 109 | dynClient, err := dynamic.NewForConfig(ctrl.GetConfigOrDie()) 110 | if err != nil { 111 | setupLog.Error(err, "unable to get dynamic client") 112 | } 113 | 114 | if err = (&controllers.HealthCheckReconciler{ 115 | Client: mgr.GetClient(), 116 | DynClient: dynClient, 117 | Recorder: mgr.GetEventRecorderFor("HealthCheck"), 118 | Log: ctrl.Log.WithName("controllers").WithName("HealthCheck"), 119 | MaxParallel: maxParallel, 120 | }).SetupWithManager(mgr); err != nil { 121 | setupLog.Error(err, "unable to create controller", "controller", "HealthCheck") 122 | os.Exit(1) 123 | } 124 | // +kubebuilder:scaffold:builder 125 | 126 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 127 | setupLog.Error(err, "unable to set up health check") 128 | os.Exit(1) 129 | } 130 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 131 | setupLog.Error(err, "unable to set up ready check") 132 | os.Exit(1) 133 | } 134 | 135 | setupLog.Info("starting manager") 136 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 137 | setupLog.Error(err, "problem running manager") 138 | os.Exit(1) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "70...100" 3 | 4 | ignore: 5 | - "**/zz_generated.*" 6 | - "bin" 7 | - "config" 8 | - "hack" -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: certmanager.k8s.io/v1alpha1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: certmanager.k8s.io/v1alpha1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICENAME) and $(NAMESPACE) will be substituted by kustomize 18 | commonName: $(SERVICENAME).$(NAMESPACE).svc 19 | dnsNames: 20 | - $(SERVICENAME).$(NAMESPACE).svc.cluster.local 21 | issuerRef: 22 | kind: Issuer 23 | name: selfsigned-issuer 24 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 25 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | # the following config is for teaching kustomize how to do var substitution 5 | vars: 6 | - name: NAMESPACE # namespace of the service and the certificate CR 7 | objref: 8 | kind: Service 9 | version: v1 10 | name: webhook-service 11 | fieldref: 12 | fieldpath: metadata.namespace 13 | - name: CERTIFICATENAME 14 | objref: 15 | kind: Certificate 16 | group: certmanager.k8s.io 17 | version: v1alpha1 18 | name: serving-cert # this name should match the one in certificate.yaml 19 | - name: SERVICENAME 20 | objref: 21 | kind: Service 22 | version: v1 23 | name: webhook-service 24 | 25 | configurations: 26 | - kustomizeconfig.yaml 27 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: certmanager.k8s.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: certmanager.k8s.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: certmanager.k8s.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: certmanager.k8s.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/activemonitor.keikoproj.io_healthchecks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.17.2 7 | name: healthchecks.activemonitor.keikoproj.io 8 | spec: 9 | group: activemonitor.keikoproj.io 10 | names: 11 | kind: HealthCheck 12 | listKind: HealthCheckList 13 | plural: healthchecks 14 | shortNames: 15 | - hc 16 | - hcs 17 | singular: healthcheck 18 | scope: Namespaced 19 | versions: 20 | - additionalPrinterColumns: 21 | - jsonPath: .status.status 22 | name: LATEST STATUS 23 | type: string 24 | - jsonPath: .status.successCount 25 | name: 'SUCCESS CNT ' 26 | type: string 27 | - jsonPath: .status.failedCount 28 | name: FAIL CNT 29 | type: string 30 | - jsonPath: .status.remedySuccessCount 31 | name: 'REMEDY SUCCESS CNT ' 32 | type: string 33 | - jsonPath: .status.remedyFailedCount 34 | name: REMEDY FAIL CNT 35 | type: string 36 | - jsonPath: .metadata.creationTimestamp 37 | name: Age 38 | type: date 39 | name: v1alpha1 40 | schema: 41 | openAPIV3Schema: 42 | description: HealthCheck is the Schema for the healthchecks API 43 | properties: 44 | apiVersion: 45 | description: |- 46 | APIVersion defines the versioned schema of this representation of an object. 47 | Servers should convert recognized schemas to the latest internal value, and 48 | may reject unrecognized values. 49 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 50 | type: string 51 | kind: 52 | description: |- 53 | Kind is a string value representing the REST resource this object represents. 54 | Servers may infer this from the endpoint the client submits requests to. 55 | Cannot be updated. 56 | In CamelCase. 57 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 58 | type: string 59 | metadata: 60 | type: object 61 | spec: 62 | description: |- 63 | HealthCheckSpec defines the desired state of HealthCheck 64 | Either RepeatAfterSec or Schedule must be defined for the health check to run 65 | properties: 66 | backoffFactor: 67 | type: string 68 | backoffMax: 69 | type: integer 70 | backoffMin: 71 | type: integer 72 | description: 73 | type: string 74 | level: 75 | type: string 76 | remedyResetInterval: 77 | type: integer 78 | remedyRunsLimit: 79 | type: integer 80 | remedyworkflow: 81 | description: Workflow struct describes a Remedy workflow 82 | properties: 83 | generateName: 84 | type: string 85 | resource: 86 | description: ResourceObject is the resource object to create on 87 | kubernetes 88 | properties: 89 | namespace: 90 | description: |- 91 | Namespace in which to create this object 92 | defaults to the service account namespace 93 | type: string 94 | serviceAccount: 95 | type: string 96 | source: 97 | description: Source of the K8 resource file(s) 98 | properties: 99 | file: 100 | description: FileArtifact contains information about an 101 | artifact in a filesystem 102 | properties: 103 | path: 104 | type: string 105 | type: object 106 | inline: 107 | type: string 108 | url: 109 | description: URLArtifact contains information about an 110 | artifact at an http endpoint. 111 | properties: 112 | path: 113 | type: string 114 | verifyCert: 115 | type: boolean 116 | type: object 117 | type: object 118 | required: 119 | - namespace 120 | - source 121 | type: object 122 | workflowtimeout: 123 | type: integer 124 | type: object 125 | repeatAfterSec: 126 | type: integer 127 | schedule: 128 | description: ScheduleSpec contains the cron expression 129 | properties: 130 | cron: 131 | description: 'cron expressions can be found here: https://godoc.org/github.com/robfig/cron' 132 | type: string 133 | type: object 134 | workflow: 135 | description: Workflow struct describes an Argo workflow 136 | properties: 137 | generateName: 138 | type: string 139 | resource: 140 | description: ResourceObject is the resource object to create on 141 | kubernetes 142 | properties: 143 | namespace: 144 | description: |- 145 | Namespace in which to create this object 146 | defaults to the service account namespace 147 | type: string 148 | serviceAccount: 149 | type: string 150 | source: 151 | description: Source of the K8 resource file(s) 152 | properties: 153 | file: 154 | description: FileArtifact contains information about an 155 | artifact in a filesystem 156 | properties: 157 | path: 158 | type: string 159 | type: object 160 | inline: 161 | type: string 162 | url: 163 | description: URLArtifact contains information about an 164 | artifact at an http endpoint. 165 | properties: 166 | path: 167 | type: string 168 | verifyCert: 169 | type: boolean 170 | type: object 171 | type: object 172 | required: 173 | - namespace 174 | - source 175 | type: object 176 | workflowtimeout: 177 | type: integer 178 | type: object 179 | required: 180 | - workflow 181 | type: object 182 | status: 183 | description: HealthCheckStatus defines the observed state of HealthCheck 184 | properties: 185 | errorMessage: 186 | type: string 187 | failedCount: 188 | type: integer 189 | finishedAt: 190 | format: date-time 191 | type: string 192 | lastFailedAt: 193 | format: date-time 194 | type: string 195 | lastFailedWorkflow: 196 | type: string 197 | lastSuccessfulWorkflow: 198 | type: string 199 | remedyErrorMessage: 200 | type: string 201 | remedyFailedCount: 202 | type: integer 203 | remedyFinishedAt: 204 | format: date-time 205 | type: string 206 | remedyLastFailedAt: 207 | format: date-time 208 | type: string 209 | remedyStatus: 210 | type: string 211 | remedySuccessCount: 212 | type: integer 213 | remedyTotalRuns: 214 | type: integer 215 | remedyTriggeredAt: 216 | format: date-time 217 | type: string 218 | startedAt: 219 | format: date-time 220 | type: string 221 | status: 222 | type: string 223 | successCount: 224 | type: integer 225 | totalHealthCheckRuns: 226 | type: integer 227 | type: object 228 | type: object 229 | served: true 230 | storage: true 231 | subresources: 232 | status: {} 233 | -------------------------------------------------------------------------------- /config/crd/bases/argoproj_v1alpha1_workflows.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: workflows.argoproj.io 5 | spec: 6 | group: argoproj.io 7 | names: 8 | kind: Workflow 9 | listKind: WorkflowList 10 | plural: workflows 11 | shortNames: 12 | - wf 13 | singular: workflow 14 | scope: Namespaced 15 | versions: 16 | - name: v1alpha1 17 | additionalPrinterColumns: 18 | - description: Status of the workflow 19 | jsonPath: .status.phase 20 | name: Status 21 | type: string 22 | - description: When the workflow was started 23 | format: date-time 24 | jsonPath: .status.startedAt 25 | name: Age 26 | type: date 27 | schema: 28 | openAPIV3Schema: 29 | properties: 30 | apiVersion: 31 | type: string 32 | kind: 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | type: object 38 | x-kubernetes-map-type: atomic 39 | x-kubernetes-preserve-unknown-fields: true 40 | status: 41 | type: object 42 | x-kubernetes-map-type: atomic 43 | x-kubernetes-preserve-unknown-fields: true 44 | required: 45 | - metadata 46 | - spec 47 | type: object 48 | served: true 49 | storage: true 50 | subresources: {} -------------------------------------------------------------------------------- /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/activemonitor.keikoproj.io_healthchecks.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patches: 9 | # [WEBHOOK] patches here are for enabling the conversion webhook for each CRD 10 | #- patches/webhook_in_healthchecks.yaml 11 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 12 | 13 | # [CAINJECTION] patches here are for enabling the CA injection for each CRD 14 | #- patches/cainjection_in_healthchecks.yaml 15 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 16 | 17 | # the following config is for teaching kustomize how to do kustomization for CRDs. 18 | configurations: 19 | - kustomizeconfig.yaml 20 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_healthchecks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 8 | name: healthchecks.activemonitor.keikoproj.io 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_healthchecks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: healthchecks.activemonitor.keikoproj.io 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhook: 11 | clientConfig: 12 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 13 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 14 | caBundle: Cg== 15 | service: 16 | namespace: system 17 | name: webhook-service 18 | path: /convert 19 | conversionReviewVersions: ["v1","v1beta1"] 20 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: active-monitor-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: active-monitor- 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 crd/kustomization.yaml 20 | #- ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment next line. 'WEBHOOK' components are required. 22 | #- ../certmanager 23 | 24 | patches: 25 | - manager_image_patch.yaml 26 | # Protect the /metrics endpoint by putting it behind auth. 27 | # Only one of manager_auth_proxy_patch.yaml and 28 | # manager_prometheus_metrics_patch.yaml should be enabled. 29 | - manager_auth_proxy_patch.yaml 30 | # If you want your controller-manager to expose the /metrics 31 | # endpoint w/o any authn/z, uncomment the following line and 32 | # comment manager_auth_proxy_patch.yaml. 33 | # Only one of manager_auth_proxy_patch.yaml and 34 | # manager_prometheus_metrics_patch.yaml should be enabled. 35 | #- manager_prometheus_metrics_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 38 | #- manager_webhook_patch.yaml 39 | 40 | # [CAINJECTION] Uncomment next line to enable the CA injection in the admission webhooks. 41 | # Uncomment 'CAINJECTION' in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 42 | # 'CERTMANAGER' needs to be enabled to use ca injection 43 | #- webhookcainjection_patch.yaml 44 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch configures the manager to use the secure metrics endpoint with authentication and authorization 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: manager 12 | args: 13 | - "--metrics-bind-address=:8443" 14 | - "--metrics-secure=true" 15 | ports: 16 | - containerPort: 8443 17 | name: https 18 | protocol: TCP 19 | -------------------------------------------------------------------------------- /config/default/manager_image_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 | # Change the value of image field below to your controller image URL 11 | - image: keikoproj/active-monitor:latest 12 | name: manager 13 | -------------------------------------------------------------------------------- /config/default/manager_prometheus_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables Prometheus scraping for the manager pod. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | metadata: 10 | annotations: 11 | prometheus.io/scrape: 'true' 12 | spec: 13 | containers: 14 | # Expose the prometheus metrics on default port 15 | - name: manager 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(NAMESPACE) and $(CERTIFICATENAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /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 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /active-monitor-controller 28 | args: 29 | - --enable-leader-election 30 | image: controller:latest 31 | name: manager 32 | resources: 33 | limits: 34 | cpu: 100m 35 | memory: 30Mi 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | terminationGracePeriodSeconds: 10 40 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | prometheus.io/port: "8443" 6 | prometheus.io/scheme: https 7 | prometheus.io/scrape: "true" 8 | labels: 9 | control-plane: controller-manager 10 | name: controller-manager-metrics-service 11 | namespace: system 12 | spec: 13 | ports: 14 | - name: https 15 | port: 8443 16 | targetPort: https 17 | selector: 18 | control-plane: controller-manager 19 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 3 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - activemonitor.keikoproj.io 9 | resources: 10 | - healthchecks 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - activemonitor.keikoproj.io 21 | resources: 22 | - healthchecks/status 23 | verbs: 24 | - get 25 | - patch 26 | - update 27 | - apiGroups: 28 | - argoproj.io 29 | resources: 30 | - workflow 31 | - workflows 32 | - workflowtaskresults 33 | - workflowtasksets 34 | verbs: 35 | - create 36 | - delete 37 | - get 38 | - list 39 | - patch 40 | - update 41 | - watch 42 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/activemonitor_v1alpha1_healthcheck.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: healthcheck-sample 5 | spec: 6 | # Add fields here 7 | foo: bar 8 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keikoproj/active-monitor/3bc763b1d3bc78e285b9f8d0af8032e28c0a6af2/config/webhook/manifests.yaml -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /deploy/deploy-active-monitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: activemonitor-controller-sa 5 | namespace: health 6 | --- 7 | kind: Role 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | metadata: 10 | name: activemonitor-controller-role 11 | namespace: health 12 | rules: 13 | - apiGroups: 14 | - "*" 15 | resources: 16 | - pods 17 | - deployments 18 | - replicasets 19 | - services 20 | - configmaps 21 | - secrets 22 | - workflows 23 | - workflowtasksets 24 | - serviceaccounts 25 | - roles 26 | - rolebindings 27 | verbs: 28 | - "*" 29 | --- 30 | kind: ClusterRole 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | metadata: 33 | name: activemonitor-controller-clusterrole 34 | rules: 35 | - apiGroups: 36 | - "*" 37 | - activemonitor.keikoproj.io 38 | resources: 39 | - workflows 40 | - monitors 41 | - pods 42 | - events 43 | - healthchecks 44 | - healthchecks/status 45 | - serviceaccounts 46 | - clusterroles 47 | - clusterrolebindings 48 | verbs: 49 | - "*" 50 | --- 51 | kind: RoleBinding 52 | apiVersion: rbac.authorization.k8s.io/v1 53 | metadata: 54 | name: activemonitor-controller-rb 55 | namespace: health 56 | subjects: 57 | - kind: ServiceAccount 58 | name: activemonitor-controller-sa 59 | namespace: health 60 | roleRef: 61 | kind: Role 62 | name: activemonitor-controller-role 63 | apiGroup: rbac.authorization.k8s.io 64 | --- 65 | kind: ClusterRoleBinding 66 | apiVersion: rbac.authorization.k8s.io/v1 67 | metadata: 68 | name: activemonitor-controller-cluster-rb 69 | subjects: 70 | - kind: ServiceAccount 71 | name: activemonitor-controller-sa 72 | namespace: health 73 | roleRef: 74 | kind: ClusterRole 75 | name: activemonitor-controller-clusterrole 76 | apiGroup: rbac.authorization.k8s.io 77 | --- 78 | apiVersion: apps/v1 79 | kind: Deployment 80 | metadata: 81 | name: activemonitor-controller 82 | namespace: health 83 | spec: 84 | replicas: 1 85 | selector: 86 | matchLabels: 87 | name: activemonitor-controller 88 | template: 89 | metadata: 90 | labels: 91 | name: activemonitor-controller 92 | spec: 93 | containers: 94 | - name: active-monitor 95 | image: keikoproj/active-monitor:latest 96 | imagePullPolicy: Always 97 | resources: 98 | requests: 99 | cpu: "50m" 100 | memory: "80Mi" 101 | serviceAccountName: activemonitor-controller-sa 102 | -------------------------------------------------------------------------------- /examples/Remedy_Examples/Readme.md: -------------------------------------------------------------------------------- 1 | ## Remedy Example 2 | In this example we assume that: 3 | - Prometheus is up and running in the cluster in the system namespace. 4 | - It can be accessed over http at: prometheus.system.svc.cluster.local on port 9090 i.e., http://prometheus.system.svc.cluster.local:9090 5 | - You already have memory-demo pod running. (It can be created with `kubectl apply -f stress.yaml`) 6 | 7 | ## Healthcheck Container 8 | In the example for healthcheck workflow there is a container `ravihari/ctrmemory:v2` 9 | The container takes the following arguments in this order: 10 | - promanalysis.py (script already present in the container) 11 | - "http://prometheus.system.svc.cluster.local:9090" (Prometheus address) 12 | - namespace name 13 | - pod name 14 | - container name 15 | - threshold (if the value is greater than threshold the healthcheck fails) 16 | 17 | ## Kubectl Container 18 | In the example for remedy workflow there is a container `ravihari/kubectl:v1` 19 | The container takes any kubectl commands as arguments to run. 20 | -------------------------------------------------------------------------------- /examples/Remedy_Examples/inlineMemoryRemedy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: fail-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: fail-workflow- 11 | resource: 12 | namespace: health # workflow will be submitted in this ns 13 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | labels: 20 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 21 | spec: 22 | ttlSecondsAfterFinished: 60 23 | entrypoint: start 24 | templates: 25 | - name: start 26 | retryStrategy: 27 | limit: 1 28 | container: 29 | image: ravihari/ctrmemory:v2 30 | command: ["python"] 31 | args: ["promanalysis.py", "http://prometheus.system.svc.cluster.local:9090", "health", "memory-demo", "memory-demo-ctr", "95"] 32 | remedyworkflow: 33 | generateName: remedy-test- 34 | resource: 35 | namespace: health # workflow will be submitted in this ns 36 | serviceAccount: activemonitor-remedy-sa # workflow will be submitted using this acct 37 | source: 38 | inline: | 39 | apiVersion: argoproj.io/v1alpha1 40 | kind: Workflow 41 | spec: 42 | ttlSecondsAfterFinished: 60 43 | entrypoint: kubectl 44 | templates: 45 | - 46 | container: 47 | args: ["kubectl delete po/memory-demo"] 48 | command: ["/bin/bash", "-c"] 49 | image: "ravihari/kubectl:v1" 50 | name: kubectl 51 | -------------------------------------------------------------------------------- /examples/Remedy_Examples/inlineMemoryRemedy_limit.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: fail-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 30 # duration in seconds 8 | level: cluster 9 | remedyRunsLimit: 2 10 | remedyResetInterval: 300 11 | workflow: 12 | generateName: randomfail-workflow- 13 | resource: 14 | namespace: health # workflow will be submitted in this ns 15 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this 16 | source: 17 | inline: | 18 | apiVersion: argoproj.io/v1alpha1 19 | kind: Workflow 20 | metadata: 21 | labels: 22 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 23 | spec: 24 | ttlSecondsAfterFinished: 60 25 | podGC: 26 | strategy: OnWorkflowSuccess 27 | entrypoint: start 28 | templates: 29 | - name: start 30 | container: 31 | image: docker/whalesay:latest 32 | command: [/bin/bash, -c] 33 | args: ["echo 'Running command';P=(1 0); exit ${P[RANDOM%2]};"] 34 | remedyworkflow: 35 | generateName: remedy-test- 36 | resource: 37 | namespace: health # workflow will be submitted in this ns 38 | serviceAccount: activemonitor-remedy-sa # workflow will be submitted using this acct 39 | source: 40 | inline: | 41 | apiVersion: argoproj.io/v1alpha1 42 | kind: Workflow 43 | metadata: 44 | labels: 45 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 46 | generateName: hello-world- 47 | spec: 48 | entrypoint: whalesay 49 | templates: 50 | - 51 | container: 52 | args: 53 | - "hello world" 54 | command: 55 | - cowsay 56 | image: "docker/whalesay:latest" 57 | name: whalesay 58 | -------------------------------------------------------------------------------- /examples/Remedy_Examples/inlineMemoryRemedy_samesa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: fail-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: fail-workflow- 11 | resource: 12 | namespace: health # workflow will be submitted in this ns 13 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | spec: 19 | ttlSecondsAfterFinished: 60 20 | entrypoint: start 21 | templates: 22 | - name: start 23 | retryStrategy: 24 | limit: 1 25 | container: 26 | image: ravihari/ctrmemory:v2 27 | command: ["python"] 28 | args: ["promanalysis.py", "http://prometheus.system.svc.cluster.local:9090", "health", "memory-demo", "memory-demo-ctr", "95"] 29 | remedyworkflow: 30 | generateName: remedy-test- 31 | resource: 32 | namespace: health # workflow will be submitted in this ns 33 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this acct 34 | source: 35 | inline: | 36 | apiVersion: argoproj.io/v1alpha1 37 | kind: Workflow 38 | metadata: 39 | generateName: remedy-test- 40 | spec: 41 | entrypoint: kubectl 42 | templates: 43 | - 44 | container: 45 | args: ["kubectl delete po/memory-demo"] 46 | command: ["/bin/bash", "-c"] 47 | image: "ravihari/kubectl:v1" 48 | name: kubectl 49 | -------------------------------------------------------------------------------- /examples/Remedy_Examples/stress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: memory-demo 5 | namespace: health 6 | spec: 7 | containers: 8 | - name: memory-demo-ctr 9 | image: ravihari/stress-ng 10 | resources: 11 | limits: 12 | memory: "100Mi" 13 | requests: 14 | memory: "100Mi" 15 | command: ["/usr/bin/stress-ng"] 16 | args: ["--vm", "1", "--vm-bytes", "98M", "--vm-hang", "1", "-b", "60000"] 17 | -------------------------------------------------------------------------------- /examples/bdd/inlineCustomBackoffTest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: inline-hello-custom-retry 5 | generateName: inline-hello- 6 | namespace: health 7 | spec: 8 | schedule: 9 | cron: "@every 3s" 10 | level: cluster 11 | backoffFactor: "0.1" 12 | backoffMin: 1 13 | backoffMax: 2 14 | workflow: 15 | generateName: inline-hello- 16 | windowtimeout: 1200 17 | resource: 18 | namespace: health # workflow will be submitted in this ns 19 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this acct 20 | source: 21 | inline: | 22 | apiVersion: argoproj.io/v1alpha1 23 | kind: Workflow 24 | metadata: 25 | labels: 26 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 27 | generateName: hello-world- 28 | spec: 29 | entrypoint: whalesay 30 | templates: 31 | - 32 | container: 33 | args: 34 | - "hello world" 35 | command: 36 | - cowsay 37 | image: "docker/whalesay:latest" 38 | name: whalesay 39 | -------------------------------------------------------------------------------- /examples/bdd/inlineHelloTest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: inline-hello-pause 5 | generateName: inline-hello- 6 | namespace: health 7 | spec: 8 | repeatAfterSec: 0 # duration in seconds 9 | # schedule: 10 | # cron: "@every 1m" 11 | level: cluster 12 | workflow: 13 | generateName: inline-hello- 14 | resource: 15 | namespace: health # workflow will be submitted in this ns 16 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this acct 17 | source: 18 | inline: | 19 | apiVersion: argoproj.io/v1alpha1 20 | kind: Workflow 21 | metadata: 22 | labels: 23 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 24 | generateName: hello-world- 25 | spec: 26 | entrypoint: whalesay 27 | templates: 28 | - 29 | container: 30 | args: 31 | - "hello world" 32 | command: 33 | - cowsay 34 | image: "docker/whalesay:latest" 35 | name: whalesay 36 | -------------------------------------------------------------------------------- /examples/bdd/inlineMemoryRemedyUnitTest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: inline-monitor-remedy 5 | generateName: fail-healthcheck- 6 | namespace: health 7 | spec: 8 | repeatAfterSec: 5 # duration in seconds 9 | level: cluster 10 | remedyRunsLimit: 2 11 | remedyResetInterval: 300 12 | workflow: 13 | generateName: fail-workflow- 14 | resource: 15 | namespace: health # workflow will be submitted in this ns 16 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this 17 | source: 18 | inline: | 19 | apiVersion: argoproj.io/v1alpha1 20 | kind: Workflow 21 | metadata: 22 | labels: 23 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 24 | spec: 25 | ttlSecondsAfterFinished: 60 26 | entrypoint: start 27 | templates: 28 | - name: start 29 | retryStrategy: 30 | limit: 1 31 | container: 32 | image: ravihari/ctrmemory:v2 33 | command: ["python"] 34 | args: ["promanalysis.py", "http://prometheus.system.svc.cluster.local:9090", "health", "memory-demo", "memory-demo-ctr", "95"] 35 | remedyworkflow: 36 | generateName: remedy-test- 37 | resource: 38 | namespace: health # workflow will be submitted in this ns 39 | serviceAccount: activemonitor-remedy-sa # workflow will be submitted using this acct 40 | source: 41 | inline: | 42 | apiVersion: argoproj.io/v1alpha1 43 | kind: Workflow 44 | metadata: 45 | labels: 46 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 47 | spec: 48 | ttlSecondsAfterFinished: 60 49 | entrypoint: kubectl 50 | templates: 51 | - 52 | container: 53 | args: ["kubectl delete po/memory-demo"] 54 | command: ["/bin/bash", "-c"] 55 | image: "ravihari/kubectl:v1" 56 | name: kubectl 57 | -------------------------------------------------------------------------------- /examples/bdd/inlineMemoryRemedyUnitTest_Namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: inline-monitor-remedy-namespace 5 | generateName: fail-healthcheck- 6 | namespace: health 7 | spec: 8 | schedule: 9 | cron: "@every 5s" 10 | level: namespace 11 | workflow: 12 | generateName: fail-workflow- 13 | resource: 14 | namespace: health # workflow will be submitted in this ns 15 | serviceAccount: activemonitor-healthcheck-sa # workflow will be submitted using this 16 | source: 17 | inline: | 18 | apiVersion: argoproj.io/v1alpha1 19 | kind: Workflow 20 | spec: 21 | entrypoint: start 22 | templates: 23 | - name: start 24 | retryStrategy: 25 | limit: 1 26 | container: 27 | image: ravihari/ctrmemory:v2 28 | command: ["python"] 29 | args: ["promanalysis.py", "http://prometheus.system.svc.cluster.local:9090", "health", "memory-demo", "memory-demo-ctr", "95"] 30 | remedyworkflow: 31 | generateName: remedy-test- 32 | resource: 33 | namespace: health # workflow will be submitted in this ns 34 | serviceAccount: activemonitor-remedy-sa # workflow will be submitted using this acct 35 | source: 36 | inline: | 37 | apiVersion: argoproj.io/v1alpha1 38 | kind: Workflow 39 | spec: 40 | entrypoint: kubectl 41 | templates: 42 | - 43 | container: 44 | args: ["kubectl delete po/memory-demo"] 45 | command: ["/bin/bash", "-c"] 46 | image: "ravihari/kubectl:v1" 47 | name: kubectl 48 | -------------------------------------------------------------------------------- /examples/inlineDns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: dns-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | description: "Monitor pod dns connections" 10 | workflow: 11 | generateName: dns-workflow- 12 | resource: 13 | namespace: health # workflow will be submitted in this ns 14 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this 15 | source: 16 | inline: | 17 | apiVersion: argoproj.io/v1alpha1 18 | kind: Workflow 19 | metadata: 20 | labels: 21 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 22 | generateName: dns-workflow- 23 | spec: 24 | activeDeadlineSeconds: 65 25 | ttlSecondsAfterFinished: 60 26 | entrypoint: start 27 | templates: 28 | - name: start 29 | retryStrategy: 30 | limit: 3 31 | container: 32 | image: tutum/dnsutils 33 | command: [sh, -c] 34 | args: ["nslookup www.google.com"] 35 | -------------------------------------------------------------------------------- /examples/inlineFail.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: fail-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: fail-workflow- 11 | resource: 12 | namespace: health # workflow will be submitted in this ns 13 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | generateName: fail-workflow- 20 | spec: 21 | ttlSecondsAfterFinished: 60 22 | entrypoint: start 23 | templates: 24 | - name: start 25 | retryStrategy: 26 | limit: 3 27 | container: 28 | image: alpine 29 | command: [sh, -c] 30 | args: ["fail"] 31 | -------------------------------------------------------------------------------- /examples/inlineFailrandom.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: fail-healthcheck- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: fail-workflow- 11 | resource: 12 | namespace: health # workflow will be submitted in this ns 13 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | labels: 20 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 21 | spec: 22 | ttlSecondsAfterFinished: 60 23 | entrypoint: start 24 | templates: 25 | - name: start 26 | retryStrategy: 27 | limit: 1 28 | container: 29 | image: docker/whalesay:latest 30 | command: [/bin/bash, -c] 31 | args: ["P=(0 1); exit ${P[RANDOM%2]};"] 32 | -------------------------------------------------------------------------------- /examples/inlineHello.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | name: inline-hello 5 | # generateName: inline-hello- 6 | namespace: health 7 | spec: 8 | # repeatAfterSec: 60 # duration in seconds 9 | schedule: 10 | cron: "@every 1m" 11 | level: cluster 12 | workflow: 13 | generateName: inline-hello- 14 | resource: 15 | namespace: health # workflow will be submitted in this ns 16 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this acct 17 | source: 18 | inline: | 19 | apiVersion: argoproj.io/v1alpha1 20 | kind: Workflow 21 | metadata: 22 | labels: 23 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 24 | generateName: hello-world- 25 | spec: 26 | entrypoint: whalesay 27 | templates: 28 | - 29 | container: 30 | args: 31 | - "hello world" 32 | command: 33 | - cowsay 34 | image: "docker/whalesay:latest" 35 | name: whalesay 36 | -------------------------------------------------------------------------------- /examples/inlineHello_cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: inline-hello- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: inline-hello- 11 | resource: 12 | namespace: health 13 | serviceAccount: testclsa 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | labels: 20 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 21 | generateName: hello-world- 22 | spec: 23 | entrypoint: whalesay 24 | templates: 25 | - 26 | container: 27 | args: 28 | - "hello world" 29 | command: 30 | - cowsay 31 | image: "docker/whalesay:latest" 32 | name: whalesay 33 | -------------------------------------------------------------------------------- /examples/inlineHello_cluster_cron.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: inline-hello- 5 | namespace: health 6 | spec: 7 | schedule: 8 | cron: "@every 1m" 9 | level: cluster 10 | workflow: 11 | generateName: inline-hello- 12 | resource: 13 | namespace: health 14 | serviceAccount: testclsa 15 | source: 16 | inline: | 17 | apiVersion: argoproj.io/v1alpha1 18 | kind: Workflow 19 | metadata: 20 | labels: 21 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 22 | generateName: hello-world- 23 | spec: 24 | entrypoint: whalesay 25 | templates: 26 | - 27 | container: 28 | args: 29 | - "hello world" 30 | command: 31 | - cowsay 32 | image: "docker/whalesay:latest" 33 | name: whalesay 34 | -------------------------------------------------------------------------------- /examples/inlineHello_cluster_cron_repeat.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: inline-hello- 5 | namespace: health 6 | spec: 7 | level: cluster 8 | workflow: 9 | generateName: inline-hello- 10 | resource: 11 | namespace: health 12 | serviceAccount: testclsa 13 | source: 14 | inline: | 15 | apiVersion: argoproj.io/v1alpha1 16 | kind: Workflow 17 | metadata: 18 | generateName: hello-world- 19 | spec: 20 | entrypoint: whalesay 21 | templates: 22 | - 23 | container: 24 | args: 25 | - "hello world" 26 | command: 27 | - cowsay 28 | image: "docker/whalesay:latest" 29 | name: whalesay 30 | -------------------------------------------------------------------------------- /examples/inlineHello_ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: inline-hello- 5 | namespace: test 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: namespace 9 | workflow: 10 | generateName: inline-hello- 11 | resource: 12 | namespace: test # workflow will be submitted in this ns 13 | serviceAccount: testnssa 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | labels: 20 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 21 | generateName: hello-world- 22 | spec: 23 | entrypoint: whalesay 24 | templates: 25 | - 26 | container: 27 | args: 28 | - "hello world" 29 | command: 30 | - cowsay 31 | image: "docker/whalesay:latest" 32 | name: whalesay 33 | -------------------------------------------------------------------------------- /examples/inlineLoops.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: example-inline-loops- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | level: cluster 9 | workflow: 10 | generateName: example-inline-loops- 11 | resource: 12 | namespace: health # workflow will be submitted in this ns 13 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this 14 | source: 15 | inline: | 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Workflow 18 | metadata: 19 | labels: 20 | workflows.argoproj.io/controller-instanceid: activemonitor-workflows 21 | generateName: loops- 22 | spec: 23 | entrypoint: loop-example 24 | templates: 25 | - name: loop-example 26 | steps: 27 | - - name: print-message 28 | template: whalesay 29 | arguments: 30 | parameters: 31 | - name: message 32 | value: "{{item}}" 33 | withItems: 34 | - hello world 35 | - goodbye world 36 | 37 | - name: whalesay 38 | inputs: 39 | parameters: 40 | - name: message 41 | container: 42 | image: docker/whalesay:latest 43 | command: [cowsay] 44 | args: ["{{inputs.parameters.message}}"] 45 | -------------------------------------------------------------------------------- /examples/url.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: activemonitor.keikoproj.io/v1alpha1 2 | kind: HealthCheck 3 | metadata: 4 | generateName: url-hello- 5 | namespace: health 6 | spec: 7 | repeatAfterSec: 60 # duration in seconds 8 | workflow: 9 | generateName: url-hello- 10 | resource: 11 | namespace: health # workflow will be submitted in this ns 12 | serviceAccount: activemonitor-controller-sa # workflow will be submitted using this sa 13 | source: 14 | # provide your workflow via URL 15 | url: 16 | path: https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml 17 | verifyCert: false 18 | -------------------------------------------------------------------------------- /examples/workflows/deployment_workflow.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Workflow 3 | metadata: 4 | generateName: "synthetic-deployment-" 5 | namespace: kube-system 6 | spec: 7 | entrypoint: synthetic-example 8 | onExit: exit-handler 9 | templates: 10 | - name: synthetic-example 11 | steps: 12 | - - name: create-deployment 13 | template: create-deployment 14 | - - name: sleep 15 | template: sleep-n-sec 16 | - - name: check-deployment 17 | template: check-deployment 18 | - - name: delete-deployment 19 | template: delete-deployment 20 | 21 | - name: exit-handler 22 | steps: 23 | - - name: cleanup 24 | template: cleanup-workflow 25 | when: "{{workflow.status}} == Failed" 26 | 27 | - name: create-deployment 28 | script: 29 | image: lachlanevenson/k8s-kubectl 30 | command: [sh] 31 | source: | 32 | kubectl run operator-nginx --image=nginx:latest --replicas=2 --port=80 -n monitoring 33 | 34 | - name: sleep-n-sec 35 | container: 36 | image: alpine:latest 37 | command: [sh, -c] 38 | args: ["echo sleeping for 40 seconds; sleep 40; echo done"] 39 | 40 | - name: check-deployment 41 | script: 42 | image: lachlanevenson/k8s-kubectl 43 | command: [sh] 44 | source: | 45 | kubectl get po -n monitoring | grep operator-nginx | grep Running 46 | 47 | - name: delete-deployment 48 | script: 49 | image: lachlanevenson/k8s-kubectl 50 | command: [sh] 51 | source: | 52 | kubectl delete deploy operator-nginx -n monitoring; [[ $? == 0 ]] && echo "Workflow ran successfully" 53 | 54 | - name: cleanup-workflow 55 | script: 56 | image: lachlanevenson/k8s-kubectl 57 | command: [sh] 58 | source: | 59 | kubectl delete deploy operator-nginx -n monitoring; [[ $? == 0 ]] && echo "Workflow failed and completed cleanup" 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/keikoproj/active-monitor 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/argoproj/argo-workflows/v3 v3.6.5 7 | github.com/ghodss/yaml v1.0.0 8 | github.com/go-logr/logr v1.4.2 9 | github.com/keikoproj/inverse-exp-backoff v0.1.1 10 | github.com/mitchellh/mapstructure v1.5.0 11 | github.com/onsi/ginkgo/v2 v2.23.4 12 | github.com/onsi/gomega v1.37.0 13 | github.com/prometheus/client_golang v1.22.0 14 | github.com/robfig/cron/v3 v3.0.1 15 | github.com/sirupsen/logrus v1.9.3 16 | github.com/stretchr/testify v1.10.0 17 | golang.org/x/net v0.39.0 18 | gopkg.in/yaml.v3 v3.0.1 19 | k8s.io/api v0.32.4 20 | k8s.io/apimachinery v0.32.4 21 | k8s.io/client-go v0.32.4 22 | sigs.k8s.io/controller-runtime v0.20.4 23 | ) 24 | 25 | require ( 26 | cel.dev/expr v0.19.1 // indirect 27 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 28 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect 29 | github.com/beorn7/perks v1.0.1 // indirect 30 | github.com/blang/semver/v4 v4.0.0 // indirect 31 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 32 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 34 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 35 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 36 | github.com/felixge/httpsnoop v1.0.4 // indirect 37 | github.com/fsnotify/fsnotify v1.8.0 // indirect 38 | github.com/fxamacker/cbor/v2 v2.7.1 // indirect 39 | github.com/go-logr/stdr v1.2.2 // indirect 40 | github.com/go-logr/zapr v1.3.0 // indirect 41 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 42 | github.com/go-openapi/jsonreference v0.21.0 // indirect 43 | github.com/go-openapi/swag v0.23.1 // indirect 44 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 45 | github.com/gogo/protobuf v1.3.2 // indirect 46 | github.com/golang/protobuf v1.5.4 // indirect 47 | github.com/google/btree v1.1.3 // indirect 48 | github.com/google/cel-go v0.22.0 // indirect 49 | github.com/google/gnostic-models v0.6.9 // indirect 50 | github.com/google/go-cmp v0.7.0 // indirect 51 | github.com/google/gofuzz v1.2.0 // indirect 52 | github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect 53 | github.com/google/uuid v1.6.0 // indirect 54 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 55 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect 56 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 57 | github.com/josharian/intern v1.0.0 // indirect 58 | github.com/json-iterator/go v1.1.12 // indirect 59 | github.com/mailru/easyjson v0.9.0 // indirect 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 61 | github.com/modern-go/reflect2 v1.0.2 // indirect 62 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 63 | github.com/pkg/errors v0.9.1 // indirect 64 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 65 | github.com/prometheus/client_model v0.6.2 // indirect 66 | github.com/prometheus/common v0.63.0 // indirect 67 | github.com/prometheus/procfs v0.16.1 // indirect 68 | github.com/spf13/cobra v1.8.1 // indirect 69 | github.com/spf13/pflag v1.0.6 // indirect 70 | github.com/stoewer/go-strcase v1.3.0 // indirect 71 | github.com/x448/float16 v0.8.4 // indirect 72 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 73 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 74 | go.opentelemetry.io/otel v1.34.0 // indirect 75 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 76 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect 77 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 78 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 79 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 80 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 81 | go.uber.org/automaxprocs v1.6.0 // indirect 82 | go.uber.org/multierr v1.11.0 // indirect 83 | go.uber.org/zap v1.27.0 // indirect 84 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 85 | golang.org/x/oauth2 v0.28.0 // indirect 86 | golang.org/x/sync v0.13.0 // indirect 87 | golang.org/x/sys v0.32.0 // indirect 88 | golang.org/x/term v0.31.0 // indirect 89 | golang.org/x/text v0.24.0 // indirect 90 | golang.org/x/time v0.11.0 // indirect 91 | golang.org/x/tools v0.31.0 // indirect 92 | gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect 93 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect 94 | google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197 // indirect 95 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect 96 | google.golang.org/grpc v1.71.1 // indirect 97 | google.golang.org/protobuf v1.36.6 // indirect 98 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 99 | gopkg.in/inf.v0 v0.9.1 // indirect 100 | gopkg.in/yaml.v2 v2.4.0 // indirect 101 | k8s.io/apiextensions-apiserver v0.32.4 // indirect 102 | k8s.io/apiserver v0.32.4 // indirect 103 | k8s.io/component-base v0.32.4 // indirect 104 | k8s.io/klog/v2 v2.130.1 // indirect 105 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 106 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect 107 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect 108 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 109 | sigs.k8s.io/randfill v1.0.0 // indirect 110 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 111 | sigs.k8s.io/yaml v1.4.0 // indirect 112 | ) 113 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= 2 | cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 3 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 7 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 8 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 9 | github.com/argoproj/argo-workflows/v3 v3.6.5 h1:0dvQr+CrhYgCc7ZJ4ZxusLVL/LKwHnpkT1lw/BP4+W4= 10 | github.com/argoproj/argo-workflows/v3 v3.6.5/go.mod h1:w6kuZAqk9vLskhg8hT/67xCu89SFq5FE6sFk5POQD0o= 11 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= 12 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 13 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 14 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 15 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 16 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 17 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 18 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 19 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 20 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 21 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 22 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 23 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 24 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 28 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 30 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 31 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 32 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 33 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 34 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 35 | github.com/evanphx/json-patch v5.8.0+incompatible h1:1Av9pn2FyxPdvrWNQszj1g6D6YthSmvCfcN6SYclTJg= 36 | github.com/evanphx/json-patch v5.8.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 37 | github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 38 | github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 39 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 40 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 41 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 42 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 43 | github.com/fxamacker/cbor/v2 v2.7.1 h1:e41dNILEDbsGj2nl/I0WrHszwH2p7UZLuANfMRfhGxc= 44 | github.com/fxamacker/cbor/v2 v2.7.1/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 45 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 46 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 47 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 48 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 49 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 50 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 51 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 52 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 53 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 54 | github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= 55 | github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= 56 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 57 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 58 | github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= 59 | github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= 60 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 61 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 62 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 63 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 64 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 65 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 69 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 70 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 71 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 72 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 73 | github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= 74 | github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= 75 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 76 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 77 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 78 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 79 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 80 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 81 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 82 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 83 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 84 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 85 | github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4= 86 | github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= 87 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 88 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 89 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 90 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 91 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 92 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 93 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 94 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 95 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 96 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 97 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 98 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 99 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 100 | github.com/keikoproj/inverse-exp-backoff v0.1.1 h1:S/PkLppg0GRwKe227nSOycv72+8Fgub8ORMguH7X0uc= 101 | github.com/keikoproj/inverse-exp-backoff v0.1.1/go.mod h1:99uGNpxpxKz4i8Y1K7Ppou1oI7q7mE8Fc8NpT3jnS4Y= 102 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 103 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 104 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 105 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 106 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 107 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 108 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 109 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 110 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 111 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 112 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 113 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 114 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 115 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 116 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 117 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 118 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 119 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 120 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 121 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 122 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 123 | github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= 124 | github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= 125 | github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 126 | github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 127 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 128 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 129 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 130 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 131 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 132 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 133 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 134 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 135 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 136 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 137 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 138 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 139 | github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= 140 | github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= 141 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 142 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 143 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 144 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 145 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 146 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 147 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 148 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 149 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 150 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 151 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 152 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 153 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 154 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 155 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 156 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 157 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 158 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 159 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 160 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 161 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 162 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 163 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 164 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 165 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 166 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 167 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 168 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 169 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 170 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 171 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 172 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 173 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 174 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 175 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 176 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 177 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 178 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= 179 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= 180 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= 181 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= 182 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 183 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 184 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 185 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 186 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 187 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 188 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 189 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 190 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 191 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 192 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 193 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 194 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 195 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 196 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 197 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 198 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 199 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 200 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 201 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 202 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 203 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 204 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 205 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 206 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 207 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 208 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 209 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 210 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 211 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 212 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 213 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 214 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 215 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 216 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 217 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 218 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 219 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 220 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 221 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 222 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 223 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 224 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 225 | golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= 226 | golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 227 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 229 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 230 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 231 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 232 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 233 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 234 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 235 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 236 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 237 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 238 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 239 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 240 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 241 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 242 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 243 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 244 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 245 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 246 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 247 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 248 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 249 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 250 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 251 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 252 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 253 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 254 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 255 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 256 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 257 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 258 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 259 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 260 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 261 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 262 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 263 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 264 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 265 | gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= 266 | gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 267 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 268 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 269 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 270 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 271 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 272 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw= 273 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0= 274 | google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197 h1:9DuBh3k1jUho2DHdxH+kbJwthIAq02vGvZNrD2ggF+Y= 275 | google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= 276 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 h1:29cjnHVylHwTzH66WfFZqgSQgnxzvWE+jvBwpZCLRxY= 277 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 278 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 279 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 280 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 281 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 282 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 283 | google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= 284 | google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 285 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 286 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 287 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 288 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 289 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 290 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 291 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 292 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 293 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 294 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 295 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 296 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 297 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 298 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 299 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 300 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 301 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 302 | k8s.io/api v0.32.4 h1:kw8Y/G8E7EpNy7gjB8gJZl3KJkNz8HM2YHrZPtAZsF4= 303 | k8s.io/api v0.32.4/go.mod h1:5MYFvLvweRhyKylM3Es/6uh/5hGp0dg82vP34KifX4g= 304 | k8s.io/apiextensions-apiserver v0.32.4 h1:IA+CoR63UDOijR/vEpow6wQnX4V6iVpzazJBskHrpHE= 305 | k8s.io/apiextensions-apiserver v0.32.4/go.mod h1:Y06XO/b92H8ymOdG1HlA1submf7gIhbEDc3RjriqZOs= 306 | k8s.io/apimachinery v0.32.4 h1:8EEksaxA7nd7xWJkkwLDN4SvWS5ot9g6Z/VZb3ju25I= 307 | k8s.io/apimachinery v0.32.4/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 308 | k8s.io/apiserver v0.32.4 h1:Yf7sd/y+GOQKH1Qf6wUeayZrYXe2SKZ17Bcq7VQM5HQ= 309 | k8s.io/apiserver v0.32.4/go.mod h1:JFUMNtE2M5yqLZpIsgCb06SkVSW1YcxW1oyLSTfjXR8= 310 | k8s.io/client-go v0.32.4 h1:zaGJS7xoYOYumoWIFXlcVrsiYioRPrXGO7dBfVC5R6M= 311 | k8s.io/client-go v0.32.4/go.mod h1:k0jftcyYnEtwlFW92xC7MTtFv5BNcZBr+zn9jPlT9Ic= 312 | k8s.io/component-base v0.32.4 h1:HuF+2JVLbFS5GODLIfPCb1Td6b+G2HszJoArcWOSr5I= 313 | k8s.io/component-base v0.32.4/go.mod h1:10KloJEYw1keU/Xmjfy9TKJqUq7J2mYdiD1VDXoco4o= 314 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 315 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 316 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 317 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 318 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= 319 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 320 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= 321 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 322 | sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= 323 | sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= 324 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 325 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 326 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 327 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 328 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 329 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 330 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 331 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 332 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 333 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ -------------------------------------------------------------------------------- /hack/kind.cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | - role: worker 8 | -------------------------------------------------------------------------------- /images/monitoring-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keikoproj/active-monitor/3bc763b1d3bc78e285b9f8d0af8032e28c0a6af2/images/monitoring-example.png -------------------------------------------------------------------------------- /internal/controllers/healthcheck_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | activemonitorv1alpha1 "github.com/keikoproj/active-monitor/api/v1alpha1" 14 | . "github.com/onsi/ginkgo/v2" 15 | . "github.com/onsi/gomega" 16 | "gopkg.in/yaml.v3" 17 | apierrors "k8s.io/apimachinery/pkg/api/errors" 18 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 19 | "k8s.io/apimachinery/pkg/types" 20 | "k8s.io/client-go/kubernetes/scheme" 21 | ) 22 | 23 | var ( 24 | healthCheckNamespace = "health" 25 | healthCheckName = "inline-monitor-remedy" 26 | healthCheckKey = types.NamespacedName{Name: healthCheckName, Namespace: healthCheckNamespace} 27 | healthCheckNameNs = "inline-monitor-remedy-namespace" 28 | healthCheckKeyNs = types.NamespacedName{Name: healthCheckNameNs, Namespace: healthCheckNamespace} 29 | healthCheckNamePause = "inline-hello-pause" 30 | healthCheckKeyPause = types.NamespacedName{Name: healthCheckNamePause, Namespace: healthCheckNamespace} 31 | healthCheckNameRetry = "inline-hello-custom-retry" 32 | healthCheckKeyRetry = types.NamespacedName{Name: healthCheckNameRetry, Namespace: healthCheckNamespace} 33 | healthCheckAlreadyScheduled = "inline-monitor-remedy-already-scheduled" 34 | healthCheckKeyAlreadyScheduled = types.NamespacedName{Name: healthCheckAlreadyScheduled, Namespace: healthCheckNamespace} 35 | 36 | sharedCtrl *HealthCheckReconciler 37 | ) 38 | 39 | const timeout = time.Second * 60 40 | 41 | var _ = Describe("Active-Monitor Controller", func() { 42 | Describe("healthCheck CR can be reconciled at cluster level", func() { 43 | var instance *activemonitorv1alpha1.HealthCheck 44 | It("instance should be parsable", func() { 45 | healthCheckYaml, err := os.ReadFile("../../examples/bdd/inlineMemoryRemedyUnitTest.yaml") 46 | Expect(err).ToNot(HaveOccurred()) 47 | instance, err = parseHealthCheckYaml(healthCheckYaml) 48 | Expect(err).ToNot(HaveOccurred()) 49 | Expect(instance).To(BeAssignableToTypeOf(&activemonitorv1alpha1.HealthCheck{})) 50 | Expect(instance.GetName()).To(Equal(healthCheckName)) 51 | }) 52 | 53 | It("instance should be reconciled", func() { 54 | instance.SetNamespace(healthCheckNamespace) 55 | err := k8sClient.Create(context.TODO(), instance) 56 | if apierrors.IsInvalid(err) { 57 | log.Error(err, "failed to create object, got an invalid object error") 58 | return 59 | } 60 | Expect(err).NotTo(HaveOccurred()) 61 | defer k8sClient.Delete(context.TODO(), instance) 62 | 63 | Eventually(func() error { 64 | if err := k8sClient.Get(context.TODO(), healthCheckKey, instance); err != nil { 65 | return err 66 | } 67 | 68 | if instance.Status.StartedAt != nil && instance.Status.SuccessCount+instance.Status.FailedCount >= 3 { 69 | return nil 70 | } 71 | return fmt.Errorf("HealthCheck is not valid") 72 | }, timeout).Should(Succeed()) 73 | 74 | By("Verify healthCheck has been reconciled by checking for status") 75 | Expect(instance.Status.ErrorMessage).ShouldNot(BeEmpty()) 76 | }) 77 | }) 78 | 79 | Describe("healthCheck CR can be reconciled at namespace level", func() { 80 | var instance *activemonitorv1alpha1.HealthCheck 81 | 82 | It("instance should be parsable", func() { 83 | // healthCheckYaml, err := os.ReadFile("../examples/inlineHello.yaml") 84 | healthCheckYaml, err := os.ReadFile("../../examples/bdd/inlineMemoryRemedyUnitTest_Namespace.yaml") 85 | Expect(err).ToNot(HaveOccurred()) 86 | 87 | instance, err = parseHealthCheckYaml(healthCheckYaml) 88 | Expect(err).ToNot(HaveOccurred()) 89 | Expect(instance).To(BeAssignableToTypeOf(&activemonitorv1alpha1.HealthCheck{})) 90 | Expect(instance.GetName()).To(Equal(healthCheckNameNs)) 91 | }) 92 | 93 | It("instance should be reconciled", func() { 94 | instance.SetNamespace(healthCheckNamespace) 95 | err := k8sClient.Create(context.TODO(), instance) 96 | if apierrors.IsInvalid(err) { 97 | log.Error(err, "failed to create object, got an invalid object error") 98 | return 99 | } 100 | Expect(err).NotTo(HaveOccurred()) 101 | defer k8sClient.Delete(context.TODO(), instance) 102 | 103 | Eventually(func() error { 104 | if err := k8sClient.Get(context.TODO(), healthCheckKeyNs, instance); err != nil { 105 | return err 106 | } 107 | 108 | if instance.Status.StartedAt != nil { 109 | return nil 110 | } 111 | return fmt.Errorf("HealthCheck is not valid") 112 | }, timeout).Should(Succeed()) 113 | 114 | By("Verify healthCheck has been reconciled by checking for status") 115 | Expect(instance.Status.ErrorMessage).ShouldNot(BeEmpty()) 116 | }) 117 | }) 118 | 119 | Describe("healthCheck CR will be paused with repeatAfterSec set to 0", func() { 120 | var instance *activemonitorv1alpha1.HealthCheck 121 | 122 | It("instance should be parsable", func() { 123 | healthCheckYaml, err := os.ReadFile("../../examples/bdd/inlineHelloTest.yaml") 124 | Expect(err).ToNot(HaveOccurred()) 125 | 126 | instance, err = parseHealthCheckYaml(healthCheckYaml) 127 | Expect(err).ToNot(HaveOccurred()) 128 | Expect(instance).To(BeAssignableToTypeOf(&activemonitorv1alpha1.HealthCheck{})) 129 | Expect(instance.GetName()).To(Equal(healthCheckNamePause)) 130 | }) 131 | 132 | It("instance should be reconciled", func() { 133 | instance.SetNamespace(healthCheckNamespace) 134 | err := k8sClient.Create(context.TODO(), instance) 135 | if apierrors.IsInvalid(err) { 136 | log.Error(err, "failed to create object, got an invalid object error") 137 | return 138 | } 139 | Expect(err).NotTo(HaveOccurred()) 140 | defer k8sClient.Delete(context.TODO(), instance) 141 | 142 | Eventually(func() error { 143 | if err := k8sClient.Get(context.TODO(), healthCheckKeyPause, instance); err != nil { 144 | return err 145 | } 146 | 147 | if instance.Status.Status == "Stopped" { 148 | return nil 149 | } 150 | return fmt.Errorf("HealthCheck is not valid") 151 | }, timeout).Should(Succeed()) 152 | 153 | By("Verify healthCheck has been reconciled by checking for status") 154 | Expect(instance.Status.ErrorMessage).ShouldNot(BeEmpty()) 155 | }) 156 | }) 157 | 158 | Describe("healthCheck CR will be reconcile if it is executed and rescheduled", func() { 159 | var instance *activemonitorv1alpha1.HealthCheck 160 | 161 | It("instance should be parsable", func() { 162 | healthCheckYaml, err := os.ReadFile("../../examples/bdd/inlineHelloTest.yaml") 163 | Expect(err).ToNot(HaveOccurred()) 164 | 165 | instance, err = parseHealthCheckYaml(healthCheckYaml) 166 | Expect(err).ToNot(HaveOccurred()) 167 | Expect(instance).To(BeAssignableToTypeOf(&activemonitorv1alpha1.HealthCheck{})) 168 | Expect(instance.GetName()).To(Equal(healthCheckNamePause)) 169 | }) 170 | 171 | It("instance should be reconciled", func() { 172 | instance.SetNamespace(healthCheckNamespace) 173 | instance.SetName(healthCheckAlreadyScheduled) 174 | instance.Spec.RepeatAfterSec = 60 175 | sharedCtrl.RepeatTimersByName[instance.Name] = time.AfterFunc(time.Second*60, func() {}) 176 | 177 | err := k8sClient.Create(context.TODO(), instance) 178 | if apierrors.IsInvalid(err) { 179 | log.Error(err, "failed to create object, got an invalid object error") 180 | return 181 | } 182 | Expect(err).NotTo(HaveOccurred()) 183 | defer k8sClient.Delete(context.TODO(), instance) 184 | 185 | Eventually(func() error { 186 | if err := k8sClient.Get(context.TODO(), healthCheckKeyAlreadyScheduled, instance); err != nil { 187 | return err 188 | } 189 | 190 | if instance.Status.StartedAt != nil { 191 | return nil 192 | } 193 | return fmt.Errorf("HealthCheck is not valid") 194 | // return nil 195 | }, timeout).Should(Succeed()) 196 | 197 | By("Verify healthCheck has been reconciled by checking for status") 198 | Expect(instance.Status.ErrorMessage).ShouldNot(BeEmpty()) 199 | }) 200 | }) 201 | 202 | Describe("healthCheck CR will properly parse backoff customizations", func() { 203 | var instance *activemonitorv1alpha1.HealthCheck 204 | 205 | It("instance should be parsable", func() { 206 | healthCheckYaml, err := os.ReadFile("../../examples/bdd/inlineCustomBackoffTest.yaml") 207 | Expect(err).ToNot(HaveOccurred()) 208 | 209 | instance, err = parseHealthCheckYaml(healthCheckYaml) 210 | Expect(err).ToNot(HaveOccurred()) 211 | Expect(instance).To(BeAssignableToTypeOf(&activemonitorv1alpha1.HealthCheck{})) 212 | Expect(instance.GetName()).To(Equal(healthCheckNameRetry)) 213 | }) 214 | 215 | It("instance should be reconciled", func() { 216 | instance.SetNamespace(healthCheckNamespace) 217 | err := k8sClient.Create(context.TODO(), instance) 218 | if apierrors.IsInvalid(err) { 219 | log.Error(err, "failed to create object, got an invalid object error") 220 | return 221 | } 222 | Expect(err).NotTo(HaveOccurred()) 223 | defer k8sClient.Delete(context.TODO(), instance) 224 | 225 | Eventually(func() error { 226 | if err := k8sClient.Get(context.TODO(), healthCheckKeyRetry, instance); err != nil { 227 | return err 228 | } 229 | 230 | if instance.Status.StartedAt != nil { 231 | return nil 232 | } 233 | return fmt.Errorf("HealthCheck is not valid") 234 | }, timeout).Should(Succeed()) 235 | 236 | By("Verify healthCheck has been reconciled by checking for status") 237 | Expect(instance.Status.ErrorMessage).ShouldNot(BeEmpty()) 238 | }) 239 | }) 240 | }) 241 | 242 | func parseHealthCheckYaml(data []byte) (*activemonitorv1alpha1.HealthCheck, error) { 243 | var err error 244 | o := &unstructured.Unstructured{} 245 | err = yaml.Unmarshal(data, &o.Object) 246 | if err != nil { 247 | return nil, err 248 | } 249 | a := &activemonitorv1alpha1.HealthCheck{} 250 | err = scheme.Scheme.Convert(o, a, 0) 251 | if err != nil { 252 | return nil, err 253 | } 254 | 255 | return a, nil 256 | } 257 | 258 | func TestHealthCheckReconciler_IsStorageError(t *testing.T) { 259 | c := &HealthCheckReconciler{} 260 | 261 | type test struct { 262 | name string 263 | err error 264 | want bool 265 | } 266 | table := []test{ 267 | { 268 | name: "got storage error", 269 | err: errors.New("Operation cannot be fulfilled on pods: StorageError: invalid object, Code: 4, UID in object meta: "), 270 | want: true, 271 | }, 272 | { 273 | name: "no storage error", 274 | err: errors.New("Reconciler error"), 275 | want: false, 276 | }, 277 | } 278 | for _, tt := range table { 279 | tt := tt 280 | t.Run(tt.name, func(t *testing.T) { 281 | t.Parallel() 282 | got := c.IsStorageError(tt.err) 283 | require.Equal(t, got, tt.want) 284 | }) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /internal/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controllers 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "path/filepath" 22 | "runtime" 23 | "sync" 24 | "testing" 25 | 26 | corev1 "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | 29 | "github.com/go-logr/logr" 30 | . "github.com/onsi/ginkgo/v2" 31 | . "github.com/onsi/gomega" 32 | 33 | activemonitorv1alpha1 "github.com/keikoproj/active-monitor/api/v1alpha1" 34 | "k8s.io/client-go/dynamic" 35 | "k8s.io/client-go/kubernetes" 36 | "k8s.io/client-go/kubernetes/scheme" 37 | "k8s.io/client-go/rest" 38 | ctrl "sigs.k8s.io/controller-runtime" 39 | "sigs.k8s.io/controller-runtime/pkg/client" 40 | "sigs.k8s.io/controller-runtime/pkg/envtest" 41 | logf "sigs.k8s.io/controller-runtime/pkg/log" 42 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 43 | // +kubebuilder:scaffold:imports 44 | ) 45 | 46 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 47 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 48 | 49 | var ( 50 | cfg *rest.Config 51 | k8sClient client.Client 52 | testEnv *envtest.Environment 53 | ctx context.Context 54 | cancel context.CancelFunc 55 | ) 56 | 57 | var wg *sync.WaitGroup 58 | var log logr.Logger 59 | 60 | func TestControllers(t *testing.T) { 61 | RegisterFailHandler(Fail) 62 | 63 | RunSpecs(t, "Controller Suite") 64 | } 65 | 66 | var _ = BeforeSuite(func() { 67 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 68 | 69 | ctx, cancel = context.WithCancel(context.TODO()) 70 | 71 | By("bootstrapping test environment") 72 | testEnv = &envtest.Environment{ 73 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 74 | ErrorIfCRDPathMissing: true, 75 | 76 | // The BinaryAssetsDirectory is only required if you want to run the tests directly 77 | // without call the makefile target test. If not informed it will look for the 78 | // default path defined in controller-runtime which is /usr/local/kubebuilder/. 79 | // Note that you must have the required binaries setup under the bin directory to perform 80 | // the tests directly. When we run make test it will be setup and used automatically. 81 | BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", 82 | fmt.Sprintf("1.28.0-%s-%s", runtime.GOOS, runtime.GOARCH)), 83 | } 84 | 85 | var err error 86 | // cfg is defined in this file globally. 87 | cfg, err = testEnv.Start() 88 | Expect(err).NotTo(HaveOccurred()) 89 | Expect(cfg).NotTo(BeNil()) 90 | 91 | err = activemonitorv1alpha1.AddToScheme(scheme.Scheme) 92 | Expect(err).NotTo(HaveOccurred()) 93 | 94 | //+kubebuilder:scaffold:scheme 95 | 96 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 97 | Expect(err).NotTo(HaveOccurred()) 98 | Expect(k8sClient).NotTo(BeNil()) 99 | 100 | // Create "health" namespace for testing 101 | err = k8sClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "health"}}) 102 | Expect(err).To(BeNil()) 103 | 104 | k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ 105 | Scheme: scheme.Scheme, 106 | }) 107 | Expect(err).ToNot(HaveOccurred()) 108 | 109 | sharedCtrl = &HealthCheckReconciler{ 110 | Client: k8sManager.GetClient(), 111 | DynClient: dynamic.NewForConfigOrDie(k8sManager.GetConfig()), 112 | Recorder: k8sManager.GetEventRecorderFor("HealthCheck"), 113 | kubeclient: kubernetes.NewForConfigOrDie(k8sManager.GetConfig()), 114 | Log: log, 115 | TimerLock: sync.RWMutex{}, 116 | } 117 | err = sharedCtrl.SetupWithManager(k8sManager) 118 | Expect(err).ToNot(HaveOccurred()) 119 | 120 | go func() { 121 | defer GinkgoRecover() 122 | err = k8sManager.Start(ctx) 123 | Expect(err).ToNot(HaveOccurred(), "failed to run manager") 124 | }() 125 | }) 126 | 127 | var _ = AfterSuite(func() { 128 | By("stopping manager") 129 | ctx.Done() 130 | cancel() 131 | 132 | By("tearing down the test environment") 133 | err := testEnv.Stop() 134 | Expect(err).NotTo(HaveOccurred()) 135 | }) 136 | -------------------------------------------------------------------------------- /internal/metrics/collector.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "encoding/json" 5 | wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" 6 | "github.com/mitchellh/mapstructure" 7 | "github.com/prometheus/client_golang/prometheus" 8 | log "github.com/sirupsen/logrus" 9 | "sigs.k8s.io/controller-runtime/pkg/metrics" 10 | "strings" 11 | ) 12 | 13 | var hcName = "healthcheck_name" 14 | var wf = "workflow" 15 | 16 | // MonitorProcessed will be used to track the number of processed events 17 | var ( 18 | MonitorSuccess = prometheus.NewCounterVec(prometheus.CounterOpts{ 19 | Name: "healthcheck_success_count", 20 | Help: "The total number of successful healthcheck resources", 21 | }, 22 | []string{hcName, wf}, 23 | ) 24 | MonitorError = prometheus.NewCounterVec(prometheus.CounterOpts{ 25 | Name: "healthcheck_error_count", 26 | Help: "The total number of errored healthcheck resources", 27 | }, 28 | []string{hcName, wf}, 29 | ) 30 | MonitorRuntime = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 31 | Name: "healthcheck_runtime_seconds", 32 | Help: "Time taken for the workflow to complete.", 33 | }, 34 | []string{hcName, wf}, 35 | ) 36 | MonitorStartedTime = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 37 | Name: "healthcheck_starttime", 38 | Help: "Time taken for the workflow to complete.", 39 | }, 40 | []string{hcName, wf}, 41 | ) 42 | MonitorFinishedTime = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 43 | Name: "healthcheck_finishedtime", 44 | Help: "Time taken for the workflow to complete.", 45 | }, 46 | []string{hcName, wf}, 47 | ) 48 | 49 | CustomGaugeMetricsMap = make(map[string]*prometheus.GaugeVec) 50 | ) 51 | 52 | // customMetricMap defines the custom metric structure 53 | type customMetricMap struct { 54 | Name string 55 | Help string 56 | Metrictype string 57 | Value float64 58 | } 59 | 60 | func init() { 61 | // Register custom metrics with the global prometheus registry 62 | metrics.Registry.MustRegister(MonitorSuccess, MonitorError, MonitorRuntime, MonitorStartedTime, MonitorFinishedTime) 63 | } 64 | 65 | // CreateDynamicPrometheusMetric initializes and registers custom metrics dynamically 66 | func CreateDynamicPrometheusMetric(name string, workflowStatus *wfv1.WorkflowStatus, registry *prometheus.Registry) { 67 | if workflowStatus.Outputs == nil || workflowStatus.Outputs.Parameters == nil { 68 | return 69 | } 70 | 71 | for _, parameter := range workflowStatus.Outputs.Parameters { 72 | var jsonMap map[string][]interface{} 73 | json.Unmarshal([]byte(*parameter.Value), &jsonMap) 74 | 75 | for _, customMetricRaw := range jsonMap["metrics"] { 76 | var metric customMetricMap 77 | if err := mapstructure.Decode(customMetricRaw.(map[string]interface{}), &metric); err != nil { 78 | log.Errorf("Failed to decode metric for %s: %s", name, err) 79 | continue 80 | } 81 | 82 | if metric.Name == "" { 83 | log.Errorf("Skipping metric collection. Invalid metric %s: %#v", name, metric) 84 | continue 85 | } 86 | 87 | // replace "-" to "_" to make it Prometheus friendly metric names 88 | metric.Name = strings.ReplaceAll(name, "-", "_") + "_" + metric.Name 89 | 90 | if _, ok := CustomGaugeMetricsMap[metric.Name]; !ok { 91 | CustomGaugeMetricsMap[metric.Name] = prometheus.NewGaugeVec( 92 | prometheus.GaugeOpts{ 93 | Name: metric.Name, 94 | Help: metric.Help, 95 | }, 96 | []string{hcName}, 97 | ) 98 | if err := registry.Register(CustomGaugeMetricsMap[metric.Name]); err != nil { 99 | log.Errorf("Error registering %s metric %s\n", metric.Name, err) 100 | } 101 | } 102 | CustomGaugeMetricsMap[metric.Name].With(prometheus.Labels{hcName: name}).Set(metric.Value) 103 | log.Printf("Successfully collected metric for %s, metric: %#v", name, metric) 104 | } 105 | log.Debugf("Here is the registered CustomGaugeMetricsMap %v\n", CustomGaugeMetricsMap) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /internal/metrics/collector_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "testing" 5 | 6 | wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | var registry = prometheus.NewRegistry() 11 | 12 | func TestCollectEmptyMetric(t *testing.T) { 13 | value := "" 14 | var parameters = []wfv1.Parameter{{Value: (*wfv1.AnyString)(&value)}} 15 | 16 | wfstatus := &wfv1.WorkflowStatus{ 17 | Outputs: &wfv1.Outputs{ 18 | Parameters: parameters, 19 | }, 20 | } 21 | CreateDynamicPrometheusMetric("test-flow", wfstatus, registry) 22 | } 23 | 24 | func TestCollectMetric(t *testing.T) { 25 | value := "{\"metrics\":[{\"name\": \"custom_total\", \"value\": 123, \"metrictype\": \"gauge\", \"help\": \"test help\"}, {\"name\": \"custom_metric\", \"value\": 12.3, \"metrictype\": \"gauge\"}]}" 26 | var parameters = []wfv1.Parameter{{Value: (*wfv1.AnyString)(&value)}} 27 | 28 | wfstatus := &wfv1.WorkflowStatus{ 29 | Outputs: &wfv1.Outputs{ 30 | Parameters: parameters, 31 | }, 32 | } 33 | CreateDynamicPrometheusMetric("test-flow", wfstatus, registry) 34 | } 35 | 36 | func TestCollectNoNameMetric(t *testing.T) { 37 | value := "{\"metrics\":[{\"value\": 123, \"metrictype\": \"gauge\", \"help\": \"test help\"}, {\"name\": \"custom_metric\", \"value\": 12.3, \"metrictype\": \"gauge\"}]}" 38 | var parameters = []wfv1.Parameter{{Value: (*wfv1.AnyString)(&value)}} 39 | 40 | wfstatus := &wfv1.WorkflowStatus{ 41 | Outputs: &wfv1.Outputs{ 42 | Parameters: parameters, 43 | }, 44 | } 45 | CreateDynamicPrometheusMetric("test-flow", wfstatus, registry) 46 | } 47 | 48 | func TestCollectNoValueMetric(t *testing.T) { 49 | value := "{\"metrics\":[{\"name\": \"custom_total\", \"metrictype\": \"gauge\", \"help\": \"test help\"}, {\"name\": \"custom_metric\", \"value\": 12.3, \"metrictype\": \"gauge\"}]}" 50 | var parameters = []wfv1.Parameter{{Value: (*wfv1.AnyString)(&value)}} 51 | 52 | wfstatus := &wfv1.WorkflowStatus{ 53 | Outputs: &wfv1.Outputs{ 54 | Parameters: parameters, 55 | }, 56 | } 57 | CreateDynamicPrometheusMetric("test-flow", wfstatus, registry) 58 | } 59 | -------------------------------------------------------------------------------- /internal/store/inline.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "errors" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // InlineReader implements the ArtifactReader interface for inlined artifacts 10 | type InlineReader struct { 11 | inlineArtifact *string 12 | } 13 | 14 | // NewInlineReader creates a new ArtifactReader for inline 15 | func NewInlineReader(inlineArtifact *string) (ArtifactReader, error) { 16 | // This should never happen! 17 | if inlineArtifact == nil || *inlineArtifact == "" { 18 | return nil, errors.New("InlineArtifact does not exist") 19 | } 20 | return &InlineReader{inlineArtifact}, nil 21 | } 22 | 23 | func (reader *InlineReader) Read() ([]byte, error) { 24 | log.Debug("reading fileArtifact from inline") 25 | return []byte(*reader.inlineArtifact), nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/keikoproj/active-monitor/api/v1alpha1" 7 | ) 8 | 9 | // ArtifactReader enables reading artifacts from an external store 10 | type ArtifactReader interface { 11 | Read() ([]byte, error) 12 | } 13 | 14 | // GetArtifactReader returns the ArtifactReader for this location 15 | func GetArtifactReader(loc *v1alpha1.ArtifactLocation) (ArtifactReader, error) { 16 | if loc.Inline != nil { 17 | return NewInlineReader(loc.Inline) 18 | } else if loc.URL != nil { 19 | return NewURLReader(loc.URL) 20 | } 21 | return nil, fmt.Errorf("unknown artifact location: %v", *loc) 22 | } 23 | -------------------------------------------------------------------------------- /internal/store/url.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/keikoproj/active-monitor/api/v1alpha1" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // URLReader implements the ArtifactReader interface for urls 15 | type URLReader struct { 16 | urlArtifact *v1alpha1.URLArtifact 17 | } 18 | 19 | // NewURLReader creates a new ArtifactReader for workflows at URL endpoints. 20 | func NewURLReader(urlArtifact *v1alpha1.URLArtifact) (ArtifactReader, error) { 21 | if urlArtifact == nil { 22 | return nil, errors.New("URLArtifact cannot be empty") 23 | } 24 | return &URLReader{urlArtifact}, nil 25 | } 26 | 27 | func (reader *URLReader) Read() ([]byte, error) { 28 | logrus.Debugf("reading urlArtifact from %s", reader.urlArtifact.Path) 29 | insecureSkipVerify := !reader.urlArtifact.VerifyCert 30 | client := &http.Client{ 31 | Transport: &http.Transport{ 32 | TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}, 33 | }, 34 | } 35 | resp, err := client.Get(reader.urlArtifact.Path) 36 | if err != nil { 37 | logrus.Warnf("failed to read url %s: %s", reader.urlArtifact.Path, err) 38 | return nil, err 39 | } 40 | defer resp.Body.Close() 41 | 42 | if resp.StatusCode != http.StatusOK { 43 | logrus.Warnf("failed to read %s. status code: %d", reader.urlArtifact.Path, resp.StatusCode) 44 | return nil, errors.New("status code " + strconv.Itoa(resp.StatusCode)) 45 | } 46 | 47 | content, err := ioutil.ReadAll(resp.Body) 48 | if err != nil { 49 | logrus.Warnf("failed to read url body for %s: %s", reader.urlArtifact.Path, err) 50 | return nil, err 51 | } 52 | return content, nil 53 | } 54 | --------------------------------------------------------------------------------