├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── config.yml
│ └── story.md
├── PULL_REQUEST_TEMPLATE.md
├── ct.yaml
├── renovate.json5
└── workflows
│ ├── changelog.yml
│ ├── push_pr.yml
│ ├── release-chart.yml
│ ├── release-integration.yml
│ ├── repolinter.yml
│ ├── security.yaml
│ └── trigger-release.yml
├── .gitignore
├── .golangci.yml
├── .trivyignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── THIRD_PARTY_NOTICES.md
├── charts
└── nri-metadata-injection
│ ├── .helmignore
│ ├── Chart.lock
│ ├── Chart.yaml
│ ├── README.md
│ ├── README.md.gotmpl
│ ├── ci
│ └── test-values.yaml
│ ├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── admission-webhooks
│ │ ├── job-patch
│ │ │ ├── README.md
│ │ │ ├── clusterrole.yaml
│ │ │ ├── clusterrolebinding.yaml
│ │ │ ├── job-createSecret.yaml
│ │ │ ├── job-patchWebhook.yaml
│ │ │ ├── psp.yaml
│ │ │ ├── role.yaml
│ │ │ ├── rolebinding.yaml
│ │ │ └── serviceaccount.yaml
│ │ └── mutatingWebhookConfiguration.yaml
│ ├── cert-manager.yaml
│ ├── deployment.yaml
│ └── service.yaml
│ ├── tests
│ ├── cluster_test.yaml
│ ├── job_serviceaccount_test.yaml
│ ├── rbac_test.yaml
│ └── volume_mounts_test.yaml
│ └── values.yaml
├── cmd
└── server
│ └── main.go
├── docs
├── k8s-api-lifecycle.svg
├── lifecycle.md
└── performance.md
├── e2e-tests
├── k8s-e2e-bootstraping.sh
└── tests.sh
├── entrypoint.sh
├── go.mod
├── go.sum
├── openapi.yaml
└── src
└── server
├── readiness_probe.go
├── readiness_probe_test.go
├── testdata
└── expectedAdmissionReviewPatch.json
├── webhook.go
└── webhook_test.go
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This is a comment.
2 | # Each line is a file pattern followed by one or more owners.
3 |
4 | # These owners will be the default owners for everything in
5 | # the repo. Unless a later match takes precedence.
6 |
7 | * @newrelic/k8s-agents
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ^^ Provide a general summary of the issue in the title above. ^^
11 |
12 | ## Description
13 | Describe the problem you're encountering.
14 | TIP: Do NOT share sensitive information, whether personal, proprietary, or otherwise!
15 |
16 | ## Expected Behavior
17 | Tell us what you expected to happen.
18 |
19 | ## [Troubleshooting](https://discuss.newrelic.com/t/troubleshooting-frameworks/108787) or [NR Diag](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) results
20 | Provide any other relevant log data.
21 | TIP: Scrub logs and diagnostic information for sensitive information
22 |
23 | ## Steps to Reproduce
24 | Please be as specific as possible.
25 | TIP: Link a sample application that demonstrates the issue.
26 |
27 | ## Your Environment
28 | Include as many relevant details about your environment as possible including the running version of New Relic software and any relevant configurations.
29 |
30 | ## Additional context
31 | Add any other context about the problem here. For example, relevant community posts or support tickets.
32 |
33 | ## For Maintainers Only or Hero Triaging this bug
34 | *Suggested Priority (P1,P2,P3,P4,P5):*
35 | *Suggested T-Shirt size (S, M, L, XL, Unknown):*
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Feature Request
4 | url: https://issues.newrelic.com
5 | about: We welcome feature requests from our open-source community through the New Relic Issue Tracker.
6 | - name: Troubleshooting
7 | url: https://github.com/newrelic/k8s-metadata-injection/blob/main/README.md#support
8 | about: Check out the README for troubleshooting directions
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/story.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Story
3 | about: Issue describing development work to fulfill a feature request
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | priority: ''
8 | ---
9 | ### Description
10 | _What's the goal of this unit of work? What is included? What isn't included?_
11 |
12 | ### Acceptance Criteria
13 | _What tasks need to be accomplished to achieve the goal?_
14 |
15 | ### Design Consideration/Limitations
16 | _Why is this the route we should take to achieve our goal?_
17 | _What can't be achieved within this story?_
18 |
19 | ### Dependencies
20 | _Do any other teams or parts of the New Relic product need to be considered?_
21 | _Some common areas: UI, collector, documentation_
22 |
23 | ### Additional context
24 | _What else should we know about this story that might not fit into the other categories?_
25 |
26 | ### Estimates
27 | _Please provide initial t-shirt size. S = 1-3 days, M = 3-5 days (1 week), L = 1-2 weeks (1 sprint)_
28 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 | ## Type of change
5 |
6 |
7 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
8 | - [ ] New feature / enhancement (non-breaking change which adds functionality)
9 | - [ ] Security fix
10 | - [ ] Bug fix (non-breaking change which fixes an issue)
11 |
12 | ## Checklist:
13 |
14 |
15 | - [ ] Add changelog entry following the [contributing guide](../CONTRIBUTING.md#pull-requests)
16 | - [ ] Documentation has been updated
17 | - [ ] This change requires changes in testing:
18 | - [ ] unit tests
19 | - [ ] E2E tests
20 |
--------------------------------------------------------------------------------
/.github/ct.yaml:
--------------------------------------------------------------------------------
1 | # Chart linter defaults to `master` branch so we need to specify this as the default branch
2 | # or `cl` will fail with a not-so-helpful error that says:
3 | # "Error linting charts: Error identifying charts to process: Error running process: exit status 128"
4 | target-branch: main
5 |
6 | chart-repos:
7 | - newrelic=https://helm-charts.newrelic.com
8 |
9 | # Charts will be released manually.
10 | check-version-increment: false
11 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "github>newrelic/k8s-agents-automation:renovate-base.json5"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.yml:
--------------------------------------------------------------------------------
1 | # This action requires that any PR should touch at
2 | # least one CHANGELOG file.
3 |
4 | name: changelog
5 |
6 | on:
7 | pull_request:
8 | types: [opened, synchronize, reopened, labeled, unlabeled]
9 |
10 | jobs:
11 | check-changelog:
12 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-changelog.yml@main
13 |
--------------------------------------------------------------------------------
/.github/workflows/push_pr.yml:
--------------------------------------------------------------------------------
1 | name: Lint, Build, E2E Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 | - renovate/**
9 | release:
10 | types: [published]
11 | pull_request:
12 | workflow_dispatch:
13 |
14 | jobs:
15 | build:
16 | name: Build integration for
17 | runs-on: ubuntu-latest
18 | strategy:
19 | matrix:
20 | goos: [ linux ]
21 | goarch: [ amd64, arm64, arm ]
22 | steps:
23 | - uses: actions/checkout@v4
24 | - uses: actions/setup-go@v5
25 | with:
26 | go-version-file: 'go.mod'
27 | - name: Build integration
28 | env:
29 | GOOS: ${{ matrix.goos }}
30 | GOARCH: ${{ matrix.goarch }}
31 | run: |
32 | make compile
33 |
34 | chart-lint:
35 | name: Helm chart Lint
36 | runs-on: ubuntu-24.04
37 | timeout-minutes: 10
38 | strategy:
39 | max-parallel: 5
40 | matrix:
41 | kubernetes-version: ["v1.32.0", "v1.31.0", "v1.30.0", "v1.29.5", "v1.28.3"]
42 | steps:
43 | - uses: actions/checkout@v4
44 | with:
45 | fetch-depth: 0
46 | - uses: helm/chart-testing-action@v2.7.0
47 |
48 | - name: Lint charts
49 | run: ct --config .github/ct.yaml lint --debug
50 |
51 | - name: Check for changed installable charts
52 | id: list-changed
53 | run: |
54 | changed=$(ct --config .github/ct.yaml list-changed)
55 | if [[ -n "$changed" ]]; then
56 | echo "::set-output name=changed::true"
57 | fi
58 | - name: Run helm unit tests
59 | if: steps.list-changed.outputs.changed == 'true'
60 | run: |
61 | helm plugin install https://github.com/helm-unittest/helm-unittest
62 | for chart in $(ct --config .github/ct.yaml list-changed); do
63 | if [ -d "$chart/tests/" ]; then
64 | helm unittest $chart
65 | else
66 | echo "No unit tests found for $chart"
67 | fi
68 | done
69 | - name: Setup Minikube
70 | uses: manusa/actions-setup-minikube@v2.14.0
71 | if: steps.list-changed.outputs.changed == 'true'
72 | with:
73 | minikube version: v1.36.0
74 | kubernetes version: ${{ matrix.kubernetes-version }}
75 | github token: ${{ secrets.GITHUB_TOKEN }}
76 | - uses: actions/setup-go@v5
77 | if: steps.list-changed.outputs.changed == 'true'
78 | with:
79 | go-version-file: 'go.mod'
80 | - name: Create image for chart testing
81 | if: steps.list-changed.outputs.changed == 'true'
82 | run: |
83 | GOOS=linux GOARCH=amd64 make compile # Set GOOS and GOARCH explicitly since Dockerfile expects them in the binary name
84 | DOCKER_BUILDKIT=1 docker build -t e2e/metadata-injection:test .
85 | minikube image load e2e/metadata-injection:test
86 | kubectl create ns ct
87 | - name: Test install charts
88 | if: steps.list-changed.outputs.changed == 'true'
89 | run: ct install --namespace ct --config .github/ct.yaml --debug
90 | - name: Test upgrade charts
91 | if: steps.list-changed.outputs.changed == 'true'
92 | run: ct install --namespace ct --config .github/ct.yaml --debug --upgrade
93 |
94 | test:
95 | name: Unit tests
96 | needs: [ build ]
97 | runs-on: ubuntu-latest
98 | steps:
99 | - uses: actions/checkout@v4
100 | - uses: actions/setup-go@v5
101 | with:
102 | go-version-file: 'go.mod'
103 | - name: Run unit tests
104 | run: make test
105 | - name: Upload coverage to Codecov
106 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
107 | with:
108 | fail_ci_if_error: false
109 | env:
110 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
111 |
112 | static-analysis:
113 | name: Static analysis and linting
114 | runs-on: ubuntu-latest
115 | steps:
116 | - uses: actions/checkout@v4
117 | - uses: actions/setup-go@v5
118 | with:
119 | go-version-file: 'go.mod'
120 | - uses: newrelic/newrelic-infra-checkers@v1
121 | # - name: Semgrep
122 | # uses: returntocorp/semgrep-action@v1
123 | # with:
124 | # auditOn: push
125 | - name: golangci-lint
126 | uses: golangci/golangci-lint-action@v8
127 | continue-on-error: ${{ github.event_name != 'pull_request' }}
128 | with:
129 | only-new-issues: true
130 |
131 | e2e-kubernetes:
132 | name: Kubernetes E2E tests
133 | needs: [ test ]
134 | runs-on: ubuntu-24.04
135 | env:
136 | E2E_KUBERNETES_VERSION: ${{ matrix.k8s-version }}
137 | DOCKER_BUILDKIT: '1' # Setting DOCKER_BUILDKIT=1 ensures TARGETOS and TARGETARCH are populated
138 | strategy:
139 | fail-fast: false
140 | max-parallel: 5
141 | matrix:
142 | k8s-version: ["v1.32.0", "v1.31.0", "v1.30.0", "v1.29.5", "v1.28.3"]
143 | cri: [ containerd ]
144 | steps:
145 | - uses: actions/checkout@v4
146 | - uses: actions/setup-go@v5
147 | with:
148 | go-version-file: 'go.mod'
149 | - run: make e2e-test
150 |
151 | notify-failure:
152 | if: ${{ always() && failure() }}
153 | needs: [ e2e-kubernetes ]
154 | runs-on: ubuntu-latest
155 | steps:
156 | - name: Notify failure via Slack
157 | uses: archive/github-actions-slack@c643e5093620d65506466f2c9b317d5d29a5e517 # v2.10.1
158 | with:
159 | slack-bot-user-oauth-access-token: ${{ secrets.slack_token }}
160 | slack-channel: ${{ secrets.slack_channel }}
161 | slack-text: "❌ `${{ env.ORIGINAL_REPO_NAME }}`: <${{ github.server_url }}/${{ env.ORIGINAL_REPO_NAME }}/actions/runs/${{ github.run_id }}|'Kubernetes E2E tests' failed>."
--------------------------------------------------------------------------------
/.github/workflows/release-chart.yml:
--------------------------------------------------------------------------------
1 | name: Release k8s-metadata-injection chart
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | release-chart:
9 | permissions:
10 | contents: write
11 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-release-chart.yml@main
12 | secrets:
13 | gh_token: "${{ secrets.GITHUB_TOKEN }}"
14 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }}
15 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/release-integration.yml:
--------------------------------------------------------------------------------
1 | name: Pre-release and Release pipeline
2 |
3 | on:
4 | release:
5 | types: [prereleased, released]
6 | tags:
7 | - 'v*'
8 | workflow_dispatch:
9 |
10 | jobs:
11 | release-integration:
12 | permissions:
13 | contents: write
14 | pull-requests: write
15 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-release-integration.yml@main
16 | with:
17 | repo_name: k8s-metadata-injection
18 | artifact_path: bin/
19 | docker_image_name: newrelic/k8s-metadata-injection
20 | chart_directory: charts/nri-metadata-injection
21 | secrets:
22 | dockerhub_username: ${{ secrets.K8S_AGENTS_DOCKERHUB_USERNAME }}
23 | dockerhub_token: ${{ secrets.K8S_AGENTS_DOCKERHUB_TOKEN }}
24 | bot_token: ${{ secrets.K8S_AGENTS_BOT_TOKEN }}
25 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }}
26 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }}
27 |
--------------------------------------------------------------------------------
/.github/workflows/repolinter.yml:
--------------------------------------------------------------------------------
1 | # NOTE: This file should always be named `repolinter.yml` to allow
2 | # workflow_dispatch to work properly
3 | name: Repolinter Action
4 |
5 | # NOTE: This workflow will ONLY check the default branch!
6 | # Currently there is no elegant way to specify the default
7 | # branch in the event filtering, so branches are instead
8 | # filtered in the "Test Default Branch" step.
9 | on: [push, workflow_dispatch]
10 |
11 | jobs:
12 | repolint:
13 | name: Run Repolinter
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Test Default Branch
17 | id: default-branch
18 | uses: actions/github-script@v7.0.1
19 | with:
20 | script: |
21 | const data = await github.rest.repos.get(context.repo)
22 | return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0]
23 | - name: Checkout Self
24 | if: ${{ steps.default-branch.outputs.result == 'true' }}
25 | uses: actions/checkout@v4
26 | - name: Run Repolinter
27 | if: ${{ steps.default-branch.outputs.result == 'true' }}
28 | uses: newrelic/repolinter-action@v1
29 | with:
30 | config_url: https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/community-plus.yml
31 | output_type: issue
32 |
--------------------------------------------------------------------------------
/.github/workflows/security.yaml:
--------------------------------------------------------------------------------
1 | name: Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | - renovate/**
9 | pull_request:
10 | schedule:
11 | - cron: "0 3 * * *"
12 |
13 | jobs:
14 | trivy:
15 | permissions:
16 | contents: read # for actions/checkout to fetch code
17 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
18 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-security.yaml@main
19 | secrets:
20 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }}
21 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-release.yml:
--------------------------------------------------------------------------------
1 | name: Trigger release creation
2 |
3 | # This workflow triggers a release creation with changelog and the release notes created by the release toolkit.
4 | # This workflow should be triggered merely from the default branch.
5 | # For more details about how to release follow https://github.com/newrelic/coreint-automation/blob/main/docs/release_runbook.md
6 |
7 | on:
8 | workflow_dispatch:
9 | schedule:
10 | - cron: "0 12 * * 1" # Monday at 12pm UTC or 5am PT
11 |
12 | jobs:
13 | trigger-release:
14 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-trigger-release.yml@main
15 | with:
16 | bot_email: '${{ vars.K8S_AGENTS_BOT_EMAIL }}'
17 | bot_name: '${{ vars.K8S_AGENTS_BOT_NAME }}'
18 | secrets:
19 | bot_token: ${{ secrets.K8S_AGENTS_BOT_TOKEN }}
20 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }}
21 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | deploy/local.*
2 | bin
3 |
4 | # Downloaded chart dependencies
5 | **/charts/*.tgz
6 |
7 | # Release toolkit
8 | CHANGELOG.partial.md
9 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | linters:
3 | default: none
4 | enable:
5 | - asciicheck
6 | - bidichk
7 | - bodyclose
8 | - containedctx
9 | - contextcheck
10 | - cyclop
11 | - dogsled
12 | - dupl
13 | - durationcheck
14 | - err113
15 | - errcheck
16 | - errname
17 | - errorlint
18 | - exhaustive
19 | - forbidigo
20 | - forcetypeassert
21 | - funlen
22 | - gochecknoglobals
23 | - gochecknoinits
24 | - gocognit
25 | - goconst
26 | - gocritic
27 | - gocyclo
28 | - godot
29 | - goheader
30 | - gomodguard
31 | - goprintffuncname
32 | - gosec
33 | - govet
34 | - grouper
35 | - ineffassign
36 | - ireturn
37 | - maintidx
38 | - makezero
39 | - misspell
40 | - mnd
41 | - nestif
42 | - nilerr
43 | - nilnil
44 | - noctx
45 | - nolintlint
46 | - nonamedreturns
47 | - nosprintfhostport
48 | - paralleltest
49 | - prealloc
50 | - predeclared
51 | - promlinter
52 | - revive
53 | - rowserrcheck
54 | - sqlclosecheck
55 | - staticcheck
56 | - thelper
57 | - tparallel
58 | - unconvert
59 | - unparam
60 | - unused
61 | - wastedassign
62 | - whitespace
63 | - wrapcheck
64 | settings:
65 | dupl:
66 | threshold: 100
67 | funlen:
68 | lines: 100
69 | statements: 50
70 | gocyclo:
71 | min-complexity: 10
72 | govet:
73 | settings:
74 | printf:
75 | funcs:
76 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
77 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
78 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
79 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
80 | misspell:
81 | locale: US
82 | mnd:
83 | checks:
84 | - argument
85 | - case
86 | - condition
87 | - return
88 | nolintlint:
89 | require-explanation: false
90 | require-specific: true
91 | allow-unused: false
92 | exclusions:
93 | generated: lax
94 | presets:
95 | - comments
96 | - common-false-positives
97 | - legacy
98 | - std-error-handling
99 | rules:
100 | - linters:
101 | - paralleltest
102 | text: does not use range value in test Run
103 | paths:
104 | - third_party$
105 | - builtin$
106 | - examples$
107 | formatters:
108 | enable:
109 | - gofmt
110 | - gofumpt
111 | - goimports
112 | exclusions:
113 | generated: lax
114 | paths:
115 | - third_party$
116 | - builtin$
117 | - examples$
118 |
--------------------------------------------------------------------------------
/.trivyignore:
--------------------------------------------------------------------------------
1 | # We are running the 2.16.0 version of github.com/emicklei/go-restful that had the fix backported, but trivy still points it out as false-positive
2 | # This is going to be fixed by 2.15 of the kubernetes client go, they decided not to backport the fix since they are not using the impacted feature.
3 | CVE-2022-1996
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## Unreleased
9 |
10 | ## v1.34.1 - 2025-06-02
11 |
12 | ### ⛓️ Dependencies
13 | - Updated alpine to v3.22.0
14 |
15 | ## v1.34.0 - 2025-05-19
16 |
17 | ### 🚀 Enhancements
18 | - Pass container security context into admission webhooks @kondracek-nr [#619](https://github.com/newrelic/k8s-metadata-injection/pull/619)
19 |
20 | ### ⛓️ Dependencies
21 | - Updated golang.org/x/crypto to v0.38.0
22 | - Updated go to v1.24.3
23 |
24 | ## v1.33.1 - 2025-05-05
25 |
26 | ### ⛓️ Dependencies
27 | - Updated kubernetes packages to v0.33.0
28 |
29 | ## v1.33.0 - 2025-04-28
30 |
31 | ### ⛓️ Dependencies
32 | - Upgraded golang.org/x/net from 0.36.0 to 0.38.0
33 |
34 | ## v1.32.2 - 2025-04-14
35 |
36 | ### ⛓️ Dependencies
37 | - Updated go to v1.24.2
38 | - Updated golang.org/x/crypto to v0.37.0
39 |
40 | ## v1.32.1 - 2025-04-07
41 |
42 | ### ⛓️ Dependencies
43 | - Updated github.com/fsnotify/fsnotify to v1.9.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.9.0)
44 |
45 | ## v1.32.0 - 2025-03-24
46 |
47 | ### 🚀 Enhancements
48 | - Add v1.32 support and drop support for v1.27 @kpattaswamy [#602](https://github.com/newrelic/k8s-metadata-injection/pull/602)
49 |
50 | ### ⛓️ Dependencies
51 | - Updated kubernetes packages to v0.32.3
52 | - Upgraded golang.org/x/net from 0.33.0 to 0.36.0
53 |
54 | ## v1.31.3 - 2025-03-10
55 |
56 | ### ⛓️ Dependencies
57 | - Updated golang.org/x/crypto to v0.36.0
58 |
59 | ## v1.31.2 - 2025-02-17
60 |
61 | ### ⛓️ Dependencies
62 | - Updated kubernetes packages to v0.32.1
63 | - Updated alpine to v3.21.3
64 |
65 | ## v1.31.1 - 2025-01-27
66 |
67 | ### ⛓️ Dependencies
68 | - Updated go to v1.23.5
69 |
70 | ## v1.31.0 - 2025-01-20
71 |
72 | ### ⛓️ Dependencies
73 | - Updated golang.org/x/crypto to v0.32.0
74 | - Upgraded golang.org/x/net from 0.30.0 to 0.33.0
75 |
76 | ## v1.30.5 - 2025-01-13
77 |
78 | ### ⛓️ Dependencies
79 | - Updated alpine to v3.21.2
80 |
81 | ## v1.30.4 - 2024-12-23
82 |
83 | ### ⛓️ Dependencies
84 | - Updated kubernetes packages to v0.32.0
85 | - Updated go to v1.23.4
86 | - Updated golang.org/x/crypto to v0.31.0
87 |
88 | ## v1.30.3 - 2024-12-09
89 |
90 | ### ⛓️ Dependencies
91 | - Updated alpine to v3.21.0
92 |
93 | ## v1.30.2 - 2024-11-18
94 |
95 | ### ⛓️ Dependencies
96 | - Updated go to v1.23.3
97 | - Updated golang.org/x/crypto to v0.29.0
98 |
99 | ## v1.30.1 - 2024-11-04
100 |
101 | ### ⛓️ Dependencies
102 | - Updated kubernetes packages to v0.31.2
103 | - Updated github.com/fsnotify/fsnotify to v1.8.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.8.0)
104 |
105 | ## v1.30.0 - 2024-10-28
106 |
107 | ### 🛡️ Security notices
108 | - Update kube-webhook-certgen to 1.4.3 to patch cve
109 |
110 | ### 🚀 Enhancements
111 | - Add 1.31 support and drop 1.26 @zeitlerc [#574](https://github.com/newrelic/k8s-metadata-injection/pull/574)
112 |
113 | ### ⛓️ Dependencies
114 | - Updated golang.org/x/crypto to v0.28.0
115 |
116 | ## v1.29.2 - 2024-10-07
117 |
118 | ### ⛓️ Dependencies
119 | - Updated go to v1.23.2
120 | - Updated kubernetes packages to v0.31.1
121 |
122 | ## v1.29.1 - 2024-09-09
123 |
124 | ### ⛓️ Dependencies
125 | - Updated golang.org/x/crypto to v0.27.0
126 | - Updated alpine to v3.20.3
127 |
128 | ## v1.29.0 - 2024-09-02
129 |
130 | ### 🚀 Enhancements
131 | - Avoid Using PSP in k8s >= 1.25 @xqi-nr [#529](https://github.com/newrelic/k8s-metadata-injection/pull/529)
132 |
133 | ### ⛓️ Dependencies
134 | - Updated go to v1.23.0
135 | - Updated kubernetes packages to v0.31.0
136 |
137 | ## v1.28.4 - 2024-08-12
138 |
139 | ### ⛓️ Dependencies
140 | - Updated golang.org/x/crypto to v0.26.0
141 |
142 | ## v1.28.3 - 2024-07-29
143 |
144 | ### ⛓️ Dependencies
145 | - Updated alpine to v3.20.2
146 | - Updated kubernetes packages to v0.30.3
147 |
148 | ## v1.28.2 - 2024-07-22
149 |
150 | ### ⛓️ Dependencies
151 | - Updated golang.org/x/crypto to v0.25.0
152 |
153 | ## v1.28.1 - 2024-07-08
154 |
155 | ### ⛓️ Dependencies
156 | - Updated kubernetes packages to v0.30.2
157 |
158 | ## v1.28.0 - 2024-06-24
159 |
160 | ### 🚀 Enhancements
161 | - Add 1.29 and 1.30 support and drop 1.25 and 1.24 @dbudziwojskiNR [#551](https://github.com/newrelic/k8s-metadata-injection/pull/551)
162 |
163 | ### ⛓️ Dependencies
164 | - Updated alpine to v3.20.1
165 |
166 | ## v1.27.4 - 2024-06-17
167 |
168 | ### ⛓️ Dependencies
169 | - Updated go to v1.22.4
170 | - Updated golang.org/x/crypto to v0.24.0
171 |
172 | ## v1.27.3 - 2024-06-10
173 |
174 | ### ⛓️ Dependencies
175 | - Updated go to v1.22.3
176 |
177 | ## v1.27.2 - 2024-05-27
178 |
179 | ### ⛓️ Dependencies
180 | - Updated alpine to v3.20.0
181 |
182 | ## v1.27.1 - 2024-05-13
183 |
184 | ### ⛓️ Dependencies
185 | - Updated golang.org/x/crypto to v0.23.0
186 |
187 | ## v1.27.0 - 2024-04-29
188 |
189 | ### ⛓️ Dependencies
190 | - Upgraded golang.org/x/net from 0.21.0 to 0.23.0
191 |
192 | ## v1.26.4 - 2024-04-15
193 |
194 | ### ⛓️ Dependencies
195 | - Updated golang.org/x/crypto to v0.22.0
196 |
197 | ## v1.26.3 - 2024-03-25
198 |
199 | ### ⛓️ Dependencies
200 | - Updated kubernetes packages to v0.29.3
201 |
202 | ## v1.26.2 - 2024-03-11
203 |
204 | ### ⛓️ Dependencies
205 | - Updated golang.org/x/crypto to v0.21.0
206 |
207 | ## v1.26.1 - 2024-03-04
208 |
209 | ### ⛓️ Dependencies
210 | - Updated kubernetes packages to v0.29.2
211 |
212 | ## v1.26.0 - 2024-02-26
213 |
214 | ### 🚀 Enhancements
215 | - Add linux node selector @dbudziwojskiNR [#523](https://github.com/newrelic/k8s-metadata-injection/pull/523)
216 |
217 | ### ⛓️ Dependencies
218 | - Updated go.uber.org/zap to v1.27.0
219 |
220 | ## v1.25.1 - 2024-02-19
221 |
222 | ### ⛓️ Dependencies
223 | - Updated golang.org/x/crypto to v0.19.0
224 |
225 | ## v1.25.0 - 2024-02-05
226 |
227 | ### 🚀 Enhancements
228 | - Add Codecov @dbudziwojskiNR [#513](https://github.com/newrelic/k8s-metadata-injection/pull/513)
229 |
230 | ## v1.24.2 - 2024-01-29
231 |
232 | ### ⛓️ Dependencies
233 | - Updated kubernetes packages to v0.29.1
234 | - Updated alpine to v3.19.1
235 |
236 | ## v1.24.1 - 2024-01-22
237 |
238 | ### ⛓️ Dependencies
239 | - Updated go to v1.21.6
240 |
241 | ## v1.24.0 - 2024-01-15
242 |
243 | ### 🚀 Enhancements
244 | - Trigger release creation by @juanjjaramillo [#506](https://github.com/newrelic/k8s-metadata-injection/pull/506)
245 | - Remove reusable workflows by @juanjjaramillo [#491](https://github.com/newrelic/k8s-metadata-injection/pull/491)
246 |
247 | ## v1.23.2 - 2024-01-08
248 |
249 | ### ⛓️ Dependencies
250 | - Updated kubernetes packages to v0.29.0
251 | - Updated golang.org/x/crypto to v0.18.0
252 |
253 | ## v1.23.1 - 2023-12-25
254 |
255 | ### 🚀 Enhancements
256 | - Update e2e testing workflow to also run on release in [#485](https://github.com/newrelic/k8s-metadata-injection/pull/485)
257 |
258 | ### ⛓️ Dependencies
259 | - Updated kubernetes packages to v0.28.4
260 | - Updated go to v1.21.5
261 | - Updated alpine to v3.19.0
262 |
263 | ## v1.23.0 - 2023-12-06
264 |
265 | ### 🚀 Enhancements
266 | - Update reusable workflow dependency by @juanjjaramillo [#490](https://github.com/newrelic/k8s-metadata-injection/pull/490)
267 | - Reusable release workflow now provides a mechanism for opting out of helm chart updates [#488](https://github.com/newrelic/k8s-metadata-injection/pull/488)
268 |
269 | ### ⛓️ Dependencies
270 | - Updated golang.org/x/crypto to v0.16.0
271 | - Updated alpine to v3.18.5
272 |
273 | ## v1.22.1 - 2023-11-16
274 |
275 | ### ⛓️ Dependencies
276 | - Updated golang.org/x/crypto to v0.15.0
277 |
278 | ## v1.22.0 - 2023-11-13
279 |
280 | ### 🚀 Enhancements
281 | - Update k8s version in e2e tests by @svetlanabrennan in [#459](https://github.com/newrelic/k8s-metadata-injection/pull/459)
282 |
283 | ## v1.21.0 - 2023-11-13
284 |
285 | ### 🚀 Enhancements
286 | - Replace k8s v1.28.0-rc.1 with k8s 1.28.3 support by @svetlanabrennan in [#458](https://github.com/newrelic/k8s-metadata-injection/pull/458)
287 |
288 | ## v1.20.0 - 2023-11-06
289 |
290 | ### 🛡️ Security notices
291 | - Pin Slack notification action to a hash, not to a tag by @juanjjaramillo in [#447](https://github.com/newrelic/k8s-metadata-injection/pull/447)
292 |
293 | ## v1.19.0 - 2023-10-30
294 |
295 | ### 🚀 Enhancements
296 | - Remove 1.23 support by @svetlanabrennan in [#441](https://github.com/newrelic/k8s-metadata-injection/pull/441)
297 | - Add k8s 1.28.0-rc.1 support by @svetlanabrennan in [#443](https://github.com/newrelic/k8s-metadata-injection/pull/443)
298 | - Upload sarif when running periodically or pushing to main by @juanjaramillo in [#444](https://github.com/newrelic/k8s-metadata-injection/pull/444)
299 | - Improve Trivy scan by using Docker image by @juanjjaramillo in [#446](https://github.com/newrelic/k8s-metadata-injection/pull/446)
300 |
301 | ## v1.18.4 - 2023-10-23
302 |
303 | ### 🐞 Bug fixes
304 | - Trivy scans should only run on the 'Security' workflow by juanjjaramillo in [#436](https://github.com/newrelic/k8s-metadata-injection/pull/436)
305 |
306 | ### ⛓️ Dependencies
307 | - Updated kubernetes packages to v0.28.3
308 | - Updated github.com/fsnotify/fsnotify to v1.7.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.7.0)
309 |
310 | ## v1.18.3 - 2023-10-16
311 |
312 | ### 🐞 Bug fixes
313 | - Address CVE-2023-44487 and CVE-2023-39325 by juanjjaramillo in [#434](https://github.com/newrelic/k8s-metadata-injection/pull/434)
314 |
315 | ## v1.18.2 - 2023-10-09
316 |
317 | ### ⛓️ Dependencies
318 | - Updated golang.org/x/crypto to v0.14.0
319 |
320 | ## v1.18.1 - 2023-10-02
321 |
322 | ### 🐞 Bug fixes
323 | - Fix release workflow to include build-time metadata on release image by juanjjaramillo in [#425](https://github.com/newrelic/k8s-metadata-injection/pull/425)
324 |
325 | ## v1.18.0 - 2023-09-29
326 |
327 | ### 🚀 Enhancements
328 | - Improve readability of `release-integration-reusable.yml` by @juanjjaramillo in [#422](https://github.com/newrelic/k8s-metadata-injection/pull/422)
329 |
330 | ## v1.17.0 - 2023-09-29
331 |
332 | ### 🚀 Enhancements
333 | - Make explicit that we are only using a single file by @juanjjaramillo in [#416](https://github.com/newrelic/k8s-metadata-injection/pull/416)
334 |
335 | ### 🐞 Bug fixes
336 | - Fix action to fetch `version-update.go` by @juanjjaramillo in [#420](https://github.com/newrelic/k8s-metadata-injection/pull/420)
337 | - Add quotation to variables to handle spaces by @juanjjaramillo in [#417](https://github.com/newrelic/k8s-metadata-injection/pull/417)
338 |
339 | ### ⛓️ Dependencies
340 | - Updated alpine to v3.18.4
341 |
342 | ## v1.16.1 - 2023-09-26
343 |
344 | ### ⛓️ Dependencies
345 | - Updated go.uber.org/zap to v1.26.0
346 |
347 | ## v1.16.0 - 2023-09-21
348 |
349 | ### 🚀 Enhancements
350 | - update contributing.md docs by @svetlanabrennan in [#389](https://github.com/newrelic/k8s-metadata-injection/pull/389)
351 |
352 | ## v1.15.2 - 2023-09-20
353 |
354 | ### ⛓️ Dependencies
355 | - Updated go.uber.org/zap to v1.26.0
356 |
357 | ## v1.15.1 - 2023-09-18
358 |
359 | ### ⛓️ Dependencies
360 | - Updated kubernetes packages to v0.28.2
361 | - Updated go to 1.21
362 | - Updated golang.org/x/crypto to v0.13.0
363 |
364 | ## v1.15.0 - 2023-09-11
365 |
366 | ### 🚀 Enhancements
367 | - Update K8s Versions in E2E Tests by @xqi-nr in [#369](https://github.com/newrelic/k8s-metadata-injection/pull/369)
368 |
369 | ## v1.14.1 - 2023-09-04
370 |
371 | ### ⛓️ Dependencies
372 | - Updated kubernetes packages to v0.28.1
373 |
374 | ## v1.14.0 - 2023-08-31
375 |
376 | ### 🚀 Enhancements
377 | - Remove old maintainers @svetlanabrennan [#355](https://github.com/newrelic/k8s-metadata-injection/pull/355)
378 |
379 | ## v1.13.0 - 2023-08-28
380 |
381 | ### 🚀 Enhancements
382 | - Define GitHub bot name and email @juanjjaramillo [#343](https://github.com/newrelic/k8s-metadata-injection/pull/343)
383 |
384 | ### ⛓️ Dependencies
385 | - Updated alpine to v3.18.3
386 |
387 | ## v1.12.0 - 2023-08-23
388 |
389 | ### 🛡️ Security notices
390 | - Meet internal security standards @juanjjaramillo [#334](https://github.com/newrelic/k8s-metadata-injection/pull/334)
391 |
392 | ## 1.11.0
393 | ## What's Changed
394 | - Add configuration of certmanager durations @cdobbyn [#323](https://github.com/newrelic/k8s-metadata-injection/pull/323)
395 | - Add changelog workflow @svetlanabrennan [#316](https://github.com/newrelic/k8s-metadata-injection/pull/316)
396 | - Update code owners @juanjjaramillo [#318](https://github.com/newrelic/k8s-metadata-injection/pull/318)
397 | - Add pull request template @svetlanabrennan [#317](https://github.com/newrelic/k8s-metadata-injection/pull/317)
398 | - Add More Logs for NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME Injection @xqi-nr [#325](https://github.com/newrelic/k8s-metadata-injection/pull/325)
399 |
400 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.2...v1.11.0
401 |
402 | ## 1.10.2
403 | ## What's Changed
404 | * Update CHANGELOG.md by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/302
405 | * Bump versions by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/303
406 | * chore(deps): bump aquasecurity/trivy-action from 0.10.0 to 0.11.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/304
407 | * chore(deps): bump alpine from 3.18.0 to 3.18.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/305
408 | * chore(deps): bump k8s.io/apimachinery from 0.27.2 to 0.27.3 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/306
409 | * chore(deps): bump k8s.io/api from 0.27.2 to 0.27.3 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/307
410 | * upgrade go version by @xqi-nr in https://github.com/newrelic/k8s-metadata-injection/pull/308
411 |
412 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.1...v1.10.2
413 |
414 | ## 1.10.1
415 |
416 | ## What's Changed
417 | * Fix helm unittests by @htroisi in https://github.com/newrelic/k8s-metadata-injection/pull/292
418 | * Bump app and chart versions by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/293
419 | * Update Helm unit test reference by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/294
420 | * chore(deps): bump alpine from 3.17.3 to 3.18.0 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/295
421 | * chore(deps): bump k8s.io/api from 0.27.1 to 0.27.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/296
422 | * chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.4 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/301
423 |
424 |
425 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.0...v1.10.1
426 |
427 | ## 1.10.0
428 |
429 | - Update dependencies
430 | - Update rennovate workflow
431 | - Bump Helm chart version
432 |
433 | ## 1.9.0
434 |
435 | - Update dependencies
436 | - Update chart maintainers
437 | - Add support for Pod annotations in batch job pods (#261)
438 |
439 | ## 1.8.0
440 |
441 | - Updated dependencies
442 | - Fix: Resolve the issue about MutatingWebhookConfiguration being not supported in v1beta1
443 |
444 | ## 1.7.5
445 |
446 | - Updated dependencies
447 |
448 | ## 1.7.4
449 |
450 | - Fix: Update dependencies to address vulnerability issue (#234)
451 |
452 | ## 1.7.3
453 |
454 | - Updated dependencies
455 | - Fix: Re-enable trivy for high vulnerabilities (#202)
456 |
457 | ## 1.7.2
458 |
459 | - Fix: Update transitive dependencies to address trivy vulnerability issue (#164)
460 |
461 | ## 1.7.1
462 |
463 | - Updated dependencies
464 |
465 | ## 1.7.0
466 |
467 | - Updated dependencies
468 |
469 | ## 1.6.0
470 |
471 | - Adds support for Kubernetes 1.22
472 |
473 | ## 1.5.0
474 |
475 | - Dependencies have been updated to their latest versions (#93)
476 |
477 | ## 1.4.0
478 |
479 | - Support multiarch images
480 |
481 | ## 1.3.2
482 |
483 | - Update k8s-webhook-cert-manager to 1.3.2
484 | This new version introduces a fix to extend support to version 1.19.x of Kubernetes
485 |
486 | ## 1.3.1
487 |
488 | - Use Github Actions for releasing
489 |
490 | ## 1.3.0
491 |
492 | - Update k8s-webhook-cert-manager to 1.3.0
493 | - Update to golang version 1.14.6 and alpine 3.12.0
494 |
495 | ## 1.2.0
496 |
497 | - Update deployment apiVersion to apps/v1
498 | - Add kubernetes.io/legacy-unknown signer with approve permission to rbac for 1.18 compatibility
499 |
500 | ## 1.1.4
501 |
502 | - Update k8s-webhook-cert-manager to 1.2.1
503 |
504 | ## 1.1.3
505 |
506 | - Update k8s-webhook-cert-manager to 1.2.0
507 |
508 | ## 1.1.2
509 |
510 | - Update k8s-webhook-cert-manager to 1.1.1
511 |
512 | ## 1.1.1
513 |
514 | - Change default server timeout to 1s
515 |
516 | ## 1.1.0
517 |
518 | ### Added
519 |
520 | - OpenShift support!
521 |
522 | ### Changed
523 |
524 | - Deployment and Service resources are now explicitly assigned to a namespace.
525 | - The webhook server now listens on a non-root port by default: 8443.
526 |
527 | ## 1.0.0
528 |
529 | - Initial version of the webhook.
530 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome. Before contributing please read the
4 | [code of conduct](./CODE_OF_CONDUCT.md) and [search the issue tracker](issues); your issue may have already been discussed or fixed in `main`. 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](./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. Add an entry as an unordered list to the CHANGELOG under the `Unreleased` section under an L3 header that specifies the type of your PR. If there is no L3 header for your type of PR already in the Unreleased section, add a new L3 header. Include your github handle and a link to your PR in the entry. Here's an example of how it should look:
20 | ```md
21 | ## Unreleased
22 |
23 | ### bugfix
24 | - Fix some bug in some file @yourGithubHandle [#123](linkToThisPR)
25 | ```
26 |
27 | - Here are the accepted L3 headers (case sensitive)
28 | + `breaking`
29 | + `security`
30 | + `enhancement`
31 | + `bugfix`
32 | + `dependency`
33 |
34 | - Any other headers won't be accepted and the changelog check will fail.
35 | - You can skip the changelog requirement by using the "Skip Changelog" label if your pull request is only updating files related to the CI/CD process or minor doc changes.
36 |
37 | 4. You may merge the Pull Request in once you have the sign-off of one other developers, or if you do not have permission to do that, you may request the other reviewer to merge it for you.
38 |
39 | ## Contributor License Agreement
40 |
41 | 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.
42 |
43 | For more information about CLAs, please check out Alex Russell’s excellent post,
44 | [“Why Do I Need to Sign This?”](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/).
45 |
46 | ## Slack
47 |
48 | We host a public Slack with a dedicated channel for contributors and maintainers of open source projects hosted by New Relic. If you are contributing to this project, you're welcome to request access to the #oss-contributors channel in the newrelicusers.slack.com workspace. To request access, please use this [link](https://join.slack.com/t/newrelicusers/shared_invite/zt-1ayj69rzm-~go~Eo1whIQGYnu3qi15ng).
49 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.22.0
2 |
3 | # Set by docker automatically
4 | # If building with `docker build`, make sure to set GOOS/GOARCH explicitly when calling make:
5 | # `make compile GOOS=something GOARCH=something`
6 | # Otherwise the makefile will not append them to the binary name and docker build will fail.
7 | ARG TARGETOS
8 | ARG TARGETARCH
9 |
10 | RUN mkdir /app
11 | WORKDIR /app
12 |
13 | ADD --chmod=755 entrypoint.sh ./
14 | ADD --chmod=755 bin/k8s-metadata-injection-${TARGETOS}-${TARGETARCH} ./
15 | RUN mv k8s-metadata-injection-${TARGETOS}-${TARGETARCH} k8s-metadata-injection
16 |
17 | CMD ["/app/entrypoint.sh"]
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2019] New Relic, Inc
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BIN_DIR = ./bin
2 | TEST_COVERAGE_DIR := $(BIN_DIR)/test-coverage
3 | BINARY_NAME ?= $(BIN_DIR)/k8s-metadata-injection
4 | DOCKER_IMAGE_NAME ?= newrelic/k8s-metadata-injection
5 | # This default tag is used during e2e test execution in the ci
6 | DOCKER_IMAGE_TAG ?= local-dev
7 |
8 | GOLANGCILINT_VERSION = 1.43.0
9 |
10 | # required for enabling Go modules inside $GOPATH
11 | export GO111MODULE=on
12 |
13 | # GOOS and GOARCH will likely come from env
14 | GOOS ?=
15 | GOARCH ?=
16 | CGO_ENABLED ?= 0
17 |
18 | ifneq ($(strip $(GOOS)), )
19 | BINARY_NAME := $(BINARY_NAME)-$(GOOS)
20 | endif
21 |
22 | ifneq ($(strip $(GOARCH)), )
23 | BINARY_NAME := $(BINARY_NAME)-$(GOARCH)
24 | endif
25 |
26 | .PHONY: all
27 | all: build
28 |
29 | .PHONY: build
30 | build: test compile
31 |
32 | compile:
33 | @echo "=== $(INTEGRATION) === [ compile ]: Building $(INTEGRATION)..."
34 | go mod download
35 | CGO_ENABLED=$(CGO_ENABLED) go build -o $(BINARY_NAME) ./cmd/server
36 |
37 | .PHONY: compile-multiarch
38 | compile-multiarch:
39 | $(MAKE) compile GOOS=linux GOARCH=amd64
40 | $(MAKE) compile GOOS=linux GOARCH=arm64
41 | $(MAKE) compile GOOS=linux GOARCH=arm
42 |
43 | .PHONY: build-container
44 | build-container:
45 | docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) $$DOCKERARGS .
46 |
47 | .PHONY: test
48 | test:
49 | @echo "[test] Running unit tests"
50 | @mkdir -p $(TEST_COVERAGE_DIR)
51 | @go test ./... -count=1 -coverprofile=$(TEST_COVERAGE_DIR)/coverage.out -covermode=count
52 |
53 | .PHONY: e2e-test
54 | e2e-test:
55 | @echo "[test] Running e2e tests"
56 | ./e2e-tests/tests.sh
57 |
58 | .PHONY: benchmark-test
59 | benchmark-test:
60 | @echo "[test] Running benchmark tests"
61 | @go test -run=^Benchmark* -bench .
62 |
63 | # rt-update-changelog runs the release-toolkit run.sh script by piping it into bash to update the CHANGELOG.md.
64 | # It also passes down to the script all the flags added to the make target. To check all the accepted flags,
65 | # see: https://github.com/newrelic/release-toolkit/blob/main/contrib/ohi-release-notes/run.sh
66 | # e.g. `make rt-update-changelog -- -v`
67 | rt-update-changelog:
68 | curl "https://raw.githubusercontent.com/newrelic/release-toolkit/v1/contrib/ohi-release-notes/run.sh" | bash -s -- $(filter-out $@,$(MAKECMDGOALS))
69 |
70 |
71 | .PHONY: compile rt-update-changelog
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Kubernetes Metadata injection for New Relic APM agents [](https://codecov.io/gh/newrelic/k8s-metadata-injection) [](https://travis-ci.com/newrelic/k8s-metadata-injection) [](https://goreportcard.com/report/github.com/newrelic/k8s-metadata-injection)
4 |
5 | # Table of contents
6 |
7 | - [Documentation](#documentation)
8 | - [Development](#development)
9 | - [Prerequisites](#prerequisites)
10 | - [Dependency management](#dependency-management)
11 | - [Configuration](#configuration)
12 | - [Run](#run)
13 | - [Tests](#tests)
14 | - [API Documentation](#api-documentation)
15 | - [Performance](#performance)
16 | - [Certificates management](#certificates-management)
17 | - [Automatic](#automatic)
18 | - [Custom](#custom)
19 | - [Contributing](#contributing)
20 | - [License](#license)
21 | - [Release a new version](#release-a-new-version)
22 |
23 | ## Documentation
24 |
25 | If you wish to read higher-level documentation about this project, please, visit the [official documentation site](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection).
26 |
27 | # How does it work?
28 |
29 | New Relic APM agents requires the following environment variables to provide Kubernetes object information in the context of an specific application distributed trace, transaction trace or error trace.
30 |
31 | - `NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME`
32 | - `NEW_RELIC_METADATA_KUBERNETES_NODE_NAME`
33 | - `NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME`
34 | - `NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME`
35 | - `NEW_RELIC_METADATA_KUBERNETES_POD_NAME`
36 | - `NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME`
37 | - `NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME`
38 |
39 | These environment variables are automatically injected in the pods using a MutatingAdmissionWebhook provided by this project.
40 |
41 | Please refer to the [official documentation](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection) to learn more about the reasoning behind this project.
42 |
43 | ## Helm chart
44 |
45 | You can install this integration using [`nri-bundle` helm chart](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the
46 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository:
47 |
48 | ```shell
49 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection
50 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml
51 | ```
52 |
53 | For further information of the configuration needed for the chart just read the [chart's README](/charts/nri-metadata-injection/README.md).
54 |
55 | ## Development
56 |
57 | ### Prerequisites
58 |
59 | For the development process [Minikube](https://kubernetes.io/docs/getting-started-guides/minikube) and [Skaffold](https://github.com/GoogleCloudPlatform/skaffold) tools are used.
60 |
61 | - [Install Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/).
62 | - [Install Skaffold](https://github.com/GoogleCloudPlatform/skaffold#installation).
63 |
64 | Currently the project compiles with **Go 1.11.4**.
65 |
66 | ### Dependency management
67 |
68 | [Go modules](https://github.com/golang/go/wiki/Modules) are used for managing dependencies. This project does not need to be in your GOROOT, if you wish so.
69 |
70 | Currently for K8s libraries it uses version 1.13.1. Only couple of libraries are direct dependencies, the rest are indirect. You need to point all of them to the same K8s version to make sure that everything works as expected. For the moment this process is manual.
71 |
72 | ### Configuration
73 |
74 | - Copy the deployment file `deploy/newrelic-metadata-injection.yaml` to `deploy/local.yaml`.
75 | - Edit the file and set the following value as container image: `internal/k8s-metadata-injector`.
76 | - Make sure that `imagePullPolicy: Always` is not present in the file (otherwise, the image won't be pulled).
77 |
78 | ### Run
79 |
80 | Run `skaffold run`. This will build a docker image, build the webhook server inside it, and finally deploy the webhook server to your Minikube and use the Kubernetes API server to sign its TLS certificate ([see section about certificates](#3-install-the-certificates)).
81 |
82 | To follow the logs, you can run `skaffold run --tail`. To delete the resources created by Skaffold you can run `skaffold delete`.
83 |
84 | If you would like to enable automatic redeploy on changes to the repository, you can run `skaffold dev`. It automatically tails the logs and delete the resources when interrupted (i.e. with a `Ctrl + C`).
85 |
86 | ### Tests
87 |
88 | For running unit tests, use
89 |
90 | ```bash
91 | make test
92 | ```
93 |
94 | For running benchmark tests, use:
95 |
96 | ```bash
97 | make benchmark-test
98 | ```
99 |
100 | There are also some basic E2E tests, they are prepared to run using
101 | [Minikube](https://github.com/kubernetes/minikube). To run them, execute:
102 |
103 | ``` bash
104 | make e2e-test
105 | ```
106 |
107 | The e2e tests make the assumption that you are running on an AMD system so in case the test doesn't generate the needed binary, run the below command.
108 | For instance if you run on an M2 mac arm64 is the target arch but it is not made by default.
109 |
110 | ```bash
111 | make compile-multiarch
112 | ```
113 |
114 | You can specify against which version of K8s you want to execute the tests:
115 |
116 | ``` bash
117 | E2E_KUBERNETES_VERSION=v1.10.0 E2E_START_MINIKUBE=yes make e2e-test
118 | ```
119 |
120 | ### API Documentation
121 |
122 | Please use the [Open Api 3.0 spec file](openapi.yaml) as documentation reference. Note that it describes the schema of the requests the webhook server replies to. This schema depends on the currently supported Kubernetes versions.
123 |
124 | You can go to [editor.swagger.io](editor.swagger.io) and paste its contents there to see a rendered version.
125 |
126 | ### Performance
127 |
128 | Please refer to [docs/performance.md](docs/performance.md).
129 |
130 | ## Certificates management
131 |
132 | Admission webhooks are called by the Kubernetes API server and it needs to authenticate the webhooks using TLS. In this project we offer 2 different options of certificate management.
133 |
134 | Either certificate management choice made, the important thing is to have the secret created with the correct name and namespace, and also to have the correct CA bundle in the MutatingWebhookConfiguration resource. As long as this is done the webhook server will be able to pick it up.
135 |
136 | ### Automatic
137 |
138 | Please refer to the [setup instructions in the official documentation](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection#install).
139 |
140 | For the automatic certificate management, the [k8s-webhook-cert-manager](https://github.com/newrelic/k8s-webhook-cert-manager) is used. Feel free to check the repository to know more about it.
141 |
142 | The manifest file at [deploy/job.yaml](./deploy/job.yaml) contains a service account that has the following **cluster** permissions (**RBAC based**) to be capable of automatically manage the certificates:
143 |
144 | - `MutatingWebhookConfiguration` - **get**, **create** and **patch**: to be able to create the webhook and patch its CA bundle.
145 | - `CertificateSigningRequests` - **create**, **get** and **delete**: to be able to sign the certificate required for the webhook server without leaving duplicates.
146 | - `CertificateSigningRequests/Approval` - **update**: to be able to approve CertificateSigningRequests.
147 | - `Secrets` - **create**, **get** and **patch**: to be able to manage the TLS secret used to store the key/cert pair used in the webhook server.
148 | - `ConfigMaps` - **get**: to be able go get the k8s api server's CA bundle, used in the MutatingWebhookConfiguration.
149 |
150 | If you wish to learn more about TLS certificates management inside Kubernetes, check out [the official documentation for Managing TLS Certificates in a Cluster](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#create-a-certificate-signing-request-object-to-send-to-the-kubernetes-api).
151 |
152 | ### Custom
153 |
154 | Otherwise, if you want to use the custom certificate management option you have to create the TLS secret with the signed certificate/key pair and patch the webhook's CA bundle:
155 |
156 | ```bash
157 | $ kubectl create secret tls newrelic-metadata-injection-secret \
158 | --key=server-key.pem \
159 | --cert=signed-server-cert.pem \
160 | --dry-run -o yaml |
161 | kubectl -n default apply -f -
162 |
163 | $ caBundle=$(cat caBundle.pem | base64 | td -d '\n')
164 | $ kubectl patch mutatingwebhookconfiguration newrelic-metadata-injection-cfg --type='json' -p "[{'op': 'replace', 'path': '/webhooks/0/clientConfig/caBundle', 'value':'${caBundle}'}]"
165 | ```
166 |
167 | ## Release a new version
168 |
169 | - Update the version in `deploy/newrelic-metadata-injection.yaml`.
170 | - Update the version in `WEBHOOK_DOCKER_IMAGE_TAG` in the `Makefile`.
171 | - Create a Github release.
172 | - Launch the `k8s-metadata-injection-release` job in Jenkins.
173 |
174 | ## Support
175 |
176 | Should you need assistance with New Relic products, you are in good hands with several support diagnostic tools and support channels.
177 |
178 | >New Relic offers NRDiag, [a client-side diagnostic utility](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) that automatically detects common problems with New Relic agents. If NRDiag detects a problem, it suggests troubleshooting steps. NRDiag can also automatically attach troubleshooting data to a New Relic Support ticket. Remove this section if it doesn't apply.
179 |
180 | If the issue has been confirmed as a bug or is a feature request, file a GitHub issue.
181 |
182 | **Support Channels**
183 |
184 | - [New Relic Documentation](https://docs.newrelic.com): Comprehensive guidance for using our platform
185 | - [New Relic Community](https://forum.newrelic.com/t/new-relic-kubernetes-open-source-integration/109093): The best place to engage in troubleshooting questions
186 | - [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications
187 | - [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level
188 | - [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan).
189 |
190 | ## Privacy
191 |
192 | At New Relic we take your privacy and the security of your information seriously, and are committed to protecting your information. We must emphasize the importance of not sharing personal data in public forums, and ask all users to scrub logs and diagnostic information for sensitive information, whether personal, proprietary, or otherwise.
193 |
194 | We define “Personal Data” as any information relating to an identified or identifiable individual, including, for example, your name, phone number, post code or zip code, Device ID, IP address, and email address.
195 |
196 | For more information, review [New Relic’s General Data Privacy Notice](https://newrelic.com/termsandconditions/privacy).
197 |
198 | ## Contribute
199 |
200 | We encourage your contributions to improve this project! Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project.
201 |
202 | If you have any questions, or to execute our corporate CLA (which is required if your contribution is on behalf of a company), drop us an email at opensource@newrelic.com.
203 |
204 | **A note about vulnerabilities**
205 |
206 | As noted in our [security policy](../../security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals.
207 |
208 | If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [Hackerour bug bounty program](https://docs.newrelic.com/docs/security/security-privacy/information-security/report-security-vulnerabilities/).
209 |
210 | If you would like to contribute to this project, review [these guidelines](./CONTRIBUTING.md).
211 |
212 | To all contributors, we thank you! Without your contribution, this project would not be what it is today.
213 |
214 | ## License
215 |
216 | Kubernetes Metadata injection is licensed under the [Apache 2.0](http://apache.org/licenses/LICENSE-2.0.txt) License.
217 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/.helmignore:
--------------------------------------------------------------------------------
1 | templates/admission-webhooks/job-patch/README.md
2 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/Chart.lock:
--------------------------------------------------------------------------------
1 | dependencies:
2 | - name: common-library
3 | repository: https://helm-charts.newrelic.com
4 | version: 1.3.1
5 | digest: sha256:cfa7bfb136b9bcfe87e37d3556c3fedecc58f42685c4ce39485da106408b6619
6 | generated: "2025-01-08T01:20:29.871261607Z"
7 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: nri-metadata-injection
3 | description: A Helm chart to deploy the New Relic metadata injection webhook.
4 | home: https://hub.docker.com/r/newrelic/k8s-metadata-injection
5 | icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg
6 | sources:
7 | - https://github.com/newrelic/k8s-metadata-injection
8 | - https://github.com/newrelic/k8s-metadata-injection/tree/master/charts/nri-metadata-injection
9 | version: 4.26.1
10 | appVersion: 1.34.1
11 | keywords:
12 | - infrastructure
13 | - newrelic
14 | - monitoring
15 | dependencies:
16 | - name: common-library
17 | version: 1.3.1
18 | repository: "https://helm-charts.newrelic.com"
19 | maintainers:
20 | - name: juanjjaramillo
21 | url: https://github.com/juanjjaramillo
22 | - name: csongnr
23 | url: https://github.com/csongnr
24 | - name: dbudziwojskiNR
25 | url: https://github.com/dbudziwojskiNR
26 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/README.md:
--------------------------------------------------------------------------------
1 | # nri-metadata-injection
2 |
3 | A Helm chart to deploy the New Relic metadata injection webhook.
4 |
5 | **Homepage:**
6 |
7 | # Helm installation
8 |
9 | You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the
10 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository:
11 |
12 | ```shell
13 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection
14 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml
15 | ```
16 |
17 | ## Source Code
18 |
19 | *
20 | *
21 |
22 | ## Values managed globally
23 |
24 | This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which
25 | means that it honors a wide range of defaults and globals common to most New Relic Helm charts.
26 |
27 | Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at
28 | [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md).
29 |
30 | ## Values
31 |
32 | | Key | Type | Default | Description |
33 | |-----|------|---------|-------------|
34 | | affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` |
35 | | certManager.enabled | bool | `false` | Use cert manager for webhook certs |
36 | | certManager.rootCertificateDuration | string | `"43800h"` | Sets the root certificate duration. Defaults to 43800h (5 years). |
37 | | certManager.webhookCertificateDuration | string | `"8760h"` | Sets certificate duration. Defaults to 8760h (1 year). |
38 | | cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` |
39 | | containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` |
40 | | customTLSCertificate | bool | `false` | Use custom tls certificates for the webhook, or let the chart handle it automatically. Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection |
41 | | dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` |
42 | | fullnameOverride | string | `""` | Override the full name of the release |
43 | | hostNetwork | bool | false | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` |
44 | | image | object | See `values.yaml` | Image for the New Relic Metadata Injector |
45 | | image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. |
46 | | injectOnlyLabeledNamespaces | bool | `false` | Enable the metadata decoration only for pods living in namespaces labeled with 'newrelic-metadata-injection=enabled'. |
47 | | jobImage | object | See `values.yaml` | Image for creating the needed certificates of this webhook to work |
48 | | jobImage.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. |
49 | | jobImage.volumeMounts | list | `[]` | Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies Enforce a read-only root. |
50 | | jobImage.volumes | list | `[]` | Volumes to add to the job container |
51 | | labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` |
52 | | nameOverride | string | `""` | Override the name of the chart |
53 | | nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` |
54 | | podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. |
55 | | podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` |
56 | | podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` |
57 | | priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` |
58 | | rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. |
59 | | replicas | int | `1` | |
60 | | resources | object | 100m/30M -/80M | Image for creating the needed certificates of this webhook to work |
61 | | timeoutSeconds | int | `28` | Webhook timeout Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts |
62 | | tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` |
63 |
64 | ## Maintainers
65 |
66 | * [juanjjaramillo](https://github.com/juanjjaramillo)
67 | * [csongnr](https://github.com/csongnr)
68 | * [dbudziwojskiNR](https://github.com/dbudziwojskiNR)
69 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/README.md.gotmpl:
--------------------------------------------------------------------------------
1 | {{ template "chart.header" . }}
2 | {{ template "chart.deprecationWarning" . }}
3 |
4 | {{ template "chart.description" . }}
5 |
6 | {{ template "chart.homepageLine" . }}
7 |
8 | # Helm installation
9 |
10 | You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the
11 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository:
12 |
13 | ```shell
14 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection
15 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml
16 | ```
17 |
18 | {{ template "chart.sourcesSection" . }}
19 |
20 | ## Values managed globally
21 |
22 | This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which
23 | means that it honors a wide range of defaults and globals common to most New Relic Helm charts.
24 |
25 | Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at
26 | [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md).
27 |
28 | {{ template "chart.valuesSection" . }}
29 |
30 | {{ if .Maintainers }}
31 | ## Maintainers
32 | {{ range .Maintainers }}
33 | {{- if .Name }}
34 | {{- if .Url }}
35 | * [{{ .Name }}]({{ .Url }})
36 | {{- else }}
37 | * {{ .Name }}
38 | {{- end }}
39 | {{- end }}
40 | {{- end }}
41 | {{- end }}
42 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/ci/test-values.yaml:
--------------------------------------------------------------------------------
1 | cluster: test-cluster
2 |
3 | image:
4 | repository: e2e/metadata-injection
5 | tag: test # Defaults to AppVersion
6 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | Your deployment of the New Relic metadata injection webhook is complete. You can check on the progress of this by running the following command:
2 |
3 | kubectl get deployments -o wide -w --namespace {{ .Release.Namespace }} {{ template "newrelic.common.naming.fullname" . }}
4 |
5 | {{- if .Values.customTLSCertificate }}
6 | You have configure the chart to use a custom tls certificate, make sure to read the 'Manage custom certificates' section of the official docs to find the instructions on how to finish setting up the webhook.
7 |
8 | https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection
9 | {{- end }}
10 |
11 | To validate the injection of metadata create a dummy pod containing Busybox by running:
12 |
13 | kubectl create -f https://git.io/vPieo
14 |
15 | Check if New Relic environment variables were injected:
16 |
17 | kubectl exec busybox0 -- env | grep NEW_RELIC_METADATA_KUBERNETES
18 |
19 | NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME=fsi
20 | NEW_RELIC_METADATA_KUBERNETES_NODE_NAME=nodea
21 | NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME=default
22 | NEW_RELIC_METADATA_KUBERNETES_POD_NAME=busybox0
23 | NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME=busybox
24 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 |
3 | {{- /* Allow to change pod defaults dynamically */ -}}
4 | {{- define "nri-metadata-injection.securityContext.pod" -}}
5 | {{- if include "newrelic.common.securityContext.pod" . -}}
6 | {{- include "newrelic.common.securityContext.pod" . -}}
7 | {{- else -}}
8 | fsGroup: 1001
9 | runAsUser: 1001
10 | runAsGroup: 1001
11 | {{- end -}}
12 | {{- end -}}
13 |
14 | {{- /*
15 | Naming helpers
16 | */ -}}
17 |
18 | {{- define "nri-metadata-injection.name.admission" -}}
19 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission") }}
20 | {{- end -}}
21 |
22 | {{- define "nri-metadata-injection.fullname.admission" -}}
23 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }}
24 | {{- end -}}
25 |
26 | {{- define "nri-metadata-injection.fullname.admission.serviceAccount" -}}
27 | {{- if include "newrelic.common.serviceAccount.create" . -}}
28 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }}
29 | {{- else -}}
30 | {{ include "newrelic.common.serviceAccount.name" . }}
31 | {{- end -}}
32 | {{- end -}}
33 |
34 | {{- define "nri-metadata-injection.name.admission-create" -}}
35 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-create") }}
36 | {{- end -}}
37 |
38 | {{- define "nri-metadata-injection.fullname.admission-create" -}}
39 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-create") }}
40 | {{- end -}}
41 |
42 | {{- define "nri-metadata-injection.name.admission-patch" -}}
43 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-patch") }}
44 | {{- end -}}
45 |
46 | {{- define "nri-metadata-injection.fullname.admission-patch" -}}
47 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-patch") }}
48 | {{- end -}}
49 |
50 | {{- define "nri-metadata-injection.name.self-signed-issuer" -}}
51 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "self-signed-issuer") }}
52 | {{- end -}}
53 |
54 | {{- define "nri-metadata-injection.fullname.self-signed-issuer" -}}
55 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "self-signed-issuer") }}
56 | {{- end -}}
57 |
58 | {{- define "nri-metadata-injection.name.root-issuer" -}}
59 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "root-issuer") }}
60 | {{- end -}}
61 |
62 | {{- define "nri-metadata-injection.fullname.root-issuer" -}}
63 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "root-issuer") }}
64 | {{- end -}}
65 |
66 | {{- define "nri-metadata-injection.name.webhook-cert" -}}
67 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "webhook-cert") }}
68 | {{- end -}}
69 |
70 | {{- define "nri-metadata-injection.fullname.webhook-cert" -}}
71 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "webhook-cert") }}
72 | {{- end -}}
73 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/README.md:
--------------------------------------------------------------------------------
1 | The manifests in this directory are modified version of the manifests coming from
2 | the [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/f1729dcfd2040660d4f3dcbe3b2f797415990711/charts/kube-prometheus-stack/templates/prometheus-operator/admission-webhooks/job-patch)
3 | Helm chart.
4 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
6 | annotations:
7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
9 | labels:
10 | app: {{ include "newrelic.common.naming.name" $ }}-admission
11 | {{- include "newrelic.common.labels" . | nindent 4 }}
12 | rules:
13 | - apiGroups:
14 | - admissionregistration.k8s.io
15 | resources:
16 | - mutatingwebhookconfigurations
17 | verbs:
18 | - get
19 | - update
20 | {{- if .Values.rbac.pspEnabled }}
21 | - apiGroups: ['policy']
22 | resources: ['podsecuritypolicies']
23 | verbs: ['use']
24 | resourceNames:
25 | - {{ include "nri-metadata-injection.fullname.admission" . }}
26 | {{- end }}
27 | {{- end }}
28 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRoleBinding
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
6 | annotations:
7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
9 | labels:
10 | {{- include "newrelic.common.labels" . | nindent 4 }}
11 | app: {{ include "nri-metadata-injection.name.admission" . }}
12 | roleRef:
13 | apiGroup: rbac.authorization.k8s.io
14 | kind: ClusterRole
15 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
16 | subjects:
17 | - kind: ServiceAccount
18 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }}
19 | namespace: {{ .Release.Namespace }}
20 | {{- end }}
21 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: batch/v1
3 | kind: Job
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission-create" . }}
6 | namespace: {{ .Release.Namespace }}
7 | annotations:
8 | "helm.sh/hook": pre-install,pre-upgrade
9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
10 | labels:
11 | app: {{ include "nri-metadata-injection.name.admission-create" . }}
12 | {{- include "newrelic.common.labels" . | nindent 4 }}
13 | spec:
14 | template:
15 | metadata:
16 | name: {{ include "nri-metadata-injection.fullname.admission-create" . }}
17 | {{- if .Values.podAnnotations }}
18 | annotations:
19 | {{- toYaml .Values.podAnnotations | nindent 8 }}
20 | {{- end }}
21 | labels:
22 | app: {{ include "nri-metadata-injection.name.admission-create" . }}
23 | {{- include "newrelic.common.labels" . | nindent 8 }}
24 | spec:
25 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }}
26 | imagePullSecrets:
27 | {{- . | nindent 8 -}}
28 | {{- end }}
29 | containers:
30 | - name: create
31 | image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }}
32 | imagePullPolicy: {{ .Values.jobImage.pullPolicy }}
33 | {{- with include "newrelic.common.securityContext.container" . }}
34 | securityContext:
35 | {{- . | nindent 12 }}
36 | {{- end }}
37 | args:
38 | - create
39 | - --host={{ include "newrelic.common.naming.fullname" . }},{{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc
40 | - --namespace={{ .Release.Namespace }}
41 | - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }}
42 | - --cert-name=tls.crt
43 | - --key-name=tls.key
44 | {{- if .Values.jobImage.volumeMounts }}
45 | volumeMounts:
46 | {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }}
47 | {{- end }}
48 | {{- if .Values.jobImage.volumes }}
49 | volumes:
50 | {{- .Values.jobImage.volumes | toYaml | nindent 8 }}
51 | {{- end }}
52 | restartPolicy: OnFailure
53 | serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }}
54 | securityContext:
55 | runAsGroup: 2000
56 | runAsNonRoot: true
57 | runAsUser: 2000
58 | nodeSelector:
59 | kubernetes.io/os: linux
60 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }}
61 | {{- if .Values.tolerations }}
62 | tolerations:
63 | {{- toYaml .Values.tolerations | nindent 8 }}
64 | {{- end }}
65 | {{- end }}
66 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: batch/v1
3 | kind: Job
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission-patch" . }}
6 | namespace: {{ .Release.Namespace }}
7 | annotations:
8 | "helm.sh/hook": post-install,post-upgrade
9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
10 | labels:
11 | app: {{ include "nri-metadata-injection.name.admission-patch" . }}
12 | {{- include "newrelic.common.labels" . | nindent 4 }}
13 | spec:
14 | template:
15 | metadata:
16 | name: {{ include "nri-metadata-injection.fullname.admission-patch" . }}
17 | {{- if .Values.podAnnotations }}
18 | annotations:
19 | {{- toYaml .Values.podAnnotations | nindent 8 }}
20 | {{- end }}
21 | labels:
22 | app: {{ include "nri-metadata-injection.name.admission-patch" . }}
23 | {{- include "newrelic.common.labels" . | nindent 8 }}
24 | spec:
25 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }}
26 | imagePullSecrets:
27 | {{- . | nindent 8 -}}
28 | {{- end }}
29 | containers:
30 | - name: patch
31 | image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }}
32 | imagePullPolicy: {{ .Values.jobImage.pullPolicy }}
33 | {{- with include "newrelic.common.securityContext.container" . }}
34 | securityContext:
35 | {{- . | nindent 12 }}
36 | {{- end }}
37 | args:
38 | - patch
39 | - --webhook-name={{ include "newrelic.common.naming.fullname" . }}
40 | - --namespace={{ .Release.Namespace }}
41 | - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }}
42 | - --patch-failure-policy=Ignore
43 | - --patch-validating=false
44 | {{- if .Values.jobImage.volumeMounts }}
45 | volumeMounts:
46 | {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }}
47 | {{- end }}
48 | {{- if .Values.jobImage.volumes }}
49 | volumes:
50 | {{- .Values.jobImage.volumes | toYaml | nindent 8 }}
51 | {{- end }}
52 | restartPolicy: OnFailure
53 | serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }}
54 | securityContext:
55 | runAsGroup: 2000
56 | runAsNonRoot: true
57 | runAsUser: 2000
58 | nodeSelector:
59 | kubernetes.io/os: linux
60 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }}
61 | {{- if .Values.tolerations }}
62 | tolerations:
63 | {{- toYaml .Values.tolerations | nindent 8 }}
64 | {{- end }}
65 | {{- end }}
66 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled) (.Values.rbac.pspEnabled) (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy")) }}
2 | apiVersion: policy/v1beta1
3 | kind: PodSecurityPolicy
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
6 | annotations:
7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
9 | labels:
10 | app: {{ include "nri-metadata-injection.name.admission" . }}
11 | {{- include "newrelic.common.labels" . | nindent 4 }}
12 | spec:
13 | privileged: false
14 | # Required to prevent escalations to root.
15 | # allowPrivilegeEscalation: false
16 | # This is redundant with non-root + disallow privilege escalation,
17 | # but we can provide it for defense in depth.
18 | #requiredDropCapabilities:
19 | # - ALL
20 | # Allow core volume types.
21 | volumes:
22 | - 'configMap'
23 | - 'emptyDir'
24 | - 'projected'
25 | - 'secret'
26 | - 'downwardAPI'
27 | - 'persistentVolumeClaim'
28 | hostNetwork: false
29 | hostIPC: false
30 | hostPID: false
31 | runAsUser:
32 | # Permits the container to run with root privileges as well.
33 | rule: 'RunAsAny'
34 | seLinux:
35 | # This policy assumes the nodes are using AppArmor rather than SELinux.
36 | rule: 'RunAsAny'
37 | supplementalGroups:
38 | rule: 'MustRunAs'
39 | ranges:
40 | # Forbid adding the root group.
41 | - min: 0
42 | max: 65535
43 | fsGroup:
44 | rule: 'MustRunAs'
45 | ranges:
46 | # Forbid adding the root group.
47 | - min: 0
48 | max: 65535
49 | readOnlyRootFilesystem: false
50 | {{- end }}
51 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: Role
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
6 | namespace: {{ .Release.Namespace }}
7 | annotations:
8 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
10 | labels:
11 | app: {{ include "nri-metadata-injection.name.admission" . }}
12 | {{- include "newrelic.common.labels" . | nindent 4 }}
13 | rules:
14 | - apiGroups:
15 | - ""
16 | resources:
17 | - secrets
18 | verbs:
19 | - get
20 | - create
21 | {{- end }}
22 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml:
--------------------------------------------------------------------------------
1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: RoleBinding
4 | metadata:
5 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
6 | namespace: {{ .Release.Namespace }}
7 | annotations:
8 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
10 | labels:
11 | app: {{ include "nri-metadata-injection.name.admission" . }}
12 | {{- include "newrelic.common.labels" . | nindent 4 }}
13 | roleRef:
14 | apiGroup: rbac.authorization.k8s.io
15 | kind: Role
16 | name: {{ include "nri-metadata-injection.fullname.admission" . }}
17 | subjects:
18 | - kind: ServiceAccount
19 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }}
20 | namespace: {{ .Release.Namespace }}
21 | {{- end }}
22 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | {{- $createServiceAccount := include "newrelic.common.serviceAccount.create" . -}}
2 | {{- if (and $createServiceAccount (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) -}}
3 | apiVersion: v1
4 | kind: ServiceAccount
5 | metadata:
6 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }}
7 | namespace: {{ .Release.Namespace }}
8 | annotations:
9 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade
10 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
11 | labels:
12 | app: {{ include "nri-metadata-injection.name.admission" . }}
13 | {{- include "newrelic.common.labels" . | nindent 4 }}
14 | {{- end }}
15 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: admissionregistration.k8s.io/v1
2 | kind: MutatingWebhookConfiguration
3 | metadata:
4 | name: {{ include "newrelic.common.naming.fullname" . }}
5 | {{- if .Values.certManager.enabled }}
6 | annotations:
7 | certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }}
8 | cert-manager.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }}
9 | {{- end }}
10 | labels:
11 | {{- include "newrelic.common.labels" . | nindent 4 }}
12 | webhooks:
13 | - name: metadata-injection.newrelic.com
14 | clientConfig:
15 | service:
16 | name: {{ include "newrelic.common.naming.fullname" . }}
17 | namespace: {{ .Release.Namespace }}
18 | path: "/mutate"
19 | {{- if not .Values.certManager.enabled }}
20 | caBundle: ""
21 | {{- end }}
22 | rules:
23 | - operations: ["CREATE"]
24 | apiGroups: [""]
25 | apiVersions: ["v1"]
26 | resources: ["pods"]
27 | {{- if .Values.injectOnlyLabeledNamespaces }}
28 | scope: Namespaced
29 | namespaceSelector:
30 | matchLabels:
31 | newrelic-metadata-injection: enabled
32 | {{- end }}
33 | failurePolicy: Ignore
34 | timeoutSeconds: {{ .Values.timeoutSeconds }}
35 | sideEffects: None
36 | admissionReviewVersions: ["v1", "v1beta1"]
37 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/cert-manager.yaml:
--------------------------------------------------------------------------------
1 | {{ if .Values.certManager.enabled }}
2 | ---
3 | # Create a selfsigned Issuer, in order to create a root CA certificate for
4 | # signing webhook serving certificates
5 | apiVersion: cert-manager.io/v1
6 | kind: Issuer
7 | metadata:
8 | name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }}
9 | namespace: {{ .Release.Namespace }}
10 | spec:
11 | selfSigned: {}
12 | ---
13 | # Generate a CA Certificate used to sign certificates for the webhook
14 | apiVersion: cert-manager.io/v1
15 | kind: Certificate
16 | metadata:
17 | name: {{ include "newrelic.common.naming.fullname" . }}-root-cert
18 | namespace: {{ .Release.Namespace }}
19 | spec:
20 | secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert
21 | duration: {{ .Values.certManager.rootCertificateDuration}}
22 | issuerRef:
23 | name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }}
24 | commonName: "ca.webhook.nri"
25 | isCA: true
26 | ---
27 | # Create an Issuer that uses the above generated CA certificate to issue certs
28 | apiVersion: cert-manager.io/v1
29 | kind: Issuer
30 | metadata:
31 | name: {{ include "nri-metadata-injection.fullname.root-issuer" . }}
32 | namespace: {{ .Release.Namespace }}
33 | spec:
34 | ca:
35 | secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert
36 | ---
37 |
38 | # Finally, generate a serving certificate for the webhook to use
39 | apiVersion: cert-manager.io/v1
40 | kind: Certificate
41 | metadata:
42 | name: {{ include "nri-metadata-injection.fullname.webhook-cert" . }}
43 | namespace: {{ .Release.Namespace }}
44 | spec:
45 | secretName: {{ include "nri-metadata-injection.fullname.admission" . }}
46 | duration: {{ .Values.certManager.webhookCertificateDuration }}
47 | issuerRef:
48 | name: {{ include "nri-metadata-injection.fullname.root-issuer" . }}
49 | dnsNames:
50 | - {{ include "newrelic.common.naming.fullname" . }}
51 | - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}
52 | - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc
53 | {{ end }}
54 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "newrelic.common.naming.fullname" . }}
5 | namespace: {{ .Release.Namespace }}
6 | labels:
7 | {{- include "newrelic.common.labels" . | nindent 4 }}
8 | spec:
9 | replicas: {{ .Values.replicas }}
10 | selector:
11 | matchLabels:
12 | {{- /* We cannot use the common library here because of a legacy issue */}}
13 | {{- /* `selector` is immutable and the previous chart did not have all the idiomatic labels */}}
14 | app.kubernetes.io/name: {{ include "newrelic.common.naming.name" . }}
15 | template:
16 | metadata:
17 | {{- if .Values.podAnnotations }}
18 | annotations:
19 | {{- toYaml .Values.podAnnotations | nindent 8 }}
20 | {{- end }}
21 | labels:
22 | {{- include "newrelic.common.labels.podLabels" . | nindent 8 }}
23 | spec:
24 | {{- with include "nri-metadata-injection.securityContext.pod" . }}
25 | securityContext:
26 | {{- . | nindent 8 -}}
27 | {{- end }}
28 | {{- with include "newrelic.common.priorityClassName" . }}
29 | priorityClassName: {{ . }}
30 | {{- end }}
31 | {{- with include "newrelic.common.dnsConfig" . }}
32 | dnsConfig:
33 | {{- . | nindent 8 }}
34 | {{- end }}
35 | hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }}
36 | {{- if include "newrelic.common.hostNetwork" . }}
37 | dnsPolicy: ClusterFirstWithHostNet
38 | {{- end }}
39 |
40 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.image.pullSecrets ) "context" .) }}
41 | imagePullSecrets:
42 | {{- . | nindent 8 -}}
43 | {{- end }}
44 | containers:
45 | - name: {{ include "newrelic.common.naming.name" . }}
46 | image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }}
47 | imagePullPolicy: {{ .Values.image.pullPolicy }}
48 | {{- with include "newrelic.common.securityContext.container" . }}
49 | securityContext:
50 | {{- . | nindent 10 }}
51 | {{- end }}
52 | env:
53 | - name: clusterName
54 | value: {{ include "newrelic.common.cluster" . }}
55 | ports:
56 | - containerPort: 8443
57 | protocol: TCP
58 | volumeMounts:
59 | - name: tls-key-cert-pair
60 | mountPath: /etc/tls-key-cert-pair
61 | readinessProbe:
62 | httpGet:
63 | path: /health
64 | port: 8080
65 | initialDelaySeconds: 1
66 | periodSeconds: 1
67 | {{- if .Values.resources }}
68 | resources:
69 | {{ toYaml .Values.resources | nindent 10 }}
70 | {{- end }}
71 | volumes:
72 | - name: tls-key-cert-pair
73 | secret:
74 | secretName: {{ include "nri-metadata-injection.fullname.admission" . }}
75 | nodeSelector:
76 | kubernetes.io/os: linux
77 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }}
78 | {{- with include "newrelic.common.tolerations" . }}
79 | tolerations:
80 | {{- . | nindent 8 -}}
81 | {{- end }}
82 | {{- with include "newrelic.common.affinity" . }}
83 | affinity:
84 | {{- . | nindent 8 -}}
85 | {{- end }}
86 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "newrelic.common.naming.fullname" . }}
5 | namespace: {{ .Release.Namespace }}
6 | labels:
7 | {{- include "newrelic.common.labels" . | nindent 4 }}
8 | spec:
9 | ports:
10 | - port: 443
11 | targetPort: 8443
12 | selector:
13 | {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }}
14 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/tests/cluster_test.yaml:
--------------------------------------------------------------------------------
1 | suite: test cluster environment variable setup
2 | templates:
3 | - templates/deployment.yaml
4 | release:
5 | name: release
6 | namespace: ns
7 | tests:
8 | - it: clusterName env is properly set
9 | set:
10 | cluster: my-cluster
11 | asserts:
12 | - contains:
13 | path: spec.template.spec.containers[0].env
14 | content:
15 | name: clusterName
16 | value: my-cluster
17 | - it: fail when cluster is not defined
18 | asserts:
19 | - failedTemplate:
20 | errorMessage: There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required.
21 | - it: has a linux node selector by default
22 | set:
23 | cluster: my-cluster
24 | asserts:
25 | - equal:
26 | path: spec.template.spec.nodeSelector
27 | value:
28 | kubernetes.io/os: linux
29 | - it: has a linux node selector and additional selectors
30 | set:
31 | cluster: my-cluster
32 | nodeSelector:
33 | aCoolTestLabel: aCoolTestValue
34 | asserts:
35 | - equal:
36 | path: spec.template.spec.nodeSelector
37 | value:
38 | kubernetes.io/os: linux
39 | aCoolTestLabel: aCoolTestValue
40 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml:
--------------------------------------------------------------------------------
1 | suite: test job' serviceAccount
2 | templates:
3 | - templates/admission-webhooks/job-patch/job-createSecret.yaml
4 | - templates/admission-webhooks/job-patch/job-patchWebhook.yaml
5 | release:
6 | name: my-release
7 | namespace: my-namespace
8 | tests:
9 | - it: RBAC points to the service account that is created by default
10 | set:
11 | cluster: test-cluster
12 | rbac.create: true
13 | serviceAccount.create: true
14 | asserts:
15 | - equal:
16 | path: spec.template.spec.serviceAccountName
17 | value: my-release-nri-metadata-injection-admission
18 |
19 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled
20 | set:
21 | cluster: test-cluster
22 | rbac.create: true
23 | serviceAccount.create: false
24 | serviceAccount.name: sa-test
25 | asserts:
26 | - equal:
27 | path: spec.template.spec.serviceAccountName
28 | value: sa-test
29 |
30 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled
31 | set:
32 | cluster: test-cluster
33 | rbac.create: true
34 | serviceAccount.create: false
35 | asserts:
36 | - equal:
37 | path: spec.template.spec.serviceAccountName
38 | value: default
39 |
40 | - it: has a linux node selector by default
41 | set:
42 | cluster: my-cluster
43 | asserts:
44 | - equal:
45 | path: spec.template.spec.nodeSelector
46 | value:
47 | kubernetes.io/os: linux
48 |
49 | - it: has a linux node selector and additional selectors
50 | set:
51 | cluster: my-cluster
52 | nodeSelector:
53 | aCoolTestLabel: aCoolTestValue
54 | asserts:
55 | - equal:
56 | path: spec.template.spec.nodeSelector
57 | value:
58 | kubernetes.io/os: linux
59 | aCoolTestLabel: aCoolTestValue
60 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/tests/rbac_test.yaml:
--------------------------------------------------------------------------------
1 | suite: test RBAC creation
2 | templates:
3 | - templates/admission-webhooks/job-patch/rolebinding.yaml
4 | - templates/admission-webhooks/job-patch/clusterrolebinding.yaml
5 | release:
6 | name: my-release
7 | namespace: my-namespace
8 | tests:
9 | - it: RBAC points to the service account that is created by default
10 | set:
11 | cluster: test-cluster
12 | rbac.create: true
13 | serviceAccount.create: true
14 | asserts:
15 | - equal:
16 | path: subjects[0].name
17 | value: my-release-nri-metadata-injection-admission
18 |
19 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled
20 | set:
21 | cluster: test-cluster
22 | rbac.create: true
23 | serviceAccount.create: false
24 | serviceAccount.name: sa-test
25 | asserts:
26 | - equal:
27 | path: subjects[0].name
28 | value: sa-test
29 |
30 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled
31 | set:
32 | cluster: test-cluster
33 | rbac.create: true
34 | serviceAccount.create: false
35 | asserts:
36 | - equal:
37 | path: subjects[0].name
38 | value: default
39 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/tests/volume_mounts_test.yaml:
--------------------------------------------------------------------------------
1 | suite: check volume mounts is properly set
2 | templates:
3 | - templates/admission-webhooks/job-patch/job-createSecret.yaml
4 | - templates/admission-webhooks/job-patch/job-patchWebhook.yaml
5 | release:
6 | name: release
7 | namespace: ns
8 | tests:
9 | - it: clusterName env is properly set
10 | set:
11 | cluster: my-cluster
12 | jobImage:
13 | volumeMounts:
14 | - name: test-volume
15 | volumePath: /test-volume
16 | volumes:
17 | - name: test-volume-container
18 | emptyDir: {}
19 |
20 | asserts:
21 | - contains:
22 | path: spec.template.spec.containers[0].volumeMounts
23 | content:
24 | name: test-volume
25 | volumePath: /test-volume
26 | - contains:
27 | path: spec.template.spec.volumes
28 | content:
29 | name: test-volume-container
30 | emptyDir: {}
31 |
--------------------------------------------------------------------------------
/charts/nri-metadata-injection/values.yaml:
--------------------------------------------------------------------------------
1 | # -- Override the name of the chart
2 | nameOverride: ""
3 | # -- Override the full name of the release
4 | fullnameOverride: ""
5 |
6 | # -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster`
7 | cluster: ""
8 |
9 | # -- Image for the New Relic Metadata Injector
10 | # @default -- See `values.yaml`
11 | image:
12 | registry:
13 | repository: newrelic/k8s-metadata-injection
14 | tag: "" # Defaults to chart's appVersion
15 | pullPolicy: IfNotPresent
16 | # -- The secrets that are needed to pull images from a custom registry.
17 | pullSecrets: []
18 | # - name: regsecret
19 |
20 | # -- Image for creating the needed certificates of this webhook to work
21 | # @default -- See `values.yaml`
22 | jobImage:
23 | registry: # Defaults to registry.k8s.io
24 | repository: ingress-nginx/kube-webhook-certgen
25 | tag: v1.4.3
26 | pullPolicy: IfNotPresent
27 | # -- The secrets that are needed to pull images from a custom registry.
28 | pullSecrets: []
29 | # - name: regsecret
30 |
31 | # -- Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies
32 | # Enforce a read-only root.
33 | volumeMounts: []
34 | # - name: tmp
35 | # mountPath: /tmp
36 |
37 | # -- Volumes to add to the job container
38 | volumes: []
39 | # - name: tmp
40 | # emptyDir: {}
41 |
42 | rbac:
43 | # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects.
44 | pspEnabled: false
45 |
46 | replicas: 1
47 |
48 | # -- Additional labels for chart objects. Can be configured also with `global.labels`
49 | labels: {}
50 | # -- Annotations to be added to all pods created by the integration.
51 | podAnnotations: {}
52 | # -- Additional labels for chart pods. Can be configured also with `global.podLabels`
53 | podLabels: {}
54 |
55 | # -- Image for creating the needed certificates of this webhook to work
56 | # @default -- 100m/30M -/80M
57 | resources:
58 | limits:
59 | memory: 80M
60 | requests:
61 | cpu: 100m
62 | memory: 30M
63 |
64 | # -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName`
65 | priorityClassName: ""
66 | # -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork`
67 | # @default -- false
68 | hostNetwork:
69 | # -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig`
70 | dnsConfig: {}
71 | # -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext`
72 | podSecurityContext: {}
73 | # -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext`
74 | containerSecurityContext: {}
75 |
76 | certManager:
77 | # certManager.enabled -- Use cert manager for webhook certs
78 | enabled: false
79 | # -- Sets the root certificate duration. Defaults to 43800h (5 years).
80 | rootCertificateDuration: 43800h
81 | # -- Sets certificate duration. Defaults to 8760h (1 year).
82 | webhookCertificateDuration: 8760h
83 |
84 | # -- Sets pod/node affinities. Can be configured also with `global.affinity`
85 | affinity: {}
86 | # -- Sets pod's node selector. Can be configured also with `global.nodeSelector`
87 | nodeSelector: {}
88 | # -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations`
89 | tolerations: []
90 |
91 | # -- Enable the metadata decoration only for pods living in namespaces labeled
92 | # with 'newrelic-metadata-injection=enabled'.
93 | injectOnlyLabeledNamespaces: false
94 |
95 | # -- Use custom tls certificates for the webhook, or let the chart handle it
96 | # automatically.
97 | # Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection
98 | customTLSCertificate: false
99 |
100 | # -- Webhook timeout
101 | # Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts
102 | timeoutSeconds: 28
103 |
--------------------------------------------------------------------------------
/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "os"
10 | "os/signal"
11 | "path/filepath"
12 | "strings"
13 | "syscall"
14 | "time"
15 |
16 | "github.com/fsnotify/fsnotify"
17 | "github.com/kelseyhightower/envconfig"
18 | "go.uber.org/zap"
19 | "go.uber.org/zap/zapcore"
20 |
21 | "github.com/newrelic/k8s-metadata-injection/src/server"
22 | )
23 |
24 | const (
25 | appName = "new-relic-k8s-metadata-injection"
26 | )
27 |
28 | // specification contains the specs for this app.
29 | type specification struct {
30 | Port int `default:"8443"` // Webhook server port.
31 | TLSCertFile string `default:"/etc/tls-key-cert-pair/tls.crt" envconfig:"tls_cert_file"` // File containing the x509 Certificate for HTTPS.
32 | TLSKeyFile string `default:"/etc/tls-key-cert-pair/tls.key" envconfig:"tls_key_file"` // File containing the x509 private key for TLSCERTFILE.
33 | ClusterName string `default:"cluster" split_words:"true"` // The name of the Kubernetes cluster.
34 | Timeout time.Duration `default:"1s"` // Server timeout for the pod mutation.
35 | }
36 |
37 | func main() {
38 | var s specification
39 | err := envconfig.Process(strings.Replace(appName, "-", "_", -1), &s)
40 | if err != nil {
41 | log.Fatal(err.Error())
42 | }
43 |
44 | logger := setupLogger()
45 | defer func() { _ = logger.Sync() }()
46 |
47 | pair, err := tls.LoadX509KeyPair(s.TLSCertFile, s.TLSKeyFile)
48 | if err != nil {
49 | logger.Errorw("failed to load key pair", "err", err)
50 | }
51 |
52 | watcher, _ := fsnotify.NewWatcher()
53 | defer func() { _ = watcher.Close() }()
54 | // Watch the parent directory of the key/cert files so we can catch
55 | // symlink updates of k8s secrets volumes and reload the certificates whenever they change.
56 | watchDir, _ := filepath.Split(s.TLSCertFile)
57 | if err := watcher.Add(watchDir); err != nil {
58 | logger.Errorw("could not watch folder", "folder", watchDir, "err", err)
59 | }
60 |
61 | whsvr := &server.Webhook{
62 | KeyFile: s.TLSKeyFile,
63 | CertFile: s.TLSCertFile,
64 | Cert: &pair,
65 | ClusterName: s.ClusterName,
66 | CertWatcher: watcher,
67 | Server: &http.Server{
68 | Addr: fmt.Sprintf(":%d", s.Port),
69 | },
70 | Logger: logger,
71 | }
72 | whsvr.Server.TLSConfig = &tls.Config{GetCertificate: whsvr.GetCert}
73 |
74 | mux := http.NewServeMux()
75 | mux.Handle("/mutate", withLoggingMiddleware(logger)(withTimeoutMiddleware(s.Timeout)(whsvr)))
76 | whsvr.Server.Handler = mux
77 |
78 | // The health check needs to be in another server because it cannot be under TLS.
79 | readinessProbe := server.TLSReadyReadinessProbe(whsvr)
80 | go func() {
81 | logger.Info("starting the TLS readiness server")
82 | if err := http.ListenAndServe(":8080", readinessProbe); err != nil {
83 | logger.Errorw("failed to start TLS readiness server", "err", err)
84 | }
85 | }()
86 |
87 | go func() {
88 | logger.Info("starting the webhook server")
89 | if err := whsvr.Server.ListenAndServeTLS("", ""); err != nil {
90 | logger.Errorw("failed to start webhook server", "err", err)
91 | }
92 | }()
93 |
94 | signalChan := make(chan os.Signal, 1)
95 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
96 |
97 | var debounceTimer <-chan time.Time
98 | for {
99 | select {
100 | case <-debounceTimer:
101 | pair, err := tls.LoadX509KeyPair(whsvr.CertFile, whsvr.KeyFile)
102 | if err != nil {
103 | logger.Errorw("reload cert error", "err", err)
104 | break
105 | }
106 | whsvr.Lock()
107 | whsvr.Cert = &pair
108 | whsvr.Unlock()
109 | logger.Info("cert/key pair reloaded!")
110 | case event := <-whsvr.CertWatcher.Events:
111 | if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
112 | debounceTimer = time.After(500 * time.Millisecond)
113 | }
114 | case <-signalChan:
115 | logger.Info("got OS shutdown signal, shutting down webhook server gracefully...")
116 | _ = watcher.Close()
117 | _ = whsvr.Server.Shutdown(context.Background())
118 | return
119 | }
120 | }
121 | }
122 |
123 | func withTimeoutMiddleware(timeout time.Duration) func(next http.Handler) http.Handler {
124 | return func(next http.Handler) http.Handler {
125 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126 | http.TimeoutHandler(next, timeout, "server timeout").ServeHTTP(w, r)
127 | })
128 | }
129 | }
130 |
131 | func withLoggingMiddleware(logger *zap.SugaredLogger) func(next http.Handler) http.Handler {
132 | return func(next http.Handler) http.Handler {
133 | fn := func(w http.ResponseWriter, r *http.Request) {
134 | scheme := "http"
135 | if r.TLS != nil {
136 | scheme = "https"
137 | }
138 | logger.Infof("%s %s://%s%s %s\" from %s", r.Method, scheme, r.Host, r.RequestURI, r.Proto, r.RemoteAddr)
139 |
140 | next.ServeHTTP(w, r)
141 | }
142 |
143 | return http.HandlerFunc(fn)
144 | }
145 | }
146 |
147 | func setupLogger() *zap.SugaredLogger {
148 | config := zap.NewProductionConfig()
149 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // We want human readable timestamps.
150 |
151 | zapLogger, err := config.Build()
152 | if err != nil {
153 | log.Fatalf("can't initialize zap logger: %v", err)
154 | }
155 | return zapLogger.Sugar()
156 | }
157 |
--------------------------------------------------------------------------------
/docs/k8s-api-lifecycle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/lifecycle.md:
--------------------------------------------------------------------------------
1 | # Kubernetes Request internal lifecycle
2 |
3 | Kubernetes API serves as the foundation for the declarative configuration schema for the system, among others. Kubernetes itself is decomposed into multiple components, which interact through its API.
4 |
5 | Any request made to K8s API is affected by a timeout; by default **60 seconds** (see [source code](https://github.com/kubernetes/apiserver/blob/b8915a5609e4d7553d92f0d431ba04ecf9b52777/pkg/server/config.go#L262)).
6 |
7 | ## Mutating Webhook
8 |
9 | The injection of New Relic APM Metadata is implemented as an Admission Webhook (`MutatingAdmissionWebhook`) using [Kubernetes Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers).
10 |
11 | Admission Controllers run as part of the Kubernetes API Request Lifecycle.
12 |
13 | > An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.
14 | >
15 | > --- https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-are-they
16 |
17 | The default request timeout between the Kubernetes API and any Admission Webhook is **30 seconds** (the value is being forced. See [source code](https://github.com/kubernetes/apiserver/blob/e3d77264915da75023b171c7e370415e740851c7/pkg/util/webhook/webhook.go#L36)).
18 |
19 | ### Response
20 |
21 | Kubernetes API expects that successful calls return a `200 OK` HTTP code including a `ReviewResponse` in the body.
22 |
23 | #### Response schema
24 |
25 | Please refer to our [Open Api 3.0 spec](/openapi.yaml) file in order to know more about the shape of the response body.
26 |
27 | #### K8s API Error Handling
28 |
29 | Please find here a flow diagram of the internal Kubernetes API Lifecycle. Pay special attention to the [failurePolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#webhook-v1beta1-admissionregistration) config value and how it determines the final behaviour.
30 |
31 | 
32 |
33 | As an alternative, you can see the [source code](https://github.com/kubernetes/apiserver/blob/master/pkg/admission/plugin/webhook/mutating/dispatcher.go#L56) of the Dispatcher that executes the Mutating Webhook.
34 |
35 | #### Non-intrusive commitment
36 |
37 | We are committed to keep the webhook as a non-intrusive mechanism for injecting the required env vars into the containers.
38 | In order to achieve this we took the following design decisions:
39 |
40 | 1. The `failurePolicy` is set to `Ignore`. Any response from our webhook with a non success HTTP Status Code will be then skipped, so the Kubernetes API will continue the execution of the request lifecycle.
41 | 2. Any `200 OK` response coming from the webhook contains `allowed: true` within the `response` included in the body. This tells the Kubernetes API that the creation of such workload is allowed, letting then to continue the execution of the request lifecycle.
42 |
--------------------------------------------------------------------------------
/docs/performance.md:
--------------------------------------------------------------------------------
1 | # Performance
2 |
3 | The injection of New Relic APM Metadata is implemented as a webhook using Kubernetes MutatingAdmissionWebhook, which is in fact an AdmissionWebhook.
4 |
5 | Admission webhooks run as part of the Kubernetes API Request Lifecycle.
6 |
7 | > An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.
8 | >
9 | > --- https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-are-they
10 |
11 | One of our top priorities is to code it as lightweight as possible, making the webhook behave as non-intrusive and to perform almost at no cost.
12 |
13 | Therefore, we ran the following benchmark and performance tests.
14 |
15 | ## Kubernetes Request lifecycle
16 |
17 | Please refer to the [Request internal lifecycle](lifecycle.md) documentation.
18 |
19 | ## Benchmark of the webhook code
20 |
21 | The MutatingAdmissionWebhook code has been tested using Golang Benchmarks.
22 |
23 | The slowest run was on average 1005508 ns/op. The fastest on average was 434999 ns/op. The "average of the average" was 653154 ns/op. All these values are 1 millisecond or less. On a real word situation it will be slower due to different size of each pod's creation payload and to the TLS overhead.
24 |
25 | The code of the benchmark can be found [here](./webhook_test.go).
26 |
27 | ### Results
28 |
29 | * These tests were ran on a 2018 Macbook Pro with a core i7 2.7 GHz and 16 GB of memory.
30 | * They were recorded on Jan 4th, 2019.
31 | * Latest commit SHA was `ab4b0de131c4e2ea51089e1441d5e571baa3803e`
32 |
33 |
34 | ```
35 | $ go test -bench .
36 |
37 | goos: darwin
38 | goarch: amd64
39 | pkg: github.com/newrelic/k8s-metadata-injection
40 | Benchmark_WebhookPerformance-8 3000 434999 ns/op
41 | PASS
42 | ok github.com/newrelic/k8s-metadata-injection 1.420s
43 |
44 | $ go test -bench .
45 | goos: darwin
46 | goarch: amd64
47 | pkg: github.com/newrelic/k8s-metadata-injection
48 | Benchmark_WebhookPerformance-8 2000 604123 ns/op
49 | PASS
50 | ok github.com/newrelic/k8s-metadata-injection 1.342s
51 |
52 | $ go test -bench .
53 | goos: darwin
54 | goarch: amd64
55 | pkg: github.com/newrelic/k8s-metadata-injection
56 | Benchmark_WebhookPerformance-8 3000 505401 ns/op
57 | PASS
58 | ok github.com/newrelic/k8s-metadata-injection 1.647s
59 |
60 | $ go test -bench .
61 | goos: darwin
62 | goarch: amd64
63 | pkg: github.com/newrelic/k8s-metadata-injection
64 | Benchmark_WebhookPerformance-8 2000 642582 ns/op
65 | PASS
66 | ok github.com/newrelic/k8s-metadata-injection 1.433s
67 |
68 | $ go test -bench .
69 | goos: darwin
70 | goarch: amd64
71 | pkg: github.com/newrelic/k8s-metadata-injection
72 | Benchmark_WebhookPerformance-8 2000 747675 ns/op
73 | PASS
74 | ok github.com/newrelic/k8s-metadata-injection 1.662s
75 |
76 | $ go test -bench .
77 | goos: darwin
78 | goarch: amd64
79 | pkg: github.com/newrelic/k8s-metadata-injection
80 | Benchmark_WebhookPerformance-8 2000 837064 ns/op
81 | PASS
82 | ok github.com/newrelic/k8s-metadata-injection 1.847s
83 |
84 | $ go test -bench .
85 | goos: darwin
86 | goarch: amd64
87 | pkg: github.com/newrelic/k8s-metadata-injection
88 | Benchmark_WebhookPerformance-8 1000 1005508 ns/op
89 | PASS
90 | ok github.com/newrelic/k8s-metadata-injection 1.183s
91 |
92 | $ go test -bench .
93 | goos: darwin
94 | goarch: amd64
95 | pkg: github.com/newrelic/k8s-metadata-injection
96 | Benchmark_WebhookPerformance-8 3000 447884 ns/op
97 | PASS
98 | ok github.com/newrelic/k8s-metadata-injection 1.461s
99 | ```
100 |
101 | ## Benchmark having the Mutating Webhook in place
102 |
103 | After running the code benchmark we realized that running benchmarks in real clusters are not important. As the service is simple and extremely fast, it should have a close to **zero** perceptive impact in pod creation in the cases where it would run.
104 |
105 | We will use different tools to ensure performance is focused in this service in the form of Golang benchmarks and a server side timeout to prevent perceptive interference in pod creation time.
106 |
107 |
--------------------------------------------------------------------------------
/e2e-tests/k8s-e2e-bootstraping.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | E2E_KUBERNETES_VERSION=${E2E_KUBERNETES_VERSION:-v1.32.0}
4 | E2E_MINIKUBE_DRIVER=${E2E_MINIKUBE_DRIVER:-docker}
5 | E2E_SUDO=${E2E_SUDO:-}
6 |
7 | start_minikube() {
8 | export MINIKUBE_WANTREPORTERRORPROMPT=false
9 | export MINIKUBE_HOME=$HOME
10 | export CHANGE_MINIKUBE_NONE_USER=true
11 | mkdir -p "$HOME"/.kube
12 | touch "$HOME"/.kube/config
13 | export KUBECONFIG=$HOME/.kube/config
14 |
15 | printf "Starting Minikube with Kubernetes version %s...\n" "${E2E_KUBERNETES_VERSION}"
16 | $E2E_SUDO minikube start --driver="$E2E_MINIKUBE_DRIVER" --kubernetes-version="$E2E_KUBERNETES_VERSION"
17 | }
18 |
19 | get_pod_name_by_label() {
20 | pod_name=""
21 | i=1
22 | while [ "$i" -ne 10 ]
23 | do
24 | pod_name=$(kubectl -n default get pods -l "$1" -o name | sed 's/pod\///g; s/pods\///g')
25 | if [ "$pod_name" != "" ]; then
26 | break
27 | fi
28 | sleep 1
29 | i=$((i + 1))
30 | done
31 | printf "%s" "$pod_name"
32 | }
33 |
34 | wait_for_pod() {
35 | set +e
36 | desired_status=${2:-'Running'}
37 | is_pod_in_desired_status=false
38 | i=1
39 | while [ "$i" -ne 30 ]
40 | do
41 | pod_status="$(kubectl -n default get pod "$1" -o jsonpath='{.status.phase}')"
42 | if [ "$pod_status" = "$desired_status" ]; then
43 | is_pod_in_desired_status=true
44 | printf "pod %s is %s\n" "$1" "$desired_status"
45 | break
46 | fi
47 |
48 | printf "Waiting for pod %s to be %s\n" "$1" "$desired_status"
49 | sleep 3
50 | i=$((i + 1))
51 | done
52 | if [ $is_pod_in_desired_status = "false" ]; then
53 | printf "pod %s does not transition to %s within 1 minute 30 seconds\n" "$1" "$desired_status"
54 | kubectl -n default get pods
55 | kubectl -n default describe pod "$1"
56 | exit 1
57 | fi
58 | set -e
59 | }
60 |
61 | ### Bootstraping
62 |
63 | cd "$(dirname "$0")"
64 |
65 | start_minikube
66 | minikube version
67 | minikube update-context
68 |
69 | is_kube_running="false"
70 |
71 | set +e
72 | # this for loop waits until kubectl can access the api server that Minikube has created
73 | i=1
74 | while [ "$i" -ne 90 ] # timeout for 3 minutes
75 | do
76 | kubectl get po 1>/dev/null 2>&1
77 | if [ $? -ne 1 ]; then
78 | is_kube_running="true"
79 | break
80 | fi
81 |
82 | printf "waiting for Kubernetes cluster up\n"
83 | sleep 2
84 | i=$((i + 1))
85 | done
86 |
87 | if [ $is_kube_running = "false" ]; then
88 | minikube logs
89 | printf "Kubernetes did not start within 3 minutes. Something went wrong.\n"
90 | exit 1
91 | fi
92 | set -e
93 |
--------------------------------------------------------------------------------
/e2e-tests/tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | printf 'bootstrapping starts:\n'
5 | # shellcheck disable=SC1090
6 | . "$(dirname "$0")/k8s-e2e-bootstraping.sh"
7 | printf 'bootstrapping complete\n'
8 |
9 | HELM_RELEASE_NAME="nri-metadata-injection"
10 | WEBHOOK_LABEL="app.kubernetes.io/name=nri-metadata-injection,app.kubernetes.io/instance=${HELM_RELEASE_NAME}"
11 | DUMMY_DEPLOYMENT_NAME="dummy-deployment"
12 | DUMMY_POD_LABEL="app=${DUMMY_DEPLOYMENT_NAME}"
13 | ENV_VARS_PREFIX="NEW_RELIC_METADATA_KUBERNETES"
14 | NAMESPACE_NAME="$(kubectl config view --minify --output 'jsonpath={..namespace}')"
15 | IMAGE_NAME="e2e/k8s-metadata-injection"
16 | IMAGE_TAG="e2e"
17 |
18 | finish() {
19 | printf "webhook logs:\n"
20 | kubectl logs "$(get_pod_name_by_label "$WEBHOOK_LABEL")" || true
21 |
22 | helm uninstall "$HELM_RELEASE_NAME" || true
23 | kubectl delete deployment ${DUMMY_DEPLOYMENT_NAME} || true
24 | }
25 |
26 | # ensure that we build docker image in minikube
27 | [ "$E2E_MINIKUBE_DRIVER" = "none" ] || eval "$(minikube docker-env --shell bash)"
28 |
29 | # build webhook docker image
30 |
31 | # Set GOOS and GOARCH explicitly since Dockerfile expects them in the binary name
32 | GOOS="linux" GOARCH="amd64" IMAGE_NAME="$IMAGE_NAME" DOCKER_IMAGE_TAG="$IMAGE_TAG" make -C .. compile build-container
33 |
34 | trap finish EXIT
35 | chmod go-r /home/runner/.kube/config
36 | # install the metadata-injection webhook
37 | helm repo add newrelic https://helm-charts.newrelic.com
38 | helm dependency build ../charts/nri-metadata-injection
39 | if ! helm upgrade --install "$HELM_RELEASE_NAME" ../charts/nri-metadata-injection \
40 | --wait \
41 | --set cluster=YOUR-CLUSTER-NAME \
42 | --set image.pullPolicy=Never \
43 | --set image.tag="$IMAGE_TAG"
44 | then
45 | printf "Helm failed to install this release\n"
46 | exit 1
47 | fi
48 |
49 | ### Testing
50 |
51 | # deploy a pod
52 | kubectl create deployment "$DUMMY_DEPLOYMENT_NAME" --image=nginx:latest --dry-run=client -o yaml | kubectl apply -f-
53 |
54 | pod_name="$(get_pod_name_by_label "$DUMMY_POD_LABEL")"
55 | if [ "$pod_name" = "" ]; then
56 | printf "not found any pod with label %s\n" "$DUMMY_POD_LABEL"
57 | kubectl describe deployment "$DUMMY_DEPLOYMENT_NAME"
58 | exit 1
59 | fi
60 | wait_for_pod "$pod_name"
61 |
62 | kubectl get pods
63 | kubectl describe pod "${pod_name}"
64 |
65 | printf "getting env vars for %s\n" "${pod_name}"
66 | set +e # This grep can be empty in the webhook is not correctly running and we want logs and a proper error
67 | date
68 | env_vars="$(kubectl exec "${pod_name}" -- env | grep "${ENV_VARS_PREFIX}")"
69 | set -e
70 | printf "\nInjected environment variables:\n"
71 | printf "%s\n" "$env_vars"
72 |
73 | errors=""
74 | for PAIR in \
75 | "CLUSTER_NAME YOUR-CLUSTER-NAME" \
76 | "NODE_NAME minikube" \
77 | "NAMESPACE_NAME ${NAMESPACE_NAME}" \
78 | "POD_NAME ${pod_name}" \
79 | "CONTAINER_NAME nginx" \
80 | "CONTAINER_IMAGE_NAME nginx:latest" \
81 | "DEPLOYMENT_NAME ${DUMMY_DEPLOYMENT_NAME}"
82 | do
83 | k=$(echo "$PAIR" | awk '{ print $1 }')
84 | v=$(echo "$PAIR" | awk '{ print $2 }')
85 | if ! echo "$env_vars" | grep -q "${ENV_VARS_PREFIX}_${k}=${v}$"; then
86 | errors="${errors}\n${ENV_VARS_PREFIX}_${k}=${v} is not present"
87 | fi
88 | done
89 |
90 | if [ -n "$errors" ]; then
91 | printf "Test errors:%s\n" "$errors"
92 | exit 1
93 | else
94 | printf "Tests are passing successfully\n\n"
95 | fi
96 |
--------------------------------------------------------------------------------
/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | export NEW_RELIC_K8S_METADATA_INJECTION_CLUSTER_NAME=${clusterName}
4 |
5 | exec /app/k8s-metadata-injection 2>&1
6 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/newrelic/k8s-metadata-injection
2 |
3 | go 1.24.3
4 |
5 | require (
6 | github.com/fsnotify/fsnotify v1.9.0
7 | github.com/kelseyhightower/envconfig v1.4.0
8 | github.com/stretchr/testify v1.10.0
9 | go.uber.org/zap v1.27.0
10 | k8s.io/api v0.33.0
11 | k8s.io/apimachinery v0.33.0
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
16 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
17 | github.com/go-logr/logr v1.4.2 // indirect
18 | github.com/gogo/protobuf v1.3.2 // indirect
19 | github.com/google/gofuzz v1.2.0 // indirect
20 | github.com/json-iterator/go v1.1.12 // indirect
21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
22 | github.com/modern-go/reflect2 v1.0.2 // indirect
23 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
24 | github.com/x448/float16 v0.8.4 // indirect
25 | go.uber.org/multierr v1.10.0 // indirect
26 | golang.org/x/net v0.38.0 // indirect
27 | golang.org/x/sys v0.33.0 // indirect
28 | golang.org/x/text v0.25.0 // indirect
29 | gopkg.in/inf.v0 v0.9.1 // indirect
30 | gopkg.in/yaml.v3 v3.0.1 // indirect
31 | k8s.io/klog/v2 v2.130.1 // indirect
32 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
33 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
34 | sigs.k8s.io/randfill v1.0.0 // indirect
35 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
36 | sigs.k8s.io/yaml v1.4.0 // indirect
37 | )
38 |
39 | // To avoid CVE-2022-27191 triggering a security scan
40 | replace golang.org/x/crypto => golang.org/x/crypto v0.38.0
41 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
6 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
7 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
8 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
9 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
10 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
11 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
12 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
13 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
14 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
15 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
16 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
17 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
18 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
19 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
20 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
21 | github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
22 | github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
23 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
24 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
25 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
26 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
27 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
28 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
29 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
31 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
32 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
33 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
35 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
36 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
37 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
38 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
39 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
40 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
42 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
43 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
44 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
45 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
46 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
47 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
48 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
49 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
50 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
51 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
52 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
53 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
54 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
55 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
56 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
57 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
58 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
59 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
60 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
61 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
62 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
63 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
64 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
65 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
66 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
67 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
68 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
69 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
70 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
71 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
72 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
73 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
74 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
75 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
76 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
77 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
78 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
79 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
80 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
81 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
82 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
83 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
84 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
85 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
86 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
87 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
88 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
89 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
91 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
92 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
93 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
94 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
95 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
96 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
97 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
98 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
99 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
100 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
101 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
102 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
103 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
104 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
105 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
107 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
108 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
109 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
110 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
111 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
112 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
113 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
114 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
115 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
116 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
117 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
118 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
119 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
120 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
121 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
122 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
123 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
124 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
125 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
126 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
127 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
128 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
129 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
130 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
131 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
132 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
133 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
134 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
135 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
136 | k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
137 | k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
138 | k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
139 | k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
140 | k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
141 | k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
142 | k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
143 | k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
144 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
145 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
146 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
147 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
148 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
149 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
150 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
151 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
152 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
153 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
154 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
155 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
156 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
157 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
158 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
159 |
--------------------------------------------------------------------------------
/openapi.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.0
2 | info:
3 | description: >-
4 | The injection of New Relic APM Metadata is implemented as a webhook using
5 | Kubernetes MutatingAdmissionWebhook.
6 | title: New Relic Kubernetes Metadata Injection MutatingAdmissionWebhook
7 | version: 1.0.0
8 | servers:
9 | - url: 'https://newrelic-metadata-injection-svc.default.svc/'
10 | paths:
11 | /mutate:
12 | post:
13 | parameters:
14 | - description: >-
15 | The expected timeout. This value is the timeout that the Kubernetes
16 | API client uses when calling the webhook. This is provided in order
17 | to cancel the operation in case it reaches such timeout since
18 | Kubernetes will discard it anyway.
19 | example: >-
20 | 30s
21 | in: query
22 | name: timeout
23 | schema:
24 | type: string
25 | responses:
26 | '200':
27 | content:
28 | application/json:
29 | schema:
30 | $ref: '#/components/schemas/AdmissionReviewResponse'
31 | description: >-
32 | The request was successful. Note that in case the status field is
33 | present, the mutation was not executed.
34 | '400':
35 | description: Bad request.
36 | '500':
37 | description: Internal server error.
38 | description: >-
39 | The injection of New Relic APM Metadata is implemented as a webhook
40 | using Kubernetes MutatingAdmissionWebhook. This is the entry point of
41 | such Webhook.
42 | operationId: mutate
43 | requestBody:
44 | content:
45 | application/json:
46 | schema:
47 | $ref: '#/components/schemas/AdmissionReviewRequest'
48 | description: >-
49 | `AdmissionReview` with Request object. It contains the info about the
50 | object to be mutated.
51 | required: true
52 | summary: Mutate pod. Inject New Relic Agent env vars.
53 | components:
54 | schemas:
55 | AdmissionRequest:
56 | properties:
57 | uid:
58 | description: >-
59 | Identifier for the individual request/response. This should be
60 | copied over from the corresponding AdmissionRequest
61 | type: string
62 | type: object
63 | AdmissionResponse:
64 | properties:
65 | allowed:
66 | default: false
67 | description: >-
68 | Indicates whether or not the admission request was permitted. If
69 | false, the creation of the related workload will fail no matters the
70 | chosen failurePolicy
71 | type: boolean
72 | patch:
73 | description: >-
74 | The patch body. Currently there is only support "JSONPatch" which
75 | implements RFC 6902
76 | example: >-
77 | W3sib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2VudiIsInZhbHVlIjpbeyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfQ0xVU1RFUl9OQU1FIiwidmFsdWUiOiJmb29iYXIifV19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX05PREVfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJzcGVjLm5vZGVOYW1lIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzAvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfTkFNRVNQQUNFX05BTUUiLCJ2YWx1ZUZyb20iOnsiZmllbGRSZWYiOnsiZmllbGRQYXRoIjoibWV0YWRhdGEubmFtZXNwYWNlIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzAvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfUE9EX05BTUUiLCJ2YWx1ZUZyb20iOnsiZmllbGRSZWYiOnsiZmllbGRQYXRoIjoibWV0YWRhdGEubmFtZSJ9fX19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX0NPTlRBSU5FUl9OQU1FIiwidmFsdWUiOiJjMSJ9fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMC9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19ERVBMT1lNRU5UX05BTUUiLCJ2YWx1ZSI6InRlc3QifX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52IiwidmFsdWUiOlt7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19DTFVTVEVSX05BTUUiLCJ2YWx1ZSI6ImZvb2JhciJ9XX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfTk9ERV9OQU1FIiwidmFsdWVGcm9tIjp7ImZpZWxkUmVmIjp7ImZpZWxkUGF0aCI6InNwZWMubm9kZU5hbWUifX19fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMS9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19OQU1FU1BBQ0VfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMS9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19QT0RfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfQ09OVEFJTkVSX05BTUUiLCJ2YWx1ZSI6ImMyIn19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8xL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX0RFUExPWU1FTlRfTkFNRSIsInZhbHVlIjoidGVzdCJ9fV0=
78 | type: string
79 | patchType:
80 | description: The type of Patch. Currently only "JSONPatch" is allowed
81 | enum:
82 | - JSONPatch
83 | example: JSONPatch
84 | type: string
85 | status:
86 | $ref: '#/components/schemas/AdmissionResponseStatus'
87 | uid:
88 | description: >-
89 | Identifier for the individual request/response. This should be
90 | copied over from the corresponding AdmissionRequest
91 | type: string
92 | type: object
93 | AdmissionResponseStatus:
94 | description: Status is present in case the mutation was not executed.
95 | properties:
96 | message:
97 | description: >-
98 | A human-readable description of of why this operation is in the
99 | "Failure" status or was not "allowed".
100 | type: string
101 | reason:
102 | description: >-
103 | A machine-readable description of why this operation is in the
104 | "Failure" status or was not "allowed". If "message" is set then this
105 | value is not used.
106 | type: string
107 | type: object
108 | AdmissionReviewRequest:
109 | properties:
110 | request:
111 | $ref: '#/components/schemas/AdmissionRequest'
112 | required:
113 | - request
114 | type: object
115 | AdmissionReviewResponse:
116 | properties:
117 | response:
118 | $ref: '#/components/schemas/AdmissionResponse'
119 | required:
120 | - response
121 | type: object
122 | externalDocs:
123 | description: k8s-metadata-injection repository
124 | url: 'https://github.com/newrelic/k8s-metadata-injection'
125 |
--------------------------------------------------------------------------------
/src/server/readiness_probe.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "net/http"
4 |
5 | // TLSReadyReadinessProbe defines a readiness check for a Webhook struct based on the presence of its TLS certificate and key.
6 | // It requires the whole webhook as parameter to be able to RLock on the certificate for the presence confirmation.
7 | func TLSReadyReadinessProbe(webhook *Webhook) http.HandlerFunc {
8 | return func(w http.ResponseWriter, r *http.Request) {
9 | webhook.RLock()
10 | defer webhook.RUnlock()
11 |
12 | if webhook.Cert == nil {
13 | response := "Certificate not present"
14 | w.WriteHeader(503)
15 | if _, err := w.Write([]byte(response)); err != nil {
16 | webhook.Logger.Errorw("can't write response", "err", err, "response", response)
17 | }
18 | return
19 | }
20 |
21 | okResponse := "OK"
22 | if _, err := w.Write([]byte(okResponse)); err != nil {
23 | webhook.Logger.Errorw("can't write response", "err", err, "response", okResponse)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/server/readiness_probe_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "crypto/tls"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestTLSReadyReadinessProbe(t *testing.T) {
13 | cases := []struct {
14 | desc string
15 | certificate *tls.Certificate
16 | responseCode int
17 | }{
18 | {
19 | desc: "certificate not present (bad health)",
20 | certificate: nil,
21 | responseCode: 503,
22 | },
23 | {
24 | desc: "certificate present (good health)",
25 | certificate: &tls.Certificate{},
26 | responseCode: 200,
27 | },
28 | }
29 |
30 | webhook := Webhook{}
31 | healthCheck := http.HandlerFunc(TLSReadyReadinessProbe(&webhook))
32 | server := httptest.NewServer(healthCheck)
33 |
34 | for _, c := range cases {
35 | t.Run(c.desc, func(t *testing.T) {
36 | webhook.Cert = c.certificate
37 |
38 | resp, err := http.Get(server.URL)
39 |
40 | assert.NoError(t, err)
41 | assert.Equal(t, c.responseCode, resp.StatusCode)
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/server/testdata/expectedAdmissionReviewPatch.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "op": "add",
4 | "path": "/spec/containers/0/env",
5 | "value": [
6 | {
7 | "name": "NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME",
8 | "value": "foobar"
9 | }
10 | ]
11 | },
12 | {
13 | "op": "add",
14 | "path": "/spec/containers/0/env/-",
15 | "value": {
16 | "name": "NEW_RELIC_METADATA_KUBERNETES_NODE_NAME",
17 | "valueFrom": {
18 | "fieldRef": {
19 | "fieldPath": "spec.nodeName"
20 | }
21 | }
22 | }
23 | },
24 | {
25 | "op": "add",
26 | "path": "/spec/containers/0/env/-",
27 | "value": {
28 | "name": "NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME",
29 | "valueFrom": {
30 | "fieldRef": {
31 | "fieldPath": "metadata.namespace"
32 | }
33 | }
34 | }
35 | },
36 | {
37 | "op": "add",
38 | "path": "/spec/containers/0/env/-",
39 | "value": {
40 | "name": "NEW_RELIC_METADATA_KUBERNETES_POD_NAME",
41 | "valueFrom": {
42 | "fieldRef": {
43 | "fieldPath": "metadata.name"
44 | }
45 | }
46 | }
47 | },
48 | {
49 | "op": "add",
50 | "path": "/spec/containers/0/env/-",
51 | "value": {
52 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME",
53 | "value": "c1"
54 | }
55 | },
56 | {
57 | "op": "add",
58 | "path": "/spec/containers/0/env/-",
59 | "value": {
60 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME",
61 | "value": "newrelic/image:latest"
62 | }
63 | },
64 | {
65 | "op": "add",
66 | "path": "/spec/containers/0/env/-",
67 | "value": {
68 | "name": "NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME",
69 | "value": "test"
70 | }
71 | },
72 | {
73 | "op": "add",
74 | "path": "/spec/containers/1/env",
75 | "value": [
76 | {
77 | "name": "NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME",
78 | "value": "foobar"
79 | }
80 | ]
81 | },
82 | {
83 | "op": "add",
84 | "path": "/spec/containers/1/env/-",
85 | "value": {
86 | "name": "NEW_RELIC_METADATA_KUBERNETES_NODE_NAME",
87 | "valueFrom": {
88 | "fieldRef": {
89 | "fieldPath": "spec.nodeName"
90 | }
91 | }
92 | }
93 | },
94 | {
95 | "op": "add",
96 | "path": "/spec/containers/1/env/-",
97 | "value": {
98 | "name": "NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME",
99 | "valueFrom": {
100 | "fieldRef": {
101 | "fieldPath": "metadata.namespace"
102 | }
103 | }
104 | }
105 | },
106 | {
107 | "op": "add",
108 | "path": "/spec/containers/1/env/-",
109 | "value": {
110 | "name": "NEW_RELIC_METADATA_KUBERNETES_POD_NAME",
111 | "valueFrom": {
112 | "fieldRef": {
113 | "fieldPath": "metadata.name"
114 | }
115 | }
116 | }
117 | },
118 | {
119 | "op": "add",
120 | "path": "/spec/containers/1/env/-",
121 | "value": {
122 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME",
123 | "value": "c2"
124 | }
125 | },
126 | {
127 | "op": "add",
128 | "path": "/spec/containers/1/env/-",
129 | "value": {
130 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME",
131 | "value": "newrelic/image2:1.0.0"
132 | }
133 | },
134 | {
135 | "op": "add",
136 | "path": "/spec/containers/1/env/-",
137 | "value": {
138 | "name": "NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME",
139 | "value": "test"
140 | }
141 | }
142 | ]
143 |
--------------------------------------------------------------------------------
/src/server/webhook.go:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/morvencao/kube-mutating-webhook-tutorial/
2 |
3 | package server
4 |
5 | import (
6 | "crypto/tls"
7 | "encoding/json"
8 | "fmt"
9 | "io"
10 | "net/http"
11 | "strings"
12 | "sync"
13 |
14 | "github.com/fsnotify/fsnotify"
15 | "go.uber.org/zap"
16 | admissionv1 "k8s.io/api/admission/v1"
17 | corev1 "k8s.io/api/core/v1"
18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 | "k8s.io/apimachinery/pkg/runtime"
20 | "k8s.io/apimachinery/pkg/runtime/serializer"
21 | )
22 |
23 | var (
24 | runtimeScheme = runtime.NewScheme()
25 | codecs = serializer.NewCodecFactory(runtimeScheme)
26 | deserializer = codecs.UniversalDeserializer()
27 | )
28 |
29 | var ignoredNamespaces = []string{
30 | metav1.NamespaceSystem,
31 | metav1.NamespacePublic,
32 | }
33 |
34 | func createEnvVarFromFieldPath(envVarName, fieldPath string) corev1.EnvVar {
35 | return corev1.EnvVar{Name: envVarName, ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: fieldPath}}}
36 | }
37 |
38 | func createEnvVarFromString(envVarName, envVarValue string) corev1.EnvVar {
39 | return corev1.EnvVar{Name: envVarName, Value: envVarValue}
40 | }
41 |
42 | // getEnvVarsToInject returns the environment variables to inject in the given container
43 | func (whsvr *Webhook) getEnvVarsToInject(pod *corev1.Pod, container *corev1.Container) []corev1.EnvVar {
44 | vars := []corev1.EnvVar{
45 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME", whsvr.ClusterName),
46 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_NODE_NAME", "spec.nodeName"),
47 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME", "metadata.namespace"),
48 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_POD_NAME", "metadata.name"),
49 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME", container.Name),
50 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME", container.Image),
51 | }
52 |
53 | whsvr.Logger.Infow("creating env variables", "cluster_name", whsvr.ClusterName, "container_name", container.Name, "container_image", container.Image)
54 | // Guess the name of the deployment. We check whether the Pod is Owned by a ReplicaSet and confirms with the
55 | // naming convention for a Deployment. This can give a false positive if the user uses ReplicaSets directly.
56 | if len(pod.OwnerReferences) == 1 && pod.OwnerReferences[0].Kind == "ReplicaSet" {
57 | podParts := strings.Split(pod.GenerateName, "-")
58 | if len(podParts) >= 3 {
59 | deployment := strings.Join(podParts[:len(podParts)-2], "-")
60 | vars = append(vars, createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME", deployment))
61 | }
62 | }
63 |
64 | return vars
65 | }
66 |
67 | // Webhook is a webhook server that can accept requests from the Apiserver
68 | type Webhook struct {
69 | sync.RWMutex
70 | CertFile string
71 | KeyFile string
72 | Cert *tls.Certificate
73 | ClusterName string
74 | Logger *zap.SugaredLogger
75 | Server *http.Server
76 | CertWatcher *fsnotify.Watcher
77 | }
78 |
79 | // GetCert returns the certificate that should be used by the server in the TLS handshake.
80 | func (whsvr *Webhook) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
81 | whsvr.Lock()
82 | defer whsvr.Unlock()
83 | return whsvr.Cert, nil
84 | }
85 |
86 | type patchOperation struct {
87 | Op string `json:"op"`
88 | Path string `json:"path"`
89 | Value interface{} `json:"value,omitempty"`
90 | }
91 |
92 | // Check whether the target resource needs to be mutated
93 | func mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool {
94 | // skip special kubernetes system namespaces
95 | for _, namespace := range ignoredList {
96 | if metadata.Namespace == namespace {
97 | return false
98 | }
99 | }
100 | return true
101 | }
102 |
103 | func (whsvr *Webhook) updateContainer(pod *corev1.Pod, index int, container *corev1.Container) (patch []patchOperation) {
104 | // Create map with all environment variable names
105 | envVarMap := map[string]bool{}
106 | for _, envVar := range container.Env {
107 | envVarMap[envVar.Name] = true
108 | }
109 |
110 | // Create a patch for each EnvVar in toInject (if they are not yet defined on the container)
111 | first := len(envVarMap) == 0
112 | var value interface{}
113 | basePath := fmt.Sprintf("/spec/containers/%d/env", index)
114 |
115 | for _, inject := range whsvr.getEnvVarsToInject(pod, container) {
116 | if _, present := envVarMap[inject.Name]; !present {
117 | value = inject
118 | path := basePath
119 |
120 | if first {
121 | // For the first element we have to create the list
122 | value = []corev1.EnvVar{inject}
123 | first = false
124 | } else {
125 | // For the other elements we can append to the list
126 | path = path + "/-"
127 | }
128 |
129 | patch = append(patch, patchOperation{
130 | Op: "add",
131 | Path: path,
132 | Value: value,
133 | })
134 | }
135 | }
136 | return patch
137 | }
138 |
139 | // create mutation patch for resources
140 | func (whsvr *Webhook) createPatch(pod *corev1.Pod) ([]byte, error) {
141 | var patch []patchOperation
142 |
143 | for i, container := range pod.Spec.Containers {
144 | patch = append(patch, whsvr.updateContainer(pod, i, &container)...)
145 | }
146 |
147 | return json.Marshal(patch)
148 | }
149 |
150 | // main mutation process
151 | func (whsvr *Webhook) mutate(ar *admissionv1.AdmissionReview) ([]byte, error) {
152 | req := ar.Request
153 | var pod corev1.Pod
154 | if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
155 | whsvr.Logger.Errorw("could not unmarshal raw object", "err", err, "object", string(req.Object.Raw))
156 | return nil, err
157 | }
158 |
159 | whsvr.Logger.Infow("received admission review", "kind", req.Kind, "namespace", req.Namespace, "name",
160 | req.Name, "pod", pod.Name, "UID", req.UID, "operation", req.Operation, "userinfo", req.UserInfo)
161 |
162 | // determine whether to perform mutation
163 | if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) {
164 | whsvr.Logger.Infow("skipped mutation", "namespace", pod.Namespace, "pod", pod.Name, "reason", "policy check (special namespaces)")
165 | return nil, nil
166 | }
167 |
168 | patchBytes, err := whsvr.createPatch(&pod)
169 | if err != nil {
170 | return nil, err
171 | }
172 |
173 | whsvr.Logger.Infow("admission response created", "response", string(patchBytes))
174 | return patchBytes, nil
175 | }
176 |
177 | // Serve method for webhook server
178 | func (whsvr *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
179 | var body []byte
180 |
181 | if whsvr.Logger == nil {
182 | whsvr.Logger = zap.NewNop().Sugar()
183 | }
184 |
185 | if r.Body != nil {
186 | if data, err := io.ReadAll(r.Body); err == nil {
187 | body = data
188 | }
189 | }
190 | if len(body) == 0 {
191 | whsvr.Logger.Error("empty body")
192 | http.Error(w, "empty body", http.StatusBadRequest)
193 | return
194 | }
195 |
196 | // verify the content type is accurate
197 | contentType := r.Header.Get("Content-Type")
198 | if contentType != "application/json" {
199 | whsvr.Logger.Errorw("invalid content type", "expected", "application/json", "context type", contentType)
200 | http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
201 | return
202 | }
203 |
204 | admissionReviewRequest := admissionv1.AdmissionReview{}
205 | if _, _, err := deserializer.Decode(body, nil, &admissionReviewRequest); err != nil {
206 | whsvr.Logger.Errorw("can't decode body", "err", err, "body", body)
207 | http.Error(w, fmt.Sprintf("could not decode request body: %q", err.Error()), http.StatusBadRequest)
208 | return
209 | }
210 |
211 | if len(admissionReviewRequest.Request.Object.Raw) == 0 {
212 | whsvr.Logger.Errorw("object not present in request body", "body", body)
213 | http.Error(w, fmt.Sprintf("object not present in request body: %q", body), http.StatusBadRequest)
214 | return
215 | }
216 |
217 | patch, err := whsvr.mutate(&admissionReviewRequest)
218 | if err != nil {
219 | whsvr.Logger.Errorw("error during mutation", "err", err)
220 | http.Error(w, fmt.Sprintf("error during mutation: %q", err.Error()), http.StatusInternalServerError)
221 | return
222 | }
223 |
224 | switch admissionReviewRequest.APIVersion {
225 | // Types should be backward compatible between v1 and v1beta1 according to
226 | // https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response.
227 | case "admission.k8s.io/v1", "admission.k8s.io/v1beta1":
228 | default:
229 | whsvr.Logger.Errorw("unsupported admission API Version request", "version", admissionReviewRequest.APIVersion)
230 | http.Error(w, fmt.Sprintf("unsupported admission API Version request: %q", admissionReviewRequest.APIVersion), http.StatusBadRequest)
231 | return
232 | }
233 |
234 | admissionReviewResponse := admissionv1.AdmissionReview{
235 | TypeMeta: metav1.TypeMeta{
236 | Kind: admissionReviewRequest.Kind,
237 | APIVersion: admissionReviewRequest.APIVersion,
238 | },
239 | Response: &admissionv1.AdmissionResponse{
240 | Allowed: true, // Always allow the creation of the pod since this webhook does not act as Validating Webhook.
241 | },
242 | }
243 |
244 | if len(patch) > 0 {
245 | admissionReviewResponse.Response.Patch = patch
246 | admissionReviewResponse.Response.PatchType = func() *admissionv1.PatchType {
247 | pt := admissionv1.PatchTypeJSONPatch // Only PatchTypeJSONPatch is allowed by now.
248 | return &pt
249 | }()
250 | }
251 |
252 | if admissionReviewRequest.Request != nil {
253 | admissionReviewResponse.Response.UID = admissionReviewRequest.Request.UID
254 | }
255 |
256 | resp, err := json.Marshal(admissionReviewResponse)
257 | if err != nil {
258 | whsvr.Logger.Errorw("can't decode response", "err", err)
259 | http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
260 | return
261 | }
262 | whsvr.Logger.Info("writing response")
263 | if _, err := w.Write(resp); err != nil {
264 | whsvr.Logger.Errorw("can't write response", "err", err)
265 | http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
266 | return
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/src/server/webhook_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "os"
9 |
10 | "net/http"
11 | "net/http/httptest"
12 | "testing"
13 |
14 | "github.com/stretchr/testify/assert"
15 | admissionv1 "k8s.io/api/admission/v1"
16 | corev1 "k8s.io/api/core/v1"
17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18 | "k8s.io/apimachinery/pkg/runtime"
19 | "k8s.io/apimachinery/pkg/types"
20 | )
21 |
22 | func TestServeHTTP(t *testing.T) {
23 | patchForValidBody, err := os.ReadFile("testdata/expectedAdmissionReviewPatch.json")
24 | if err != nil {
25 | t.Fatalf("cannot read testdata file: %v", err)
26 | }
27 | var expectedPatchForValidBody bytes.Buffer
28 | if len(patchForValidBody) > 0 {
29 | if err := json.Compact(&expectedPatchForValidBody, patchForValidBody); err != nil {
30 | t.Fatal(err.Error())
31 | }
32 | }
33 |
34 | missingObjectRequestBody := bytes.Replace(makeTestData(t, "default"), []byte("\"object\""), []byte("\"foo\""), -1)
35 |
36 | patchTypeForValidBody := admissionv1.PatchTypeJSONPatch
37 | cases := []struct {
38 | name string
39 | requestBody []byte
40 | contentType string
41 | expectedStatusCode int
42 | expectedBodyWhenHTTPError string
43 | expectedAdmissionReview admissionv1.AdmissionReview
44 | }{
45 | {
46 | name: "mutation applied - valid body",
47 | requestBody: makeTestData(t, "default"),
48 | contentType: "application/json",
49 | expectedStatusCode: http.StatusOK,
50 | expectedAdmissionReview: admissionv1.AdmissionReview{
51 | TypeMeta: metav1.TypeMeta{
52 | Kind: "AdmissionReview",
53 | APIVersion: "admission.k8s.io/v1",
54 | },
55 | Response: &admissionv1.AdmissionResponse{
56 | UID: types.UID("1"),
57 | Allowed: true,
58 | Result: nil,
59 | Patch: expectedPatchForValidBody.Bytes(),
60 | PatchType: &patchTypeForValidBody,
61 | },
62 | },
63 | },
64 | {
65 | name: "mutation not applied - valid body for ignored namespaces",
66 | requestBody: makeTestData(t, "kube-system"),
67 | contentType: "application/json",
68 | expectedStatusCode: http.StatusOK,
69 | expectedAdmissionReview: admissionv1.AdmissionReview{
70 | TypeMeta: metav1.TypeMeta{
71 | Kind: "AdmissionReview",
72 | APIVersion: "admission.k8s.io/v1",
73 | },
74 | Response: &admissionv1.AdmissionResponse{
75 | UID: types.UID("1"),
76 | Allowed: true,
77 | Result: nil,
78 | Patch: nil,
79 | PatchType: nil,
80 | },
81 | },
82 | },
83 | {
84 | name: "empty body",
85 | contentType: "application/json",
86 | expectedStatusCode: http.StatusBadRequest,
87 | expectedBodyWhenHTTPError: "empty body" + "\n",
88 | },
89 | {
90 | name: "wrong content-type",
91 | requestBody: makeTestData(t, "default"),
92 | contentType: "application/yaml",
93 | expectedStatusCode: http.StatusUnsupportedMediaType,
94 | expectedBodyWhenHTTPError: "invalid Content-Type, expect `application/json`" + "\n",
95 | },
96 | {
97 | name: "invalid body",
98 | requestBody: []byte{0, 1, 2},
99 | contentType: "application/json",
100 | expectedStatusCode: http.StatusBadRequest,
101 | expectedBodyWhenHTTPError: "could not decode request body: \"yaml: control characters are not allowed\"\n",
102 | },
103 | {
104 | name: "mutation fails - object not present in request body",
105 | requestBody: missingObjectRequestBody,
106 | contentType: "application/json",
107 | expectedStatusCode: http.StatusBadRequest,
108 | expectedBodyWhenHTTPError: fmt.Sprintf("object not present in request body: %q\n", missingObjectRequestBody),
109 | },
110 | }
111 |
112 | whsvr := &Webhook{
113 | ClusterName: "foobar",
114 | Server: &http.Server{},
115 | }
116 |
117 | server := httptest.NewServer(whsvr)
118 | defer server.Close()
119 |
120 | for i, c := range cases {
121 | t.Run(fmt.Sprintf("[%d] %s", i, c.name), func(t *testing.T) {
122 | resp, err := http.Post(server.URL, c.contentType, bytes.NewReader(c.requestBody))
123 | assert.NoError(t, err)
124 | assert.Equal(t, c.expectedStatusCode, resp.StatusCode)
125 |
126 | gotBody, err := io.ReadAll(resp.Body)
127 | if err != nil {
128 | t.Fatalf("could not read body: %v", err)
129 | }
130 | var gotReview admissionv1.AdmissionReview
131 | if err := json.Unmarshal(gotBody, &gotReview); err != nil {
132 | assert.Equal(t, c.expectedBodyWhenHTTPError, string(gotBody))
133 | return
134 | }
135 |
136 | assert.Equal(t, c.expectedAdmissionReview, gotReview)
137 | })
138 | }
139 | }
140 |
141 | func Benchmark_WebhookPerformance(b *testing.B) {
142 | body := makeTestData(b, "default")
143 |
144 | whsvr := &Webhook{
145 | ClusterName: "foobar",
146 | Server: &http.Server{
147 | Addr: ":8080",
148 | },
149 | }
150 |
151 | server := httptest.NewServer(whsvr)
152 | defer server.Close()
153 |
154 | b.ResetTimer()
155 | for i := 0; i < b.N; i++ {
156 | http.Post(server.URL, "application/json", bytes.NewReader(body)) //nolint: errcheck
157 | }
158 | }
159 |
160 | func makeTestData(t testing.TB, namespace string) []byte {
161 | t.Helper()
162 |
163 | pod := corev1.Pod{
164 | ObjectMeta: metav1.ObjectMeta{
165 | Name: "test-123-123",
166 | GenerateName: "test-123-123", // required for creating metadata for deployment
167 | Annotations: map[string]string{},
168 | Namespace: namespace,
169 | OwnerReferences: []metav1.OwnerReference{{Kind: "ReplicaSet"}}, // required for populating metadata for deployment
170 | },
171 | Spec: corev1.PodSpec{
172 | Volumes: []corev1.Volume{{Name: "v0"}},
173 | InitContainers: []corev1.Container{{Name: "c0"}},
174 | Containers: []corev1.Container{{Name: "c1", Image: "newrelic/image:latest"}, {Name: "c2", Image: "newrelic/image2:1.0.0"}},
175 | ImagePullSecrets: []corev1.LocalObjectReference{{Name: "p0"}},
176 | },
177 | }
178 |
179 | raw, err := json.Marshal(&pod)
180 | if err != nil {
181 | t.Fatalf("Could not create test pod: %v", err)
182 | }
183 |
184 | review := admissionv1.AdmissionReview{
185 | TypeMeta: metav1.TypeMeta{
186 | Kind: "AdmissionReview",
187 | APIVersion: "admission.k8s.io/v1",
188 | },
189 | Request: &admissionv1.AdmissionRequest{
190 | Kind: metav1.GroupVersionKind{},
191 | Object: runtime.RawExtension{
192 | Raw: raw,
193 | },
194 | Operation: admissionv1.Create,
195 | UID: types.UID("1"),
196 | },
197 | }
198 | reviewJSON, err := json.Marshal(review)
199 | if err != nil {
200 | t.Fatalf("Failed to create AdmissionReview: %v", err)
201 | }
202 | return reviewJSON
203 | }
204 |
--------------------------------------------------------------------------------