├── .chglog
├── CHANGELOG.tpl.md
└── config.yml
├── .githooks
├── commit-msg
├── pre-commit
└── pre-push
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── objective.md
├── renovate.json
├── stale.yml
└── workflows
│ ├── compile.yml
│ ├── release.yml
│ ├── security.yml
│ ├── snapshot.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── PROJECT
├── README.md
├── alerts.go
├── api
└── v1
│ ├── alerts_apmcondition_types.go
│ ├── alerts_apmcondition_types_test.go
│ ├── alerts_apmcondition_webhook.go
│ ├── alerts_apmcondition_webhook_test.go
│ ├── alerts_nrqlcondition_types.go
│ ├── alerts_nrqlcondition_types_test.go
│ ├── alerts_nrqlcondition_webhook.go
│ ├── alerts_nrqlcondition_webhook_test.go
│ ├── alerts_policy_types.go
│ ├── alerts_policy_types_test.go
│ ├── alerts_policy_webhook.go
│ ├── alerts_policy_webhook_test.go
│ ├── alertschannel_types.go
│ ├── alertschannel_types_test.go
│ ├── alertschannel_webhook.go
│ ├── alertschannel_webhook_test.go
│ ├── apmalertcondition_types.go
│ ├── apmalertcondition_types_test.go
│ ├── apmalertcondition_webhook.go
│ ├── apmalertcondition_webhook_test.go
│ ├── common.go
│ ├── groupversion_info.go
│ ├── nrqlalertcondition_types.go
│ ├── nrqlalertcondition_types_integration_test.go
│ ├── nrqlalertcondition_types_test.go
│ ├── nrqlalertcondition_webhook.go
│ ├── nrqlalertcondition_webhook_test.go
│ ├── policy_types.go
│ ├── policy_types_test.go
│ ├── policy_webhook.go
│ ├── policy_webhook_test.go
│ ├── suite_test.go
│ └── zz_generated.deepcopy.go
├── build
├── .keep
├── compile.mk
├── docker.mk
├── document.mk
├── generate.mk
├── generate
│ └── boilerplate.go.txt
├── kube.mk
├── lint.mk
├── package
│ └── Dockerfile
├── release.mk
├── test.mk
├── tools.mk
└── util.mk
├── cla.md
├── config
├── certmanager
│ ├── certificate.yaml
│ ├── kustomization.yaml
│ └── kustomizeconfig.yaml
├── crd
│ ├── bases
│ │ ├── nr.k8s.newrelic.com_alertsapmconditions.yaml
│ │ ├── nr.k8s.newrelic.com_alertschannels.yaml
│ │ ├── nr.k8s.newrelic.com_alertsnrqlconditions.yaml
│ │ ├── nr.k8s.newrelic.com_alertspolicies.yaml
│ │ ├── nr.k8s.newrelic.com_apmalertconditions.yaml
│ │ ├── nr.k8s.newrelic.com_nrqlalertconditions.yaml
│ │ └── nr.k8s.newrelic.com_policies.yaml
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── patches
│ │ ├── cainjection_in_alertsapmconditions.yaml
│ │ ├── cainjection_in_alertschannels.yaml
│ │ ├── cainjection_in_alertsnrqlconditions.yaml
│ │ ├── cainjection_in_alertspolicies.yaml
│ │ ├── cainjection_in_apmalertconditions.yaml
│ │ ├── cainjection_in_newrelicpolicies.yaml
│ │ ├── cainjection_in_nrqlalertconditions.yaml
│ │ ├── webhook_in_alertsapmconditions.yaml
│ │ ├── webhook_in_alertschannels.yaml
│ │ ├── webhook_in_alertsnrqlconditions.yaml
│ │ ├── webhook_in_alertspolicies.yaml
│ │ ├── webhook_in_apmalertconditions.yaml
│ │ ├── webhook_in_newrelicpolicies.yaml
│ │ └── webhook_in_nrqlalertconditions.yaml
├── default
│ ├── kustomization.yaml
│ ├── manager_auth_proxy_patch.yaml
│ ├── manager_webhook_patch.yaml
│ └── webhookcainjection_patch.yaml
├── development
│ └── kustomization.yaml
├── manager
│ ├── kustomization.yaml
│ ├── manager.yaml
│ └── new_relic_agent_patch.yaml
├── prometheus
│ ├── kustomization.yaml
│ └── monitor.yaml
├── rbac
│ ├── alertsapmcondition_editor_role.yaml
│ ├── alertsapmcondition_viewer_role.yaml
│ ├── alertschannel_editor_role.yaml
│ ├── alertschannel_viewer_role.yaml
│ ├── alertsnrqlcondition_editor_role.yaml
│ ├── alertsnrqlcondition_viewer_role.yaml
│ ├── alertspolicy_editor_role.yaml
│ ├── alertspolicy_viewer_role.yaml
│ ├── apmalertcondition_editor_role.yaml
│ ├── apmalertcondition_viewer_role.yaml
│ ├── auth_proxy_role.yaml
│ ├── auth_proxy_role_binding.yaml
│ ├── auth_proxy_service.yaml
│ ├── kustomization.yaml
│ ├── leader_election_role.yaml
│ ├── leader_election_role_binding.yaml
│ ├── newrelicpolicy_editor_role.yaml
│ ├── newrelicpolicy_viewer_role.yaml
│ ├── nrqlalertcondition_editor_role.yaml
│ ├── nrqlalertcondition_viewer_role.yaml
│ ├── role.yaml
│ ├── role_binding.yaml
│ ├── secrets_role.yaml
│ └── secrets_role_binding.yaml
├── samples
│ ├── nr-alerts_v1_nrqlalertcondition.yaml
│ ├── nr-alerts_v1beta1_nrqlalertcondition.yaml
│ ├── nr_v1_apmalertcondition.yaml
│ └── nr_v1_newrelicpolicy.yaml
└── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ ├── manifests.yaml
│ └── service.yaml
├── controllers
├── alerts_apmcondition_controller.go
├── alerts_apmcondition_controller_integration_test.go
├── alerts_nrqlcondition_controller.go
├── alerts_nrqlcondition_controller_integration_test.go
├── alerts_policy_controller.go
├── alerts_policy_controller_integration_api_test.go
├── alerts_policy_controller_integration_test.go
├── alertschannel_controller.go
├── alertschannel_controller_test.go
├── apmalertcondition_controller.go
├── apmalertcondition_controller_integration_test.go
├── nrqlalertcondition_controller.go
├── nrqlalertcondition_controller_integration_test.go
├── policy_controller.go
├── policy_controller_integration_api_test.go
├── policy_controller_integration_test.go
└── suite_test.go
├── errors
└── collector.go
├── examples
├── development
│ ├── personal-api-key-patch.yaml
│ ├── personalized-policy-name-patch.yaml
│ └── use-staging-patch.yaml
├── example_alerts_channel_email.yaml
├── example_alerts_channel_webhook.yaml
├── example_apm_alert_condition.yaml
├── example_new_relic_agent_config.yaml
├── example_nrql_alert_condition.yaml
├── example_policy.yaml
├── example_policy_apm.yaml
├── example_secret.yaml
└── kustomization.yaml
├── go.mod
├── go.sum
├── hack
└── boilerplate.go.txt
├── interfaces
├── interfacesfakes
│ ├── fake_k8s_client.go
│ └── fake_new_relic_alerts_client.go
└── new_relic_alert_client.go
├── internal
├── info
│ └── main.go
└── testutil
│ └── alerts.go
├── main.go
├── nrgoagent.go
├── scripts
└── release.sh
└── tools
└── tools.go
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | {{ if .Versions -}}
2 | {{ if .Unreleased.CommitGroups -}}
3 |
4 | ## [Unreleased]
5 |
6 | {{ range .Unreleased.CommitGroups -}}
7 | ### {{ .Title }}
8 | {{ range .Commits -}}
9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
10 | {{ end -}}
11 | {{ end -}}
12 | {{ end -}}
13 |
14 | {{ range .Versions -}}
15 |
16 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
17 | {{ range .CommitGroups -}}
18 | ### {{ .Title }}
19 | {{ range .Commits -}}
20 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
21 | {{ end }}
22 | {{ end -}}
23 |
24 | {{ if .NoteGroups -}}
25 | {{ range .NoteGroups -}}
26 | ### {{ .Title }}
27 | {{ range .Notes }}
28 | {{ .Body }}
29 | {{ end }}
30 | {{ end -}}
31 | {{ end -}}
32 | {{ end -}}
33 |
34 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
35 | {{ range .Versions -}}
36 | {{ if .Tag.Previous -}}
37 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
38 | {{ end -}}
39 | {{ end -}}
40 | {{ end -}}
41 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/newrelic/newrelic-kubernetes-operator
6 | options:
7 | commits:
8 | filters:
9 | Type:
10 | - docs
11 | - feat
12 | - fix
13 |
14 | commit_groups:
15 | title_maps:
16 | docs: Documentation Updates
17 | feat: Features
18 | fix: Bug Fixes
19 |
20 | refs:
21 | actions:
22 | - Closes
23 | - Fixes
24 | - Resolves
25 |
26 | issues:
27 | prefix:
28 | - #
29 |
30 | header:
31 | pattern: "^(\\w*)(?:\\(([\\/\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
32 | pattern_maps:
33 | - Type
34 | - Scope
35 | - Subject
36 |
37 | notes:
38 | keywords:
39 | - BREAKING CHANGE
40 |
--------------------------------------------------------------------------------
/.githooks/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo
4 | echo "Checking commit message..."
5 | echo
6 |
7 | make lint-commit COMMIT_MSG_FILE=$1
8 |
9 |
--------------------------------------------------------------------------------
/.githooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo
3 | echo "Running pre-commit checks..."
4 | echo
5 |
6 | make lint
7 |
--------------------------------------------------------------------------------
/.githooks/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo
3 | echo "Running tests before pushing..."
4 | echo
5 |
6 | make lint test-unit
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: 'Report a bug '
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 | A clear and concise description of what the problem is.
12 |
13 | ### Go Version
14 | Provide the output of `go version` here please
15 |
16 | ### Current behavior
17 | A clear and concise description of what you're currently experiencing.
18 |
19 | ### Expected behavior
20 | A clear and concise description of what you expected to happen.
21 |
22 | ### Steps To Reproduce
23 | Steps to reproduce the behavior:
24 | 1. Do this...
25 | 2. Then do this... etc
26 | 3. See error
27 |
28 | ### Debug Output (if applicable)
29 | If applicable, add associated log output to help explain the problem.
30 |
31 | ### Additional Context
32 | Add any other context about the problem here.
33 |
34 | ### References or Related Issues
35 | Are there any other related GitHub issues (open or closed) or Pull Requests that should be linked here?
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Feature Description
11 | A clear and concise description of the feature you want or need.
12 |
13 | ### Describe Alternatives
14 | A clear and concise description of any alternative solutions or features you've considered. Are there examples you could link us to?
15 |
16 | ### Additional context
17 | Add any other context here.
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/objective.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Objective
3 | about: Custom objective template
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Objective
11 | State the desired outcome of this ticket.
12 |
13 | ### Acceptance Criteria
14 | - Acceptance criterion 1
15 | - Acceptance criterion 2
16 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "gomodTidy": true,
4 | "ignoreDeps": [
5 | "golang.org/x/crypto",
6 | "golang.org/x/net",
7 | "golang.org/x/sys",
8 | "golang.org/x/tools"
9 | ],
10 | "commitMessagePrefix": "chore(deps):",
11 | "reviewers": ["team:developer-toolkit"],
12 | "labels": ["dependencies"],
13 | "prConcurrentLimit": 4,
14 | "postUpdateOptions": ["gomodTidy"]
15 | }
16 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 30
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8 | daysUntilClose: 14
9 |
10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
11 | onlyLabels: []
12 |
13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
14 | exemptLabels:
15 | - enhancement
16 | - help wanted
17 | - priority:low
18 | - priority:medium
19 | - priority:high
20 | - priority:blocker
21 | - good first issue
22 | - pinned
23 |
24 | # Set to true to ignore issues in a project (defaults to false)
25 | exemptProjects: false
26 |
27 | # Set to true to ignore issues in a milestone (defaults to false)
28 | exemptMilestones: false
29 |
30 | # Set to true to ignore issues with an assignee (defaults to false)
31 | exemptAssignees: false
32 |
33 | # Label to use when marking as stale
34 | staleLabel: stale
35 |
36 | # Comment to post when marking as stale. Set to `false` to disable
37 | markComment: >
38 | This issue has been automatically marked as stale because it has not had
39 | any recent activity. It will be closed if no further activity occurs.
40 |
41 | # Comment to post when removing the stale label.
42 | # unmarkComment: >
43 | # Your comment here.
44 |
45 | # Comment to post when closing a stale Issue or Pull Request.
46 | closeComment: >
47 | This issue has been automatically closed due to a lack of activity
48 | for an extended period of time.
49 |
50 | # Limit to only `issues` or `pulls`
51 | only: issues
52 |
--------------------------------------------------------------------------------
/.github/workflows/compile.yml:
--------------------------------------------------------------------------------
1 | name: Compiling
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | # Compile on all supported OSes
11 | compile:
12 | strategy:
13 | matrix:
14 | go-version:
15 | - 1.14.x
16 | platform:
17 | - ubuntu-latest
18 | - macos-latest
19 | - windows-latest
20 | runs-on: ${{ matrix.platform }}
21 | steps:
22 | - name: Install Go
23 | uses: actions/setup-go@v1
24 | with:
25 | go-version: ${{ matrix.go-version }}
26 |
27 | - name: Add GOBIN to PATH
28 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
29 | shell: bash
30 |
31 | - name: Checkout code
32 | uses: actions/checkout@v2
33 |
34 | - name: Compile
35 | run: make compile-only
36 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Install Go
13 | uses: actions/setup-go@v1
14 | with:
15 | go-version: ${{ matrix.go-version }}
16 |
17 | - name: Add GOBIN to PATH
18 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
19 | shell: bash
20 |
21 | - name: Checkout code
22 | uses: actions/checkout@v2
23 | with:
24 | # Needed for release notes
25 | fetch-depth: 0
26 |
27 | - name: Publish Release
28 | shell: bash
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.DEV_TOOLKIT_TOKEN }}
31 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
32 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
33 | run: make release-publish
34 |
--------------------------------------------------------------------------------
/.github/workflows/security.yml:
--------------------------------------------------------------------------------
1 | name: Security Scan
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | schedule:
7 | - cron: '0 6 * * *'
8 |
9 | jobs:
10 | security:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@master
14 | - name: Run Snyk to check for vulnerabilities
15 | uses: snyk/actions/golang@master
16 | env:
17 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
18 | with:
19 | args: --severity-threshold=high
20 |
21 |
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Snapshot
2 |
3 | on:
4 | push:
5 | branches: [master]
6 |
7 | jobs:
8 | snapshot:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Install Go
12 | uses: actions/setup-go@v1
13 | with:
14 | go-version: ${{ matrix.go-version }}
15 |
16 | - name: Add GOBIN to PATH
17 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
18 | shell: bash
19 |
20 | - name: Checkout code
21 | uses: actions/checkout@v2
22 |
23 | - name: Publish Snapshot to Docker
24 | shell: bash
25 | run: make docker-push
26 | env:
27 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
28 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Install Go
14 | uses: actions/setup-go@v1
15 | with:
16 | go-version: 1.14.x
17 |
18 | - name: Add GOBIN to PATH
19 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
20 | shell: bash
21 |
22 | - name: Checkout code
23 | uses: actions/checkout@v2
24 | with:
25 | fetch-depth: 0
26 |
27 | - name: Cache deps
28 | uses: actions/cache@v1
29 | with:
30 | path: ~/go/pkg/mod
31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
32 | restore-keys: |
33 | ${{ runner.os }}-go-
34 |
35 | - name: Lint
36 | run: make lint
37 |
38 | test-unit:
39 | needs: lint
40 | runs-on: ubuntu-latest
41 | steps:
42 | - name: Install Go
43 | uses: actions/setup-go@v1
44 | with:
45 | go-version: 1.14.x
46 |
47 | - name: Add GOBIN to PATH
48 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
49 | shell: bash
50 |
51 | - name: Install kubebuilder
52 | run: |
53 | version=2.3.1
54 | arch=amd64
55 | curl -L -O https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${version}/kubebuilder_${version}_linux_${arch}.tar.gz
56 | tar -zxvf kubebuilder_${version}_linux_${arch}.tar.gz
57 | sudo mv kubebuilder_${version}_linux_${arch} /usr/local/kubebuilder
58 | shell: bash
59 |
60 | - name: Add Kubebuilder to PATH
61 | run: echo "/usr/local/kubebuilder/bin" >> $GITHUB_PATH
62 | shell: bash
63 |
64 | - name: Checkout code
65 | uses: actions/checkout@v2
66 |
67 | - name: Cache deps
68 | uses: actions/cache@v1
69 | with:
70 | path: ~/go/pkg/mod
71 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
72 | restore-keys: |
73 | ${{ runner.os }}-go-
74 |
75 | - name: Unit Tests
76 | run: make test-unit
77 |
78 | test-integration:
79 | needs: lint
80 | runs-on: ubuntu-latest
81 | steps:
82 | - name: Install Go
83 | uses: actions/setup-go@v1
84 | with:
85 | go-version: 1.14.x
86 |
87 | - name: Add GOBIN to PATH
88 | run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
89 | shell: bash
90 |
91 | - name: Install kubebuilder
92 | run: |
93 | version=2.3.1
94 | arch=amd64
95 | curl -L -O https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${version}/kubebuilder_${version}_linux_${arch}.tar.gz
96 | tar -zxvf kubebuilder_${version}_linux_${arch}.tar.gz
97 | sudo mv kubebuilder_${version}_linux_${arch} /usr/local/kubebuilder
98 | shell: bash
99 |
100 | - name: Add Kubebuilder to PATH
101 | run: echo "/usr/local/kubebuilder/bin" >> $GITHUB_PATH
102 | shell: bash
103 |
104 | - name: Checkout code
105 | uses: actions/checkout@v2
106 |
107 | - name: Cache deps
108 | uses: actions/cache@v1
109 | with:
110 | path: ~/go/pkg/mod
111 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
112 | restore-keys: |
113 | ${{ runner.os }}-go-
114 |
115 | - name: Integration Tests
116 | run: make test-integration
117 | env:
118 | NEW_RELIC_ACCOUNT_ID: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
119 | NEW_RELIC_API_KEY: ${{ secrets.NEW_RELIC_API_KEY }}
120 | NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
121 | NEW_RELIC_REGION: ${{ secrets.NEW_RELIC_REGION }}
122 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | bin/
8 | coverage/
9 | dist/
10 | tmp
11 |
12 | # Test binary, build with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # Misc
19 | .DS_Store
20 | .vscode/
21 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # options for analysis running
2 | run:
3 | # timeout for analysis, e.g. 30s, 5m, default is 1m
4 | deadline: 5m
5 |
6 | # exit code when at least one issue was found, default is 1
7 | issues-exit-code: 1
8 |
9 | # include test files or not, default is true
10 | tests: true
11 |
12 | # all available settings of specific linters
13 | linters-settings:
14 | govet:
15 | # report about shadowed variables
16 | check-shadowing: true
17 | golint:
18 | # minimal confidence for issues, default is 0.8
19 | min-confidence: 0.5
20 | gocyclo:
21 | # minimal code complexity to report, 30 by default (but we recommend 10-20)
22 | min-complexity: 20
23 | maligned:
24 | # print struct with more effective memory layout or not, false by default
25 | suggest-new: true
26 | depguard:
27 | list-type: blacklist
28 | include-go-root: false
29 | packages:
30 | - github.com/davecgh/go-spew/spew
31 | misspell:
32 | ignore-words:
33 | - newrelic
34 | lll:
35 | # max line length, lines longer will be reported. Default is 120.
36 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option
37 | line-length: 150
38 |
39 | linters:
40 | disable-all: true
41 | enable:
42 | - deadcode
43 | - errcheck
44 | - gocyclo
45 | - gofmt
46 | - golint
47 | - gosimple
48 | - govet
49 | - ineffassign
50 | - maligned
51 | - misspell
52 | - staticcheck
53 | - structcheck
54 | - unconvert
55 | - unused
56 | - varcheck
57 | - vet
58 |
59 | issues:
60 | # disable limits on issue reporting
61 | max-per-linter: 0
62 | max-same-issues: 0
63 |
64 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | project_name: newrelic-kubernetes-operator
2 |
3 | env:
4 | - GO111MODULE=on
5 |
6 | before:
7 | hooks:
8 | - go mod download
9 |
10 | builds:
11 | -
12 | id: newrelic
13 | dir: .
14 | binary: manager
15 | env:
16 | - CGO_ENABLED=0
17 | goos:
18 | - linux
19 | goarch:
20 | - amd64
21 | ldflags:
22 | - -s -w -X main.version={{.Version}} -X main.appName={{.Binary}}
23 |
24 | release:
25 | # Mark as a pre-release for now
26 | prerelease: true
27 | name_template: "{{.ProjectName}} v{{.Version}}"
28 |
29 | archives:
30 | -
31 | id: "default"
32 | builds:
33 | - newrelic
34 | replacements:
35 | linux: Linux
36 | amd64: x86_64
37 | files:
38 | - CHANGELOG.md
39 | - LICENSE
40 | - README.md
41 |
42 | dockers:
43 | -
44 | dockerfile: build/package/Dockerfile
45 | image_templates:
46 | - 'newrelic/kubernetes-operator:{{ .Tag }}'
47 | - 'newrelic/kubernetes-operator:v{{ .Major }}.{{ .Minor }}'
48 | - 'newrelic/kubernetes-operator:latest'
49 | binaries:
50 | - manager
51 | build_flag_templates:
52 | - "--pull"
53 | - "--label=repository=http://github.com/newrelic/newrelic-kubernetes-operator"
54 | - "--label=homepage=https://developer.newrelic.com/"
55 | - "--label=maintainer=Developer Toolkit "
56 |
57 | # Already using git-chglog
58 | changelog:
59 | skip: true
60 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [v0.0.8] - 2021-07-09
3 | ### Bug Fixes
4 | - namespace's prefix deleted(manager.yaml)
5 | - **float:** check type
6 | - **float:** typo
7 |
8 | ### Features
9 | - support for managing channels from policy
10 | - **alertCondition:** support for lost signal
11 | - **nrqlCondition:** add ability to create Baseline NRQL Alerts
12 | - **types:** keeping fields as pointers
13 |
14 |
15 | ## [v0.0.7] - 2020-10-15
16 | ### Features
17 | - add parent kubernetes object references
18 |
19 |
20 | ## [v0.0.6] - 2020-09-09
21 | ### Bug Fixes
22 | - rollback ns change
23 | - update manager namespace created
24 |
25 | ### Documentation Updates
26 | - remove hardcoded examples in README
27 |
28 |
29 | ## [v0.0.5] - 2020-09-01
30 | ### Features
31 | - support for secrets in the headers of webhook alerts channels
32 | - add schema for headers and payload definition
33 |
34 |
35 | ## [v0.0.4] - 2020-08-10
36 |
37 | ## [v0.0.3] - 2020-08-05
38 | ### Bug Fixes
39 | - **buikd:** match release manager docker image tag with goreleaser
40 |
41 | ### Documentation Updates
42 | - add instructions for monitoring with the go agent
43 |
44 | ### Features
45 | - **alertChannel:** add initial support for alertChannel CRD
46 |
47 |
48 | ## [v0.0.2] - 2020-06-11
49 | ### Bug Fixes
50 | - set LeaderElectionID
51 | - use per-function contexts and printer package
52 | - revert gnostic version to pre-case-change
53 | - try go 1.14
54 | - go.mod wrangling
55 | - skip go mod tidy
56 |
57 | ### Documentation Updates
58 | - update the README
59 | - update the other examples
60 | - update policy example
61 | - revert go version change
62 |
63 | ### Features
64 | - support kubectl diff
65 | - **ci:** add release workflow + goreleaser
66 |
67 |
68 | ## v0.0.1 - 2020-05-29
69 | ### Bug Fixes
70 | - log only when error occurs
71 | - **build:** Fix manifest generation with renamed configs dir
72 | - **build:** Disable CGO for all compiling operations
73 | - **build:** ensure we generate the interfaces
74 | - **build:** no trailing slash for BUILD_DIR, use updated CONFIG_ROOT for make deploy
75 | - **build:** Correct spelling of DOCKER_IMAGE
76 | - **changelog:** reference correct repo in git-chglog config
77 | - **config:** increase memory for operator
78 | - **rbac:** update rbac permissions
79 |
80 | ### Documentation Updates
81 | - **README:** cleaned up instructions for running
82 | - **build:** Recommend kustomize build ... | kubectl apply -f -
83 | - **build:** Need to install cert-manager
84 | - **build:** Documentation driven development
85 | - **build:** Correct url for kustomize build in quick start
86 | - **policy:** change example policy to create nrql & apm conditions
87 | - **readme:** reorganize the README a bit
88 | - **readme:** update table of contents
89 | - **readme:** update examples, update helpful commands, other minor updates
90 |
91 | ### Features
92 | - **alerts:** add apm alerts methods to interface
93 | - **api:** merged upstream changes and revved API version
94 | - **api:** extend the CRD to include API key and region
95 | - **api:** fixed the tests
96 | - **api:** refactored alerts client behavior to read from condition
97 | - **api:** added webhook tests
98 | - **api:** fixing linting
99 | - **api:** fixing more linting
100 | - **api:** added kubbernetes secrets support
101 | - **api:** added secrets rbac bindings
102 | - **ci:** add release
103 | - **conditions:** adds initial template for APM conditions
104 | - **examples:** add secrets example yaml, continue doc updates
105 | - **manager:** add switch for dev logging
106 | - **manager:** Add custom version / service info headers
107 | - **manager:** Add version flag, and version / appName vars to pass through
108 | - **policy:** added update logic to conditions created by policy controller
109 | - **policy:** added tests and default webhook for policy
110 | - **policy:** Adds policy scaffolding.
111 | - **policy:** add defaulting and validation logic
112 | - **policy:** added policy condition creation and deletion
113 | - **policy:** Added most of reconcile function
114 |
115 | [Unreleased]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.8...HEAD
116 | [v0.0.8]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.7...v0.0.8
117 | [v0.0.7]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.6...v0.0.7
118 | [v0.0.6]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.5...v0.0.6
119 | [v0.0.5]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.4...v0.0.5
120 | [v0.0.4]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.3...v0.0.4
121 | [v0.0.3]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.2...v0.0.3
122 | [v0.0.2]: https://github.com/newrelic/newrelic-kubernetes-operator/compare/v0.0.1...v0.0.2
123 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome. Before contributing please read the
4 | [code of conduct](blog/master/CODE_OF_CONDUCT.md) and [search the issue tracker](issues); your issue may have already been discussed or fixed in `master`. To contribute,
5 | [fork](https://help.github.com/articles/fork-a-repo/) this repository, commit your changes, and [send a Pull Request](https://help.github.com/articles/using-pull-requests/).
6 |
7 | Note that our [code of conduct](blog/master/CODE_OF_CONDUCT.md) applies to all platforms and venues related to this project; please follow it in all your interactions with the project and its participants.
8 |
9 | ## Feature Requests
10 |
11 | Feature requests should be submitted in the [Issue tracker](issues), with a description of the expected behavior & use case, where they’ll remain closed until sufficient interest, [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc).
12 | Before submitting an Issue, please search for similar ones in the
13 | [closed issues](issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement).
14 |
15 | ## Pull Requests
16 |
17 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
18 | 2. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
19 | 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
20 |
21 | ## Contributor License Agreement
22 |
23 | Keep in mind that when you submit your Pull Request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource@newrelic.com.
24 |
25 | For more information about CLAs, please check out Alex Russell’s excellent post,
26 | [“Why Do I Need to Sign This?”](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/).
27 |
28 | # Slack
29 |
30 | For contributors and maintainers of open source projects hosted by New Relic, we host a public Slack with a channel dedicated to this project. If you are contributing to this project, you're welcome to request access to that community space.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #############################
2 | # Global vars
3 | #############################
4 | PROJECT_NAME := $(shell basename $(shell pwd))
5 | PROJECT_VER ?= $(shell git describe --tags --always --dirty | sed -e '/^v/s/^v\(.*\)$$/\1/g') # Strip leading 'v' if found
6 | # Last released version (not dirty)
7 | PROJECT_VER_TAGGED ?= $(shell git describe --tags --always --abbrev=0 | sed -e '/^v/s/^v\(.*\)$$/\1/g') # Strip leading 'v' if found
8 |
9 | SRCDIR ?= .
10 | GO = go
11 |
12 | # The root module (from go.mod)
13 | PROJECT_MODULE ?= $(shell $(GO) list -m)
14 |
15 | #############################
16 | # Targets
17 | #############################
18 | all: build
19 |
20 | # Humans running make:
21 | build: git-hooks check-version clean generate lint test cover-report compile
22 |
23 | # Build command for CI tooling
24 | build-ci: check-version clean lint test compile-only
25 |
26 | # All clean commands
27 | clean: cover-clean compile-clean release-clean
28 |
29 | # Import fragments
30 | include build/compile.mk
31 | include build/docker.mk
32 | include build/document.mk
33 | include build/generate.mk
34 | include build/kube.mk
35 | include build/lint.mk
36 | include build/release.mk
37 | include build/test.mk
38 | include build/tools.mk
39 | include build/util.mk
40 |
41 | .PHONY: all build build-ci clean
42 |
--------------------------------------------------------------------------------
/PROJECT:
--------------------------------------------------------------------------------
1 | domain: k8s.newrelic.com
2 | repo: github.com/newrelic/newrelic-kubernetes-operator
3 | resources:
4 | - group: nr
5 | kind: NrqlAlertCondition
6 | version: v1
7 | - group: nr
8 | kind: Policy
9 | version: v1
10 | - group: nr
11 | kind: ApmAlertCondition
12 | version: v1
13 | - group: nr
14 | kind: AlertsChannel
15 | version: v1
16 | - group: nr
17 | kind: AlertsNrqlCondition
18 | version: v1
19 | - group: nr
20 | kind: AlertsPolicy
21 | version: v1
22 | - group: nr
23 | kind: AlertsAPMCondition
24 | version: v1
25 | version: "2"
26 |
--------------------------------------------------------------------------------
/alerts.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 | "os"
20 |
21 | "github.com/newrelic/go-agent/v3/newrelic"
22 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
23 | ctrl "sigs.k8s.io/controller-runtime"
24 |
25 | nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
26 | "github.com/newrelic/newrelic-kubernetes-operator/controllers"
27 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
28 | // +kubebuilder:scaffold:imports
29 | )
30 |
31 | func registerAlerts(mgr *ctrl.Manager, nrApp *newrelic.Application) error {
32 |
33 | // nrqlalertcondition
34 | nrqlAlertConditionReconciler := &controllers.NrqlAlertConditionReconciler{
35 | Client: (*mgr).GetClient(),
36 | Log: ctrl.Log.WithName("controllers").WithName("NrqlAlertCondition"),
37 | Scheme: (*mgr).GetScheme(),
38 | AlertClientFunc: interfaces.InitializeAlertsClient,
39 | NewRelicAgent: *nrApp,
40 | }
41 |
42 | if err := nrqlAlertConditionReconciler.SetupWithManager(*mgr); err != nil {
43 | setupLog.Error(err, "unable to create controller", "controller", "NrqlAlertCondition")
44 | os.Exit(1)
45 | }
46 |
47 | nrqlAlertCondition := &nrv1.NrqlAlertCondition{}
48 | if err := nrqlAlertCondition.SetupWebhookWithManager(*mgr); err != nil {
49 | setupLog.Error(err, "unable to create webhook", "webhook", "NrqlAlertCondition")
50 | os.Exit(1)
51 | }
52 |
53 | // alertsnrqlcondition
54 | alertsNrqlConditionReconciler := &controllers.AlertsNrqlConditionReconciler{
55 | Client: (*mgr).GetClient(),
56 | Log: ctrl.Log.WithName("controllers").WithName("AlertsNrqlCondition"),
57 | Scheme: (*mgr).GetScheme(),
58 | AlertClientFunc: interfaces.InitializeAlertsClient,
59 | NewRelicAgent: *nrApp,
60 | }
61 |
62 | if err := alertsNrqlConditionReconciler.SetupWithManager(*mgr); err != nil {
63 | setupLog.Error(err, "unable to create controller", "controller", "AlertsNrqlCondition")
64 | os.Exit(1)
65 | }
66 |
67 | alertsNrqlCondition := &nrv1.AlertsNrqlCondition{}
68 | if err := alertsNrqlCondition.SetupWebhookWithManager(*mgr); err != nil {
69 | setupLog.Error(err, "unable to create webhook", "webhook", "AlertsNrqlCondition")
70 | os.Exit(1)
71 | }
72 |
73 | // apmalertcondition
74 | apmReconciler := &controllers.ApmAlertConditionReconciler{
75 | Client: (*mgr).GetClient(),
76 | Log: ctrl.Log.WithName("controllers").WithName("ApmAlertCondition"),
77 | Scheme: (*mgr).GetScheme(),
78 | AlertClientFunc: interfaces.InitializeAlertsClient,
79 | NewRelicAgent: *nrApp,
80 | }
81 |
82 | if err := apmReconciler.SetupWithManager(*mgr); err != nil {
83 | setupLog.Error(err, "unable to create controller", "controller", "ApmAlertCondition")
84 | os.Exit(1)
85 | }
86 |
87 | apmAlertCondition := &nrv1.ApmAlertCondition{}
88 | if err := apmAlertCondition.SetupWebhookWithManager(*mgr); err != nil {
89 | setupLog.Error(err, "unable to create webhook", "webhook", "ApmAlertCondition")
90 | os.Exit(1)
91 | }
92 |
93 | // alertsapmcondition
94 | alertsAPMReconciler := &controllers.AlertsAPMConditionReconciler{
95 | Client: (*mgr).GetClient(),
96 | Log: ctrl.Log.WithName("controllers").WithName("AlertsAPMCondition"),
97 | Scheme: (*mgr).GetScheme(),
98 | AlertClientFunc: interfaces.InitializeAlertsClient,
99 | NewRelicAgent: *nrApp,
100 | }
101 |
102 | if err := alertsAPMReconciler.SetupWithManager(*mgr); err != nil {
103 | setupLog.Error(err, "unable to create controller", "controller", "AlertsAPMCondition")
104 | os.Exit(1)
105 | }
106 |
107 | alertsAPMCondition := &nrv1.AlertsAPMCondition{}
108 | if err := alertsAPMCondition.SetupWebhookWithManager(*mgr); err != nil {
109 | setupLog.Error(err, "unable to create webhook", "webhook", "AlertsAPMCondition")
110 | os.Exit(1)
111 | }
112 |
113 | // policy
114 | policyReconciler := &controllers.PolicyReconciler{
115 | Client: (*mgr).GetClient(),
116 | Log: ctrl.Log.WithName("controllers").WithName("Policy"),
117 | Scheme: (*mgr).GetScheme(),
118 | AlertClientFunc: interfaces.InitializeAlertsClient,
119 | NewRelicAgent: *nrApp,
120 | }
121 |
122 | if err := policyReconciler.SetupWithManager(*mgr); err != nil {
123 | setupLog.Error(err, "unable to create controller", "controller", "Policy")
124 | os.Exit(1)
125 | }
126 |
127 | policy := &nrv1.Policy{}
128 | if err := policy.SetupWebhookWithManager(*mgr); err != nil {
129 | setupLog.Error(err, "unable to create webhook", "webhook", "Policy")
130 | os.Exit(1)
131 | }
132 |
133 | //alertsChannel
134 | alertsChannelReconciler := &controllers.AlertsChannelReconciler{
135 | Client: (*mgr).GetClient(),
136 | Log: ctrl.Log.WithName("controllers").WithName("alertsChannel"),
137 | Scheme: (*mgr).GetScheme(),
138 | AlertClientFunc: interfaces.InitializeAlertsClient,
139 | NewRelicAgent: *nrApp,
140 | }
141 |
142 | if err := alertsChannelReconciler.SetupWithManager(*mgr); err != nil {
143 | setupLog.Error(err, "unable to create controller", "controller", "alertsChannel")
144 | os.Exit(1)
145 | }
146 |
147 | alertsChannel := &nrv1.AlertsChannel{}
148 | if err := alertsChannel.SetupWebhookWithManager(*mgr); err != nil {
149 | setupLog.Error(err, "unable to create webhook", "webhook", "AlertsChannel")
150 | os.Exit(1)
151 | }
152 |
153 | // alertspolicy
154 | alertsPolicyReconciler := &controllers.AlertsPolicyReconciler{
155 | Client: (*mgr).GetClient(),
156 | Log: ctrl.Log.WithName("controllers").WithName("AlertsPolicy"),
157 | Scheme: (*mgr).GetScheme(),
158 | AlertClientFunc: interfaces.InitializeAlertsClient,
159 | NewRelicAgent: *nrApp,
160 | }
161 | if err := alertsPolicyReconciler.SetupWithManager(*mgr); err != nil {
162 | setupLog.Error(err, "unable to create controller", "controller", "AlertsPolicy")
163 | os.Exit(1)
164 | }
165 |
166 | alertsPolicy := &nrv1.AlertsPolicy{}
167 | if err := alertsPolicy.SetupWebhookWithManager(*mgr); err != nil {
168 | setupLog.Error(err, "unable to create webhook", "webhook", "AlertsPolicy")
169 | os.Exit(1)
170 | }
171 |
172 | return nil
173 | }
174 |
--------------------------------------------------------------------------------
/api/v1/alerts_apmcondition_types.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | )
9 |
10 | // AlertsAPMConditionSpec defines the desired state of AlertsAPMCondition
11 | type AlertsAPMConditionSpec struct {
12 | AlertsGenericConditionSpec `json:",inline"`
13 | AlertsAPMSpecificSpec `json:",inline"`
14 | }
15 |
16 | type AlertsAPMSpecificSpec struct {
17 | Metric string `json:"metric,omitempty"`
18 | UserDefined alerts.ConditionUserDefined `json:"user_defined,omitempty"`
19 | Scope string `json:"condition_scope,omitempty"`
20 | Entities []string `json:"entities,omitempty"`
21 | GCMetric string `json:"gc_metric,omitempty"`
22 | ViolationCloseTimer int `json:"violation_close_timer,omitempty"`
23 | }
24 |
25 | // AlertsAPMConditionStatus defines the observed state of AlertsAPMCondition
26 | type AlertsAPMConditionStatus struct {
27 | AppliedSpec *AlertsAPMConditionSpec `json:"applied_spec"`
28 | ConditionID int `json:"condition_id"`
29 | }
30 |
31 | // +kubebuilder:object:root=true
32 | // +kubebuilder:printcolumn:name="Created",type="boolean",JSONPath=".status.created"
33 |
34 | // AlertsAPMCondition is the Schema for the alertsapmconditions API
35 | type AlertsAPMCondition struct {
36 | metav1.TypeMeta `json:",inline"`
37 | metav1.ObjectMeta `json:"metadata,omitempty"`
38 |
39 | Spec AlertsAPMConditionSpec `json:"spec,omitempty"`
40 | Status AlertsAPMConditionStatus `json:"status,omitempty"`
41 | }
42 |
43 | // +kubebuilder:object:root=true
44 |
45 | // AlertsAPMConditionList contains a list of AlertsAPMCondition
46 | type AlertsAPMConditionList struct {
47 | metav1.TypeMeta `json:",inline"`
48 | metav1.ListMeta `json:"metadata,omitempty"`
49 | Items []AlertsAPMCondition `json:"items"`
50 | }
51 |
52 | func init() {
53 | SchemeBuilder.Register(&AlertsAPMCondition{}, &AlertsAPMConditionList{})
54 | }
55 |
56 | func (in AlertsAPMConditionSpec) APICondition() alerts.Condition {
57 | jsonString, _ := json.Marshal(in)
58 | var APICondition alerts.Condition
59 | json.Unmarshal(jsonString, &APICondition) //nolint
60 |
61 | // Set the condition terms from the APITerms slice, not the Terms slice.
62 | // This is due to the fact that the type definitions are different
63 | // between the REST API and Nerdgraph.
64 | for _, term := range in.AlertsGenericConditionSpec.APMTerms {
65 | jsonString, _ := json.Marshal(term)
66 | var APITerm alerts.ConditionTerm
67 | json.Unmarshal(jsonString, &APITerm) //nolint
68 |
69 | APICondition.Terms = append(APICondition.Terms, APITerm)
70 | }
71 |
72 | return APICondition
73 | }
74 |
--------------------------------------------------------------------------------
/api/v1/alerts_apmcondition_types_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 |
9 | . "github.com/onsi/ginkgo"
10 | . "github.com/onsi/gomega"
11 | )
12 |
13 | var _ = Describe("AlertsAPMConditionSpec", func() {
14 | var condition AlertsAPMConditionSpec
15 |
16 | BeforeEach(func() {
17 | condition = AlertsAPMConditionSpec{
18 | AlertsGenericConditionSpec{
19 | APMTerms: []AlertConditionTerm{
20 | {
21 | Duration: "30",
22 | Operator: "above",
23 | Priority: "critical",
24 | Threshold: "1.5",
25 | TimeFunction: "all",
26 | },
27 | },
28 | Type: "apm_app_metric",
29 | Name: "APM Condition",
30 | RunbookURL: "http://test.com/runbook",
31 | PolicyID: 0,
32 | ID: 888,
33 | Enabled: true,
34 | ExistingPolicyID: "42",
35 | },
36 | AlertsAPMSpecificSpec{
37 | Metric: "apdex",
38 | Entities: []string{"333"},
39 | UserDefined: alerts.ConditionUserDefined{
40 | Metric: "Custom/foo",
41 | ValueFunction: "average",
42 | },
43 | Scope: "application",
44 | GCMetric: "",
45 | ViolationCloseTimer: 60,
46 | },
47 | }
48 | })
49 |
50 | Describe("APICondition", func() {
51 | It("converts AlertsAPMConditionSpec object to Condition object from go client, retaining field values", func() {
52 | apiCondition := condition.APICondition()
53 |
54 | Expect(fmt.Sprint(reflect.TypeOf(apiCondition))).To(Equal("alerts.Condition"))
55 |
56 | Expect(apiCondition.Type).To(Equal(alerts.ConditionTypes.APMApplicationMetric))
57 | Expect(apiCondition.Name).To(Equal("APM Condition"))
58 | Expect(apiCondition.RunbookURL).To(Equal("http://test.com/runbook"))
59 | Expect(apiCondition.Metric).To(Equal(alerts.MetricTypes.Apdex))
60 | Expect(apiCondition.Entities[0]).To(Equal("333"))
61 | Expect(apiCondition.Scope).To(Equal("application"))
62 | Expect(apiCondition.GCMetric).To(Equal(""))
63 |
64 | Expect(apiCondition.ID).To(Equal(888))
65 | Expect(apiCondition.ViolationCloseTimer).To(Equal(60))
66 | Expect(apiCondition.Enabled).To(Equal(true))
67 |
68 | apiTerm := apiCondition.Terms[0]
69 |
70 | Expect(fmt.Sprint(reflect.TypeOf(apiTerm))).To(Equal("alerts.ConditionTerm"))
71 |
72 | Expect(apiTerm.Duration).To(Equal(30))
73 | Expect(apiTerm.Operator).To(Equal(alerts.OperatorTypes.Above))
74 | Expect(apiTerm.Priority).To(Equal(alerts.PriorityTypes.Critical))
75 | Expect(apiTerm.Threshold).To(Equal(float64(1.5)))
76 | Expect(apiTerm.TimeFunction).To(Equal(alerts.TimeFunctionTypes.All))
77 |
78 | userDefinedCondition := apiCondition.UserDefined
79 | Expect(fmt.Sprint(reflect.TypeOf(userDefinedCondition))).To(Equal("alerts.ConditionUserDefined"))
80 |
81 | Expect(userDefinedCondition.Metric).To(Equal("Custom/foo"))
82 | Expect(userDefinedCondition.ValueFunction).To(Equal(alerts.ValueFunctionTypes.Average))
83 | })
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/api/v1/alerts_apmcondition_webhook_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 |
12 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
13 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
14 | )
15 |
16 | var _ = Describe("alertsAPMCondition_webhook", func() {
17 | var (
18 | r AlertsAPMCondition
19 | alertsClient *interfacesfakes.FakeNewRelicAlertsClient
20 | )
21 |
22 | BeforeEach(func() {
23 | k8Client = testk8sClient
24 | alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
25 | fakeAlertFunc := func(string, string) (interfaces.NewRelicAlertsClient, error) {
26 | return alertsClient, nil
27 | }
28 | alertClientFunc = fakeAlertFunc
29 | r = AlertsAPMCondition{
30 | ObjectMeta: v1.ObjectMeta{
31 | Name: "test apm condition",
32 | },
33 | Spec: AlertsAPMConditionSpec{
34 | AlertsGenericConditionSpec{
35 | APMTerms: []AlertConditionTerm{
36 | {
37 | Duration: "30",
38 | Operator: "above",
39 | Priority: "critical",
40 | Threshold: "0.9",
41 | TimeFunction: "all",
42 | },
43 | },
44 | Type: "apm_app_metric",
45 | Name: "K8s generated apm alert condition",
46 | Enabled: true,
47 | ExistingPolicyID: "46286",
48 | APIKey: "111222333",
49 | APIKeySecret: NewRelicAPIKeySecret{},
50 | Region: "staging",
51 | },
52 | AlertsAPMSpecificSpec{
53 | Metric: "apdex",
54 | UserDefined: alerts.ConditionUserDefined{},
55 | Scope: "",
56 | Entities: []string{"5950260"},
57 | },
58 | },
59 | }
60 |
61 | alertsClient.QueryPolicyStub = func(int, string) (*alerts.AlertsPolicy, error) {
62 | return &alerts.AlertsPolicy{
63 | ID: "46286",
64 | }, nil
65 | }
66 | })
67 |
68 | Context("ValidateCreate", func() {
69 | Context("With a valid Apm Condition", func() {
70 | It("Should create the apm condition", func() {
71 | err := r.ValidateCreate()
72 | Expect(err).ToNot(HaveOccurred())
73 |
74 | })
75 | })
76 |
77 | Context("With an invalid Type", func() {
78 | BeforeEach(func() {
79 | r.Spec.Type = "burritos"
80 | })
81 |
82 | It("Should reject the apm condition creation", func() {
83 | err := r.ValidateCreate()
84 | Expect(err).To(HaveOccurred())
85 | Expect(err.Error()).To(ContainSubstring("burritos"))
86 | })
87 | })
88 |
89 | Context("With an invalid Metric", func() {
90 | BeforeEach(func() {
91 | r.Spec.Type = "moar burritos"
92 | })
93 |
94 | It("Should reject the apm condition creation", func() {
95 | err := r.ValidateCreate()
96 | Expect(err).To(HaveOccurred())
97 | Expect(err.Error()).To(ContainSubstring("moar burritos"))
98 | })
99 | })
100 |
101 | Context("With an invalid APMTerms", func() {
102 | BeforeEach(func() {
103 | r.Spec.APMTerms[0].TimeFunction = "moar burritos"
104 | r.Spec.APMTerms[0].Priority = "moar tacos"
105 | r.Spec.APMTerms[0].Operator = "moar hamburgers"
106 |
107 | })
108 |
109 | It("Should reject the apm condition creation", func() {
110 | err := r.ValidateCreate()
111 | Expect(err).To(HaveOccurred())
112 | Expect(err.Error()).To(ContainSubstring("moar burritos"))
113 | Expect(err.Error()).To(ContainSubstring("moar tacos"))
114 | Expect(err.Error()).To(ContainSubstring("moar hamburgers"))
115 | })
116 | })
117 |
118 | Context("With an invalid userDefined type", func() {
119 | BeforeEach(func() {
120 | r.Spec.UserDefined = alerts.ConditionUserDefined{
121 | Metric: "Custom/foo",
122 | ValueFunction: "invalid type",
123 | }
124 | })
125 |
126 | It("Should reject the apm condition creation", func() {
127 | err := r.ValidateCreate()
128 | Expect(err).To(HaveOccurred())
129 | Expect(err.Error()).To(ContainSubstring("invalid type"))
130 | })
131 | })
132 | })
133 |
134 | Context("ValidateUpdate", func() {
135 | Context("When deleting an existing apm Condition with a delete policy", func() {
136 | var update AlertsAPMCondition
137 |
138 | BeforeEach(func() {
139 | currentTime := v1.Time{Time: time.Now()}
140 | //make copy of existing object to update
141 | r.DeepCopyInto(&update)
142 |
143 | update.SetDeletionTimestamp(¤tTime)
144 | alertsClient.GetPolicyStub = func(int) (*alerts.Policy, error) {
145 | return &alerts.Policy{}, errors.New("no alert policy found for id 49092")
146 | }
147 | })
148 |
149 | It("Should allow the deletion anyway", func() {
150 | err := update.ValidateUpdate(&r)
151 | Expect(err).ToNot(HaveOccurred())
152 | })
153 | })
154 | })
155 |
156 | })
157 |
--------------------------------------------------------------------------------
/api/v1/alerts_nrqlcondition_types_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package v1
4 |
5 | import (
6 | "fmt"
7 | "reflect"
8 |
9 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
10 |
11 | . "github.com/onsi/ginkgo"
12 | . "github.com/onsi/gomega"
13 | )
14 |
15 | var _ = Describe("AlertsNrqlConditionSpec", func() {
16 | var condition AlertsNrqlConditionSpec
17 |
18 | BeforeEach(func() {
19 | condition = AlertsNrqlConditionSpec{}
20 | condition.Enabled = true
21 | condition.ExistingPolicyID = "42"
22 | condition.Terms = []AlertsNrqlConditionTerm{
23 | {
24 | Operator: alerts.AlertsNRQLConditionTermsOperatorTypes.ABOVE,
25 | Priority: alerts.NrqlConditionPriorities.Critical,
26 | Threshold: "5",
27 | ThresholdDuration: 60,
28 | ThresholdOccurrences: alerts.ThresholdOccurrences.AtLeastOnce,
29 | },
30 | }
31 | condition.Nrql = alerts.NrqlConditionQuery{
32 | Query: "SELECT 1 FROM MyEvents",
33 | EvaluationOffset: 5,
34 | }
35 | condition.Type = "NRQL"
36 | condition.Name = "NRQL Condition"
37 | condition.RunbookURL = "http://test.com/runbook"
38 | condition.ValueFunction = &alerts.NrqlConditionValueFunctions.SingleValue
39 | condition.ViolationTimeLimit = alerts.NrqlConditionViolationTimeLimits.OneHour
40 | condition.ID = 777
41 | condition.ExpectedGroups = 2
42 | condition.IgnoreOverlap = true
43 | })
44 |
45 | Describe("ToNrqlConditionInput", func() {
46 | It("converts AlertsNrqlConditionSpec object to NrqlConditionInput object, retaining field values", func() {
47 | conditionInput := condition.ToNrqlConditionInput()
48 |
49 | Expect(fmt.Sprint(reflect.TypeOf(conditionInput))).To(Equal("alerts.NrqlConditionInput"))
50 |
51 | // Expect(conditionInput.Type).To(Equal("NRQL"))
52 | Expect(conditionInput.Name).To(Equal("NRQL Condition"))
53 | Expect(conditionInput.RunbookURL).To(Equal("http://test.com/runbook"))
54 | Expect(string(*conditionInput.ValueFunction)).To(Equal(string(alerts.NrqlConditionValueFunctions.SingleValue)))
55 | //Expect(conditionInput.PolicyID).To(Equal(42))
56 | //Expect(conditionInput.ID).To(Equal(777))
57 | //Expect(conditionInput.ViolationCloseTimer).To(Equal(60))
58 | Expect(conditionInput.ViolationTimeLimit).To(Equal(alerts.NrqlConditionViolationTimeLimits.OneHour))
59 | //Expect(conditionInput.ExpectedGroups).To(Equal(2))
60 | //Expect(conditionInput.IgnoreOverlap).To(Equal(true))
61 | Expect(conditionInput.Enabled).To(Equal(true))
62 |
63 | apiTerm := conditionInput.Terms[0]
64 |
65 | Expect(fmt.Sprint(reflect.TypeOf(apiTerm))).To(Equal("alerts.NrqlConditionTerm"))
66 |
67 | //Expect(apiTerm.Duration).To(Equal(30))
68 | Expect(apiTerm.Operator).To(Equal(alerts.AlertsNRQLConditionTermsOperatorTypes.ABOVE))
69 | Expect(apiTerm.Priority).To(Equal(alerts.NrqlConditionPriorities.Critical))
70 | expectedThreshold := 5.0
71 | Expect(apiTerm.Threshold).To(Equal(&expectedThreshold))
72 | Expect(apiTerm.ThresholdDuration).To(Equal(60))
73 | Expect(apiTerm.ThresholdOccurrences).To(Equal(alerts.ThresholdOccurrences.AtLeastOnce))
74 |
75 | apiQuery := conditionInput.Nrql
76 |
77 | Expect(fmt.Sprint(reflect.TypeOf(apiQuery))).To(Equal("alerts.NrqlConditionQuery"))
78 |
79 | Expect(apiQuery.Query).To(Equal("SELECT 1 FROM MyEvents"))
80 | //Expect(apiQuery.SinceValue).To(Equal("5"))
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/api/v1/alerts_nrqlcondition_webhook.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 v1
17 |
18 | import (
19 | "context"
20 | "errors"
21 | "strings"
22 |
23 | v1 "k8s.io/api/core/v1"
24 | "k8s.io/apimachinery/pkg/types"
25 |
26 | "k8s.io/apimachinery/pkg/runtime"
27 | ctrl "sigs.k8s.io/controller-runtime"
28 | logf "sigs.k8s.io/controller-runtime/pkg/log"
29 | "sigs.k8s.io/controller-runtime/pkg/webhook"
30 |
31 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
32 | )
33 |
34 | // log is for logging in this package.
35 | var (
36 | alertsNrqlConditionLog = logf.Log.WithName("alertsnrqlcondition-resource")
37 | )
38 |
39 | func (r *AlertsNrqlCondition) SetupWebhookWithManager(mgr ctrl.Manager) error {
40 | alertClientFunc = interfaces.InitializeAlertsClient
41 | k8Client = mgr.GetClient()
42 | return ctrl.NewWebhookManagedBy(mgr).
43 | For(r).
44 | Complete()
45 | }
46 |
47 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
48 |
49 | // +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-alertsnrqlcondition,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertsnrqlconditions,verbs=create;update,versions=v1,name=malertsnrqlcondition.kb.io,sideEffects=None
50 |
51 | var _ webhook.Defaulter = &AlertsNrqlCondition{}
52 |
53 | // Default implements webhook.Defaulter so a webhook will be registered for the type
54 | func (r *AlertsNrqlCondition) Default() {
55 | alertsNrqlConditionLog.Info("default", "name", r.Name)
56 |
57 | if r.Status.AppliedSpec == nil {
58 | alertsNrqlConditionLog.Info("Setting null Applied Spec to empty interface")
59 | r.Status.AppliedSpec = &AlertsNrqlConditionSpec{}
60 | }
61 | alertsNrqlConditionLog.Info("r.Status.AppliedSpec after", "r.Status.AppliedSpec", r.Status.AppliedSpec)
62 | }
63 |
64 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
65 | // +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-alertsnrqlcondition,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertsnrqlconditions,versions=v1,name=valertsnrqlcondition.kb.io,sideEffects=None
66 |
67 | var _ webhook.Validator = &AlertsNrqlCondition{}
68 |
69 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
70 | func (r *AlertsNrqlCondition) ValidateCreate() error {
71 | alertsNrqlConditionLog.Info("validate create", "name", r.Name)
72 | //TODO this should write this value TO a new secret so code path always reads from a secret
73 | err := r.CheckForAPIKeyOrSecret()
74 | if err != nil {
75 | return err
76 | }
77 |
78 | err = r.CheckRequiredFields()
79 | if err != nil {
80 | return err
81 | }
82 | return r.CheckExistingPolicyID()
83 | }
84 |
85 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
86 | func (r *AlertsNrqlCondition) ValidateUpdate(old runtime.Object) error {
87 | alertsNrqlConditionLog.Info("validate update", "name", r.Name)
88 | prevCondition := old.(*AlertsNrqlCondition)
89 |
90 | if (r.Spec.BaselineDirection == nil && prevCondition.Spec.BaselineDirection != nil) ||
91 | (r.Spec.BaselineDirection != nil && prevCondition.Spec.BaselineDirection == nil) {
92 | return errors.New("cannot change between condition types, you must delete and create a new alert")
93 | }
94 |
95 | return nil
96 | }
97 |
98 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
99 | func (r *AlertsNrqlCondition) ValidateDelete() error {
100 | alertsNrqlConditionLog.Info("validate delete", "name", r.Name)
101 |
102 | // TODO(user): fill in your validation logic upon object deletion.
103 | return nil
104 | }
105 |
106 | func (r *AlertsNrqlCondition) CheckExistingPolicyID() error {
107 | alertsNrqlConditionLog.Info("Checking existing", "policyId", r.Spec.ExistingPolicyID)
108 | var apiKey string
109 | if r.Spec.APIKey == "" {
110 | key := types.NamespacedName{Namespace: r.Spec.APIKeySecret.Namespace, Name: r.Spec.APIKeySecret.Name}
111 | var apiKeySecret v1.Secret
112 | getErr := k8Client.Get(context.Background(), key, &apiKeySecret)
113 | if getErr != nil {
114 | alertsNrqlConditionLog.Error(getErr, "Error getting secret")
115 | return getErr
116 | }
117 | apiKey = string(apiKeySecret.Data[r.Spec.APIKeySecret.KeyName])
118 |
119 | } else {
120 | apiKey = r.Spec.APIKey
121 | }
122 |
123 | alertsClient, errAlertClient := alertClientFunc(apiKey, r.Spec.Region)
124 | if errAlertClient != nil {
125 | alertsNrqlConditionLog.Error(errAlertClient, "failed to get policy",
126 | "policyId", r.Spec.ExistingPolicyID,
127 | "API Key", interfaces.PartialAPIKey(apiKey),
128 | "region", r.Spec.Region,
129 | )
130 | return errAlertClient
131 | }
132 | _, errAlertPolicy := alertsClient.QueryPolicy(r.Spec.AccountID, r.Spec.ExistingPolicyID)
133 | if errAlertPolicy != nil {
134 | alertsNrqlConditionLog.Error(errAlertPolicy, "failed to get policy",
135 | "policyId", r.Spec.ExistingPolicyID,
136 | "API Key", interfaces.PartialAPIKey(apiKey),
137 | "region", r.Spec.Region,
138 | )
139 | return errAlertPolicy
140 | }
141 | return nil
142 | }
143 |
144 | func (r *AlertsNrqlCondition) CheckForAPIKeyOrSecret() error {
145 | if r.Spec.APIKey != "" {
146 | return nil
147 | }
148 | if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
149 | if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
150 | return nil
151 | }
152 | }
153 | return errors.New("either api_key or api_key_secret must be set")
154 | }
155 |
156 | func (r *AlertsNrqlCondition) CheckRequiredFields() error {
157 |
158 | missingFields := []string{}
159 | if r.Spec.Region == "" {
160 | missingFields = append(missingFields, "region")
161 | }
162 | if r.Spec.ExistingPolicyID == "" {
163 | missingFields = append(missingFields, "existing_policy_id")
164 | }
165 | if len(missingFields) > 0 {
166 | return errors.New(strings.Join(missingFields, " and ") + " must be set")
167 | }
168 | return nil
169 | }
170 |
--------------------------------------------------------------------------------
/api/v1/alerts_policy_webhook.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 v1
17 |
18 | import (
19 | "errors"
20 | "strings"
21 |
22 | "k8s.io/apimachinery/pkg/runtime"
23 | ctrl "sigs.k8s.io/controller-runtime"
24 | logf "sigs.k8s.io/controller-runtime/pkg/log"
25 | "sigs.k8s.io/controller-runtime/pkg/webhook"
26 |
27 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
28 |
29 | customErrors "github.com/newrelic/newrelic-kubernetes-operator/errors"
30 | )
31 |
32 | // AlertsPolicyLog is for emitting logs in this package.
33 | var AlertsPolicyLog = logf.Log.WithName("alerts-policy-resource")
34 | var defaultAlertsPolicyIncidentPreference = alerts.AlertsIncidentPreferenceTypes.PER_POLICY
35 |
36 | func (r *AlertsPolicy) SetupWebhookWithManager(mgr ctrl.Manager) error {
37 | return ctrl.NewWebhookManagedBy(mgr).
38 | For(r).
39 | Complete()
40 | }
41 |
42 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
43 |
44 | // +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-alertspolicy,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertspolicies,verbs=create;update,versions=v1,name=malertspolicy.kb.io,sideEffects=None
45 |
46 | var _ webhook.Defaulter = &AlertsPolicy{}
47 |
48 | // Default implements webhook.Defaulter so a webhook will be registered for the type
49 | func (r *AlertsPolicy) Default() {
50 | AlertsPolicyLog.Info("default", "name", r.Name)
51 |
52 | if r.Status.AppliedSpec == nil {
53 | AlertsPolicyLog.Info("Setting null Applied Spec to empty interface")
54 | r.Status.AppliedSpec = &AlertsPolicySpec{}
55 | }
56 |
57 | r.DefaultIncidentPreference()
58 | }
59 |
60 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
61 | // +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-alertspolicy,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertspolicies,versions=v1,name=valertspolicy.kb.io,sideEffects=None
62 |
63 | var _ webhook.Validator = &AlertsPolicy{}
64 |
65 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
66 | func (r *AlertsPolicy) ValidateCreate() error {
67 | AlertsPolicyLog.Info("validate create", "name", r.Name)
68 |
69 | collectedErrors := new(customErrors.ErrorCollector)
70 | err := r.CheckForAPIKeyOrSecret()
71 |
72 | if err != nil {
73 | collectedErrors.Collect(err)
74 | }
75 |
76 | err = r.CheckForDuplicateConditions()
77 | if err != nil {
78 | collectedErrors.Collect(err)
79 | }
80 |
81 | err = r.ValidateIncidentPreference()
82 | if err != nil {
83 | collectedErrors.Collect(err)
84 | }
85 |
86 | if len(*collectedErrors) > 0 {
87 | AlertsPolicyLog.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
88 | return collectedErrors
89 | }
90 |
91 | return nil
92 | }
93 |
94 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
95 | func (r *AlertsPolicy) ValidateUpdate(old runtime.Object) error {
96 | AlertsPolicyLog.Info("validate update", "name", r.Name)
97 |
98 | collectedErrors := new(customErrors.ErrorCollector)
99 |
100 | err := r.CheckForAPIKeyOrSecret()
101 | if err != nil {
102 | collectedErrors.Collect(err)
103 | }
104 |
105 | err = r.CheckForDuplicateConditions()
106 | if err != nil {
107 | collectedErrors.Collect(err)
108 | }
109 |
110 | err = r.ValidateIncidentPreference()
111 | if err != nil {
112 | collectedErrors.Collect(err)
113 | }
114 |
115 | if len(*collectedErrors) > 0 {
116 | AlertsPolicyLog.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
117 | return collectedErrors
118 | }
119 |
120 | return nil
121 | }
122 |
123 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
124 | func (r *AlertsPolicy) ValidateDelete() error {
125 | AlertsPolicyLog.Info("validate delete", "name", r.Name)
126 |
127 | err := r.CheckForAPIKeyOrSecret()
128 | if err != nil {
129 | return err
130 | }
131 |
132 | return nil
133 | }
134 |
135 | func (r *AlertsPolicy) DefaultIncidentPreference() {
136 | if r.Spec.IncidentPreference == "" {
137 | r.Spec.IncidentPreference = string(defaultAlertsPolicyIncidentPreference)
138 | }
139 |
140 | r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
141 | }
142 |
143 | func (r *AlertsPolicy) CheckForDuplicateConditions() error {
144 | var conditionHashMap = make(map[uint32]bool)
145 |
146 | for _, condition := range r.Spec.Conditions {
147 | conditionHashMap[condition.SpecHash()] = true
148 | }
149 |
150 | if len(conditionHashMap) != len(r.Spec.Conditions) {
151 | AlertsPolicyLog.Info("duplicate conditions detected or hash collision", "conditionHash", conditionHashMap)
152 | return errors.New("duplicate conditions detected or hash collision")
153 | }
154 |
155 | return nil
156 | }
157 |
158 | func (r *AlertsPolicy) ValidateIncidentPreference() error {
159 | switch r.Spec.IncidentPreference {
160 | case "PER_POLICY", "PER_CONDITION", "PER_CONDITION_AND_TARGET":
161 | return nil
162 | }
163 |
164 | AlertsPolicyLog.Info("Incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET", "IncidentPreference value", r.Spec.IncidentPreference)
165 |
166 | return errors.New("incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET")
167 | }
168 |
169 | func (r *AlertsPolicy) CheckForAPIKeyOrSecret() error {
170 | if r.Spec.APIKey != "" {
171 | return nil
172 | }
173 |
174 | if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
175 | if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
176 | return nil
177 | }
178 | }
179 |
180 | return errors.New("either api_key or api_key_secret must be set")
181 | }
182 |
--------------------------------------------------------------------------------
/api/v1/alertschannel_types.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 |
7 | v1 "k8s.io/api/core/v1"
8 | "k8s.io/apimachinery/pkg/types"
9 | "sigs.k8s.io/controller-runtime/pkg/client"
10 |
11 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 | )
14 |
15 | // AlertsChannelSpec defines the desired state of AlertsChannel
16 | type AlertsChannelSpec struct {
17 | ID int `json:"id,omitempty"`
18 | Name string `json:"name"`
19 | APIKey string `json:"api_key,omitempty"`
20 | APIKeySecret NewRelicAPIKeySecret `json:"api_key_secret,omitempty"`
21 | Region string `json:"region,omitempty"`
22 | Type string `json:"type,omitempty"`
23 | Links ChannelLinks `json:"links,omitempty"`
24 | Configuration AlertsChannelConfiguration `json:"configuration,omitempty"`
25 | }
26 |
27 | // ChannelLinks - copy of alerts.ChannelLinks
28 | type ChannelLinks struct {
29 | PolicyIDs []int `json:"policy_ids,omitempty"`
30 | PolicyNames []string `json:"policy_names,omitempty"`
31 | PolicyKubernetesObjects []metav1.ObjectMeta `json:"policy_kubernetes_objects,omitempty"`
32 | }
33 |
34 | // AlertsChannelStatus defines the observed state of AlertsChannel
35 | type AlertsChannelStatus struct {
36 | AppliedSpec *AlertsChannelSpec `json:"applied_spec"`
37 | ChannelID int `json:"channel_id"`
38 | AppliedPolicyIDs []int `json:"appliedPolicyIDs"`
39 | }
40 |
41 | type ChannelHeader struct {
42 | Name string `json:"name,omitempty"`
43 | Value string `json:"value,omitempty"`
44 | Secret string `json:"secret,omitempty"`
45 | Namespace string `json:"namespace,omitempty"`
46 | KeyName string `json:"key_name,omitempty"`
47 | }
48 |
49 | // AlertsChannelConfiguration - copy of alerts.ChannelConfiguration
50 | type AlertsChannelConfiguration struct {
51 | Recipients string `json:"recipients,omitempty"`
52 | IncludeJSONAttachment string `json:"include_json_attachment,omitempty"`
53 | AuthToken string `json:"auth_token,omitempty"`
54 | APIKey string `json:"api_key,omitempty"`
55 | Teams string `json:"teams,omitempty"`
56 | Tags string `json:"tags,omitempty"`
57 | URL string `json:"url,omitempty"`
58 | Channel string `json:"channel,omitempty"`
59 | Key string `json:"key,omitempty"`
60 | RouteKey string `json:"route_key,omitempty"`
61 | ServiceKey string `json:"service_key,omitempty"`
62 | BaseURL string `json:"base_url,omitempty"`
63 | AuthUsername string `json:"auth_username,omitempty"`
64 | AuthPassword string `json:"auth_password,omitempty"`
65 | PayloadType string `json:"payload_type,omitempty"`
66 | Region string `json:"region,omitempty"`
67 | UserID string `json:"user_id,omitempty"`
68 |
69 | Payload map[string]string `json:"payload,omitempty"`
70 |
71 | Headers []ChannelHeader `json:"headers,omitempty"`
72 | }
73 |
74 | // +kubebuilder:object:root=true
75 | // +kubebuilder:printcolumn:name="Created",type="boolean",JSONPath=".status.created"
76 |
77 | // AlertsChannel is the Schema for the AlertsChannel API
78 | type AlertsChannel struct {
79 | metav1.TypeMeta `json:",inline"`
80 | metav1.ObjectMeta `json:"metadata,omitempty"`
81 |
82 | Spec AlertsChannelSpec `json:"spec,omitempty"`
83 | Status AlertsChannelStatus `json:"status,omitempty"`
84 | }
85 |
86 | // +kubebuilder:object:root=true
87 |
88 | // AlertsChannelList contains a list of AlertsChannel
89 | type AlertsChannelList struct {
90 | metav1.TypeMeta `json:",inline"`
91 | metav1.ListMeta `json:"metadata,omitempty"`
92 | Items []AlertsChannel `json:"items"`
93 | }
94 |
95 | func init() {
96 | SchemeBuilder.Register(&AlertsChannel{}, &AlertsChannelList{})
97 | }
98 |
99 | func getSecret(name types.NamespacedName, key string, k8sClient client.Client) (string, error) {
100 | var apiKeySecret v1.Secret
101 |
102 | err := k8sClient.Get(context.Background(), name, &apiKeySecret)
103 | if err != nil {
104 | return "", err
105 | }
106 |
107 | return string(apiKeySecret.Data[key]), nil
108 | }
109 |
110 | // APIChannel - Converts AlertsChannelSpec object to alerts.Channel
111 | func (in AlertsChannelSpec) APIChannel(k8sClient client.Client) (alerts.Channel, error) {
112 | headers := in.Configuration.Headers
113 | in.Configuration.Headers = nil
114 | jsonString, _ := json.Marshal(in)
115 |
116 | var APIChannel alerts.Channel
117 | err := json.Unmarshal(jsonString, &APIChannel) // nolint
118 | if err != nil {
119 | return alerts.Channel{}, err
120 | }
121 |
122 | APIChannel.Links = alerts.ChannelLinks{}
123 |
124 | // Spec holds headers in a different format, need to convert to API format
125 | if len(headers) > 0 {
126 | APIChannel.Configuration.Headers = map[string]interface{}{}
127 | for _, header := range headers {
128 | if header.Value != "" {
129 | APIChannel.Configuration.Headers[header.Name] = header.Value
130 | } else {
131 | name := types.NamespacedName{
132 | Namespace: header.Namespace,
133 | Name: header.Secret,
134 | }
135 | value, err := getSecret(name, header.KeyName, k8sClient)
136 | if err != nil {
137 | return alerts.Channel{}, err
138 | }
139 | APIChannel.Configuration.Headers[header.Name] = value
140 | }
141 | }
142 | }
143 |
144 | return APIChannel, nil
145 | }
146 |
--------------------------------------------------------------------------------
/api/v1/alertschannel_types_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "reflect"
7 |
8 | "github.com/golang/mock/gomock"
9 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
10 | . "github.com/onsi/ginkgo"
11 | . "github.com/onsi/gomega"
12 | v1 "k8s.io/api/core/v1"
13 | "k8s.io/apimachinery/pkg/types"
14 |
15 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
16 | )
17 |
18 | var _ = Describe("AlertsChannelSpec", func() {
19 | Describe("APIChannel", func() {
20 | var client *interfacesfakes.MockClient
21 |
22 | BeforeEach(func() {
23 | ctrl := gomock.NewController(GinkgoT())
24 | defer ctrl.Finish()
25 | client = interfacesfakes.NewMockClient(ctrl)
26 | })
27 |
28 | Context("email channel", func() {
29 | var alertsChannelSpec AlertsChannelSpec
30 | BeforeEach(func() {
31 | alertsChannelSpec = AlertsChannelSpec{
32 | ID: 88,
33 | Name: "my alert channel",
34 | APIKey: "api-key",
35 | Region: "US",
36 | Type: "email",
37 | Links: ChannelLinks{
38 | PolicyIDs: []int{
39 | 1,
40 | 2,
41 | },
42 | },
43 | Configuration: AlertsChannelConfiguration{
44 | Recipients: "me@email.com",
45 | },
46 | }
47 | })
48 |
49 | It("converts AlertsChannelSpec object to alerts.Channel object from go client, retaining field values", func() {
50 | apiChannel, err := alertsChannelSpec.APIChannel(client)
51 | Expect(err).NotTo(HaveOccurred())
52 |
53 | Expect(fmt.Sprint(reflect.TypeOf(apiChannel))).To(Equal("alerts.Channel"))
54 | Expect(apiChannel.ID).To(Equal(88))
55 | Expect(apiChannel.Type).To(Equal(alerts.ChannelTypes.Email))
56 | Expect(apiChannel.Name).To(Equal("my alert channel"))
57 | apiConfiguration := apiChannel.Configuration
58 | Expect(apiConfiguration.Recipients).To(Equal("me@email.com"))
59 | })
60 | })
61 |
62 | Context("webhook channel", func() {
63 | var alertsChannelSpec AlertsChannelSpec
64 | BeforeEach(func() {
65 | alertsChannelSpec = AlertsChannelSpec{
66 | ID: 88,
67 | Name: "my alert channel",
68 | APIKey: "api-key",
69 | Region: "US",
70 | Type: "webhook",
71 | Links: ChannelLinks{
72 | PolicyIDs: []int{
73 | 1,
74 | 2,
75 | },
76 | },
77 | Configuration: AlertsChannelConfiguration{
78 | URL: "https://example.com/",
79 | Headers: []ChannelHeader{
80 | {
81 | Name: "KEY",
82 | Value: "VALUE",
83 | },
84 | {
85 | Name: "SECRET",
86 | Secret: "secret",
87 | Namespace: "default",
88 | KeyName: "secret",
89 | },
90 | },
91 | },
92 | }
93 | })
94 |
95 | It("converts AlertsChannelSpec object to alerts.Channel object from go client, retaining field values", func() {
96 | secret := v1.Secret{
97 | Data: map[string][]byte{
98 | "secret": []byte("don't tell anyone"),
99 | },
100 | }
101 | key := types.NamespacedName{
102 | Namespace: "default",
103 | Name: "secret",
104 | }
105 | client.EXPECT().
106 | Get(gomock.Eq(context.Background()),
107 | gomock.Eq(key),
108 | gomock.AssignableToTypeOf(&secret)).
109 | SetArg(2, secret)
110 |
111 | apiChannel, err := alertsChannelSpec.APIChannel(client)
112 | Expect(err).NotTo(HaveOccurred())
113 |
114 | Expect(fmt.Sprint(reflect.TypeOf(apiChannel))).To(Equal("alerts.Channel"))
115 | Expect(apiChannel.ID).To(Equal(88))
116 | Expect(apiChannel.Type).To(Equal(alerts.ChannelTypes.Webhook))
117 | Expect(apiChannel.Name).To(Equal("my alert channel"))
118 | apiConfiguration := apiChannel.Configuration
119 | Expect(apiConfiguration.URL).To(Equal("https://example.com/"))
120 | Expect(apiConfiguration.Headers["KEY"]).To(Equal("VALUE"))
121 | Expect(apiConfiguration.Headers["SECRET"]).To(Equal("don't tell anyone"))
122 | })
123 | })
124 | })
125 | })
126 |
--------------------------------------------------------------------------------
/api/v1/alertschannel_webhook.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 v1
17 |
18 | import (
19 | "errors"
20 |
21 | "k8s.io/apimachinery/pkg/runtime"
22 | ctrl "sigs.k8s.io/controller-runtime"
23 | logf "sigs.k8s.io/controller-runtime/pkg/log"
24 | "sigs.k8s.io/controller-runtime/pkg/webhook"
25 |
26 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
27 |
28 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
29 | )
30 |
31 | // log is for logging in this package.
32 | var (
33 | alertschannellog = logf.Log.WithName("alertschannel-resource")
34 | )
35 |
36 | // SetupWebhookWithManager - instantiates the Webhook
37 | func (r *AlertsChannel) SetupWebhookWithManager(mgr ctrl.Manager) error {
38 | alertClientFunc = interfaces.InitializeAlertsClient
39 | k8Client = mgr.GetClient()
40 | return ctrl.NewWebhookManagedBy(mgr).
41 | For(r).
42 | Complete()
43 | }
44 |
45 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
46 |
47 | // +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-alertschannel,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertschannels,verbs=create;update,versions=v1,name=malertschannel.kb.io,sideEffects=None
48 |
49 | var _ webhook.Defaulter = &AlertsChannel{}
50 |
51 | // Default implements webhook.Defaulter so a webhook will be registered for the type
52 | func (r *AlertsChannel) Default() {
53 | alertschannellog.Info("default", "name", r.Name)
54 |
55 | if r.Status.AppliedSpec == nil {
56 | log.Info("Setting null Applied Spec to empty interface")
57 | r.Status.AppliedSpec = &AlertsChannelSpec{}
58 | }
59 |
60 | if r.Status.AppliedPolicyIDs == nil {
61 | log.Info("Setting null AppliedPolicyIDs to empty interface")
62 | r.Status.AppliedPolicyIDs = []int{}
63 | }
64 | }
65 |
66 | // +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-alertschannel,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertschannels,versions=v1,name=valertschannel.kb.io,sideEffects=None
67 |
68 | var _ webhook.Validator = &AlertsChannel{}
69 |
70 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
71 | func (r *AlertsChannel) ValidateCreate() error {
72 | alertschannellog.Info("validate create", "name", r.Name)
73 |
74 | return r.ValidateAlertsChannel()
75 | }
76 |
77 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
78 | func (r *AlertsChannel) ValidateUpdate(old runtime.Object) error {
79 | alertschannellog.Info("validate update", "name", r)
80 |
81 | return r.ValidateAlertsChannel()
82 | }
83 |
84 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
85 | func (r *AlertsChannel) ValidateDelete() error {
86 | alertschannellog.Info("validate delete", "name", r.Name)
87 |
88 | // TODO(user): fill in your validation logic upon object deletion.
89 | return nil
90 | }
91 |
92 | // ValidateAlertsChannel - Validates create/update of AlertsChannel
93 | func (r *AlertsChannel) ValidateAlertsChannel() error {
94 | err := CheckForAPIKeyOrSecret(r.Spec.APIKey, r.Spec.APIKeySecret)
95 | if err != nil {
96 | return err
97 | }
98 |
99 | if !ValidRegion(r.Spec.Region) {
100 | return errors.New("Invalid region set, value was: " + r.Spec.Region)
101 | }
102 |
103 | var invalidAttributes InvalidAttributeSlice
104 |
105 | r.ValidateType()
106 | invalidAttributes = append(invalidAttributes, r.ValidateType()...)
107 |
108 | if len(invalidAttributes) > 0 {
109 | return errors.New("error with invalid attributes: \n" + invalidAttributes.errorString())
110 | }
111 |
112 | return nil
113 | }
114 |
115 | //ValidateType - Validates the Type attribute
116 | func (r *AlertsChannel) ValidateType() InvalidAttributeSlice {
117 | switch r.Spec.Type {
118 | case string(alerts.ChannelTypes.Email),
119 | string(alerts.ChannelTypes.OpsGenie),
120 | string(alerts.ChannelTypes.PagerDuty),
121 | string(alerts.ChannelTypes.Slack),
122 | string(alerts.ChannelTypes.User),
123 | string(alerts.ChannelTypes.VictorOps),
124 | string(alerts.ChannelTypes.Webhook):
125 |
126 | return []invalidAttribute{}
127 | default:
128 | alertschannellog.Info("Invalid Type attribute", "Type", r.Spec.Type)
129 |
130 | return []invalidAttribute{{attribute: "Type", value: r.Spec.Type}}
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/api/v1/alertschannel_webhook_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
5 | . "github.com/onsi/ginkgo"
6 | . "github.com/onsi/gomega"
7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 |
9 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
10 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
11 | )
12 |
13 | var _ = Describe("AlertsChannel_webhook", func() {
14 | var (
15 | r AlertsChannel
16 | alertsClient *interfacesfakes.FakeNewRelicAlertsClient
17 | )
18 |
19 | BeforeEach(func() {
20 | k8Client = testk8sClient
21 | alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
22 | fakeAlertFunc := func(string, string) (interfaces.NewRelicAlertsClient, error) {
23 | return alertsClient, nil
24 | }
25 | alertClientFunc = fakeAlertFunc
26 | r = AlertsChannel{
27 | ObjectMeta: v1.ObjectMeta{
28 | Name: "test alert channel",
29 | },
30 | Spec: AlertsChannelSpec{
31 | ID: 88,
32 | Name: "my alert channel",
33 | APIKey: "api-key",
34 | APIKeySecret: NewRelicAPIKeySecret{},
35 | Region: "US",
36 | Type: "webhook",
37 | Links: ChannelLinks{
38 | PolicyIDs: []int{
39 | 1,
40 | 2,
41 | },
42 | },
43 | Configuration: AlertsChannelConfiguration{
44 | BaseURL: "https://example.com",
45 | Headers: []ChannelHeader{
46 | {
47 | Name: "KEY",
48 | Value: "VALUE",
49 | },
50 | },
51 | },
52 | },
53 | }
54 |
55 | alertsClient.GetPolicyStub = func(int) (*alerts.Policy, error) {
56 | return &alerts.Policy{
57 | ID: 46286,
58 | }, nil
59 | }
60 | })
61 |
62 | Context("ValidateCreate", func() {
63 | Context("With a valid Alert Channel", func() {
64 | It("Should create the Alert Channel", func() {
65 | err := r.ValidateCreate()
66 | Expect(err).ToNot(HaveOccurred())
67 | })
68 | })
69 |
70 | Context("With an invalid Region", func() {
71 | BeforeEach(func() {
72 | r.Spec.Region = "hamburgers"
73 | })
74 |
75 | It("Should reject the Alert Channel creation", func() {
76 | err := r.ValidateCreate()
77 | Expect(err).To(HaveOccurred())
78 | Expect(err.Error()).To(ContainSubstring("hamburgers"))
79 | })
80 | })
81 |
82 | Context("With an invalid Type", func() {
83 | BeforeEach(func() {
84 | r.Spec.Type = "burritos"
85 | })
86 |
87 | It("Should reject the Alert Channel creation", func() {
88 | err := r.ValidateCreate()
89 | Expect(err).To(HaveOccurred())
90 | Expect(err.Error()).To(ContainSubstring("burritos"))
91 | })
92 | })
93 |
94 | Context("With no API Key or secret", func() {
95 | BeforeEach(func() {
96 | r.Spec.APIKey = ""
97 | })
98 |
99 | It("Should reject the Alert Channel creation", func() {
100 | err := r.ValidateCreate()
101 | Expect(err).To(HaveOccurred())
102 | Expect(err.Error()).To(ContainSubstring("either api_key or api_key_secret must be set"))
103 | })
104 | })
105 | })
106 | })
107 |
--------------------------------------------------------------------------------
/api/v1/apmalertcondition_types.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | )
9 |
10 | // ApmAlertConditionSpec defines the desired state of ApmAlertCondition
11 | type ApmAlertConditionSpec struct {
12 | GenericConditionSpec `json:",inline"`
13 | APMSpecificSpec `json:",inline"`
14 | }
15 |
16 | type APMSpecificSpec struct {
17 | Metric string `json:"metric,omitempty"`
18 | UserDefined alerts.ConditionUserDefined `json:"user_defined,omitempty"`
19 | Scope string `json:"condition_scope,omitempty"`
20 | Entities []string `json:"entities,omitempty"`
21 | GCMetric string `json:"gc_metric,omitempty"`
22 | ViolationCloseTimer int `json:"violation_close_timer,omitempty"`
23 | }
24 |
25 | // ApmAlertConditionStatus defines the observed state of ApmAlertCondition
26 | type ApmAlertConditionStatus struct {
27 | AppliedSpec *ApmAlertConditionSpec `json:"applied_spec"`
28 | ConditionID int `json:"condition_id"`
29 | }
30 |
31 | // +kubebuilder:object:root=true
32 | // +kubebuilder:printcolumn:name="Created",type="boolean",JSONPath=".status.created"
33 |
34 | // ApmAlertCondition is the Schema for the apmalertconditions API
35 | type ApmAlertCondition struct {
36 | metav1.TypeMeta `json:",inline"`
37 | metav1.ObjectMeta `json:"metadata,omitempty"`
38 |
39 | Spec ApmAlertConditionSpec `json:"spec,omitempty"`
40 | Status ApmAlertConditionStatus `json:"status,omitempty"`
41 | }
42 |
43 | // +kubebuilder:object:root=true
44 |
45 | // ApmAlertConditionList contains a list of ApmAlertCondition
46 | type ApmAlertConditionList struct {
47 | metav1.TypeMeta `json:",inline"`
48 | metav1.ListMeta `json:"metadata,omitempty"`
49 | Items []ApmAlertCondition `json:"items"`
50 | }
51 |
52 | func init() {
53 | SchemeBuilder.Register(&ApmAlertCondition{}, &ApmAlertConditionList{})
54 | }
55 |
56 | func (in ApmAlertConditionSpec) APICondition() alerts.Condition {
57 | jsonString, _ := json.Marshal(in)
58 | var APICondition alerts.Condition
59 | json.Unmarshal(jsonString, &APICondition) //nolint
60 |
61 | return APICondition
62 | }
63 |
--------------------------------------------------------------------------------
/api/v1/apmalertcondition_types_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 |
9 | . "github.com/onsi/ginkgo"
10 | . "github.com/onsi/gomega"
11 | )
12 |
13 | var _ = Describe("ApmAlertConditionSpec", func() {
14 | var condition ApmAlertConditionSpec
15 |
16 | BeforeEach(func() {
17 | condition = ApmAlertConditionSpec{
18 | GenericConditionSpec{
19 | Terms: []AlertConditionTerm{
20 | {
21 | Duration: "30",
22 | Operator: "above",
23 | Priority: "critical",
24 | Threshold: "1.5",
25 | TimeFunction: "all",
26 | },
27 | },
28 | Type: "apm_app_metric",
29 | Name: "APM Condition",
30 | RunbookURL: "http://test.com/runbook",
31 | PolicyID: 0,
32 | ID: 888,
33 | Enabled: true,
34 | ExistingPolicyID: 42,
35 | },
36 | APMSpecificSpec{
37 | Metric: "apdex",
38 | Entities: []string{"333"},
39 | UserDefined: alerts.ConditionUserDefined{
40 | Metric: "Custom/foo",
41 | ValueFunction: "average",
42 | },
43 | Scope: "application",
44 | GCMetric: "",
45 | ViolationCloseTimer: 60,
46 | },
47 | }
48 | })
49 |
50 | Describe("APICondition", func() {
51 | It("converts ApmAlertConditionSpec object to Condition object from go client, retaining field values", func() {
52 | apiCondition := condition.APICondition()
53 |
54 | Expect(fmt.Sprint(reflect.TypeOf(apiCondition))).To(Equal("alerts.Condition"))
55 |
56 | Expect(apiCondition.Type).To(Equal(alerts.ConditionTypes.APMApplicationMetric))
57 | Expect(apiCondition.Name).To(Equal("APM Condition"))
58 | Expect(apiCondition.RunbookURL).To(Equal("http://test.com/runbook"))
59 | Expect(apiCondition.Metric).To(Equal(alerts.MetricTypes.Apdex))
60 | Expect(apiCondition.Entities[0]).To(Equal("333"))
61 | Expect(apiCondition.Scope).To(Equal("application"))
62 | Expect(apiCondition.GCMetric).To(Equal(""))
63 |
64 | Expect(apiCondition.ID).To(Equal(888))
65 | Expect(apiCondition.ViolationCloseTimer).To(Equal(60))
66 | Expect(apiCondition.Enabled).To(Equal(true))
67 |
68 | apiTerm := apiCondition.Terms[0]
69 |
70 | Expect(fmt.Sprint(reflect.TypeOf(apiTerm))).To(Equal("alerts.ConditionTerm"))
71 |
72 | Expect(apiTerm.Duration).To(Equal(30))
73 | Expect(apiTerm.Operator).To(Equal(alerts.OperatorTypes.Above))
74 | Expect(apiTerm.Priority).To(Equal(alerts.PriorityTypes.Critical))
75 | Expect(apiTerm.Threshold).To(Equal(float64(1.5)))
76 | Expect(apiTerm.TimeFunction).To(Equal(alerts.TimeFunctionTypes.All))
77 |
78 | userDefinedCondition := apiCondition.UserDefined
79 | Expect(fmt.Sprint(reflect.TypeOf(userDefinedCondition))).To(Equal("alerts.ConditionUserDefined"))
80 |
81 | Expect(userDefinedCondition.Metric).To(Equal("Custom/foo"))
82 | Expect(userDefinedCondition.ValueFunction).To(Equal(alerts.ValueFunctionTypes.Average))
83 | })
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/api/v1/apmalertcondition_webhook_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 |
12 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
13 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
14 | )
15 |
16 | var _ = Describe("apmAlertCondition_webhook", func() {
17 | var (
18 | r ApmAlertCondition
19 | alertsClient *interfacesfakes.FakeNewRelicAlertsClient
20 | )
21 |
22 | BeforeEach(func() {
23 | k8Client = testk8sClient
24 | alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
25 | fakeAlertFunc := func(string, string) (interfaces.NewRelicAlertsClient, error) {
26 | return alertsClient, nil
27 | }
28 | alertClientFunc = fakeAlertFunc
29 | r = ApmAlertCondition{
30 | ObjectMeta: v1.ObjectMeta{
31 | Name: "test apm condition",
32 | },
33 | Spec: ApmAlertConditionSpec{
34 | GenericConditionSpec{
35 | Terms: []AlertConditionTerm{
36 | {
37 | Duration: "30",
38 | Operator: "above",
39 | Priority: "critical",
40 | Threshold: "0.9",
41 | TimeFunction: "all",
42 | },
43 | },
44 | Type: "apm_app_metric",
45 | Name: "K8s generated apm alert condition",
46 | Enabled: true,
47 | ExistingPolicyID: 46286,
48 | APIKey: "111222333",
49 | APIKeySecret: NewRelicAPIKeySecret{},
50 | Region: "staging",
51 | },
52 | APMSpecificSpec{
53 | Metric: "apdex",
54 | UserDefined: alerts.ConditionUserDefined{},
55 | Scope: "",
56 | Entities: []string{"5950260"},
57 | },
58 | },
59 | }
60 |
61 | alertsClient.GetPolicyStub = func(int) (*alerts.Policy, error) {
62 | return &alerts.Policy{
63 | ID: 46286,
64 | }, nil
65 | }
66 | })
67 | Context("ValidateCreate", func() {
68 | Context("With a valid Apm Condition", func() {
69 | It("Should create the apm condition", func() {
70 | err := r.ValidateCreate()
71 | Expect(err).ToNot(HaveOccurred())
72 | })
73 | })
74 |
75 | Context("With an invalid Type", func() {
76 | BeforeEach(func() {
77 | r.Spec.Type = "burritos"
78 | })
79 |
80 | It("Should reject the apm condition creation", func() {
81 | err := r.ValidateCreate()
82 | Expect(err).To(HaveOccurred())
83 | Expect(err.Error()).To(ContainSubstring("burritos"))
84 | })
85 | })
86 |
87 | Context("With an invalid Metric", func() {
88 | BeforeEach(func() {
89 | r.Spec.Type = "moar burritos"
90 | })
91 |
92 | It("Should reject the apm condition creation", func() {
93 | err := r.ValidateCreate()
94 | Expect(err).To(HaveOccurred())
95 | Expect(err.Error()).To(ContainSubstring("moar burritos"))
96 | })
97 | })
98 |
99 | Context("With an invalid Terms", func() {
100 | BeforeEach(func() {
101 | r.Spec.Terms[0].TimeFunction = "moar burritos"
102 | r.Spec.Terms[0].Priority = "moar tacos"
103 | r.Spec.Terms[0].Operator = "moar hamburgers"
104 | })
105 |
106 | It("Should reject the apm condition creation", func() {
107 | err := r.ValidateCreate()
108 | Expect(err).To(HaveOccurred())
109 | Expect(err.Error()).To(ContainSubstring("moar burritos"))
110 | Expect(err.Error()).To(ContainSubstring("moar tacos"))
111 | Expect(err.Error()).To(ContainSubstring("moar hamburgers"))
112 | })
113 | })
114 |
115 | Context("With an invalid userDefined type", func() {
116 | BeforeEach(func() {
117 | r.Spec.UserDefined = alerts.ConditionUserDefined{
118 | Metric: "Custom/foo",
119 | ValueFunction: "invalid type",
120 | }
121 | })
122 |
123 | It("Should reject the apm condition creation", func() {
124 | err := r.ValidateCreate()
125 | Expect(err).To(HaveOccurred())
126 | Expect(err.Error()).To(ContainSubstring("invalid type"))
127 | })
128 | })
129 |
130 | })
131 |
132 | Context("ValidateUpdate", func() {
133 | Context("When deleting an existing apm Condition with a delete policy", func() {
134 | var update ApmAlertCondition
135 | BeforeEach(func() {
136 | currentTime := v1.Time{Time: time.Now()}
137 | //make copy of existing object to update
138 | r.DeepCopyInto(&update)
139 |
140 | update.SetDeletionTimestamp(¤tTime)
141 | alertsClient.GetPolicyStub = func(int) (*alerts.Policy, error) {
142 | return &alerts.Policy{}, errors.New("no alert policy found for id 49092")
143 | }
144 | })
145 |
146 | It("Should allow the deletion anyway", func() {
147 | err := update.ValidateUpdate(&r)
148 | Expect(err).ToNot(HaveOccurred())
149 | })
150 | })
151 | })
152 | })
153 |
--------------------------------------------------------------------------------
/api/v1/common.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "errors"
5 | "hash"
6 |
7 | "github.com/davecgh/go-spew/spew"
8 | "github.com/newrelic/newrelic-client-go/pkg/region"
9 | )
10 |
11 | // DeepHashObject writes specified object to hash using the spew library
12 | // which follows pointers and prints actual values of the nested objects
13 | // ensuring the hash does not change when a pointer changes.
14 | func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
15 | hasher.Reset()
16 | printer := spew.ConfigState{
17 | Indent: " ",
18 | SortKeys: true,
19 | DisableMethods: true,
20 | SpewKeys: true,
21 | }
22 | printer.Fprintf(hasher, "%#v", objectToWrite)
23 | }
24 |
25 | // AlertConditionTerm represents the terms of a New Relic alert condition.
26 | type AlertConditionTerm struct {
27 | Duration string `json:"duration,omitempty"`
28 | Operator string `json:"operator,omitempty"`
29 | Priority string `json:"priority,omitempty"`
30 | Threshold string `json:"threshold"`
31 | TimeFunction string `json:"time_function,omitempty"`
32 | ViolationCloseTimer int `json:"violation_close_timer,omitempty"`
33 | }
34 |
35 | type NewRelicAPIKeySecret struct {
36 | Name string `json:"name,omitempty"`
37 | Namespace string `json:"namespace,omitempty"`
38 | KeyName string `json:"key_name,omitempty"`
39 | }
40 |
41 | //ValidRegion - returns true if a valid region is passed
42 | func ValidRegion(input string) bool {
43 | _, err := region.Parse(input)
44 | if err != nil {
45 | return false
46 | } else if input == "" {
47 | return false
48 | }
49 |
50 | return true
51 | }
52 |
53 | //CheckForAPIKeyOrSecret - returns error if a API KEY or k8 secret is not passed in
54 | func CheckForAPIKeyOrSecret(apiKey string, secret NewRelicAPIKeySecret) error {
55 | if apiKey != "" {
56 | return nil
57 | }
58 |
59 | if secret != (NewRelicAPIKeySecret{}) {
60 | if secret.Name != "" && secret.Namespace != "" && secret.KeyName != "" {
61 | return nil
62 | }
63 | }
64 |
65 | return errors.New("either api_key or api_key_secret must be set")
66 | }
67 |
--------------------------------------------------------------------------------
/api/v1/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 v1 contains API Schema definitions for the nralerts v1 API group
17 | // +kubebuilder:object:generate=true
18 | // +groupName=nr.k8s.newrelic.com
19 | package v1
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: "nr.k8s.newrelic.com", Version: "v1"}
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/v1/nrqlalertcondition_types.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | )
9 |
10 | // NrqlAlertConditionSpec defines the desired state of NrqlAlertCondition
11 | type NrqlAlertConditionSpec struct {
12 | GenericConditionSpec `json:",inline"`
13 | NrqlSpecificSpec `json:",inline"`
14 | }
15 |
16 | type GenericConditionSpec struct {
17 | Terms []AlertConditionTerm `json:"terms,omitempty"`
18 | Type string `json:"type,omitempty"`
19 | Name string `json:"name,omitempty"`
20 | RunbookURL string `json:"runbook_url,omitempty"`
21 | PolicyID int `json:"-"`
22 | ID int `json:"id,omitempty"`
23 | Enabled bool `json:"enabled"`
24 | ExistingPolicyID int `json:"existing_policy_id,omitempty"`
25 | APIKey string `json:"api_key,omitempty"`
26 | APIKeySecret NewRelicAPIKeySecret `json:"api_key_secret,omitempty"`
27 | Region string `json:"region,omitempty"`
28 | }
29 |
30 | type NrqlSpecificSpec struct {
31 | Nrql NrqlQuery `json:"nrql,omitempty"`
32 | ValueFunction string `json:"value_function,omitempty"`
33 | ExpectedGroups int `json:"expected_groups,omitempty"`
34 | IgnoreOverlap bool `json:"ignore_overlap,omitempty"`
35 | ViolationCloseTimer int `json:"violation_time_limit_seconds,omitempty"`
36 | }
37 |
38 | // NrqlQuery represents a NRQL query to use with a NRQL alert condition
39 | type NrqlQuery struct {
40 | Query string `json:"query,omitempty"`
41 | SinceValue string `json:"since_value,omitempty"`
42 | }
43 |
44 | // NrqlAlertConditionStatus defines the observed state of NrqlAlertCondition
45 | type NrqlAlertConditionStatus struct {
46 | AppliedSpec *NrqlAlertConditionSpec `json:"applied_spec"`
47 | ConditionID int `json:"condition_id"`
48 | }
49 |
50 | // +kubebuilder:object:root=true
51 | // +kubebuilder:printcolumn:name="Created",type="boolean",JSONPath=".status.created"
52 |
53 | // NrqlAlertCondition is the Schema for the nrqlalertconditions API
54 | type NrqlAlertCondition struct {
55 | metav1.TypeMeta `json:",inline"`
56 | metav1.ObjectMeta `json:"metadata,omitempty"`
57 |
58 | Spec NrqlAlertConditionSpec `json:"spec,omitempty"`
59 | Status NrqlAlertConditionStatus `json:"status,omitempty"`
60 | }
61 |
62 | // +kubebuilder:object:root=true
63 |
64 | // NrqlAlertConditionList contains a list of NrqlAlertCondition
65 | type NrqlAlertConditionList struct {
66 | metav1.TypeMeta `json:",inline"`
67 | metav1.ListMeta `json:"metadata,omitempty"`
68 | Items []NrqlAlertCondition `json:"items"`
69 | }
70 |
71 | func init() {
72 | SchemeBuilder.Register(&NrqlAlertCondition{}, &NrqlAlertConditionList{})
73 | }
74 |
75 | func (in NrqlAlertConditionSpec) APICondition() alerts.NrqlCondition {
76 | jsonString, _ := json.Marshal(in)
77 | var APICondition alerts.NrqlCondition
78 | json.Unmarshal(jsonString, &APICondition) //nolint
79 |
80 | return APICondition
81 | }
82 |
--------------------------------------------------------------------------------
/api/v1/nrqlalertcondition_types_integration_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package v1
4 |
5 | import (
6 | "fmt"
7 | "reflect"
8 |
9 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
10 |
11 | . "github.com/onsi/ginkgo"
12 | . "github.com/onsi/gomega"
13 | )
14 |
15 | var _ = Describe("NrqlAlertConditionSpec", func() {
16 | var condition NrqlAlertConditionSpec
17 |
18 | BeforeEach(func() {
19 | condition = NrqlAlertConditionSpec{
20 | GenericConditionSpec{
21 | Terms: []AlertConditionTerm{
22 | {
23 | Duration: "30",
24 | Operator: "above",
25 | Priority: "critical",
26 | Threshold: "5",
27 | TimeFunction: "all",
28 | },
29 | },
30 | Type: "NRQL",
31 | Name: "NRQL Condition",
32 | RunbookURL: "http://test.com/runbook",
33 | ID: 777,
34 | Enabled: true,
35 | ExistingPolicyID: 42,
36 | },
37 | NrqlSpecificSpec{
38 | ViolationCloseTimer: 60,
39 | ExpectedGroups: 2,
40 | IgnoreOverlap: true,
41 | ValueFunction: "max",
42 | Nrql: NrqlQuery{
43 | Query: "SELECT 1 FROM MyEvents",
44 | SinceValue: "5",
45 | },
46 | },
47 | }
48 | })
49 |
50 | Describe("APICondition", func() {
51 | It("converts NrqlAlertConditionSpec object to NrqlCondition object from go client, retaining field values", func() {
52 | apiCondition := condition.APICondition()
53 |
54 | Expect(fmt.Sprint(reflect.TypeOf(apiCondition))).To(Equal("alerts.NrqlCondition"))
55 |
56 | Expect(apiCondition.Type).To(Equal("NRQL"))
57 | Expect(apiCondition.Name).To(Equal("NRQL Condition"))
58 | Expect(apiCondition.RunbookURL).To(Equal("http://test.com/runbook"))
59 | Expect(apiCondition.ValueFunction).To(Equal(alerts.ValueFunctionTypes.Max))
60 | Expect(apiCondition.ID).To(Equal(777))
61 | Expect(apiCondition.ViolationCloseTimer).To(Equal(60))
62 | Expect(apiCondition.ExpectedGroups).To(Equal(2))
63 | Expect(apiCondition.IgnoreOverlap).To(Equal(true))
64 | Expect(apiCondition.Enabled).To(Equal(true))
65 |
66 | apiTerm := apiCondition.Terms[0]
67 |
68 | Expect(fmt.Sprint(reflect.TypeOf(apiTerm))).To(Equal("alerts.ConditionTerm"))
69 |
70 | Expect(apiTerm.Duration).To(Equal(30))
71 | Expect(apiTerm.Operator).To(Equal(alerts.OperatorTypes.Above))
72 | Expect(apiTerm.Priority).To(Equal(alerts.PriorityTypes.Critical))
73 | Expect(apiTerm.Threshold).To(Equal(float64(5)))
74 | Expect(apiTerm.TimeFunction).To(Equal(alerts.TimeFunctionTypes.All))
75 |
76 | apiQuery := apiCondition.Nrql
77 |
78 | Expect(fmt.Sprint(reflect.TypeOf(apiQuery))).To(Equal("alerts.NrqlQuery"))
79 |
80 | Expect(apiQuery.Query).To(Equal("SELECT 1 FROM MyEvents"))
81 | Expect(apiQuery.SinceValue).To(Equal("5"))
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/api/v1/nrqlalertcondition_types_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 |
9 | . "github.com/onsi/ginkgo"
10 | . "github.com/onsi/gomega"
11 | )
12 |
13 | var _ = Describe("NrqlAlertConditionSpec", func() {
14 | var condition NrqlAlertConditionSpec
15 |
16 | BeforeEach(func() {
17 | condition = NrqlAlertConditionSpec{
18 | GenericConditionSpec{
19 | Terms: []AlertConditionTerm{
20 | {
21 | Duration: "30",
22 | Operator: "above",
23 | Priority: "critical",
24 | Threshold: "5",
25 | TimeFunction: "all",
26 | },
27 | },
28 | Type: "NRQL",
29 | Name: "NRQL Condition",
30 | RunbookURL: "http://test.com/runbook",
31 | ID: 777,
32 |
33 | Enabled: true,
34 | ExistingPolicyID: 42,
35 | },
36 | NrqlSpecificSpec{
37 | ViolationCloseTimer: 60,
38 | ExpectedGroups: 2,
39 | IgnoreOverlap: true,
40 | ValueFunction: "max",
41 | Nrql: NrqlQuery{
42 | Query: "SELECT 1 FROM MyEvents",
43 | SinceValue: "5",
44 | },
45 | },
46 | }
47 | })
48 |
49 | Describe("APICondition", func() {
50 | It("converts NrqlAlertConditionSpec object to NrqlCondition object from go client, retaining field values", func() {
51 | apiCondition := condition.APICondition()
52 |
53 | Expect(fmt.Sprint(reflect.TypeOf(apiCondition))).To(Equal("alerts.NrqlCondition"))
54 |
55 | Expect(apiCondition.Type).To(Equal("NRQL"))
56 | Expect(apiCondition.Name).To(Equal("NRQL Condition"))
57 | Expect(apiCondition.RunbookURL).To(Equal("http://test.com/runbook"))
58 | Expect(apiCondition.ValueFunction).To(Equal(alerts.ValueFunctionTypes.Max))
59 | Expect(apiCondition.ID).To(Equal(777))
60 | Expect(apiCondition.ViolationCloseTimer).To(Equal(60))
61 | Expect(apiCondition.ExpectedGroups).To(Equal(2))
62 | Expect(apiCondition.IgnoreOverlap).To(Equal(true))
63 | Expect(apiCondition.Enabled).To(Equal(true))
64 |
65 | apiTerm := apiCondition.Terms[0]
66 |
67 | Expect(fmt.Sprint(reflect.TypeOf(apiTerm))).To(Equal("alerts.ConditionTerm"))
68 |
69 | Expect(apiTerm.Duration).To(Equal(30))
70 | Expect(apiTerm.Operator).To(Equal(alerts.OperatorTypes.Above))
71 | Expect(apiTerm.Priority).To(Equal(alerts.PriorityTypes.Critical))
72 | Expect(apiTerm.Threshold).To(Equal(float64(5)))
73 | Expect(apiTerm.TimeFunction).To(Equal(alerts.TimeFunctionTypes.All))
74 |
75 | apiQuery := apiCondition.Nrql
76 |
77 | Expect(fmt.Sprint(reflect.TypeOf(apiQuery))).To(Equal("alerts.NrqlQuery"))
78 |
79 | Expect(apiQuery.Query).To(Equal("SELECT 1 FROM MyEvents"))
80 | Expect(apiQuery.SinceValue).To(Equal("5"))
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/api/v1/nrqlalertcondition_webhook.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 v1
17 |
18 | import (
19 | "context"
20 | "errors"
21 | "strings"
22 |
23 | v1 "k8s.io/api/core/v1"
24 | "k8s.io/apimachinery/pkg/runtime"
25 | "k8s.io/apimachinery/pkg/types"
26 | ctrl "sigs.k8s.io/controller-runtime"
27 | logf "sigs.k8s.io/controller-runtime/pkg/log"
28 | "sigs.k8s.io/controller-runtime/pkg/webhook"
29 |
30 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
31 | )
32 |
33 | // log is for logging in this package.
34 | var (
35 | log = logf.Log.WithName("nrqlalertcondition-resource")
36 | )
37 |
38 | func (r *NrqlAlertCondition) SetupWebhookWithManager(mgr ctrl.Manager) error {
39 | alertClientFunc = interfaces.InitializeAlertsClient
40 | k8Client = mgr.GetClient()
41 | return ctrl.NewWebhookManagedBy(mgr).
42 | For(r).
43 | Complete()
44 | }
45 |
46 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
47 |
48 | // +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-nrqlalertcondition,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=nrqlalertconditions,verbs=create;update,versions=v1,name=mnrqlalertcondition.kb.io,sideEffects=None
49 |
50 | var _ webhook.Defaulter = &NrqlAlertCondition{}
51 |
52 | // Default implements webhook.Defaulter so a webhook will be registered for the type
53 | func (r *NrqlAlertCondition) Default() {
54 | log.Info("default", "name", r.Name)
55 |
56 | if r.Status.AppliedSpec == nil {
57 | log.Info("Setting null Applied Spec to empty interface")
58 | r.Status.AppliedSpec = &NrqlAlertConditionSpec{}
59 | }
60 | log.Info("r.Status.AppliedSpec after", "r.Status.AppliedSpec", r.Status.AppliedSpec)
61 | }
62 |
63 | // +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-nrqlalertcondition,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=nrqlalertconditions,versions=v1,name=vnrqlalertcondition.kb.io,sideEffects=None
64 |
65 | var _ webhook.Validator = &NrqlAlertCondition{}
66 |
67 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
68 | func (r *NrqlAlertCondition) ValidateCreate() error {
69 | log.Info("validate create", "name", r.Name)
70 | //TODO this should write this value TO a new secret so code path always reads from a secret
71 | err := r.CheckForAPIKeyOrSecret()
72 | if err != nil {
73 | return err
74 | }
75 |
76 | err = r.CheckRequiredFields()
77 | if err != nil {
78 | return err
79 | }
80 |
81 | return r.CheckExistingPolicyID()
82 | }
83 |
84 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
85 | func (r *NrqlAlertCondition) ValidateUpdate(old runtime.Object) error {
86 | log.Info("validate update", "name", r.Name)
87 | err := r.CheckForAPIKeyOrSecret()
88 | if err != nil {
89 | return err
90 | }
91 |
92 | err = r.CheckRequiredFields()
93 | if err != nil {
94 | return err
95 | }
96 |
97 | return r.CheckExistingPolicyID()
98 | }
99 |
100 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
101 | func (r *NrqlAlertCondition) ValidateDelete() error {
102 | log.Info("validate delete", "name", r.Name)
103 |
104 | // TODO(user): fill in your validation logic upon object deletion.
105 | return nil
106 | }
107 |
108 | func (r *NrqlAlertCondition) CheckExistingPolicyID() error {
109 | log.Info("Checking existing", "policyId", r.Spec.ExistingPolicyID)
110 | ctx := context.Background()
111 | var apiKey string
112 |
113 | if r.Spec.APIKey == "" {
114 | key := types.NamespacedName{Namespace: r.Spec.APIKeySecret.Namespace, Name: r.Spec.APIKeySecret.Name}
115 | var apiKeySecret v1.Secret
116 | getErr := k8Client.Get(ctx, key, &apiKeySecret)
117 | if getErr != nil {
118 | log.Error(getErr, "Error getting secret")
119 | return getErr
120 | }
121 | apiKey = string(apiKeySecret.Data[r.Spec.APIKeySecret.KeyName])
122 | } else {
123 | apiKey = r.Spec.APIKey
124 | }
125 |
126 | alertsClient, errAlertClient := alertClientFunc(apiKey, r.Spec.Region)
127 | if errAlertClient != nil {
128 | log.Error(errAlertClient, "failed to get policy",
129 | "policyId", r.Spec.ExistingPolicyID,
130 | "API Key", interfaces.PartialAPIKey(apiKey),
131 | "region", r.Spec.Region,
132 | )
133 | return errAlertClient
134 | }
135 |
136 | alertPolicy, errAlertPolicy := alertsClient.GetPolicy(r.Spec.ExistingPolicyID)
137 | if errAlertPolicy != nil {
138 | if r.GetDeletionTimestamp() != nil {
139 | log.Info("Deleting resource", "errAlertPolicy", errAlertPolicy)
140 | if strings.Contains(errAlertPolicy.Error(), "no alert policy found for id") {
141 | log.Info("ExistingAlertPolicy not found but we are deleting the condition so this is ok")
142 | return nil
143 | }
144 | }
145 | log.Error(errAlertPolicy, "failed to get policy",
146 | "policyId", r.Spec.ExistingPolicyID,
147 | "API Key", interfaces.PartialAPIKey(apiKey),
148 | "region", r.Spec.Region,
149 | )
150 | return errAlertPolicy
151 | }
152 |
153 | if alertPolicy.ID != r.Spec.ExistingPolicyID {
154 | log.Info("Alert policy returned by the API failed to match provided policy ID")
155 | return errors.New("alert policy returned by API did not match")
156 | }
157 |
158 | return nil
159 | }
160 |
161 | func (r *NrqlAlertCondition) CheckForAPIKeyOrSecret() error {
162 | if r.Spec.APIKey != "" {
163 | return nil
164 | }
165 |
166 | if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
167 | if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
168 | return nil
169 | }
170 | }
171 |
172 | return errors.New("either api_key or api_key_secret must be set")
173 | }
174 |
175 | func (r *NrqlAlertCondition) CheckRequiredFields() error {
176 | missingFields := []string{}
177 |
178 | if r.Spec.Region == "" {
179 | missingFields = append(missingFields, "region")
180 | }
181 |
182 | if r.Spec.ExistingPolicyID == 0 {
183 | missingFields = append(missingFields, "existing_policy_id")
184 | }
185 |
186 | if len(missingFields) > 0 {
187 | return errors.New(strings.Join(missingFields, " and ") + " must be set")
188 | }
189 |
190 | return nil
191 | }
192 |
--------------------------------------------------------------------------------
/api/v1/policy_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 v1
17 |
18 | import (
19 | "encoding/json"
20 | "hash/fnv"
21 |
22 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24 | "k8s.io/apimachinery/pkg/types"
25 | )
26 |
27 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
28 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
29 |
30 | // PolicySpec defines the desired state of Policy
31 | type PolicySpec struct {
32 | IncidentPreference string `json:"incident_preference,omitempty"`
33 | Name string `json:"name"`
34 | APIKey string `json:"api_key,omitempty"`
35 | APIKeySecret NewRelicAPIKeySecret `json:"api_key_secret,omitempty"`
36 | Region string `json:"region"`
37 | Conditions []PolicyCondition `json:"conditions,omitempty"`
38 | }
39 |
40 | //PolicyCondition defined the conditions contained within a a policy
41 | type PolicyCondition struct {
42 | Name string `json:"name"`
43 | Namespace string `json:"namespace"`
44 | Spec ConditionSpec `json:"spec,omitempty"`
45 | }
46 |
47 | //ConditionSpec - Merged superset of Condition types
48 | type ConditionSpec struct {
49 | GenericConditionSpec `json:",inline"`
50 | NrqlSpecificSpec `json:",inline"`
51 | APMSpecificSpec `json:",inline"`
52 | }
53 |
54 | // PolicyStatus defines the observed state of Policy
55 | type PolicyStatus struct {
56 | AppliedSpec *PolicySpec `json:"applied_spec"`
57 | PolicyID int `json:"policy_id"`
58 | }
59 |
60 | // +kubebuilder:object:root=true
61 |
62 | // Policy is the Schema for the policies API
63 | type Policy struct {
64 | metav1.TypeMeta `json:",inline"`
65 | metav1.ObjectMeta `json:"metadata,omitempty"`
66 |
67 | Spec PolicySpec `json:"spec,omitempty"`
68 | Status PolicyStatus `json:"status,omitempty"`
69 | }
70 |
71 | // +kubebuilder:object:root=true
72 |
73 | // PolicyList contains a list of Policy
74 | type PolicyList struct {
75 | metav1.TypeMeta `json:",inline"`
76 | metav1.ListMeta `json:"metadata,omitempty"`
77 | Items []Policy `json:"items"`
78 | }
79 |
80 | func init() {
81 | SchemeBuilder.Register(&Policy{}, &PolicyList{})
82 | }
83 |
84 | func (in PolicySpec) APIPolicy() alerts.Policy {
85 | jsonString, _ := json.Marshal(in)
86 | var APIPolicy alerts.Policy
87 | json.Unmarshal(jsonString, &APIPolicy) //nolint
88 |
89 | //APICondition.PolicyID = spec.ExistingPolicyId
90 |
91 | return APIPolicy
92 | }
93 |
94 | func (p *PolicyCondition) SpecHash() uint32 {
95 | //remove api keys and condition from object to enable comparison minus inherited fields
96 | strippedPolicy := PolicyCondition{
97 | Spec: p.Spec,
98 | }
99 | strippedPolicy.Spec.APIKeySecret = NewRelicAPIKeySecret{}
100 | strippedPolicy.Spec.APIKey = ""
101 | strippedPolicy.Spec.Region = ""
102 | strippedPolicy.Spec.ExistingPolicyID = 0
103 | conditionTemplateSpecHasher := fnv.New32a()
104 | DeepHashObject(conditionTemplateSpecHasher, strippedPolicy)
105 |
106 | return conditionTemplateSpecHasher.Sum32()
107 | }
108 |
109 | func (p *PolicyCondition) GetNamespace() types.NamespacedName {
110 | return types.NamespacedName{
111 | Namespace: p.Namespace,
112 | Name: p.Name,
113 | }
114 | }
115 |
116 | //Equals - comparator function to check for equality
117 | func (in PolicySpec) Equals(policyToCompare PolicySpec) bool {
118 | if in.IncidentPreference != policyToCompare.IncidentPreference {
119 | return false
120 | }
121 |
122 | if in.Name != policyToCompare.Name {
123 | return false
124 | }
125 |
126 | if in.APIKey != policyToCompare.APIKey {
127 | return false
128 | }
129 |
130 | if in.Region != policyToCompare.Region {
131 | return false
132 | }
133 |
134 | if in.APIKeySecret != policyToCompare.APIKeySecret {
135 | return false
136 | }
137 |
138 | if len(in.Conditions) != len(policyToCompare.Conditions) {
139 | return false
140 | }
141 |
142 | checkedHashes := make(map[uint32]bool)
143 |
144 | for _, condition := range in.Conditions {
145 | checkedHashes[condition.SpecHash()] = true
146 | }
147 |
148 | for _, conditionToCompare := range policyToCompare.Conditions {
149 | if _, ok := checkedHashes[conditionToCompare.SpecHash()]; !ok {
150 | return false
151 | }
152 | }
153 |
154 | return true
155 | }
156 |
157 | //GetConditionType - returns the string representative of the Condition type
158 | func GetConditionType(condition PolicyCondition) string {
159 | if condition.Spec.Type == "NRQL" {
160 | return "NrqlAlertCondition"
161 | }
162 |
163 | return "ApmAlertCondition"
164 | }
165 |
166 | func (p *PolicyCondition) GenerateSpecFromNrqlConditionSpec(nrqlConditionSpec NrqlAlertConditionSpec) {
167 | jsonString, _ := json.Marshal(nrqlConditionSpec)
168 | json.Unmarshal(jsonString, &p.Spec) //nolint
169 | }
170 |
171 | func (p *PolicyCondition) GenerateSpecFromApmConditionSpec(apmConditionSpec ApmAlertConditionSpec) {
172 | jsonString, _ := json.Marshal(apmConditionSpec)
173 | json.Unmarshal(jsonString, &p.Spec) //nolint
174 | }
175 |
176 | func (p *PolicyCondition) ReturnNrqlConditionSpec() (nrqlAlertConditionSpec NrqlAlertConditionSpec) {
177 | jsonString, _ := json.Marshal(p.Spec)
178 | json.Unmarshal(jsonString, &nrqlAlertConditionSpec) //nolint
179 |
180 | return
181 | }
182 |
183 | func (p *PolicyCondition) ReturnApmConditionSpec() (apmAlertConditionSpec ApmAlertConditionSpec) {
184 | jsonString, _ := json.Marshal(p.Spec)
185 | json.Unmarshal(jsonString, &apmAlertConditionSpec) //nolint
186 |
187 | return
188 | }
189 |
--------------------------------------------------------------------------------
/api/v1/policy_webhook.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 v1
17 |
18 | import (
19 | "errors"
20 | "strings"
21 |
22 | "k8s.io/apimachinery/pkg/runtime"
23 | ctrl "sigs.k8s.io/controller-runtime"
24 | logf "sigs.k8s.io/controller-runtime/pkg/log"
25 | "sigs.k8s.io/controller-runtime/pkg/webhook"
26 |
27 | customErrors "github.com/newrelic/newrelic-kubernetes-operator/errors"
28 | )
29 |
30 | // Log is for emitting logs in this package.
31 | var Log = logf.Log.WithName("policy-resource")
32 | var defaultPolicyIncidentPreference = "PER_POLICY"
33 |
34 | func (r *Policy) SetupWebhookWithManager(mgr ctrl.Manager) error {
35 | return ctrl.NewWebhookManagedBy(mgr).
36 | For(r).
37 | Complete()
38 | }
39 |
40 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
41 |
42 | // +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-policy,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=policies,verbs=create;update,versions=v1,name=mpolicy.kb.io,sideEffects=None
43 |
44 | var _ webhook.Defaulter = &Policy{}
45 |
46 | // Default implements webhook.Defaulter so a webhook will be registered for the type
47 | func (r *Policy) Default() {
48 | Log.Info("default", "name", r.Name)
49 |
50 | if r.Status.AppliedSpec == nil {
51 | log.Info("Setting null Applied Spec to empty interface")
52 | r.Status.AppliedSpec = &PolicySpec{}
53 | }
54 |
55 | r.DefaultIncidentPreference()
56 | }
57 |
58 | // +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-policy,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=policies,versions=v1,name=vpolicy.kb.io,sideEffects=None
59 |
60 | var _ webhook.Validator = &Policy{}
61 |
62 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
63 | func (r *Policy) ValidateCreate() error {
64 | Log.Info("validate create", "name", r.Name)
65 |
66 | collectedErrors := new(customErrors.ErrorCollector)
67 |
68 | err := r.CheckForAPIKeyOrSecret()
69 | if err != nil {
70 | collectedErrors.Collect(err)
71 | }
72 |
73 | err = r.CheckForDuplicateConditions()
74 | if err != nil {
75 | collectedErrors.Collect(err)
76 | }
77 |
78 | err = r.ValidateIncidentPreference()
79 | if err != nil {
80 | collectedErrors.Collect(err)
81 | }
82 |
83 | if len(*collectedErrors) > 0 {
84 | Log.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
85 | return collectedErrors
86 | }
87 |
88 | return nil
89 | }
90 |
91 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
92 | func (r *Policy) ValidateUpdate(old runtime.Object) error {
93 | Log.Info("validate update", "name", r.Name)
94 |
95 | collectedErrors := new(customErrors.ErrorCollector)
96 |
97 | err := r.CheckForAPIKeyOrSecret()
98 | if err != nil {
99 | collectedErrors.Collect(err)
100 | }
101 |
102 | err = r.CheckForDuplicateConditions()
103 | if err != nil {
104 | collectedErrors.Collect(err)
105 | }
106 |
107 | err = r.ValidateIncidentPreference()
108 | if err != nil {
109 | collectedErrors.Collect(err)
110 | }
111 |
112 | if len(*collectedErrors) > 0 {
113 | Log.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
114 | return collectedErrors
115 | }
116 |
117 | return nil
118 | }
119 |
120 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
121 | func (r *Policy) ValidateDelete() error {
122 | Log.Info("validate delete", "name", r.Name)
123 |
124 | err := r.CheckForAPIKeyOrSecret()
125 | if err != nil {
126 | return err
127 | }
128 |
129 | return nil
130 | }
131 |
132 | func (r *Policy) DefaultIncidentPreference() {
133 | if r.Spec.IncidentPreference == "" {
134 | r.Spec.IncidentPreference = defaultPolicyIncidentPreference
135 | }
136 |
137 | r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
138 | }
139 |
140 | func (r *Policy) CheckForDuplicateConditions() error {
141 | var conditionHashMap = make(map[uint32]bool)
142 |
143 | for _, condition := range r.Spec.Conditions {
144 | conditionHashMap[condition.SpecHash()] = true
145 | }
146 |
147 | if len(conditionHashMap) != len(r.Spec.Conditions) {
148 | log.Info("duplicate conditions detected or hash collision", "conditionHash", conditionHashMap)
149 |
150 | return errors.New("duplicate conditions detected or hash collision")
151 | }
152 |
153 | return nil
154 | }
155 |
156 | func (r *Policy) ValidateIncidentPreference() error {
157 | switch r.Spec.IncidentPreference {
158 | case "PER_POLICY", "PER_CONDITION", "PER_CONDITION_AND_TARGET":
159 | return nil
160 | }
161 |
162 | log.Info("Incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET", "IncidentPreference value", r.Spec.IncidentPreference)
163 |
164 | return errors.New("incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET")
165 | }
166 |
167 | func (r *Policy) CheckForAPIKeyOrSecret() error {
168 | if r.Spec.APIKey != "" {
169 | return nil
170 | }
171 |
172 | if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
173 | if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
174 | return nil
175 | }
176 | }
177 |
178 | return errors.New("either api_key or api_key_secret must be set")
179 | }
180 |
--------------------------------------------------------------------------------
/api/v1/suite_test.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | import (
4 | "path/filepath"
5 | "testing"
6 |
7 | apierrors "k8s.io/apimachinery/pkg/api/errors"
8 | "k8s.io/client-go/kubernetes/scheme"
9 | "k8s.io/client-go/rest"
10 | "sigs.k8s.io/controller-runtime/pkg/client"
11 | "sigs.k8s.io/controller-runtime/pkg/envtest"
12 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
13 | logf "sigs.k8s.io/controller-runtime/pkg/log"
14 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
15 |
16 | . "github.com/onsi/ginkgo"
17 | . "github.com/onsi/gomega"
18 | )
19 |
20 | var cfg *rest.Config
21 | var testk8sClient client.Client
22 | var testEnv *envtest.Environment
23 |
24 | func TestV1(t *testing.T) {
25 | RegisterFailHandler(Fail)
26 |
27 | RunSpecsWithDefaultAndCustomReporters(t,
28 | "V1 Suite",
29 | []Reporter{printer.NewlineReporter{}})
30 | }
31 |
32 | var _ = BeforeSuite(func(done Done) {
33 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
34 |
35 | By("bootstrapping test environment")
36 | testEnv = &envtest.Environment{
37 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
38 | }
39 |
40 | var err error
41 | cfg, err = testEnv.Start()
42 | Expect(err).ToNot(HaveOccurred())
43 | Expect(cfg).ToNot(BeNil())
44 |
45 | err = AddToScheme(scheme.Scheme)
46 | Expect(err).NotTo(HaveOccurred())
47 |
48 | // +kubebuilder:scaffold:scheme
49 |
50 | testk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
51 | Expect(err).ToNot(HaveOccurred())
52 | Expect(testk8sClient).ToNot(BeNil())
53 |
54 | close(done)
55 | }, 60)
56 |
57 | var _ = AfterSuite(func() {
58 | By("tearing down the test environment")
59 | err := testEnv.Stop()
60 | Expect(err).ToNot(HaveOccurred())
61 | })
62 |
63 | // this is used, but only with -tags=integration
64 | // nolint
65 | func ignoreAlreadyExists(err error) error {
66 | if apierrors.IsAlreadyExists(err) {
67 | return nil
68 | }
69 |
70 | return err
71 | }
72 |
--------------------------------------------------------------------------------
/build/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newrelic/newrelic-kubernetes-operator/887746d3ea61631e038b510d6bb3eb711a9cb418/build/.keep
--------------------------------------------------------------------------------
/build/compile.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile Fragment for Compiling
3 | #
4 |
5 | GO ?= go
6 | BUILD_DIR ?= ./bin
7 | PROJECT_MODULE ?= $(shell $(GO) list -m)
8 | # $b replaced by the binary name in the compile loop, -s/w remove debug symbols
9 | # TODO: Remove the compile time 'main.NewRelicAPIKey'
10 | LDFLAGS ?= "-s -w -X $(PROJECT_NAME)/internal/info.Name=$(PROJECT_NAME) -X $(PROJECT_MODULE)/internal/info.Version=$(PROJECT_VER)"
11 | SRCDIR ?= .
12 | COMPILE_OS ?= darwin linux windows
13 | BINARYNAME ?= manager
14 |
15 | # Determine commands by looking into cmd/*
16 | COMMANDS ?= $(wildcard ${SRCDIR}/cmd/*)
17 |
18 | compile-clean:
19 | @echo "=== $(PROJECT_NAME) === [ compile-clean ]: removing binaries..."
20 | @rm -rfv $(BUILD_DIR)/*
21 |
22 | compile: compile-only
23 |
24 | compile-all:
25 | @echo "=== $(PROJECT_NAME) === [ compile ]: building commands:"
26 | @mkdir -p $(BUILD_DIR)/$(GOOS)
27 | @for os in $(COMPILE_OS); do \
28 | echo "=== $(PROJECT_NAME) === [ compile ]: $(BUILD_DIR)/$$os/$(BINARYNAME)"; \
29 | CGO_ENABLED=0 GOOS=$$os $(GO) build -ldflags=$(LDFLAGS) -o $(BUILD_DIR)/$$os/$(BINARYNAME) . ; \
30 | done
31 |
32 | compile-only:
33 | @echo "=== $(PROJECT_NAME) === [ compile ]: building commands: $(BINARYNAME)"
34 | @mkdir -p $(BUILD_DIR)/$(GOOS)
35 | @echo "=== $(PROJECT_NAME) === [ compile ]: $(BUILD_DIR)/$(GOOS)/$$BINARYNAME";
36 | @CGO_ENABLED=0 GOOS=$(GOOS) $(GO) build -ldflags=$(LDFLAGS) -o $(BUILD_DIR)/$(GOOS)/$(BINARYNAME) . ; \
37 |
38 | # Override GOOS for these specific targets
39 | compile-darwin: GOOS=darwin
40 | compile-darwin: compile-only
41 |
42 | compile-linux: GOOS=linux
43 | compile-linux: compile-only
44 |
45 | compile-windows: GOOS=windows
46 | compile-windows: compile-only
47 |
48 |
49 | .PHONY: clean-compile compile compile-darwin compile-linux compile-only compile-windows
50 |
--------------------------------------------------------------------------------
/build/docker.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for Docker actions
3 | #
4 | DOCKER ?= docker
5 | DOCKER_FILE ?= build/package/Dockerfile
6 | DOCKER_IMAGE ?= newrelic/kubernetes-operator
7 | DOCKER_IMAGE_TAG ?= snapshot
8 |
9 | # Build the docker image
10 | docker-build: compile-linux
11 | @echo "=== $(PROJECT_NAME) === [ docker-build ]: Creating docker image: $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG) ..."
12 | docker build -f $(DOCKER_FILE) -t $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG) $(BUILD_DIR)/linux/
13 |
14 |
15 | docker-login:
16 | @echo "=== $(PROJECT_NAME) === [ docker-login ]: logging into docker hub"
17 | @if [ -z "${DOCKER_USERNAME}" ]; then \
18 | echo "Failure: DOCKER_USERNAME not set" ; \
19 | exit 1 ; \
20 | fi
21 | @if [ -z "${DOCKER_PASSWORD}" ]; then \
22 | echo "Failure: DOCKER_PASSWORD not set" ; \
23 | exit 1 ; \
24 | fi
25 | @echo "=== $(PROJECT_NAME) === [ docker-login ]: username: '$$DOCKER_USERNAME'"
26 | @echo ${DOCKER_PASSWORD} | $(DOCKER) login -u ${DOCKER_USERNAME} --password-stdin
27 |
28 |
29 | # Push the docker image
30 | docker-push: docker-login docker-build
31 | @echo "=== $(PROJECT_NAME) === [ docker-push ]: Pushing docker image: $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG) ..."
32 | $(DOCKER) push $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG)
33 |
34 | .PHONY: docker-build docker-login docker-push
35 |
--------------------------------------------------------------------------------
/build/document.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for displaying auto-generated documentation
3 | #
4 |
5 | GOTOOLS += golang.org/x/tools/cmd/godoc \
6 | github.com/git-chglog/git-chglog/cmd/git-chglog
7 | GODOC ?= godoc
8 | GODOC_HTTP ?= "localhost:6060"
9 |
10 | CHANGELOG_CMD ?= git-chglog
11 | CHANGELOG_FILE ?= CHANGELOG.md
12 | RELEASE_NOTES_FILE ?= relnotes.md
13 |
14 | docs: tools
15 | @echo "=== $(PROJECT_NAME) === [ docs ]: Starting godoc server..."
16 | @echo "=== $(PROJECT_NAME) === [ docs ]:"
17 | @echo "=== $(PROJECT_NAME) === [ docs ]: NOTE: This only works if this codebase is in your GOPATH!"
18 | @echo "=== $(PROJECT_NAME) === [ docs ]: godoc issue: https://github.com/golang/go/issues/26827"
19 | @echo "=== $(PROJECT_NAME) === [ docs ]:"
20 | @echo "=== $(PROJECT_NAME) === [ docs ]: Module Docs: http://$(GODOC_HTTP)/pkg/$(PROJECT_MODULE)"
21 | @$(GODOC) -http=$(GODOC_HTTP)
22 |
23 | changelog: tools
24 | @echo "=== $(PROJECT_NAME) === [ changelog ]: Generating changelog..."
25 | @$(CHANGELOG_CMD) --silent -o $(CHANGELOG_FILE)
26 |
27 | release-notes: tools
28 | @echo "=== $(PROJECT_NAME) === [ release-notes ]: Generating release notes..."
29 | @mkdir -p $(SRCDIR)/tmp
30 | @$(CHANGELOG_CMD) --silent -o $(SRCDIR)/tmp/$(RELEASE_NOTES_FILE) v$(PROJECT_VER_TAGGED)
31 |
32 | .PHONY: docs changelog release-notes
33 |
--------------------------------------------------------------------------------
/build/generate.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile Fragment for generating code
3 | #
4 |
5 | CONTROLLER_GEN ?= controller-gen
6 | HEADER_FILE ?= $(SRCDIR)/build/generate/boilerplate.go.txt
7 |
8 | GOTOOLS += sigs.k8s.io/controller-tools/cmd/controller-gen
9 |
10 |
11 | # Generate code
12 | generate: tools manifests
13 | @echo "=== $(PROJECT_NAME) === [ generate ]: Running $(CONTROLLER_GEN)..."
14 | @$(CONTROLLER_GEN) object:headerFile=$(HEADER_FILE) paths="./..."
15 | @cd interfaces/ && $(GO) generate
16 |
17 |
--------------------------------------------------------------------------------
/build/generate/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 | */
--------------------------------------------------------------------------------
/build/kube.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile snippet for install/uninstall of the operator
3 | #
4 |
5 | # Image URL to use all building/pushing image targets
6 | DOCKER_IMAGE ?= newrelic/kubernetes-operator:snapshot
7 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
8 | CRD_OPTIONS ?= "crd:trivialVersions=true"
9 | CONFIG_ROOT ?= $(SRCDIR)/config
10 | RBAC_ROLE_NAME ?= manager-role
11 |
12 | # Install CRDs into a cluster
13 | install: manifests
14 | @echo "=== $(PROJECT_NAME) === [ install ]: Applying operator..."
15 | @kustomize build $(CONFIG_ROOT)/crd | kubectl apply -f -
16 |
17 | # Uninstall CRDs from a cluster
18 | uninstall: manifests
19 | @echo "=== $(PROJECT_NAME) === [ uninstall ]: Deleting operator..."
20 | @kustomize build $(CONFIG_ROOT)/crd | kubectl delete -f -
21 |
22 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config
23 | deploy: manifests docker-build
24 | @echo "=== $(PROJECT_NAME) === [ deploy ]: Deploying operator as docker image ${DOCKER_IMAGE}..."
25 | @cd $(CONFIG_ROOT)/manager && kustomize edit set image controller=${DOCKER_IMAGE}
26 | @kustomize build $(CONFIG_ROOT)/default | kubectl apply -f -
27 |
28 | # Generate manifests e.g. CRD, RBAC etc.
29 | manifests: tools
30 | @echo "=== $(PROJECT_NAME) === [ manifests ]: Generating manifests..."
31 | @$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=$(RBAC_ROLE_NAME) webhook paths="./..." output:crd:artifacts:config=$(CONFIG_ROOT)/crd/bases
32 | # @$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=$(RBAC_ROLE_NAME) webhook paths="./..." output:crd:artifacts:config=$(CONFIG_ROOT)/crd/bases output:rbac:artifacts:config=$(CONFIG_ROOT)/rbac output:webhook:artifacts:config=$(CONFIG_ROOT)/webhook
33 |
--------------------------------------------------------------------------------
/build/lint.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for Linting
3 | #
4 |
5 | GO ?= go
6 | MISSPELL ?= misspell
7 | GOFMT ?= gofmt
8 | GOIMPORTS ?= goimports
9 |
10 | COMMIT_LINT_CMD ?= go-gitlint
11 | COMMIT_LINT_REGEX ?= "(bug|chore|docs|feat|fix|refactor|tests?)(\([^\)]+\))?: .*"
12 | COMMIT_LINT_START ?= "2020-06-05"
13 |
14 | GOLINTER = golangci-lint
15 |
16 | EXCLUDEDIR ?= .git
17 | SRCDIR ?= .
18 | GO_PKGS ?= $(shell ${GO} list ./... | grep -v -e "/vendor/" -e "/example")
19 | FILES ?= $(shell find ${SRCDIR} -type f | grep -v -e '.git/' -e '/vendor/')
20 | GO_FILES ?= $(shell find $(SRCDIR) -type f -name "*.go" | grep -v -e ".git/" -e '/vendor/' -e '/example/')
21 | PROJECT_MODULE ?= $(shell $(GO) list -m)
22 |
23 | GO_MOD_OUTDATED ?= go-mod-outdated
24 |
25 | GOTOOLS += github.com/client9/misspell/cmd/misspell \
26 | github.com/llorllale/go-gitlint/cmd/go-gitlint \
27 | github.com/psampaz/go-mod-outdated \
28 | github.com/golangci/golangci-lint/cmd/golangci-lint \
29 | golang.org/x/tools/cmd/goimports
30 |
31 |
32 | lint: outdated spell-check gofmt golangci lint-commit goimports
33 | lint-fix: spell-check-fix gofmt-fix goimports
34 |
35 | #
36 | # Check spelling on all the files, not just source code
37 | #
38 | spell-check: tools
39 | @echo "=== $(PROJECT_NAME) === [ spell-check ]: Checking for spelling mistakes with $(MISSPELL)..."
40 | @$(MISSPELL) -source text $(FILES)
41 |
42 | spell-check-fix: tools
43 | @echo "=== $(PROJECT_NAME) === [ spell-check-fix ]: Fixing spelling mistakes with $(MISSPELL)..."
44 | @$(MISSPELL) -source text -w $(FILES)
45 |
46 | gofmt: tools
47 | @echo "=== $(PROJECT_NAME) === [ gofmt ]: Checking file format with $(GOFMT)..."
48 | @find . -path "$(EXCLUDEDIR)" -prune -print0 | xargs -0 $(GOFMT) -e -l -s -d ${SRCDIR}
49 |
50 | gofmt-fix: tools
51 | @echo "=== $(PROJECT_NAME) === [ gofmt-fix ]: Fixing file format with $(GOFMT)..."
52 | @find . -path "$(EXCLUDEDIR)" -prune -print0 | xargs -0 $(GOFMT) -e -l -s -w ${SRCDIR}
53 |
54 | goimports: tools
55 | @echo "=== $(PROJECT_NAME) === [ goimports ]: Checking imports with $(GOIMPORTS)..."
56 | @$(GOIMPORTS) -w -local $(PROJECT_MODULE) $(GO_FILES)
57 |
58 | lint-commit: tools
59 | @echo "=== $(PROJECT_NAME) === [ lint-commit ]: Checking that commit messages are properly formatted ($(COMMIT_LINT_CMD))..."
60 | @$(COMMIT_LINT_CMD) --since=$(COMMIT_LINT_START) --subject-minlen=10 --subject-maxlen=120 --subject-regex=$(COMMIT_LINT_REGEX)
61 |
62 | golangci: tools
63 | @echo "=== $(PROJECT_NAME) === [ golangci-lint ]: Linting using $(GOLINTER) ($(COMMIT_LINT_CMD))..."
64 | @$(GOLINTER) run
65 |
66 | outdated: tools
67 | @echo "=== $(PROJECT_NAME) === [ outdated ]: Finding outdated deps with $(GO_MOD_OUTDATED)..."
68 | @$(GO) list -u -m -json all | $(GO_MOD_OUTDATED) -direct -update
69 |
70 | .PHONY: lint spell-check spell-check-fix gofmt gofmt-fix lint-fix lint-commit outdated goimports
71 |
--------------------------------------------------------------------------------
/build/package/Dockerfile:
--------------------------------------------------------------------------------
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:nonroot
4 | WORKDIR /
5 | USER nonroot:nonroot
6 |
7 | # Add the binary
8 | COPY manager /manager
9 |
10 | ENTRYPOINT ["/manager"]
11 |
--------------------------------------------------------------------------------
/build/release.mk:
--------------------------------------------------------------------------------
1 | RELEASE_SCRIPT ?= ./scripts/release.sh
2 |
3 | GOTOOLS += github.com/goreleaser/goreleaser
4 |
5 | REL_CMD ?= goreleaser
6 | DIST_DIR ?= ./dist
7 |
8 | # Example usage: make release version=0.11.0
9 | release: build
10 | @echo "=== $(PROJECT_NAME) === [ release ]: Generating release."
11 | $(RELEASE_SCRIPT) $(version)
12 |
13 | release-clean:
14 | @echo "=== $(PROJECT_NAME) === [ release-clean ]: distribution files..."
15 | @rm -rfv $(DIST_DIR) $(SRCDIR)/tmp
16 |
17 | release-publish: clean tools docker-login release-notes
18 | @echo "=== $(PROJECT_NAME) === [ release-publish ]: Publishing release via $(REL_CMD)"
19 | $(REL_CMD) --release-notes=$(SRCDIR)/tmp/$(RELEASE_NOTES_FILE)
20 |
21 | # Local Snapshot
22 | snapshot: release-clean
23 | @echo "=== $(PROJECT_NAME) === [ snapshot ]: Creating release via $(REL_CMD)"
24 | @echo "=== $(PROJECT_NAME) === [ snapshot ]: THIS WILL NOT BE PUBLISHED!"
25 | $(REL_CMD) --skip-publish --snapshot
26 |
27 |
28 | .PHONY: release release-clean release-homebrew release-publish snapshot
29 |
--------------------------------------------------------------------------------
/build/test.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for Testing
3 | #
4 |
5 | GO ?= go
6 | GOLINTER ?= golangci-lint
7 | MISSPELL ?= misspell
8 | GOFMT ?= gofmt
9 |
10 | COVERAGE_DIR ?= ./coverage/
11 | COVERMODE ?= atomic
12 | SRCDIR ?= .
13 | GO_PKGS ?= $(shell $(GO) list ./... | grep -v -e "/vendor/" -e "/example")
14 | FILES ?= $(shell find $(SRCDIR) -type f | grep -v -e '.git/' -e '/vendor/')
15 |
16 | PROJECT_MODULE ?= $(shell $(GO) list -m)
17 |
18 | LDFLAGS_UNIT ?= '-X $(PROJECT_MODULE)/internal/version.GitTag=$(PROJECT_VER_TAGGED)'
19 |
20 | GOTOOLS += github.com/stretchr/testify/assert
21 |
22 | test: test-only
23 | test-only: test-unit test-integration
24 |
25 | test-unit:
26 | @echo "=== $(PROJECT_NAME) === [ test-unit ]: running unit tests..."
27 | @mkdir -p $(COVERAGE_DIR)
28 | @$(GO) test -v -ldflags=$(LDFLAGS_UNIT) -parallel 4 -tags unit -covermode=$(COVERMODE) -coverprofile $(COVERAGE_DIR)/unit.tmp $(GO_PKGS)
29 |
30 | test-integration:
31 | @echo "=== $(PROJECT_NAME) === [ test-integration ]: running integration tests..."
32 | @mkdir -p $(COVERAGE_DIR)
33 | @$(GO) test -v -parallel 4 -timeout 30m -tags integration -covermode=$(COVERMODE) -coverprofile $(COVERAGE_DIR)/integration.tmp $(GO_PKGS)
34 |
35 |
36 | #
37 | # Coverage
38 | #
39 | cover-clean:
40 | @echo "=== $(PROJECT_NAME) === [ cover-clean ]: removing coverage files..."
41 | @rm -rfv $(COVERAGE_DIR)/*
42 |
43 | cover-report:
44 | @echo "=== $(PROJECT_NAME) === [ cover-report ]: generating coverage results..."
45 | @mkdir -p $(COVERAGE_DIR)
46 | @echo 'mode: $(COVERMODE)' > $(COVERAGE_DIR)/coverage.out
47 | @cat $(COVERAGE_DIR)/*.tmp | grep -v 'mode: $(COVERMODE)' >> $(COVERAGE_DIR)/coverage.out || true
48 | @$(GO) tool cover -html=$(COVERAGE_DIR)/coverage.out -o $(COVERAGE_DIR)/coverage.html
49 | @echo "=== $(PROJECT_NAME) === [ cover-report ]: $(COVERAGE_DIR)coverage.html"
50 |
51 | cover-view: cover-report
52 | @$(GO) tool cover -html=$(COVERAGE_DIR)/coverage.out
53 |
54 | .PHONY: test test-only test-unit test-integration cover-report cover-view
55 |
--------------------------------------------------------------------------------
/build/tools.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for installing deps
3 | #
4 |
5 | GO ?= go
6 |
7 | # These should be mirrored in /tools.go to keep versions consistent
8 | GOTOOLS += github.com/client9/misspell/cmd/misspell
9 |
10 | tools: check-version
11 | @echo "=== $(PROJECT_NAME) === [ tools ]: Installing tools required by the project..."
12 | @$(GO) install $(GOTOOLS)
13 |
14 | tools-update: check-version
15 | @echo "=== $(PROJECT_NAME) === [ tools-update ]: Updating tools required by the project..."
16 | @$(GO) get -u $(GOTOOLS)
17 |
18 |
19 | .PHONY: tools tools-update
20 |
--------------------------------------------------------------------------------
/build/util.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile fragment for utility items
3 | #
4 |
5 | NATIVEOS ?= $(shell go version | awk -F '[ /]' '{print $$4}')
6 | NATIVEARCH ?= $(shell go version | awk -F '[ /]' '{print $$5}')
7 |
8 | GIT_HOOKS_PATH ?= .githooks
9 |
10 | git-hooks:
11 | @echo "=== $(PROJECT_NAME) === [ git-hooks ]: Configuring git hooks..."
12 | @git config core.hooksPath $(GIT_HOOKS_PATH)
13 |
14 | check-version:
15 | ifdef GOOS
16 | ifneq "$(GOOS)" "$(NATIVEOS)"
17 | $(error GOOS is not $(NATIVEOS). Cross-compiling is only allowed for 'clean', 'deps-only' and 'compile-only' targets)
18 | endif
19 | else
20 | GOOS = ${NATIVEOS}
21 | endif
22 | ifdef GOARCH
23 | ifneq "$(GOARCH)" "$(NATIVEARCH)"
24 | $(error GOARCH variable is not $(NATIVEARCH). Cross-compiling is only allowed for 'clean', 'deps-only' and 'compile-only' targets)
25 | endif
26 | else
27 | GOARCH = ${NATIVEARCH}
28 | endif
29 |
30 | .PHONY: check-version
31 |
--------------------------------------------------------------------------------
/cla.md:
--------------------------------------------------------------------------------
1 | # NEW RELIC, INC.
2 | ## INDIVIDUAL CONTRIBUTOR LICENSE AGREEMENT
3 | Thank you for your interest in contributing to the open source projects of New Relic, Inc. (“New Relic”). In order to clarify the intellectual property license granted with Contributions from any person or entity, New Relic must have a Contributor License Agreement ("Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. This Agreement is for your protection as a Contributor as well as the protection of New Relic; it does not change your rights to use your own Contributions for any other purpose.
4 |
5 | You accept and agree to the following terms and conditions for Your present and future Contributions submitted to New Relic. Except for the licenses granted herein to New Relic and recipients of software distributed by New Relic, You reserve all right, title, and interest in and to Your Contributions.
6 |
7 | ## Definitions.
8 | 1. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with New Relic. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
9 | 2. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to New Relic for inclusion in, or documentation of, any of the products managed or maintained by New Relic (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to New Relic or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, New Relic for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
10 | 3. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
11 | 4. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
12 | 5. You represent that You are legally entitled to grant the above licenses. If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions to New Relic, or that Your employer has executed a separate Agreement with New Relic.
13 | 6. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are personally aware and which are associated with any part of Your Contributions.
14 | 7. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
15 | 8. Should You wish to submit work that is not Your original creation, You may submit it to New Relic separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
16 | 9. You agree to notify New Relic of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect.
--------------------------------------------------------------------------------
/config/certmanager/certificate.yaml:
--------------------------------------------------------------------------------
1 | # The following manifests contain a self-signed issuer CR and a certificate CR.
2 | # More document can be found at https://docs.cert-manager.io
3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for breaking changes
4 | apiVersion: cert-manager.io/v1alpha2
5 | kind: Issuer
6 | metadata:
7 | name: selfsigned-issuer
8 | namespace: system
9 | spec:
10 | selfSigned: {}
11 | ---
12 | apiVersion: cert-manager.io/v1alpha2
13 | kind: Certificate
14 | metadata:
15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
16 | namespace: system
17 | spec:
18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize
19 | dnsNames:
20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc
21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local
22 | issuerRef:
23 | kind: Issuer
24 | name: selfsigned-issuer
25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
26 |
--------------------------------------------------------------------------------
/config/certmanager/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - certificate.yaml
3 |
4 | configurations:
5 | - kustomizeconfig.yaml
6 |
--------------------------------------------------------------------------------
/config/certmanager/kustomizeconfig.yaml:
--------------------------------------------------------------------------------
1 | # This configuration is for teaching kustomize how to update name ref and var substitution
2 | nameReference:
3 | - kind: Issuer
4 | group: cert-manager.io
5 | fieldSpecs:
6 | - kind: Certificate
7 | group: cert-manager.io
8 | path: spec/issuerRef/name
9 |
10 | varReference:
11 | - kind: Certificate
12 | group: cert-manager.io
13 | path: spec/commonName
14 | - kind: Certificate
15 | group: cert-manager.io
16 | path: spec/dnsNames
17 |
--------------------------------------------------------------------------------
/config/crd/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # This kustomization.yaml is not intended to be run by itself,
2 | # since it depends on service name and namespace that are out of this kustomize package.
3 | # It should be run by config/default
4 | resources:
5 | - bases/nr.k8s.newrelic.com_nrqlalertconditions.yaml
6 | - bases/nr.k8s.newrelic.com_policies.yaml
7 | - bases/nr.k8s.newrelic.com_apmalertconditions.yaml
8 | - bases/nr.k8s.newrelic.com_alertschannels.yaml
9 | - bases/nr.k8s.newrelic.com_alertsnrqlconditions.yaml
10 | - bases/nr.k8s.newrelic.com_alertspolicies.yaml
11 | - bases/nr.k8s.newrelic.com_alertsapmconditions.yaml
12 | # +kubebuilder:scaffold:crdkustomizeresource
13 |
14 | patchesStrategicMerge:
15 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
16 | # patches here are for enabling the conversion webhook for each CRD
17 | #- patches/webhook_in_nrqlalertconditions.yaml
18 | #- patches/webhook_in_policies.yaml
19 | # +kubebuilder:scaffold:crdkustomizewebhookpatch
20 |
21 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
22 | # patches here are for enabling the CA injection for each CRD
23 | #- patches/cainjection_in_nrqlalertconditions.yaml
24 | #- patches/cainjection_in_policies.yaml
25 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch
26 |
27 | # the following config is for teaching kustomize how to do kustomization for CRDs.
28 | configurations:
29 | - kustomizeconfig.yaml
30 |
--------------------------------------------------------------------------------
/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_alertsapmconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: alertsapmconditions.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_alertschannels.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: alertschannels.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_alertsnrqlconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: alertsnrqlconditions.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_alertspolicies.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: alertspolicies.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_apmalertconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: apmalertconditions.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_newrelicpolicies.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: policies.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_nrqlalertconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
8 | name: nrqlalertconditions.nr.k8s.newrelic.com
9 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_alertsapmconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: alertsapmconditions.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_alertschannels.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: alertschannels.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_alertsnrqlconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: alertsnrqlconditions.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_alertspolicies.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: alertspolicies.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_apmalertconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: apmalertconditions.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_newrelicpolicies.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: policies.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_nrqlalertconditions.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables conversion webhook for CRD
2 | # CRD conversion requires k8s 1.13 or later.
3 | apiVersion: apiextensions.k8s.io/v1beta1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | name: nrqlalertconditions.nr.k8s.newrelic.com
7 | spec:
8 | conversion:
9 | strategy: Webhook
10 | webhookClientConfig:
11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
13 | caBundle: Cg==
14 | service:
15 | namespace: system
16 | name: webhook-service
17 | path: /convert
18 |
--------------------------------------------------------------------------------
/config/default/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # Adds namespace to all resources.
2 | namespace: newrelic-kubernetes-operator-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: newrelic-kubernetes-operator-
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 all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
22 | - ../certmanager
23 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
24 | #- ../prometheus
25 |
26 | patchesStrategicMerge:
27 | # Protect the /metrics endpoint by putting it behind auth.
28 | # Only one of manager_auth_proxy_patch.yaml and
29 | # manager_prometheus_metrics_patch.yaml should be enabled.
30 | - manager_auth_proxy_patch.yaml
31 | # If you want your controller-manager to expose the /metrics
32 | # endpoint w/o any authn/z, uncomment the following line and
33 | # comment manager_auth_proxy_patch.yaml.
34 | # Only one of manager_auth_proxy_patch.yaml and
35 | # manager_prometheus_metrics_patch.yaml should be enabled.
36 | #- manager_prometheus_metrics_patch.yaml
37 |
38 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
39 | - manager_webhook_patch.yaml
40 |
41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
43 | # 'CERTMANAGER' needs to be enabled to use ca injection
44 | - webhookcainjection_patch.yaml
45 |
46 | # the following config is for teaching kustomize how to do var substitution
47 | vars:
48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
49 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
50 | objref:
51 | kind: Certificate
52 | group: cert-manager.io
53 | version: v1alpha2
54 | name: serving-cert # this name should match the one in certificate.yaml
55 | fieldref:
56 | fieldpath: metadata.namespace
57 | - name: CERTIFICATE_NAME
58 | objref:
59 | kind: Certificate
60 | group: cert-manager.io
61 | version: v1alpha2
62 | name: serving-cert # this name should match the one in certificate.yaml
63 | - name: SERVICE_NAMESPACE # namespace of the service
64 | objref:
65 | kind: Service
66 | version: v1
67 | name: webhook-service
68 | fieldref:
69 | fieldpath: metadata.namespace
70 | - name: SERVICE_NAME
71 | objref:
72 | kind: Service
73 | version: v1
74 | name: webhook-service
75 |
--------------------------------------------------------------------------------
/config/default/manager_auth_proxy_patch.yaml:
--------------------------------------------------------------------------------
1 | # This patch inject a sidecar container which is a HTTP proxy for the controller manager,
2 | # it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
3 | apiVersion: apps/v1
4 | kind: Deployment
5 | metadata:
6 | name: controller-manager
7 | namespace: system
8 | spec:
9 | template:
10 | spec:
11 | containers:
12 | - name: kube-rbac-proxy
13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1
14 | args:
15 | - "--secure-listen-address=0.0.0.0:8443"
16 | - "--upstream=http://127.0.0.1:8080/"
17 | - "--logtostderr=true"
18 | - "--v=10"
19 | ports:
20 | - containerPort: 8443
21 | name: https
22 | - name: manager
23 | args:
24 | - "--metrics-addr=127.0.0.1:8080"
25 | - "--enable-leader-election"
26 |
--------------------------------------------------------------------------------
/config/default/manager_webhook_patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: controller-manager
5 | namespace: system
6 | spec:
7 | template:
8 | spec:
9 | containers:
10 | - name: manager
11 | ports:
12 | - containerPort: 9443
13 | name: webhook-server
14 | protocol: TCP
15 | volumeMounts:
16 | - mountPath: /tmp/k8s-webhook-server/serving-certs
17 | name: cert
18 | readOnly: true
19 | volumes:
20 | - name: cert
21 | secret:
22 | defaultMode: 420
23 | secretName: webhook-server-cert
24 |
--------------------------------------------------------------------------------
/config/default/webhookcainjection_patch.yaml:
--------------------------------------------------------------------------------
1 | # This patch add annotation to admission webhook config and
2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
3 | apiVersion: admissionregistration.k8s.io/v1beta1
4 | kind: MutatingWebhookConfiguration
5 | metadata:
6 | name: mutating-webhook-configuration
7 | annotations:
8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
9 | ---
10 | apiVersion: admissionregistration.k8s.io/v1beta1
11 | kind: ValidatingWebhookConfiguration
12 | metadata:
13 | name: validating-webhook-configuration
14 | annotations:
15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
16 |
--------------------------------------------------------------------------------
/config/development/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../default
5 | images:
6 | - name: newrelic/kubernetes-operator
7 | newTag: dev
8 |
9 |
--------------------------------------------------------------------------------
/config/manager/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - manager.yaml
3 | apiVersion: kustomize.config.k8s.io/v1beta1
4 | kind: Kustomization
5 | images:
6 | - name: controller
7 | newName: newrelic/kubernetes-operator
8 | newTag: v0.0.8
9 |
10 | # This enables the New Relic agent for the New Relic Operator
11 | patchesStrategicMerge:
12 | - new_relic_agent_patch.yaml
13 |
--------------------------------------------------------------------------------
/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 | - /manager
28 | args:
29 | - --enable-leader-election
30 | image: controller:latest
31 | name: manager
32 | resources:
33 | limits:
34 | cpu: 100m
35 | memory: 100Mi
36 | requests:
37 | cpu: 100m
38 | memory: 50Mi
39 | terminationGracePeriodSeconds: 10
40 |
--------------------------------------------------------------------------------
/config/manager/new_relic_agent_patch.yaml:
--------------------------------------------------------------------------------
1 | # This patch injects a secret into the container environment for the controller manager
2 | # to enable New Relic agent to send events about the operator behavior itself.
3 |
4 | ---
5 | apiVersion: apps/v1
6 | kind: Deployment
7 | metadata:
8 | name: controller-manager
9 | namespace: system
10 | spec:
11 | selector:
12 | matchLabels:
13 | control-plane: controller-manager
14 | template:
15 | spec:
16 | containers:
17 | - name: manager
18 | env:
19 | - name: NEW_RELIC_LICENSE_KEY
20 | valueFrom:
21 | secretKeyRef:
22 | name: nr-agent
23 | key: license-key
24 | optional: true
25 | - name: NEW_RELIC_APP_NAME
26 | valueFrom:
27 | configMapKeyRef:
28 | name: nr-agent
29 | key: app-name
30 | optional: true
31 | - name: NEW_RELIC_HOST
32 | valueFrom:
33 | configMapKeyRef:
34 | name: nr-agent
35 | key: host
36 | optional: true
37 | - name: NEW_RELIC_LOG
38 | value: stdout
39 | - name: NEW_RELIC_LOG_LEVEL
40 | valueFrom:
41 | configMapKeyRef:
42 | name: nr-agent
43 | key: log-level
44 | optional: true
45 | - name: NEW_RELIC_DISTRIBUTED_TRACING_ENABLED
46 | value: "true"
47 |
--------------------------------------------------------------------------------
/config/prometheus/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - monitor.yaml
3 |
--------------------------------------------------------------------------------
/config/prometheus/monitor.yaml:
--------------------------------------------------------------------------------
1 |
2 | # Prometheus Monitor Service (Metrics)
3 | apiVersion: monitoring.coreos.com/v1
4 | kind: ServiceMonitor
5 | metadata:
6 | labels:
7 | control-plane: controller-manager
8 | name: controller-manager-metrics-monitor
9 | namespace: system
10 | spec:
11 | endpoints:
12 | - path: /metrics
13 | port: https
14 | selector:
15 | control-plane: controller-manager
16 |
--------------------------------------------------------------------------------
/config/rbac/alertsapmcondition_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit alertsapmconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertsapmcondition-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertsapmconditions
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - alertsapmconditions/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/rbac/alertsapmcondition_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view alertsapmconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertsapmcondition-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertsapmconditions
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - alertsapmconditions/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/alertschannel_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit alertschannels.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertschannel-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertschannels
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - alertschannels/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/rbac/alertschannel_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view alertschannels.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertschannel-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertschannels
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - alertschannels/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/alertsnrqlcondition_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do edit alertsnrqlconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertsnrqlcondition-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertsnrqlconditions
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - alertsnrqlconditions/status
23 | verbs:
24 | - get
25 | - patch
26 | - update
27 |
--------------------------------------------------------------------------------
/config/rbac/alertsnrqlcondition_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do viewer alertsnrqlconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertsnrqlcondition-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertsnrqlconditions
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - alertsnrqlconditions/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/alertspolicy_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do edit alertspolicies.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertspolicy-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertspolicies
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - alertspolicies/status
23 | verbs:
24 | - get
25 | - patch
26 | - update
27 |
--------------------------------------------------------------------------------
/config/rbac/alertspolicy_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do viewer alertspolicies.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: alertspolicy-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - alertspolicies
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - alertspolicies/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/apmalertcondition_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit apmalertconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: apmalertcondition-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - apmalertconditions
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - apmalertconditions/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/rbac/apmalertcondition_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view apmalertconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: apmalertcondition-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - apmalertconditions
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - apmalertconditions/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: proxy-role
5 | rules:
6 | - apiGroups: ["authentication.k8s.io"]
7 | resources:
8 | - tokenreviews
9 | verbs: ["create"]
10 | - apiGroups: ["authorization.k8s.io"]
11 | resources:
12 | - subjectaccessreviews
13 | verbs: ["create"]
14 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_role_binding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: proxy-rolebinding
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: proxy-role
9 | subjects:
10 | - kind: ServiceAccount
11 | name: default
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | control-plane: controller-manager
6 | name: controller-manager-metrics-service
7 | namespace: system
8 | spec:
9 | ports:
10 | - name: https
11 | port: 8443
12 | targetPort: https
13 | selector:
14 | control-plane: controller-manager
15 |
--------------------------------------------------------------------------------
/config/rbac/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 | - secrets_role.yaml
13 | - secrets_role_binding.yaml
14 |
--------------------------------------------------------------------------------
/config/rbac/leader_election_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do leader election.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: Role
4 | metadata:
5 | name: leader-election-role
6 | rules:
7 | - apiGroups:
8 | - ""
9 | resources:
10 | - configmaps
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - create
16 | - update
17 | - patch
18 | - delete
19 | - apiGroups:
20 | - ""
21 | resources:
22 | - configmaps/status
23 | verbs:
24 | - get
25 | - update
26 | - patch
27 | - apiGroups:
28 | - ""
29 | resources:
30 | - events
31 | verbs:
32 | - create
33 |
--------------------------------------------------------------------------------
/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/newrelicpolicy_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do edit policies.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: policy-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - policies
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - policies/status
23 | verbs:
24 | - get
25 | - patch
26 | - update
27 |
--------------------------------------------------------------------------------
/config/rbac/newrelicpolicy_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do viewer policies.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: policy-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - policies
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - policies/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/nrqlalertcondition_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do edit nrqlalertconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: nrqlalertcondition-editor-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - nrqlalertconditions
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - nr.k8s.newrelic.com
21 | resources:
22 | - nrqlalertconditions/status
23 | verbs:
24 | - get
25 | - patch
26 | - update
27 |
--------------------------------------------------------------------------------
/config/rbac/nrqlalertcondition_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do viewer nrqlalertconditions.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: nrqlalertcondition-viewer-role
6 | rules:
7 | - apiGroups:
8 | - nr.k8s.newrelic.com
9 | resources:
10 | - nrqlalertconditions
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - nr.k8s.newrelic.com
17 | resources:
18 | - nrqlalertconditions/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/rbac/role.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: ClusterRole
5 | metadata:
6 | creationTimestamp: null
7 | name: manager-role
8 | rules:
9 | - apiGroups:
10 | - nr.k8s.newrelic.com
11 | resources:
12 | - alertsapmconditions
13 | verbs:
14 | - create
15 | - delete
16 | - get
17 | - list
18 | - patch
19 | - update
20 | - watch
21 | - apiGroups:
22 | - nr.k8s.newrelic.com
23 | resources:
24 | - alertsapmconditions/status
25 | verbs:
26 | - get
27 | - patch
28 | - update
29 | - apiGroups:
30 | - nr.k8s.newrelic.com
31 | resources:
32 | - alertschannels
33 | verbs:
34 | - create
35 | - delete
36 | - get
37 | - list
38 | - patch
39 | - update
40 | - watch
41 | - apiGroups:
42 | - nr.k8s.newrelic.com
43 | resources:
44 | - alertschannels/status
45 | verbs:
46 | - get
47 | - patch
48 | - update
49 | - apiGroups:
50 | - nr.k8s.newrelic.com
51 | resources:
52 | - alertsnrqlconditions
53 | verbs:
54 | - create
55 | - delete
56 | - get
57 | - list
58 | - patch
59 | - update
60 | - watch
61 | - apiGroups:
62 | - nr.k8s.newrelic.com
63 | resources:
64 | - alertsnrqlconditions/status
65 | verbs:
66 | - get
67 | - patch
68 | - update
69 | - apiGroups:
70 | - nr.k8s.newrelic.com
71 | resources:
72 | - alertspolicies
73 | verbs:
74 | - create
75 | - delete
76 | - get
77 | - list
78 | - patch
79 | - update
80 | - watch
81 | - apiGroups:
82 | - nr.k8s.newrelic.com
83 | resources:
84 | - alertspolicies/status
85 | verbs:
86 | - get
87 | - patch
88 | - update
89 | - apiGroups:
90 | - nr.k8s.newrelic.com
91 | resources:
92 | - apmalertconditions
93 | verbs:
94 | - create
95 | - delete
96 | - get
97 | - list
98 | - patch
99 | - update
100 | - watch
101 | - apiGroups:
102 | - nr.k8s.newrelic.com
103 | resources:
104 | - apmalertconditions/status
105 | verbs:
106 | - get
107 | - patch
108 | - update
109 | - apiGroups:
110 | - nr.k8s.newrelic.com
111 | resources:
112 | - nrqlalertconditions
113 | verbs:
114 | - create
115 | - delete
116 | - get
117 | - list
118 | - patch
119 | - update
120 | - watch
121 | - apiGroups:
122 | - nr.k8s.newrelic.com
123 | resources:
124 | - nrqlalertconditions/status
125 | verbs:
126 | - get
127 | - patch
128 | - update
129 | - apiGroups:
130 | - nr.k8s.newrelic.com
131 | resources:
132 | - policies
133 | verbs:
134 | - create
135 | - delete
136 | - get
137 | - list
138 | - patch
139 | - update
140 | - watch
141 | - apiGroups:
142 | - nr.k8s.newrelic.com
143 | resources:
144 | - policies/status
145 | verbs:
146 | - get
147 | - patch
148 | - update
149 |
--------------------------------------------------------------------------------
/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/rbac/secrets_role.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: ClusterRole
5 | metadata:
6 | name: secret-reader
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - secrets
12 | verbs:
13 | - get
14 | - watch
15 | - list
16 |
--------------------------------------------------------------------------------
/config/rbac/secrets_role_binding.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: ClusterRoleBinding
5 | metadata:
6 | name: secrets-rolebinding
7 | roleRef:
8 | apiGroup: rbac.authorization.k8s.io
9 | kind: ClusterRole
10 | name: secret-reader
11 | subjects:
12 | - kind: ServiceAccount
13 | name: default
14 | namespace: newrelic-kubernetes-operator-system
15 |
16 |
--------------------------------------------------------------------------------
/config/samples/nr-alerts_v1_nrqlalertcondition.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: NrqlAlertCondition
3 | metadata:
4 | name: nrqlalertcondition-sample
5 | spec:
6 | # Add fields here
7 | foo: bar
8 |
--------------------------------------------------------------------------------
/config/samples/nr-alerts_v1beta1_nrqlalertcondition.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1beta1
2 | kind: NrqlAlertCondition
3 | metadata:
4 | name: nrqlalertcondition-sample
5 | spec:
6 | # Add fields here
7 | foo: bar
8 |
--------------------------------------------------------------------------------
/config/samples/nr_v1_apmalertcondition.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: ApmAlertCondition
3 | metadata:
4 | name: apmalertcondition-sample
5 | spec:
6 | # Add fields here
7 | foo: bar
8 |
--------------------------------------------------------------------------------
/config/samples/nr_v1_newrelicpolicy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: Policy
3 | metadata:
4 | name: policy-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/service.yaml:
--------------------------------------------------------------------------------
1 |
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: webhook-service
6 | namespace: system
7 | spec:
8 | ports:
9 | - port: 443
10 | targetPort: 9443
11 | selector:
12 | control-plane: controller-manager
13 |
--------------------------------------------------------------------------------
/controllers/alerts_policy_controller_integration_api_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package controllers
4 |
5 | import (
6 | "testing"
7 |
8 | "k8s.io/apimachinery/pkg/types"
9 | ctrl "sigs.k8s.io/controller-runtime"
10 | logf "sigs.k8s.io/controller-runtime/pkg/log"
11 |
12 | "github.com/stretchr/testify/require"
13 |
14 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
15 | "github.com/newrelic/newrelic-kubernetes-operator/internal/testutil"
16 | )
17 |
18 | func TestIntegrationAlertsPolicyController(t *testing.T) {
19 | t.Parallel()
20 |
21 | // Must come before calling reconciler.Reconcile()
22 | k8sClient := testutil.AlertsPolicyTestSetup(t)
23 |
24 | namespacedName := types.NamespacedName{
25 | Namespace: "default",
26 | Name: "test-policy",
27 | }
28 |
29 | request := ctrl.Request{
30 | NamespacedName: namespacedName,
31 | }
32 |
33 | reconciler := &AlertsPolicyReconciler{
34 | Client: k8sClient,
35 | Log: logf.Log,
36 | AlertClientFunc: interfaces.InitializeAlertsClient,
37 | }
38 |
39 | // call reconcile
40 | _, err := reconciler.Reconcile(request)
41 | require.NoError(t, err)
42 | }
43 |
--------------------------------------------------------------------------------
/controllers/policy_controller_integration_api_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package controllers
4 |
5 | import (
6 | "context"
7 | "os"
8 | "path/filepath"
9 | "testing"
10 |
11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 | "k8s.io/apimachinery/pkg/runtime"
13 | "k8s.io/apimachinery/pkg/types"
14 | "k8s.io/client-go/kubernetes/scheme"
15 | ctrl "sigs.k8s.io/controller-runtime"
16 | "sigs.k8s.io/controller-runtime/pkg/client"
17 | "sigs.k8s.io/controller-runtime/pkg/envtest"
18 | logf "sigs.k8s.io/controller-runtime/pkg/log"
19 |
20 | "github.com/newrelic/newrelic-client-go/newrelic"
21 | "github.com/stretchr/testify/require"
22 |
23 | nralertsv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
24 | nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
25 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
26 | )
27 |
28 | func newIntegrationTestClient(t *testing.T) newrelic.NewRelic {
29 | envAPIKey := os.Getenv("NEW_RELIC_API_KEY")
30 | envRegion := os.Getenv("NEW_RELIC_REGION")
31 |
32 | if envAPIKey == "" {
33 | t.Skipf("acceptance testing requires NEW_RELIC_API_KEY")
34 | }
35 |
36 | if envRegion == "" {
37 | envRegion = "us"
38 | }
39 |
40 | client, _ := interfaces.NewClient(envAPIKey, envRegion)
41 |
42 | return *client
43 | }
44 |
45 | func testSetup(t *testing.T, object runtime.Object) client.Client {
46 | ctx := context.Background()
47 | testEnv := &envtest.Environment{
48 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
49 | }
50 |
51 | // var err error
52 | cfg, err := testEnv.Start()
53 | require.NoError(t, err)
54 | require.NotNil(t, cfg)
55 |
56 | err = nralertsv1.AddToScheme(scheme.Scheme)
57 | require.NoError(t, err)
58 |
59 | // +kubebuilder:scaffold:scheme
60 |
61 | k8sClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme})
62 | require.NoError(t, err)
63 | require.NotNil(t, k8sClient)
64 |
65 | err = k8sClient.Create(ctx, object)
66 | require.NoError(t, err)
67 |
68 | return k8sClient
69 | }
70 |
71 | func TestIntegrationPolicyController(t *testing.T) {
72 | t.Parallel()
73 |
74 | envAPIKey := os.Getenv("NEW_RELIC_API_KEY")
75 | envRegion := os.Getenv("NEW_RELIC_REGION")
76 |
77 | if envAPIKey == "" {
78 | t.Skipf("acceptance testing requires NEW_RELIC_API_KEY")
79 | }
80 |
81 | if envRegion == "" {
82 | envRegion = "us"
83 | }
84 |
85 | conditionSpec := &nrv1.ConditionSpec{
86 | GenericConditionSpec: nrv1.GenericConditionSpec{
87 | Terms: []nrv1.AlertConditionTerm{
88 | {
89 | Duration: "30",
90 | Operator: "above",
91 | Priority: "critical",
92 | Threshold: "5",
93 | TimeFunction: "all",
94 | },
95 | },
96 | Type: "NRQL",
97 | Name: "NRQL Condition",
98 | RunbookURL: "http://test.com/runbook",
99 | Enabled: true,
100 | },
101 | NrqlSpecificSpec: nrv1.NrqlSpecificSpec{
102 | Nrql: nrv1.NrqlQuery{
103 | Query: "SELECT 1 FROM MyEvents",
104 | SinceValue: "5",
105 | },
106 | ValueFunction: "max",
107 | ViolationCloseTimer: 60,
108 | ExpectedGroups: 2,
109 | IgnoreOverlap: true,
110 | },
111 | APMSpecificSpec: nrv1.APMSpecificSpec{},
112 | }
113 |
114 | policy := &nrv1.Policy{
115 | ObjectMeta: metav1.ObjectMeta{
116 | Name: "test-policy",
117 | Namespace: "default",
118 | },
119 | Spec: nrv1.PolicySpec{
120 | Name: "test policy",
121 | APIKey: envAPIKey,
122 | IncidentPreference: "PER_POLICY",
123 | Region: envRegion,
124 | Conditions: []nrv1.PolicyCondition{
125 | {
126 | Spec: *conditionSpec,
127 | },
128 | },
129 | },
130 | Status: nrv1.PolicyStatus{
131 | AppliedSpec: &nrv1.PolicySpec{},
132 | PolicyID: 0,
133 | },
134 | }
135 |
136 | // Must come before calling reconciler.Reconcile()
137 | k8sClient := testSetup(t, policy)
138 |
139 | namespacedName := types.NamespacedName{
140 | Namespace: "default",
141 | Name: "test-policy",
142 | }
143 |
144 | request := ctrl.Request{
145 | NamespacedName: namespacedName,
146 | }
147 |
148 | reconciler := &PolicyReconciler{
149 | Client: k8sClient,
150 | Log: logf.Log,
151 | AlertClientFunc: interfaces.InitializeAlertsClient,
152 | }
153 |
154 | // call reconcile
155 | _, err := reconciler.Reconcile(request)
156 | require.NoError(t, err)
157 |
158 | // Deferred teardown
159 | // defer func() {
160 | // _, err := client.DeletePolicy(policy.ID)
161 |
162 | // if err != nil {
163 | // t.Logf("error cleaning up alert policy %d (%s): %s", policy.ID, policy.Name, err)
164 | // }
165 | // }()
166 | }
167 |
168 | func TestIntegrationAlertsChannelController(t *testing.T) {
169 | t.Parallel()
170 |
171 | envAPIKey := os.Getenv("NEW_RELIC_API_KEY")
172 | envRegion := os.Getenv("NEW_RELIC_REGION")
173 |
174 | if envAPIKey == "" {
175 | t.Skipf("acceptance testing requires NEW_RELIC_API_KEY")
176 | }
177 |
178 | if envRegion == "" {
179 | envRegion = "us"
180 | }
181 |
182 | alertsChannel := &nrv1.AlertsChannel{
183 | ObjectMeta: metav1.ObjectMeta{
184 | Name: "myalertschannel",
185 | Namespace: "default",
186 | },
187 | Spec: nrv1.AlertsChannelSpec{
188 | Name: "my alert channel",
189 | APIKey: envAPIKey,
190 | Region: envRegion,
191 | Type: "email",
192 | Configuration: nrv1.AlertsChannelConfiguration{
193 | Recipients: "me@email.com",
194 | },
195 | },
196 |
197 | Status: nrv1.AlertsChannelStatus{
198 | AppliedSpec: &nrv1.AlertsChannelSpec{},
199 | ChannelID: 0,
200 | AppliedPolicyIDs: []int{},
201 | },
202 | }
203 |
204 | // Must come before calling reconciler.Reconcile()
205 | k8sClient := testSetup(t, alertsChannel)
206 |
207 | namespacedName := types.NamespacedName{
208 | Namespace: "default",
209 | Name: "myalertschannel",
210 | }
211 |
212 | request := ctrl.Request{
213 | NamespacedName: namespacedName,
214 | }
215 |
216 | reconciler := &AlertsChannelReconciler{
217 | Client: k8sClient,
218 | Log: logf.Log,
219 | AlertClientFunc: interfaces.InitializeAlertsClient,
220 | }
221 |
222 | // call reconcile
223 | _, err := reconciler.Reconcile(request)
224 | require.NoError(t, err)
225 |
226 | // Deferred teardown
227 | // defer func() {
228 | // _, err := client.DeletePolicy(policy.ID)
229 |
230 | // if err != nil {
231 | // t.Logf("error cleaning up alert policy %d (%s): %s", policy.ID, policy.Name, err)
232 | // }
233 | // }()
234 | }
235 |
--------------------------------------------------------------------------------
/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 | "path/filepath"
20 | "testing"
21 |
22 | "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
23 |
24 | . "github.com/onsi/ginkgo"
25 | . "github.com/onsi/gomega"
26 |
27 | apierrors "k8s.io/apimachinery/pkg/api/errors"
28 | "k8s.io/client-go/kubernetes/scheme"
29 | "k8s.io/client-go/rest"
30 | "sigs.k8s.io/controller-runtime/pkg/client"
31 | "sigs.k8s.io/controller-runtime/pkg/envtest"
32 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
33 | logf "sigs.k8s.io/controller-runtime/pkg/log"
34 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
35 |
36 | nralertsv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
37 | // +kubebuilder:scaffold:imports
38 | )
39 |
40 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to
41 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
42 |
43 | var cfg *rest.Config
44 | var k8sClient client.Client
45 | var testEnv *envtest.Environment
46 | var alertsClient *interfacesfakes.FakeNewRelicAlertsClient
47 |
48 | func TestAPIs(t *testing.T) {
49 | RegisterFailHandler(Fail)
50 |
51 | RunSpecsWithDefaultAndCustomReporters(t,
52 | "Controller Suite",
53 | []Reporter{printer.NewlineReporter{}})
54 | }
55 |
56 | var _ = BeforeSuite(func(done Done) {
57 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
58 |
59 | By("bootstrapping test environment")
60 | testEnv = &envtest.Environment{
61 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
62 | }
63 |
64 | var err error
65 | cfg, err = testEnv.Start()
66 | Expect(err).ToNot(HaveOccurred())
67 | Expect(cfg).ToNot(BeNil())
68 |
69 | err = nralertsv1.AddToScheme(scheme.Scheme)
70 | Expect(err).NotTo(HaveOccurred())
71 |
72 | // +kubebuilder:scaffold:scheme
73 |
74 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
75 | Expect(err).ToNot(HaveOccurred())
76 | Expect(k8sClient).ToNot(BeNil())
77 |
78 | close(done)
79 | }, 60)
80 |
81 | var _ = AfterSuite(func() {
82 | By("tearing down the test environment")
83 | err := testEnv.Stop()
84 | Expect(err).ToNot(HaveOccurred())
85 | })
86 |
87 | func ignoreAlreadyExists(err error) error {
88 | if apierrors.IsAlreadyExists(err) {
89 | return nil
90 | }
91 |
92 | return err
93 | }
94 |
--------------------------------------------------------------------------------
/errors/collector.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import "strings"
4 |
5 | type ErrorCollector []error
6 |
7 | func (c *ErrorCollector) Collect(e error) {
8 | if e != nil {
9 | *c = append(*c, e)
10 | }
11 | }
12 |
13 | func (c *ErrorCollector) Error() (errorString string) {
14 | messages := []string{}
15 | for i := range *c {
16 | messages = append(messages, (*c)[i].Error())
17 | }
18 | return strings.Join(messages, "\n")
19 | }
20 |
--------------------------------------------------------------------------------
/examples/development/personal-api-key-patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: Policy
3 | metadata:
4 | name: my-policy
5 | spec:
6 | api_key: "intentionally blank"
7 |
--------------------------------------------------------------------------------
/examples/development/personalized-policy-name-patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: Policy
3 | metadata:
4 | name: my-policy
5 | spec:
6 | name: k8s created policy
7 |
--------------------------------------------------------------------------------
/examples/development/use-staging-patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: Policy
3 | metadata:
4 | name: my-policy
5 | spec:
6 | region: "Staging"
7 |
--------------------------------------------------------------------------------
/examples/example_alerts_channel_email.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: AlertsChannel
3 | metadata:
4 | name: my-channel1
5 | spec:
6 | api_key:
7 | # api_key_secret:
8 | # name: nr-api-key
9 | # namespace: default
10 | # key_name: api-key
11 | name: "my alert channel"
12 | region: "US"
13 | type: "email"
14 | links:
15 | # Policy links can be by NR PolicyID, NR PolicyName OR K8s AlertPolicy object reference
16 | policy_ids:
17 | - 1
18 | policy_names:
19 | - "k8s created policy"
20 | policy_kubernetes_objects:
21 | - name: "my-policy"
22 | namespace: "default"
23 | configuration:
24 | recipients: "me@email.com"
25 |
--------------------------------------------------------------------------------
/examples/example_alerts_channel_webhook.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: nr.k8s.newrelic.com/v1
2 | kind: AlertsChannel
3 | metadata:
4 | name: my-channel1
5 | spec:
6 | api_key:
7 | # api_key_secret:
8 | # name: nr-api-key
9 | # namespace: default
10 | # key_name: api-key
11 | name: "my alert channel"
12 | region: "US"
13 | type: "webhook"
14 | links:
15 | # Policy links can be by NR PolicyID, NR PolicyName OR K8s AlertPolicy object reference
16 | policy_ids:
17 | - 1
18 | policy_names:
19 | - "k8s created policy"
20 | policy_kubernetes_objects:
21 | - name: "my-policy"
22 | namespace: "default"
23 | configuration:
24 | url: "https://example.com/"
25 | headers:
26 | - name: WEBHOOK_SOURCE
27 | value: newrelic
28 | - name: SECRET_TOKEN
29 | secret: secret
30 | namespace: default
31 | key_name: token
32 | # see https://docs.newrelic.com/docs/alerts-applied-intelligence/new-relic-alerts/alert-notifications/customize-your-webhook-payload
33 | payload:
34 | details: "$EVENT_DETAILS"
35 | current_state: "$EVENT_STATE"
36 |
--------------------------------------------------------------------------------
/examples/example_apm_alert_condition.yaml:
--------------------------------------------------------------------------------
1 | # Note: If using a k8s secret, remove `api_key`, uncomment `api_key_secret`,
2 | # add your API key to examples/example_secret.yaml, and run
3 | # `kubectl apply -f examples/example_secret.yaml`
4 |
5 | apiVersion: nr.k8s.newrelic.com/v1
6 | kind: AlertsAPMCondition
7 | metadata:
8 | name: alert-1029267
9 | spec:
10 | account_id:
11 | api_key:
12 | # api_key_secret:
13 | # name: nr-api-key
14 | # namespace: default
15 | # key_name: api-key
16 | type: "apm_app_metric"
17 | enabled: true
18 | metric: "apdex"
19 | condition_scope: application
20 | entities:
21 | - "215037795"
22 | apm_terms:
23 | - threshold: "0.9"
24 | time_function: "all"
25 | duration: "5"
26 | priority: "critical"
27 | operator: "above"
28 | name: "K8s generated apm alert condition"
29 | # Must reference an existing New Relic alert policy from your account
30 | existing_policy_id: "897188"
31 | region: "US"
32 |
--------------------------------------------------------------------------------
/examples/example_new_relic_agent_config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: nr-agent
5 | namespace: newrelic-kubernetes-operator-system
6 | type: Opaque
7 | stringData:
8 | license-key:
9 |
10 | ---
11 | apiVersion: v1
12 | kind: ConfigMap
13 | metadata:
14 | name: nr-agent
15 | namespace: newrelic-kubernetes-operator-system
16 | data:
17 | # defaults to New Relic Kubernetes Operator
18 | app-name:
19 | # Defaults to collector.newrelic.com
20 | host:
21 | # Defaults to false
22 | log-level: debug
23 |
24 |
--------------------------------------------------------------------------------
/examples/example_nrql_alert_condition.yaml:
--------------------------------------------------------------------------------
1 | # Note: If using a k8s secret, remove `api_key`, uncomment `api_key_secret`,
2 | # add your API key to examples/example_secret.yaml, and run
3 | # `kubectl apply -f examples/example_secret.yaml`
4 |
5 | apiVersion: nr.k8s.newrelic.com/v1
6 | kind: AlertsNrqlCondition
7 | metadata:
8 | name: alert-1029266
9 | spec:
10 | account_id:
11 | api_key:
12 | # api_key_secret:
13 | # name: nr-api-key
14 | # namespace: default
15 | # key_name: api-key
16 | type: "NRQL"
17 | nrql:
18 | query: "SELECT count(*) FROM Transactions"
19 | evaluationOffset: 10
20 | enabled: true
21 | terms:
22 | - threshold: "75.0"
23 | threshold_occurrences: "ALL"
24 | threshold_duration: 60
25 | priority: "CRITICAL"
26 | operator: "ABOVE"
27 | expiration:
28 | expirationDuration: 360
29 | closeViolationsOnExpiration: false
30 | openViolationOnExpiration: true
31 | # signal:
32 | # aggregation_window: 360
33 | # evaluation_offset: 30
34 | # fill_option: NONE
35 | # fill_value: 15
36 | name: "nrql condition (new)"
37 | violationTimeLimit: "ONE_HOUR"
38 | valueFunction: "SINGLE_VALUE"
39 | # Must reference an existing New Relic alert policy from your account
40 | existing_policy_id: "897188"
41 | region: "US"
42 |
--------------------------------------------------------------------------------
/examples/example_policy.yaml:
--------------------------------------------------------------------------------
1 | # Note: If using a k8s secret, remove `api_key`, uncomment `api_key_secret`,
2 | # add your API key to examples/example_secret.yaml, and run
3 | # `kubectl apply -f examples/example_secret.yaml`
4 |
5 | apiVersion: nr.k8s.newrelic.com/v1
6 | kind: AlertsPolicy
7 | metadata:
8 | name: my-policy
9 | spec:
10 | account_id:
11 | api_key:
12 | # api_key_secret:
13 | # name: nr-api-key
14 | # namespace: default
15 | # key_name: api-key
16 | name: k8s created policy
17 | incidentPreference: "PER_POLICY"
18 | region: "US"
19 | conditions:
20 | - spec:
21 | type: "NRQL"
22 | nrql:
23 | query: "SELECT count(*) FROM Transactions"
24 | evaluationOffset: 10
25 | enabled: true
26 | terms:
27 | - threshold: "75.0"
28 | threshold_occurrences: "ALL"
29 | threshold_duration: 60
30 | priority: "CRITICAL"
31 | operator: "ABOVE"
32 | name: "nrql condition"
33 | violationTimeLimit: "ONE_HOUR"
34 | valueFunction: "SINGLE_VALUE"
35 | - spec:
36 | type: "apm_app_metric"
37 | enabled: true
38 | metric: "apdex"
39 | condition_scope: application
40 | entities:
41 | - "215037795"
42 | apm_terms:
43 | - threshold: "0.9"
44 | time_function: "all"
45 | duration: "5"
46 | priority: "critical"
47 | operator: "above"
48 | name: "apm condition"
49 |
--------------------------------------------------------------------------------
/examples/example_policy_apm.yaml:
--------------------------------------------------------------------------------
1 | # Note: If using a k8s secret, remove `api_key`, uncomment `api_key_secret`,
2 | # add your API key to examples/example_secret.yaml, and run
3 | # `kubectl apply -f examples/example_secret.yaml`
4 |
5 | apiVersion: nr.k8s.newrelic.com/v1
6 | kind: AlertsPolicy
7 | metadata:
8 | name: my-apm-policy
9 | spec:
10 | account_id:
11 | api_key:
12 | # api_key_secret:
13 | # name: nr-api-key
14 | # namespace: default
15 | # key_name: api-key
16 | name: k8s created policy (new)
17 | incidentPreference: "PER_POLICY"
18 | region: "US"
19 | conditions:
20 | - spec:
21 | type: "apm_app_metric"
22 | enabled: true
23 | metric: "apdex"
24 | condition_scope: application
25 | entities:
26 | - "215037795"
27 | apm_terms:
28 | - threshold: "0.9"
29 | time_function: "all"
30 | duration: "5"
31 | priority: "critical"
32 | operator: "above"
33 | name: "K8s generated apm alert condition"
34 | - spec:
35 | type: "apm_app_metric"
36 | enabled: true
37 | metric: "apdex"
38 | condition_scope: application
39 | entities:
40 | - "215037795"
41 | apm_terms:
42 | - threshold: "0.9"
43 | time_function: "all"
44 | duration: "30"
45 | priority: "critical"
46 | operator: "above"
47 | name: "K8s generated apm alert condition 2"
--------------------------------------------------------------------------------
/examples/example_secret.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: nr-api-key
5 | namespace: default
6 | type: Opaque
7 | stringData:
8 | api-key:
9 | ---
10 | apiVersion: v1
11 | kind: Secret
12 | metadata:
13 | name: secret
14 | namespace: default
15 | type: Opaque
16 | stringData:
17 | token: super-secret-example-header-token
18 |
--------------------------------------------------------------------------------
/examples/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - example_policy.yaml
5 | patchesStrategicMerge:
6 | - development/personal-api-key-patch.yaml
7 | - development/personalized-policy-name-patch.yaml
8 | - development/use-staging-patch.yaml
9 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/newrelic/newrelic-kubernetes-operator
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/Masterminds/goutils v1.1.1 // indirect
7 | github.com/Masterminds/semver/v3 v3.1.1 // indirect
8 | github.com/davecgh/go-spew v1.1.1
9 | github.com/git-chglog/git-chglog v0.15.0 // indirect
10 | github.com/go-logr/logr v0.1.0
11 | github.com/golang/mock v1.4.3
12 | github.com/goreleaser/goreleaser v0.143.0
13 | github.com/hashicorp/golang-lru v0.5.4 // indirect
14 | github.com/llorllale/go-gitlint v0.0.0-20210608233938-d6303cc52cc5 // indirect
15 | github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3
16 | github.com/newrelic/go-agent/v3 v3.7.0
17 | github.com/newrelic/newrelic-client-go v0.60.0
18 | github.com/onsi/ginkgo v1.13.0
19 | github.com/onsi/gomega v1.10.1
20 | github.com/prometheus/client_golang v1.6.0 // indirect
21 | github.com/prometheus/common v0.10.0 // indirect
22 | github.com/psampaz/go-mod-outdated v0.8.0 // indirect
23 | github.com/stretchr/testify v1.7.0
24 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
25 | golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305
26 | gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect
27 | k8s.io/api v0.18.4
28 | k8s.io/apiextensions-apiserver v0.18.4 // indirect
29 | k8s.io/apimachinery v0.18.4
30 | k8s.io/client-go v0.18.4
31 | k8s.io/utils v0.0.0-20200601170155-a0dff01d8ea5 // indirect
32 | sigs.k8s.io/controller-runtime v0.6.0
33 | sigs.k8s.io/controller-tools v0.3.0
34 | )
35 |
--------------------------------------------------------------------------------
/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 | */
--------------------------------------------------------------------------------
/interfaces/new_relic_alert_client.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/newrelic/newrelic-client-go/newrelic"
7 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
8 | "github.com/newrelic/newrelic-client-go/pkg/config"
9 |
10 | "github.com/newrelic/newrelic-kubernetes-operator/internal/info"
11 | )
12 |
13 | //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . NewRelicAlertsClient
14 | type NewRelicAlertsClient interface {
15 | CreateNrqlCondition(int, alerts.NrqlCondition) (*alerts.NrqlCondition, error)
16 | UpdateNrqlCondition(alerts.NrqlCondition) (*alerts.NrqlCondition, error)
17 | ListNrqlConditions(int) ([]*alerts.NrqlCondition, error)
18 | DeleteNrqlCondition(int) (*alerts.NrqlCondition, error)
19 | ListConditions(int) ([]*alerts.Condition, error)
20 | CreateCondition(policyID int, condition alerts.Condition) (*alerts.Condition, error)
21 | UpdateCondition(condition alerts.Condition) (*alerts.Condition, error)
22 | DeleteCondition(id int) (*alerts.Condition, error)
23 | GetPolicy(id int) (*alerts.Policy, error)
24 | CreatePolicy(alerts.Policy) (*alerts.Policy, error)
25 | UpdatePolicy(alerts.Policy) (*alerts.Policy, error)
26 | DeletePolicy(int) (*alerts.Policy, error)
27 | ListPolicies(*alerts.ListPoliciesParams) ([]alerts.Policy, error)
28 | CreateChannel(channel alerts.Channel) (*alerts.Channel, error)
29 | DeleteChannel(id int) (*alerts.Channel, error)
30 | ListChannels() ([]*alerts.Channel, error)
31 | UpdatePolicyChannels(policyID int, channelIDs []int) (*alerts.PolicyChannels, error)
32 | DeletePolicyChannel(policyID int, ChannelID int) (*alerts.Channel, error)
33 |
34 | // NerdGraph
35 | CreatePolicyMutation(accountID int, policy alerts.AlertsPolicyInput) (*alerts.AlertsPolicy, error)
36 | UpdatePolicyMutation(accountID int, policyID string, policy alerts.AlertsPolicyUpdateInput) (*alerts.AlertsPolicy, error)
37 | DeletePolicyMutation(accountID int, id string) (*alerts.AlertsPolicy, error)
38 | QueryPolicySearch(accountID int, params alerts.AlertsPoliciesSearchCriteriaInput) ([]*alerts.AlertsPolicy, error)
39 | QueryPolicy(accountID int, id string) (*alerts.AlertsPolicy, error)
40 |
41 | CreateNrqlConditionStaticMutation(accountID int, policyID string, nrqlCondition alerts.NrqlConditionInput) (*alerts.NrqlAlertCondition, error)
42 | UpdateNrqlConditionStaticMutation(accountID int, conditionID string, nrqlCondition alerts.NrqlConditionInput) (*alerts.NrqlAlertCondition, error)
43 | CreateNrqlConditionBaselineMutation(accountID int, policyID string, nrqlCondition alerts.NrqlConditionInput) (*alerts.NrqlAlertCondition, error)
44 | UpdateNrqlConditionBaselineMutation(accountID int, conditionID string, nrqlCondition alerts.NrqlConditionInput) (*alerts.NrqlAlertCondition, error)
45 | DeleteConditionMutation(accountID int, conditionID string) (string, error)
46 | SearchNrqlConditionsQuery(accountID int, searchCriteria alerts.NrqlConditionsSearchCriteria) ([]*alerts.NrqlAlertCondition, error)
47 | GetNrqlConditionQuery(accountID int, conditionID string) (*alerts.NrqlAlertCondition, error)
48 | }
49 |
50 | func NewClient(apiKey string, regionValue string) (*newrelic.NewRelic, error) {
51 | cfg := config.New()
52 |
53 | client, err := newrelic.New(
54 | newrelic.ConfigPersonalAPIKey(apiKey),
55 | newrelic.ConfigLogLevel(cfg.LogLevel),
56 | newrelic.ConfigRegion(regionValue),
57 | newrelic.ConfigUserAgent(info.UserAgent()),
58 | newrelic.ConfigServiceName(info.Name),
59 | )
60 |
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | return client, nil
66 | }
67 |
68 | func InitializeAlertsClient(apiKey string, regionName string) (NewRelicAlertsClient, error) {
69 | client, err := NewClient(apiKey, regionName)
70 | if err != nil {
71 | return nil, fmt.Errorf("unable to create New Relic client with error: %s", err)
72 | }
73 |
74 | return &client.Alerts, nil
75 | }
76 |
77 | //PartialAPIKey - Returns a partial API key to ensure we don't log the full API Key
78 | func PartialAPIKey(apiKey string) string {
79 | partialKeyLength := min(10, len(apiKey))
80 | return apiKey[0:partialKeyLength] + "..."
81 | }
82 |
83 | func min(x, y int) int {
84 | if x > y {
85 | return y
86 | }
87 | return x
88 | }
89 |
--------------------------------------------------------------------------------
/internal/info/main.go:
--------------------------------------------------------------------------------
1 | package info
2 |
3 | // Version of this library
4 | var (
5 | Name string = "newrelic-kubernetes-operator"
6 | Version string = "dev"
7 | )
8 |
9 | const RepoURL = "https://github.com/newrelic/newrelic-kubernetes-operator"
10 |
11 | func UserAgent() string {
12 | return Name + "/" + Version + " (" + RepoURL + ")"
13 | }
14 |
--------------------------------------------------------------------------------
/internal/testutil/alerts.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "strconv"
8 | "testing"
9 |
10 | "github.com/newrelic/newrelic-client-go/pkg/alerts"
11 | "github.com/newrelic/newrelic-client-go/pkg/testhelpers"
12 | "github.com/stretchr/testify/assert"
13 | "github.com/stretchr/testify/require"
14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 | "k8s.io/client-go/kubernetes/scheme"
16 | "sigs.k8s.io/controller-runtime/pkg/client"
17 | "sigs.k8s.io/controller-runtime/pkg/envtest"
18 |
19 | nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
20 | )
21 |
22 | func AlertsPolicyTestSetup(t *testing.T) client.Client {
23 | testEnv := &envtest.Environment{
24 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
25 | }
26 |
27 | // var err error
28 | cfg, err := testEnv.Start()
29 | require.NoError(t, err)
30 | require.NotNil(t, cfg)
31 |
32 | err = nrv1.AddToScheme(scheme.Scheme)
33 | require.NoError(t, err)
34 |
35 | // +kubebuilder:scaffold:scheme
36 |
37 | k8sClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme})
38 |
39 | require.NoError(t, err)
40 | require.NotNil(t, k8sClient)
41 |
42 | return k8sClient
43 | }
44 |
45 | func NewTestAlertsPolicy(t *testing.T) *nrv1.AlertsPolicy {
46 | policyName := fmt.Sprintf("test-policy-%s", testhelpers.RandSeq(5))
47 |
48 | accountID, err := strconv.Atoi(os.Getenv("NEW_RELIC_ACCOUNT_ID"))
49 | assert.NoError(t, err)
50 |
51 | return &nrv1.AlertsPolicy{
52 | ObjectMeta: metav1.ObjectMeta{
53 | Name: policyName,
54 | Namespace: "default",
55 | },
56 | Spec: nrv1.AlertsPolicySpec{
57 | APIKey: "112233",
58 | AccountID: accountID,
59 | IncidentPreference: "PER_POLICY",
60 | Name: policyName,
61 | Region: "US",
62 | },
63 | Status: nrv1.AlertsPolicyStatus{
64 | AppliedSpec: &nrv1.AlertsPolicySpec{},
65 | },
66 | }
67 |
68 | }
69 |
70 | func NewTestAlertsNrqlCondition(t *testing.T) *nrv1.AlertsNrqlCondition {
71 | conditionName := fmt.Sprintf("test-condition-%s", testhelpers.RandSeq(5))
72 | conditionSpec := NewTestAlertsPolicyConditionSpec(t)
73 |
74 | policyCondition := nrv1.AlertsPolicyCondition{
75 | Spec: *conditionSpec,
76 | }
77 |
78 | return &nrv1.AlertsNrqlCondition{
79 | ObjectMeta: metav1.ObjectMeta{
80 | Name: conditionName,
81 | Namespace: "default",
82 | },
83 |
84 | Spec: policyCondition.ReturnNrqlConditionSpec(),
85 | Status: nrv1.AlertsNrqlConditionStatus{
86 | AppliedSpec: &nrv1.AlertsNrqlConditionSpec{},
87 | },
88 | }
89 | }
90 |
91 | func NewTestAlertsPolicyConditionSpec(t *testing.T) *nrv1.AlertsPolicyConditionSpec {
92 | accountID, err := strconv.Atoi(os.Getenv("NEW_RELIC_ACCOUNT_ID"))
93 | assert.NoError(t, err)
94 |
95 | return &nrv1.AlertsPolicyConditionSpec{
96 | AlertsGenericConditionSpec: nrv1.AlertsGenericConditionSpec{
97 | APIKey: "nraa-key",
98 | AccountID: accountID,
99 | Terms: []nrv1.AlertsNrqlConditionTerm{
100 | {
101 | Operator: alerts.AlertsNRQLConditionTermsOperatorTypes.ABOVE,
102 | Priority: alerts.NrqlConditionPriorities.Critical,
103 | Threshold: "5",
104 | ThresholdDuration: 60,
105 | ThresholdOccurrences: alerts.ThresholdOccurrences.AtLeastOnce,
106 | },
107 | },
108 | Type: "NRQL",
109 | Name: "NRQL Condition",
110 | RunbookURL: "http://test.com/runbook",
111 | ID: 777,
112 | Enabled: true,
113 | ExistingPolicyID: "42",
114 | },
115 | AlertsNrqlSpecificSpec: nrv1.AlertsNrqlSpecificSpec{
116 | ViolationTimeLimit: "60",
117 | ExpectedGroups: 2,
118 | IgnoreOverlap: true,
119 | ValueFunction: &alerts.NrqlConditionValueFunctions.SingleValue,
120 | Nrql: alerts.NrqlConditionQuery{
121 | Query: "SELECT 1 FROM MyEvents",
122 | EvaluationOffset: 5,
123 | },
124 | },
125 | AlertsAPMSpecificSpec: nrv1.AlertsAPMSpecificSpec{},
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/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 | "fmt"
21 | "os"
22 |
23 | "k8s.io/apimachinery/pkg/runtime"
24 | clientgoscheme "k8s.io/client-go/kubernetes/scheme"
25 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
26 | ctrl "sigs.k8s.io/controller-runtime"
27 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
28 |
29 | nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
30 | "github.com/newrelic/newrelic-kubernetes-operator/internal/info"
31 | // +kubebuilder:scaffold:imports
32 | )
33 |
34 | var (
35 | scheme = runtime.NewScheme()
36 | setupLog = ctrl.Log.WithName("setup")
37 | )
38 |
39 | func init() {
40 | _ = clientgoscheme.AddToScheme(scheme)
41 |
42 | _ = nrv1.AddToScheme(scheme)
43 | // +kubebuilder:scaffold:scheme
44 | }
45 |
46 | func main() {
47 | var metricsAddr string
48 | var enableLeaderElection bool
49 | var showVersion bool
50 | var devMode bool
51 |
52 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
53 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
54 | flag.BoolVar(&showVersion, "version", false, "Show version information.")
55 | flag.BoolVar(&devMode, "dev-mode", false, "Enable development level logging (stacktraces on warnings, no sampling)")
56 | flag.Parse()
57 |
58 | if showVersion {
59 | fmt.Printf("%s version %s\n", info.Name, info.Version)
60 | os.Exit(0)
61 | }
62 |
63 | logger := zap.New(zap.UseDevMode(devMode))
64 | ctrl.SetLogger(logger)
65 |
66 | opts := ctrl.Options{
67 | Scheme: scheme,
68 | MetricsBindAddress: metricsAddr,
69 | LeaderElection: enableLeaderElection,
70 | LeaderElectionID: "newrelic-kubernetes-operator",
71 | Port: 9443,
72 | }
73 |
74 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts)
75 | if err != nil {
76 | setupLog.Error(err, "unable to create manager")
77 | os.Exit(1)
78 | }
79 |
80 | // initialize NR go agent
81 | nrApp := InitializeNRAgent()
82 |
83 | //Register Alerts
84 | err = registerAlerts(&mgr, &nrApp)
85 | if err != nil {
86 | setupLog.Error(err, "unable to register alerts")
87 | os.Exit(1)
88 | }
89 |
90 | // This marker is used by kubebuilder and must remain in main.go but generated code should be refactored to another class as appropriate
91 | // This can likely be refactored once https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/simplified-scaffolding.md is completed
92 | // +kubebuilder:scaffold:builder
93 |
94 | setupLog.Info("starting manager")
95 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
96 | setupLog.Error(err, "problem running manager")
97 | os.Exit(1)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/nrgoagent.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/newrelic/go-agent/v3/newrelic"
7 | )
8 |
9 | //InitializeNRAgent - Initializes the NR Agent
10 | func InitializeNRAgent() newrelic.Application {
11 |
12 | app, err := newrelic.NewApplication(
13 | //Set a default name which will be overridden by ENV values if provided
14 | newrelic.ConfigAppName("New Relic Kubernetes Operator"),
15 | newrelic.ConfigFromEnvironment(),
16 | )
17 |
18 | // If an application could not be created then err will reveal why.
19 | if err != nil {
20 | fmt.Println("unable to create New Relic Application", err)
21 | return newrelic.Application{}
22 | }
23 |
24 | return *app
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | COLOR_RED='\033[0;31m'
4 | COLOR_NONE='\033[0m'
5 | CURRENT_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
6 |
7 | if [ $CURRENT_GIT_BRANCH != 'master' ]; then
8 | printf "\n"
9 | printf "${COLOR_RED} Error: The release.sh script must be run while on the master branch. \n ${COLOR_NONE}"
10 | printf "\n"
11 |
12 | exit 1
13 | fi
14 |
15 | if [ $# -ne 1 ]; then
16 | printf "\n"
17 | printf "${COLOR_RED} Error: Release version argument required. \n\n ${COLOR_NONE}"
18 | printf " Example: \n\n ./tools/release.sh 0.9.0 \n\n"
19 | printf " Example (make): \n\n make release version=0.9.0 \n"
20 | printf "\n"
21 |
22 | exit 1
23 | fi
24 |
25 | RELEASE_VERSION=$1
26 | GIT_USER=$(git config user.email)
27 |
28 | echo "Generating release for v${RELEASE_VERSION} using system user git user ${GIT_USER}"
29 |
30 | git checkout -b release/v${RELEASE_VERSION}
31 |
32 | # Update config/manager/kustomization.yaml to point to the docker tag for this release
33 | cd config/manager && kustomize edit set image controller=newrelic/kubernetes-operator:v${RELEASE_VERSION} && cd ../..
34 |
35 | # Auto-generate CHANGELOG updates
36 | git-chglog --next-tag v${RELEASE_VERSION} -o CHANGELOG.md
37 |
38 | # Commit updates
39 | git add CHANGELOG.md config/manager/kustomization.yaml
40 | git commit --no-verify -m "chore(release): Updates for v${RELEASE_VERSION}"
41 | git push --no-verify origin release/v${RELEASE_VERSION}
42 |
--------------------------------------------------------------------------------
/tools/tools.go:
--------------------------------------------------------------------------------
1 | // +build tools
2 |
3 | package tools
4 |
5 | import (
6 | // project-specific
7 | _ "github.com/maxbrunsfeld/counterfeiter/v6"
8 |
9 | // build/lint.mk
10 | _ "golang.org/x/tools/cmd/goimports"
11 |
12 | // build/generate.mk
13 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
14 |
15 | // build/release.mk
16 | _ "github.com/goreleaser/goreleaser"
17 | )
18 |
19 | // This file imports packages that are used when running go generate, or used
20 |
--------------------------------------------------------------------------------