├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── build-and-test.yaml
│ ├── helm-chart-test.yaml
│ ├── release.yaml
│ └── stale.yaml
├── .gitignore
├── BUILD.md
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.windows
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── THIRD_PARTY_LICENSES.md
├── cmd
└── ec2-metadata-mock
│ └── main.go
├── docs
├── configuration.md
├── defaults.md
└── usage.md
├── go.mod
├── go.sum
├── helm
├── amazon-ec2-metadata-mock
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── README.md
│ ├── ci
│ │ ├── configmap-values.yaml
│ │ ├── default-values.yaml
│ │ ├── local-image-values.yaml
│ │ └── service-config-values.yaml
│ ├── templates
│ │ ├── NOTES.txt
│ │ ├── _helpers.tpl
│ │ ├── clusterrole.yaml
│ │ ├── clusterrolebinding.yaml
│ │ ├── deployment.linux.yaml
│ │ ├── deployment.windows.yaml
│ │ ├── psp.yaml
│ │ ├── service.yaml
│ │ ├── serviceaccount.yaml
│ │ └── tests
│ │ │ ├── test-aemm-service.yaml
│ │ │ └── test-config-map.yaml
│ └── values.yaml
└── aws-logo.png
├── pkg
├── cmd
│ ├── asglifecycle
│ │ ├── asglifecycle.go
│ │ └── asglifecycle_test.go
│ ├── cmdutil
│ │ └── cmdutil.go
│ ├── events
│ │ ├── events.go
│ │ └── events_test.go
│ ├── root
│ │ ├── globalflags
│ │ │ └── globalflags.go
│ │ ├── root.go
│ │ ├── root_test.go
│ │ └── version.txt
│ └── spot
│ │ ├── spot.go
│ │ └── spot_test.go
├── config
│ ├── config.go
│ ├── defaults
│ │ ├── aemm-metadata-default-values.json
│ │ └── defaults.go
│ ├── dynamic.go
│ ├── metadata.go
│ ├── server.go
│ ├── types.go
│ └── userdata.go
├── error
│ └── error.go
├── mock
│ ├── asglifecycle
│ │ └── asglifecycle.go
│ ├── dynamic
│ │ ├── dynamic.go
│ │ └── types
│ │ │ └── types.go
│ ├── events
│ │ ├── config
│ │ │ └── config.go
│ │ ├── events.go
│ │ └── internal
│ │ │ └── types
│ │ │ └── types.go
│ ├── handlers
│ │ └── handlers.go
│ ├── imdsv2
│ │ ├── imdsv2_test.go
│ │ ├── tokengenerator.go
│ │ └── tokenvalidator.go
│ ├── root
│ │ └── root.go
│ ├── spot
│ │ ├── config
│ │ │ └── config.go
│ │ ├── internal
│ │ │ └── types
│ │ │ │ └── types.go
│ │ └── spot.go
│ ├── static
│ │ ├── static.go
│ │ └── types
│ │ │ └── types.go
│ └── userdata
│ │ └── userdata.go
└── server
│ ├── httpserver.go
│ ├── httpserver_test.go
│ └── swapper.go
├── scripts
├── build-binaries
├── build-docker-images
├── create-local-tag-for-release
├── ecr-public-login
├── ecr-template-for-helm-chart.json
├── generate-helm-chart-archives
├── generate-k8s-yaml
├── helm-login
├── install-amazon-ecr-credential-helper
├── prepare-for-release
├── push-docker-images
├── push-helm-chart
├── retag-docker-images
├── sync-catalog-information-for-helm-chart
├── sync-readme-to-ecr-public
├── sync-to-aws-homebrew-tap
├── update-versions-for-release
├── upload-resources-to-github
└── validators
│ ├── json-validator
│ └── release-version-validator
├── templates
└── third-party-licenses.tmpl
└── test
├── e2e
├── cmd
│ ├── asglifecycle-test
│ ├── dynamic-test
│ ├── events-test
│ ├── handlers-test
│ ├── imdsv2-test
│ ├── root-test
│ ├── spot-test
│ ├── static-test
│ └── userdata-test
├── golden
│ ├── 400_bad_request.golden
│ ├── 400_response.golden
│ ├── 401_response.golden
│ ├── 404_response.golden
│ ├── asglifecycle
│ │ └── latest
│ │ │ └── meta-data
│ │ │ └── index.golden
│ ├── default
│ │ ├── index.golden
│ │ └── latest
│ │ │ └── meta-data
│ │ │ └── index.golden
│ ├── dynamic
│ │ ├── fws.golden
│ │ ├── index.golden
│ │ └── instance-identity.golden
│ ├── events
│ │ └── latest
│ │ │ └── meta-data
│ │ │ ├── index.golden
│ │ │ └── network
│ │ │ └── interfaces
│ │ │ └── macs
│ │ │ └── 0e_49_61_0f_c3_11
│ │ │ └── index.golden
│ └── spot
│ │ └── latest
│ │ └── meta-data
│ │ ├── index.golden
│ │ └── spot.golden
├── run-tests
└── testdata
│ ├── aemm-config-integ.json
│ └── output
│ └── aemm-config-used.json
├── helm
├── chart-test.sh
├── ct.yaml
├── kind-config.yaml
└── mock-ip-count-test
│ ├── mock-ip-test
│ ├── test-pod-404.yaml
│ └── test-pod.yaml
├── helpers.go
├── readme-test
├── run-readme-spellcheck
└── spellcheck-Dockerfile
└── shellcheck
└── run-shellcheck
/.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 | **Describe the bug**
11 | A concise description of what the bug is.
12 |
13 | **Steps to reproduce**
14 | A step-by-step description on how to reproduce the problem.
15 |
16 | **Expected outcome**
17 | A concise description of what you expected to happen.
18 |
19 | **Application Logs**
20 | The log output when experiencing the issue.
21 |
22 |
23 | **Environment**
24 |
25 | * AEMM Version:
26 | * OS/Arch:
27 | * Installation method:
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest a feature/enhancement for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the feature**
11 | A concise description of the feature and desired behavior.
12 |
13 | **Is the feature request related to a problem?**
14 | A description of what the problem is. For example: I'm frustrated when [...]
15 |
16 | **Describe alternatives you've considered**
17 | A description of any alternative solutions or features you've considered.
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Issue #, if available:
2 |
3 | Description of changes:
4 |
5 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gomod"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | day: "monday"
8 | time: "09:00"
9 | timezone: "America/Chicago"
10 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-test.yaml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags-ignore:
8 | - "v*.*.*"
9 | pull_request:
10 | workflow_dispatch:
11 | # Run M-F at 2pm CDT
12 | schedule:
13 | - cron: '0 19 * * 1-5'
14 |
15 | env:
16 | DEFAULT_GO_VERSION: ^1.23
17 | DEFAULT_PY_VERSION: "3.9"
18 | IS_PUSH: ${{ github.event_name == 'push' }}
19 |
20 | jobs:
21 | buildAndTest:
22 | name: Build and Test
23 | runs-on: ubuntu-24.04
24 | steps:
25 | - name: Set up Go 1.x
26 | uses: actions/setup-go@v2
27 | with:
28 | go-version: ${{ env.DEFAULT_GO_VERSION }}
29 |
30 | - name: Set up Python ${{ env.DEFAULT_PY_VERSION }}
31 | uses: actions/setup-python@v2
32 | with:
33 | python-version: ${{ env.DEFAULT_PY_VERSION }}
34 |
35 | - name: Check out code into the Go module directory
36 | uses: actions/checkout@v2
37 |
38 | - name: Build
39 | run: make build
40 |
41 | - name: Unit Tests
42 | run: make unit-test
43 |
44 | - name: Lints
45 | run: make spellcheck shellcheck
46 |
47 | - name: Brew Sync Dry run
48 | run: make homebrew-sync-dry-run
49 |
50 | - name: License Test
51 | run: make license-test
52 | env:
53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54 |
55 | - name: E2E Tests
56 | run: make e2e-test
57 |
58 | - name: Mock IP Count Test
59 | run: make helm-mock-ip-count-test
60 |
61 | - name: Build Release Assets
62 | run: make build-release-assets
63 |
64 | - name: Build Docker Images Linux
65 | run: make build-docker-images-linux
66 |
67 | buildWindows:
68 | name: Build Docker Images Windows
69 | runs-on: windows-2025
70 | steps:
71 | - name: Set up Go 1.x
72 | uses: actions/setup-go@v2
73 | with:
74 | go-version: ${{ env.DEFAULT_GO_VERSION }}
75 |
76 | - name: Check out code into the Go module directory
77 | uses: actions/checkout@v2
78 |
79 | - name: Build Docker Images Windows
80 | run: make build-docker-images-windows
81 |
--------------------------------------------------------------------------------
/.github/workflows/helm-chart-test.yaml:
--------------------------------------------------------------------------------
1 | name: Helm Chart Tests
2 |
3 | on:
4 | # Run M-F at 2pm CDT
5 | schedule:
6 | - cron: '0 19 * * 1-5'
7 |
8 | jobs:
9 | chartTests:
10 | name: Helm Chart Tests
11 | runs-on: ubuntu-20.04
12 | strategy:
13 | matrix:
14 | k8sVersion: ["1.16", "1.17", "1.18", "1.19", "1.20", "1.21"]
15 | steps:
16 | - name: Set up Go 1.x
17 | uses: actions/setup-go@v2
18 | with:
19 | go-version: ${{ env.DEFAULT_GO_VERSION }}
20 |
21 | - name: Check out code into the Go module directory
22 | uses: actions/checkout@v2
23 |
24 | - name: Helm Chart Tests
25 | run: test/helm/chart-test.sh -i -k ${{ matrix.k8sVersion }}
26 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | permissions:
9 | contents: write # required for uploading releases
10 |
11 | env:
12 | DEFAULT_GO_VERSION: ^1.23
13 | DEFAULT_PY_VERSION: "3.9"
14 | GITHUB_USERNAME: ${{ secrets.EC2_BOT_GITHUB_USERNAME }}
15 | GITHUB_TOKEN: ${{ secrets.EC2_BOT_GITHUB_TOKEN }}
16 |
17 | jobs:
18 | release:
19 | name: Release
20 | runs-on: ubuntu-20.04
21 | steps:
22 | - name: Set up Go 1.x
23 | uses: actions/setup-go@v2
24 | with:
25 | go-version: ${{ env.DEFAULT_GO_VERSION }}
26 |
27 | - name: Check out code into the Go module directory
28 | uses: actions/checkout@v2
29 |
30 | - name: Validate Release Version
31 | run: make validate-release-version
32 |
33 | - name: Set Release Version
34 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
35 |
36 | - name: Build Artifacts
37 | run: make build-release-assets
38 |
39 | - name: Upload Artifacts to GitHub
40 | uses: softprops/action-gh-release@v1
41 | with:
42 | files: |
43 | build/bin/*
44 | build/k8s-resources/${{ env.RELEASE_VERSION }}/individual-resources.tar
45 | build/k8s-resources/${{ env.RELEASE_VERSION }}/all-resources.yaml
46 | build/k8s-resources/${{ env.RELEASE_VERSION }}/helm-chart-archives/*
47 |
48 | - name: Release Docker Linux
49 | run: make release-docker-linux
50 | env:
51 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
52 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
53 | AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
54 |
55 | releaseWindows:
56 | name: Release Windows
57 | runs-on: windows-2025
58 | steps:
59 | - name: Set up Go 1.x
60 | uses: actions/setup-go@v2
61 | with:
62 | go-version: ${{ env.DEFAULT_GO_VERSION }}
63 |
64 | - name: Check out code into the Go module directory
65 | uses: actions/checkout@v2
66 |
67 | - name: Release Windows Docker Image
68 | run: make release-docker-windows
69 | env:
70 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
71 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
72 | AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
73 |
74 | postRelease:
75 | name: Post Release
76 | runs-on: ubuntu-20.04
77 | needs: [release, releaseWindows]
78 | steps:
79 | - name: Set up Go 1.x
80 | uses: actions/setup-go@v2
81 | with:
82 | go-version: ${{ env.DEFAULT_GO_VERSION }}
83 |
84 | - name: Check out code into the Go module directory
85 | uses: actions/checkout@v2
86 |
87 | - name: Sync to Homebrew
88 | run: make homebrew-sync
89 |
90 | - name: Sync Helm Chart Catalog information
91 | run: make sync-catalog-information-for-helm-chart
92 | env:
93 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
94 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
95 | AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
96 |
97 | - name: Sync Helm Chart to ECR Public
98 | run: make push-helm-chart
99 | env:
100 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
101 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
102 | AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
103 |
104 | helmLint:
105 | name: Helm Lint Test
106 | runs-on: ubuntu-20.04
107 | needs: [release, releaseWindows]
108 | steps:
109 | - name: Set up Go 1.x
110 | uses: actions/setup-go@v2
111 | with:
112 | go-version: ${{ env.DEFAULT_GO_VERSION }}
113 |
114 | - name: Check out code into the Go module directory
115 | uses: actions/checkout@v2
116 |
117 | - name: Helm Lint Test
118 | run: make validate-release-version
119 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yaml:
--------------------------------------------------------------------------------
1 | name: Stale Issues / PRs
2 |
3 | on:
4 | schedule:
5 | - cron: "0 17 * * *" # Runs every day at 12:00PM CST
6 |
7 | jobs:
8 | stale:
9 | runs-on: ubuntu-latest
10 | steps:
11 | # 15+5 day stale policy for PRs
12 | # * Except PRs marked as "stalebot-ignore"
13 | - name: Stale PRs policy
14 | uses: actions/stale@v4.0.0
15 | with:
16 | repo-token: ${{ secrets.GITHUB_TOKEN }}
17 | exempt-pr-labels: "stalebot-ignore"
18 | days-before-stale: 15
19 | days-before-close: 5
20 | days-before-issue-stale: -1
21 | days-before-issue-close: -1
22 | remove-stale-when-updated: true
23 | stale-pr-label: "stale"
24 | operations-per-run: 100
25 | stale-pr-message: >
26 | This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
27 | If you want this PR to never become stale, please ask a maintainer to apply the "stalebot-ignore" label.
28 | close-pr-message: >
29 | This PR was closed because it has become stale with no activity.
30 |
31 | # 30+5 day stale policy for open issues
32 | # * Except Issues marked as "stalebot-ignore"
33 | - name: Stale Issues policy
34 | uses: actions/stale@v4.0.0
35 | with:
36 | repo-token: ${{ secrets.GITHUB_TOKEN }}
37 | exempt-issue-labels: "stalebot-ignore"
38 | days-before-stale: 30
39 | days-before-close: 5
40 | days-before-pr-stale: -1
41 | days-before-pr-close: -1
42 | remove-stale-when-updated: true
43 | stale-issue-label: "stale"
44 | operations-per-run: 100
45 | stale-issue-message: >
46 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
47 | If you want this issue to never become stale, please ask a maintainer to apply the "stalebot-ignore" label.
48 | close-issue-message: >
49 | This issue was closed because it has become stale with no activity.
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | bin/
3 |
--------------------------------------------------------------------------------
/BUILD.md:
--------------------------------------------------------------------------------
1 | # Amazon EC2 Metadata Mock: Build Instructions
2 |
3 | ## Install Go version 1.23+
4 |
5 | There are several options for installing go:
6 |
7 | 1. If you're on mac, you can simply `brew install go`
8 | 2. If you'd like a flexible go installation manager consider using gvm https://github.com/moovweb/gvm
9 | 3. For all other situations use the official go getting started guide: https://golang.org/doc/install
10 |
11 | ## Compile
12 |
13 | This project uses `make` to organize compilation, build, and test targets.
14 |
15 | To compile cmd/amazon-ec2-metadata-mock.go which will build the full static binary and pull in dependent packages:
16 | ```
17 | make build
18 | ```
19 |
20 | The resulting binary will be in the generated `build/` dir
21 |
22 | ```
23 | $ make build
24 |
25 | $ ls build/
26 | ec2-metadata-mock
27 | ```
28 |
29 | ## Test
30 |
31 | You can execute the unit tests for AEMM with `make`:
32 |
33 | ```
34 | make unit-test
35 | ```
36 |
37 |
38 | ### Run All Tests
39 |
40 | The full suite includes unit tests, integration tests, and more. See the full list in the [makefile](https://github.com/aws/amazon-ec2-metadata-mock/blob/main/Makefile):
41 |
42 | ```
43 | make test
44 | ```
45 |
46 | ## Format
47 |
48 | To keep our code readable with go conventions, we use `goimports` to format the source code.
49 | Make sure to run `goimports` before you submit a PR or you'll be caught by our tests!
50 |
51 | You can use the `make fmt` target as a convenience
52 | ```
53 | make fmt
54 | ```
55 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Require approvals from someone in the owner team before merging
2 | # More information here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
3 |
4 | * @aws/ec2-guacamole
5 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23 as builder
2 |
3 | ## GOLANG env
4 | ARG GOPROXY="https://proxy.golang.org|direct"
5 | ARG GO111MODULE="on"
6 |
7 | # Copy go.mod and download dependencies
8 | WORKDIR /amazon-ec2-metadata-mock
9 | COPY go.mod .
10 | COPY go.sum .
11 | RUN go mod download
12 |
13 | ## GOLANG env
14 | ARG CGO_ENABLED=0
15 | ARG GOOS=linux
16 | ARG GOARCH=amd64
17 |
18 | # Build
19 | COPY . .
20 | RUN make build
21 | # In case the target is build for testing:
22 | # $ docker build --target=builder -t test .
23 | ENTRYPOINT ["/amazon-ec2-metadata-mock/build/ec2-metadata-mock"]
24 |
25 | # Build the final image with only the binary
26 | FROM scratch
27 | WORKDIR /
28 | COPY --from=builder /amazon-ec2-metadata-mock/build/ec2-metadata-mock .
29 | COPY THIRD_PARTY_LICENSES.md .
30 | ENTRYPOINT ["/ec2-metadata-mock"]
31 |
--------------------------------------------------------------------------------
/Dockerfile.windows:
--------------------------------------------------------------------------------
1 | ARG WINDOWS_VERSION=1809
2 |
3 | # Build the manager binary
4 | FROM golang:1.23 as builder
5 |
6 | # GOLANG env
7 | ARG GOPROXY="https://proxy.golang.org|direct"
8 | ARG GO111MODULE="on"
9 | ARG CGO_ENABLED=0
10 | ARG GOOS=windows
11 | ARG GOARCH=amd64
12 |
13 | # Copy go.mod and download dependencies
14 | WORKDIR /amazon-ec2-metadata-mock
15 | COPY go.mod .
16 | COPY go.sum .
17 | RUN go mod download
18 |
19 | # Build
20 | COPY . .
21 | RUN go build -a -tags aemm-windows -o ./build/ec2-metadata-mock.exe ./cmd/...
22 | # In case the target is build for testing:
23 | # $ docker build --target=builder -t test .
24 | ENTRYPOINT ["/amazon-ec2-metadata-mock/build/ec2-metadata-mock.exe"]
25 |
26 | # Copy the controller-manager into a thin image
27 | FROM mcr.microsoft.com/windows/nanoserver:${WINDOWS_VERSION}
28 | WORKDIR /
29 | COPY --from=builder /amazon-ec2-metadata-mock/build/ec2-metadata-mock.exe .
30 | COPY THIRD_PARTY_LICENSES.md .
31 | ENTRYPOINT ["/ec2-metadata-mock.exe"]
32 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Amazon-ec2-metadata-mock
2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
--------------------------------------------------------------------------------
/THIRD_PARTY_LICENSES.md:
--------------------------------------------------------------------------------
1 | # Third-party Licenses
2 |
3 | - github.com/aws/amazon-ec2-metadata-mock Unknown [Apache-2.0](https://github.com/aws/amazon-ec2-metadata-mock/blob/HEAD/LICENSE)
4 | - github.com/fsnotify/fsnotify v1.6.0 [BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.6.0/LICENSE)
5 | - github.com/gorilla/mux v1.8.0 [BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)
6 | - github.com/hashicorp/hcl v1.0.0 [MPL-2.0](https://github.com/hashicorp/hcl/blob/v1.0.0/LICENSE)
7 | - github.com/magiconair/properties v1.8.7 [BSD-2-Clause](https://github.com/magiconair/properties/blob/v1.8.7/LICENSE.md)
8 | - github.com/mitchellh/go-homedir v1.1.0 [MIT](https://github.com/mitchellh/go-homedir/blob/v1.1.0/LICENSE)
9 | - github.com/mitchellh/mapstructure v1.5.0 [MIT](https://github.com/mitchellh/mapstructure/blob/v1.5.0/LICENSE)
10 | - github.com/pelletier/go-toml/v2 v2.0.6 [MIT](https://github.com/pelletier/go-toml/blob/v2.0.6/LICENSE)
11 | - github.com/spf13/afero v1.9.3 [Apache-2.0](https://github.com/spf13/afero/blob/v1.9.3/LICENSE.txt)
12 | - github.com/spf13/cast v1.5.0 [MIT](https://github.com/spf13/cast/blob/v1.5.0/LICENSE)
13 | - github.com/spf13/cobra v1.6.1 [Apache-2.0](https://github.com/spf13/cobra/blob/v1.6.1/LICENSE.txt)
14 | - github.com/spf13/jwalterweatherman v1.1.0 [MIT](https://github.com/spf13/jwalterweatherman/blob/v1.1.0/LICENSE)
15 | - github.com/spf13/pflag v1.0.5 [BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.5/LICENSE)
16 | - github.com/spf13/viper v1.15.0 [MIT](https://github.com/spf13/viper/blob/v1.15.0/LICENSE)
17 | - github.com/subosito/gotenv v1.4.2 [MIT](https://github.com/subosito/gotenv/blob/v1.4.2/LICENSE)
18 | - golang.org/x/sys/unix v0.3.0 [BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.3.0:LICENSE)
19 | - golang.org/x/text v0.5.0 [BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.5.0:LICENSE)
20 | - gopkg.in/ini.v1 v1.67.0 [Apache-2.0](https://github.com/go-ini/ini/blob/v1.67.0/LICENSE)
21 | - gopkg.in/yaml.v3 v3.0.1 [MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
22 |
--------------------------------------------------------------------------------
/cmd/ec2-metadata-mock/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "fmt"
18 |
19 | "github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/root"
20 | )
21 |
22 | func main() {
23 | rootCmd := root.NewCmd()
24 | if err := rootCmd.Execute(); err != nil {
25 | panic(fmt.Errorf("Fatal error while executing the root command: %s", err))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # AEMM Configuration
2 | This page serves as documentation for AEMM's configuration details.
3 |
4 | ## Configuring AEMM
5 | The tool can be configured in various ways:
6 |
7 | 1. CLI flags
8 |
9 | Use help commands to learn more
10 |
11 | 2. Env variables
12 | ```
13 | $ export AEMM_MOCK_DELAY_SEC=12
14 | $ export AEMM_SPOT_ITN_ACTION=stop
15 | $ env | grep AEMM // To list the tool's env variables
16 | ```
17 |
18 | > NOTE the translation of config key `spot.action` to `AEMM_SPOT_ACTION` env variable.
19 |
20 | 3. Configuration file in JSON format at `path/to/config-overrides.json`
21 | ```
22 | {
23 | "metadata": {
24 | "paths": {
25 | "ipv4-associations": "/latest/meta-data/network/interfaces/macs/0e:49:61:0f:c3:77/ipv4-associations/192.0.2.54"
26 | },
27 | "values": {
28 | "mac": "0e:49:61:0f:c3:77",
29 | "public-ipv4": "54.92.157.77"
30 | }
31 | },
32 | "spot": {
33 | "action": "terminate",
34 | "time": "2020-01-07T01:03:47Z"
35 | }
36 | }
37 | ```
38 |
39 | Use the `-c` flag to consume the configuration file and the `-s` flag to save an output of the configurations used by AEMM after precedence has been applied:
40 |
41 | ```
42 | $ ec2-metadata-mock -c path/to/config-overrides.json -s
43 | Successfully saved final configuration to local file /path/to/home/.ec2-metadata-mock/.aemm-config-used.json
44 |
45 |
46 | $ cat $HOME/.ec2-metadata-mock/.aemm-config-used.json
47 | (truncated for readability)
48 |
49 | {
50 | "config-file": "path/to/config-overrides.json",
51 | "metadata": {
52 | "paths": {
53 | "ipv4-associations": "/latest/meta-data/network/interfaces/macs/0e:49:61:0f:c3:77/ipv4-associations/192.0.2.54"
54 | },
55 | "values": {
56 | "mac": "0e:49:61:0f:c3:77",
57 | "public-ipv4": "54.92.157.77"
58 | }
59 | },
60 | "mock-delay-sec": 12,
61 | "save-config-to-file": true,
62 | "server": {
63 | "hostname": "localhost",
64 | "port": "1338"
65 | },
66 | "spot": {
67 | "action": "stop",
68 | "time": "2020-01-07T01:03:47Z"
69 | }
70 | }
71 |
72 | ```
73 |
74 | ## Precedence (Highest to Lowest)
75 | 1. overrides
76 | 2. flag
77 | 3. env
78 | 4. config
79 | 5. key/value store
80 | 6. default
81 |
82 | For example, if values from the following sources were loaded:
83 | ```
84 | Defaults in code:
85 | {
86 | "config-file": "$HOME/aemm-config.json", # by default, AEMM looks here for a config file
87 | "server": {
88 | "port": "1338"
89 | },
90 | "mock-delay-sec": 0,
91 | "save-config-to-file": false,
92 | "spot": {
93 | "action": "terminate"
94 | }
95 | }
96 |
97 | Env variables:
98 | export AEMM_MOCK_DELAY_SEC=12
99 | export AEMM_SPOT_ITN_ACTION=hibernate
100 | export AEMM_CONFIG_FILE=/path/to/my-custom-aemm-config.json
101 |
102 | Config File (at /path/to/my-custom-aemm-config.json):
103 | {
104 | "imdsv2": true,
105 | "server": {
106 | "port": "1550"
107 | },
108 | "spot": {
109 | "action": "stop"
110 | }
111 | }
112 |
113 | CLI Flags:
114 | {
115 | "mock-delay-sec": 8
116 | }
117 | ```
118 |
119 | The resulting config will have the following values (non-overridden values are truncated for readability):
120 | ```
121 | {
122 | "mock-delay-sec": 8, # from CLI flag
123 | "config-file": "/path/to/my-custom-aemm-config.json", # from env
124 | "spot": {
125 | "action": "hibernate" # from env
126 | }
127 | "imdsv2": true, # from custom config file at /path/to/my-custom-aemm-config.json
128 | "server": {
129 | "port": "1550" # from custom config file at /path/to/my-custom-aemm-config.json
130 | },
131 | "save-config-to-file": false, # from defaults in code
132 | }
133 | ```
134 |
135 | AEMM is built using Viper which is where the precedence is sourced from. More details can be found in their documentation:
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/docs/defaults.md:
--------------------------------------------------------------------------------
1 | # Default configuration
2 |
3 | ## AEMM configuration
4 | Key | Value
5 | --- | ---
6 | hostname | 0.0.0.0
7 | port | 1338
8 | input config file | $HOME/aemm-config.json
9 | output / used config file | {$HOME or working dir}/.ec2-metadata-mock/.aemm-config-used.json
10 | mock delay seconds | 0
11 | IMDS v2 only (http requests require a session token) | false
12 | spot instance action | terminate
13 | spot termination time | request time + 2 minutes in UTC
14 | scheduled events code | systemReboot
15 | scheduled events state | active
16 | scheduled events not-before | application start time in UTC
17 | scheduled events not-after | application start time + 7 days in UTC
18 | scheduled events not-before-deadline | application start time + 9 days in UTC
19 |
20 | ## Default metadata
21 | Key | Value
22 | --- | ---
23 | ami-id | ami-0a887e401f7654935
24 | ami-launch-index | 0
25 | ami-manifest-path | (unknown)
26 | block-device-mapping-ami | /dev/xvda
27 | block-device-mapping-ebs | sdb
28 | block-device-mapping-ephemeral | sdb
29 | block-device-mapping-root | /dev/xvda
30 | block-device-mapping-swap | sdcs
31 | elastic-inference-accelerator-id | eia-bfa21c7904f64a82a21b9f4540169ce1
32 | elastic-inference-accelerator-type | eia1.medium
33 | elastic-inference-associations | eia-bfa21c7904f64a82a21b9f4540169ce1
34 | event-id | instance-event-1234567890abcdef0
35 | hostname | ip-172-16-34-43.ec2.internal
36 | iam-info code | Success
37 | iam-info instanceprofilearn | arn:aws:iam::896453262835:instance-profile/baskinc-role
38 | iam-info instanceprofileid | AIPA5BOGHHXZELSK34VU4
39 | iam-info lastupdated | 2020-04-02T18:50:40Z
40 | iam-security-credentials accesskeyid | 12345678901
41 | iam-security-credentials code | Success
42 | iam-security-credentials expiration | 2020-04-02T00:49:51Z
43 | iam-security-credentials lastupdated | 2020-04-02T18:50:40Z
44 | iam-security-credentials secretaccesskey | v/12345678901
45 | iam-security-credentials token | TEST92test48TEST+y6RpoTEST92test48TEST/8oWVAiBqTEsT5Ky7ty2tEStxC1T==
46 | iam-security-credentials-role | baskinc-role
47 | instance-action | none
48 | instance-id | i-1234567890abcdef0
49 | instance-type | m4.xlarge
50 | local-hostname | ip-172-16-34-43.ec2.internal
51 | local-ipv4 | 172.16.34.43
52 | mac | 0e:49:61:0f:c3:11
53 | mac-device-number | 0
54 | mac-ipv4-associations | 192.0.2.54
55 | mac-ipv6-associations | 2001:db8:8:4::2
56 | mac-local-hostname | ip-172-16-34-43.ec2.internal
57 | mac-local-ipv4s | 172.16.34.43
58 | mac-mac | 0e:49:61:0f:c3:11
59 | mac-network-interface-id | eni-0f95d3625f5c521cc
60 | mac-owner-id | 515336597381
61 | mac-public-hostname | ec2-192-0-2-54.compute-1.amazonaws.com
62 | mac-public-ipv4s | 192.0.2.54
63 | mac-security-group-ids | sg-0b07f8f6cb485d4df
64 | mac-security-groups | ura-launch-wizard-harry-1
65 | mac-subnet-id | subnet-0ac62554
66 | mac-subnet-ipv4-cidr-block | 192.0.2.0/24
67 | mac-subnet-ipv6-cidr-blocks | 2001:db8::/32
68 | mac-vpc-id | vpc-d295a6a7
69 | mac-vpc-ipv4-cidr-block | 192.0.2.0/24
70 | mac-vpc-ipv4-cidr-blocks | 192.0.2.0/24
71 | mac-vpc-ipv6-cidr-blocks | 2001:db8::/32
72 | placement-availability-zone | us-east-1a
73 | placement-availability-zone-id | use1-az4
74 | placement-group-name | a-placement-group
75 | placement-host-id | h-0da999999f9999fb9
76 | placement-partition-number | 1
77 | placement-region | us-east-1
78 | product-codes | 3iplms73etrdhxdepv72l6ywj
79 | public-hostname | ec2-192-0-2-54.compute-1.amazonaws.com
80 | public-ipv4 | 192.0.2.54
81 | public-key | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/JxGByvHDHgQAU+0nRFWdvMPi22OgNUn9ansrI8QN1ZJGxD1ML8DRnJ3Q3zFKqqjGucfNWW0xpVib+ttkIBp8G9P/EOcX9C3FF63O3SnnIUHJsp5faRAZsTJPx0G5HUbvhBvnAcCtSqQgmr02c1l582vAWx48pOmeXXMkl9qe9V/s7K3utmeZkRLo9DqnbsDlg5GWxLC/rWKYaZR66CnMEyZ7yBy3v3abKaGGRovLkHNAgWjSSgmUTI1nT5/S2OLxxuDnsC7+BiABLPaqlIE70SzcWZ0swx68Bo2AY9T9ymGqeAM/1T4yRtg0sPB98TpT7WrY5A3iia2UVtLO/xcTt test
82 | reservation-id | r-046cb3eca3e201d2f
83 | security-groups | ura-launch-wizard-harry-1
84 | services-domain | amazonaws.com
85 | services-partition | aws
86 | tags-instance-name | test-instance
87 | tags-instance-test | test-tag
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/aws/amazon-ec2-metadata-mock
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/fsnotify/fsnotify v1.8.0
7 | github.com/gorilla/mux v1.8.1
8 | github.com/spf13/cobra v1.9.1
9 | github.com/spf13/pflag v1.0.6
10 | github.com/spf13/viper v1.19.0
11 | )
12 |
13 | require (
14 | github.com/hashicorp/hcl v1.0.0 // indirect
15 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
16 | github.com/magiconair/properties v1.8.9 // indirect
17 | github.com/mitchellh/mapstructure v1.5.0 // indirect
18 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
19 | github.com/sagikazarmark/locafero v0.6.0 // indirect
20 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
21 | github.com/sourcegraph/conc v0.3.0 // indirect
22 | github.com/spf13/afero v1.11.0 // indirect
23 | github.com/spf13/cast v1.7.0 // indirect
24 | github.com/subosito/gotenv v1.6.0 // indirect
25 | go.uber.org/multierr v1.11.0 // indirect
26 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
27 | golang.org/x/sys v0.28.0 // indirect
28 | golang.org/x/text v0.21.0 // indirect
29 | gopkg.in/ini.v1 v1.67.0 // indirect
30 | gopkg.in/yaml.v3 v3.0.1 // indirect
31 | )
32 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
6 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
7 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
8 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
11 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
12 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
13 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
14 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
15 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
16 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
17 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
18 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
19 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
20 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
21 | github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
22 | github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
23 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
24 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
25 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
26 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
27 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
28 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
29 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
30 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
31 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
32 | github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
33 | github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
34 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
35 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
36 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
37 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
38 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
39 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
40 | github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
41 | github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
42 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
43 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
44 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
45 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
46 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
47 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
48 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
49 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
50 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
51 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
52 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
53 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
54 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
55 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
56 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
57 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
58 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
59 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
60 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
61 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
62 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
63 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
64 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
65 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
66 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
67 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *~
18 | # Various IDEs
19 | .project
20 | .idea/
21 | *.tmproj
22 | .vscode/
23 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | name: amazon-ec2-metadata-mock
3 | description: A Helm chart for Amazon EC2 Metadata Mock
4 | version: 1.13.0
5 | home: https://github.com/aws/amazon-ec2-metadata-mock
6 | icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png
7 | sources:
8 | - https://github.com/aws/amazon-ec2-metadata-mock
9 | maintainers:
10 | - name: pdk27
11 | url: https://github.com/pdk27
12 | email: pdk27@users.noreply.github.com
13 | - name: brycahta
14 | url: https://github.com/brycahta
15 | email: brycahta@users.noreply.github.com
16 | keywords:
17 | - ec2
18 | - aws-ec2
19 | - imds
20 | - ec2-instance-metadata
21 | - ec2-instance-metadata-mock
22 | - spot-interruption-mock
23 | - ec2-rebalance-recommendation
24 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/ci/configmap-values.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | configMap: "test-aemm-configmap"
3 | configMapFileName: "test-aemm-config.yaml"
4 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/ci/default-values.yaml:
--------------------------------------------------------------------------------
1 | # empty values.yaml file must be present for Helm chart tests to run with default values
2 | # https://github.com/helm/charts/blob/master/test/README.md#providing-custom-test-values
3 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/ci/local-image-values.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | image:
3 | repository: "amazon-ec2-metadata-mock"
4 | tag: "test-latest"
5 | pullPolicy: "Never"
6 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/ci/service-config-values.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | servicePort: 1550
3 | serviceName: "my-aemm"
4 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | {{ .Release.Name }} has been {{- if .Release.IsInstall }} installed {{ else }} updated. {{- end}}
2 |
3 | Some useful commands:
4 | kubectl get pods --namespace {{ .Release.Namespace }}
5 | kubectl port-forward service/amazon-ec2-metadata-mock 1338
6 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "amazon-ec2-metadata-mock.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | If release name contains chart name it will be used as a full name.
13 | */}}
14 | {{- define "amazon-ec2-metadata-mock.fullname" -}}
15 | {{- if .Values.fullnameOverride -}}
16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- $name := default .Chart.Name .Values.nameOverride -}}
19 | {{- if contains $name .Release.Name -}}
20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
21 | {{- else -}}
22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
23 | {{- end -}}
24 | {{- end -}}
25 | {{- end -}}
26 |
27 | {{/*
28 | Equivalent to "amazon-ec2-metadata-mock.fullname" except that "-win" indicator is appended to the end.
29 | Name will not exceed 63 characters.
30 | */}}
31 | {{- define "amazon-ec2-metadata-mock.fullname.windows" -}}
32 | {{- include "amazon-ec2-metadata-mock.fullname" . | trunc 59 | trimSuffix "-" | printf "%s-win" -}}
33 | {{- end -}}
34 |
35 | {{/*
36 | Common labels
37 | */}}
38 | {{- define "amazon-ec2-metadata-mock.labels" -}}
39 | app.kubernetes.io/name: {{ include "amazon-ec2-metadata-mock.name" . }}
40 | helm.sh/chart: {{ include "amazon-ec2-metadata-mock.chart" . }}
41 | app.kubernetes.io/instance: {{ .Release.Name }}
42 | {{- if .Chart.AppVersion }}
43 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
44 | {{- end }}
45 | app.kubernetes.io/managed-by: {{ .Release.Service }}
46 | {{- end -}}
47 |
48 | {{/*
49 | Create chart name and version as used by the chart label.
50 | */}}
51 | {{- define "amazon-ec2-metadata-mock.chart" -}}
52 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
53 | {{- end -}}
54 |
55 | {{/*
56 | Create the name of the service account to use
57 | */}}
58 | {{- define "amazon-ec2-metadata-mock.serviceAccountName" -}}
59 | {{- if .Values.serviceAccount.create -}}
60 | {{ default (include "amazon-ec2-metadata-mock.fullname" .) .Values.serviceAccount.name }}
61 | {{- else -}}
62 | {{ default "default" .Values.serviceAccount.name }}
63 | {{- end -}}
64 | {{- end -}}
65 |
66 | {{/*
67 | Get the default node selector term prefix.
68 |
69 | In 1.14 "beta.kubernetes.io" was deprecated and is scheduled for removal in 1.18.
70 | See https://v1-14.docs.kubernetes.io/docs/setup/release/notes/#deprecations
71 | */}}
72 | {{- define "amazon-ec2-metadata-mock.defaultNodeSelectorTermsPrefix" -}}
73 | {{- $k8sVersion := printf "%s.%s" .Capabilities.KubeVersion.Major .Capabilities.KubeVersion.Minor | replace "+" "" -}}
74 | {{- semverCompare "<1.18" $k8sVersion | ternary "beta.kubernetes.io" "kubernetes.io" -}}
75 | {{- end -}}
76 |
77 | {{/*
78 | Get the default node selector OS term.
79 | */}}
80 | {{- define "amazon-ec2-metadata-mock.defaultNodeSelectorTermsOs" -}}
81 | {{- list (include "amazon-ec2-metadata-mock.defaultNodeSelectorTermsPrefix" .) "os" | join "/" -}}
82 | {{- end -}}
83 |
84 | {{/*
85 | Get the default node selector Arch term.
86 | */}}
87 | {{- define "amazon-ec2-metadata-mock.defaultNodeSelectorTermsArch" -}}
88 | {{- list (include "amazon-ec2-metadata-mock.defaultNodeSelectorTermsPrefix" .) "arch" | join "/" -}}
89 | {{- end -}}
90 |
91 | {{/*
92 | Get the node selector OS term.
93 | */}}
94 | {{- define "amazon-ec2-metadata-mock.nodeSelectorTermsOs" -}}
95 | {{- or .Values.nodeSelectorTermsOs (include "amazon-ec2-metadata-mock.defaultNodeSelectorTermsOs" .) -}}
96 | {{- end -}}
97 |
98 | {{/*
99 | Get the node selector Arch term.
100 | */}}
101 | {{- define "amazon-ec2-metadata-mock.nodeSelectorTermsArch" -}}
102 | {{- or .Values.nodeSelectorTermsArch (include "amazon-ec2-metadata-mock.defaultNodeSelectorTermsArch" .) -}}
103 | {{- end -}}
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/clusterrole.yaml:
--------------------------------------------------------------------------------
1 | # ClusterRole without any permissions for AEMM
2 |
3 | kind: ClusterRole
4 | apiVersion: rbac.authorization.k8s.io/v1
5 | metadata:
6 | name: {{ include "amazon-ec2-metadata-mock.fullname" . }}
7 | rules: [] # empty rules array to disallow all permissions for AEMM
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: {{ include "amazon-ec2-metadata-mock.fullname" . }}
5 | subjects:
6 | - kind: ServiceAccount
7 | name: {{ template "amazon-ec2-metadata-mock.serviceAccountName" . }}
8 | namespace: {{ .Release.Namespace }}
9 | roleRef:
10 | kind: ClusterRole
11 | name: {{ include "amazon-ec2-metadata-mock.fullname" . }}
12 | apiGroup: rbac.authorization.k8s.io
13 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/psp.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.pspEnabled }}
2 | apiVersion: policy/v1beta1
3 | kind: PodSecurityPolicy
4 | metadata:
5 | name: {{ template "amazon-ec2-metadata-mock.fullname" . }}
6 | labels:
7 | {{ include "amazon-ec2-metadata-mock.labels" . | indent 4 }}
8 | annotations:
9 | seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
10 | spec:
11 | privileged: false
12 | hostIPC: false
13 | hostNetwork: false # turn off host network to prevent undesired exposure of AEMM web server
14 | hostPorts:
15 | - min: 1024
16 | max: 65535
17 | hostPID: false
18 | readOnlyRootFilesystem: false
19 | allowPrivilegeEscalation: false
20 | allowedCapabilities:
21 | - '*'
22 | fsGroup:
23 | rule: RunAsAny
24 | runAsUser:
25 | rule: RunAsAny
26 | seLinux:
27 | rule: RunAsAny
28 | supplementalGroups:
29 | rule: RunAsAny
30 | volumes:
31 | - '*'
32 | ---
33 | kind: ClusterRole
34 | apiVersion: rbac.authorization.k8s.io/v1
35 | metadata:
36 | name: {{ template "amazon-ec2-metadata-mock.fullname" . }}-psp
37 | labels:
38 | {{ include "amazon-ec2-metadata-mock.labels" . | indent 4 }}
39 | rules:
40 | - apiGroups: ['policy']
41 | resources: ['podsecuritypolicies']
42 | verbs: ['use']
43 | resourceNames:
44 | - {{ template "amazon-ec2-metadata-mock.fullname" . }}
45 | ---
46 | apiVersion: rbac.authorization.k8s.io/v1
47 | kind: RoleBinding
48 | metadata:
49 | name: {{ template "amazon-ec2-metadata-mock.fullname" . }}-psp
50 | labels:
51 | {{ include "amazon-ec2-metadata-mock.labels" . | indent 4 }}
52 | roleRef:
53 | apiGroup: rbac.authorization.k8s.io
54 | kind: ClusterRole
55 | name: {{ template "amazon-ec2-metadata-mock.fullname" . }}-psp
56 | subjects:
57 | - kind: ServiceAccount
58 | name: {{ template "amazon-ec2-metadata-mock.serviceAccountName" . }}
59 | namespace: {{ .Release.Namespace }}
60 | {{- end }}
61 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ .Values.serviceName }}
5 | namespace: {{ .Release.Namespace }}
6 | labels:
7 | {{ include "amazon-ec2-metadata-mock.labels" . | indent 4 }}
8 | spec:
9 | type: "ClusterIP"
10 | selector:
11 | app.kubernetes.io/instance: {{ .Release.Name }}
12 | ports:
13 | - protocol: TCP
14 | port: {{ .Values.servicePort | default 1338 }}
15 | targetPort: 1338
16 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: {{ template "amazon-ec2-metadata-mock.serviceAccountName" . }}
5 | namespace: {{ .Release.Namespace }}
6 | {{- with .Values.serviceAccount.annotations }}
7 | annotations:
8 | {{ toYaml . | indent 4 }}
9 | {{- end }}
10 | labels:
11 | {{ include "amazon-ec2-metadata-mock.labels" . | indent 4 }}
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/tests/test-aemm-service.yaml:
--------------------------------------------------------------------------------
1 | # E2E tests to test the following post Helm chart installation:
2 | ## a simple http request to the service
3 | ## configmap setup, if set in the values file
4 |
5 | # The tests are run for each *values.yaml file in helm/amazon-ec2-metadata-mock/ci folder.
6 | # https://github.com/helm/charts/blob/master/test/README.md#providing-custom-test-values
7 |
8 | apiVersion: v1
9 | kind: Pod
10 | metadata:
11 | name: "{{ .Release.Name }}-helm-e2e-test"
12 | annotations:
13 | "helm.sh/hook": "test"
14 | "helm.sh/hook-delete-policy": "before-hook-creation"
15 | "helm.sh/hook-weight": "1" # create config-map first
16 | spec:
17 | restartPolicy: Never
18 | {{- if .Values.configMap }}
19 | volumes:
20 | - name: "aemm-config"
21 | configMap:
22 | name: {{ .Values.configMap }}
23 | {{- end }}
24 | containers:
25 | - name: simple-service-test
26 | imagePullPolicy: "{{ .Values.test.pullPolicy }}"
27 | image: "{{ .Values.test.image }}:{{ .Values.test.imageTag }}"
28 | command:
29 | - "bash"
30 | - "-c"
31 | - |
32 | SERVICE_NAME=$(echo {{ .Values.serviceName }} | tr '-' '_' | tr [:lower:] [:upper:])
33 | HOST_VAR=$(echo "${SERVICE_NAME}_SERVICE_HOST")
34 | PORT_VAR=$(echo "${SERVICE_NAME}_SERVICE_PORT")
35 | ACTUAL=$(curl http://${!HOST_VAR}:${!PORT_VAR}/latest/meta-data/services/domain)
36 | EXPECTED="amazonaws.com"
37 | [[ "$ACTUAL" == "$EXPECTED" ]] && exit 0 || exit 1
38 | {{- if .Values.configMap }}
39 | - name: config-map-test
40 | imagePullPolicy: "{{ .Values.test.pullPolicy }}"
41 | image: "{{ .Values.test.image }}:{{ .Values.test.imageTag }}"
42 | volumeMounts:
43 | - name: "aemm-config"
44 | mountPath: "config/{{ .Values.configMapFileName }}"
45 | subPath: {{ .Values.configMapFileName }}
46 | readOnly: true
47 | command:
48 | - "bash"
49 | - "-c"
50 | - |
51 | SERVICE_NAME=$(echo {{ .Values.serviceName }} | tr '-' '_' | tr [:lower:] [:upper:])
52 | HOST_VAR=$(echo "${SERVICE_NAME}_SERVICE_HOST")
53 | PORT_VAR=$(echo "${SERVICE_NAME}_SERVICE_PORT")
54 | ACTUAL=$(curl http://${!HOST_VAR}:${!PORT_VAR}/latest/meta-data/spot/termination-time)
55 | EXPECTED="1994-05-15T00:00:00Z"
56 | [[ "$ACTUAL" == "$EXPECTED" ]] && exit 0 || exit 1
57 | {{- end }}
58 |
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/templates/tests/test-config-map.yaml:
--------------------------------------------------------------------------------
1 | # Configmap used for E2E testing
2 | # The tests are run for each *values.yaml file in helm/amazon-ec2-metadata-mock/ci folder.
3 |
4 | {{- if .Values.configMap }}
5 | apiVersion: v1
6 | kind: ConfigMap
7 | metadata:
8 | name: {{ .Values.configMap }}
9 | namespace: {{ .Release.Namespace }}
10 | annotations:
11 | "helm.sh/hook": "test"
12 | "helm.sh/hook": "pre-install"
13 | "helm.sh/hook-weight": "-1" # # create config-map before the test pod
14 | "helm.sh/hook-delete-policy": "before-hook-creation"
15 | data:
16 | {{ .Values.configMapFileName }}: |
17 | spot:
18 | time: "1994-05-15T00:00:00Z"
19 | {{- end }}
--------------------------------------------------------------------------------
/helm/amazon-ec2-metadata-mock/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values to be passed into the chart's templates.
2 |
3 | image:
4 | repository: "public.ecr.aws/aws-ec2/amazon-ec2-metadata-mock"
5 | tag: "v1.13.0"
6 | pullPolicy: "IfNotPresent"
7 |
8 | # replicaCount defines the number of pods to replicate
9 | replicaCount: 1
10 |
11 | # nameOverride overrides the name of the helm chart
12 | nameOverride: ""
13 | # fullnameOverride overrides the name of the application
14 | fullnameOverride: ""
15 |
16 | # targetNodeOs creates node-OS specific deployments (e.g. "linux", "windows", "linux windows")
17 | targetNodeOs: "linux"
18 |
19 | resources:
20 | requests:
21 | memory: "64Mi"
22 | cpu: "50m"
23 | limits:
24 | memory: "128Mi"
25 | cpu: "100m"
26 |
27 | # nodeSelector tells both linux and windows deployments where to place the amazon-ec2-metadata-mock pods
28 | # By default, this value is empty and every node will receive a pod.
29 | nodeSelector: {}
30 | # linuxNodeSelector tells the linux deployments where to place the amazon-ec2-metadata-mock pods
31 | # pods. By default, this value is empty and every linux node will receive a pod.
32 | linuxNodeSelector: {}
33 | # windowsNodeSelector tells the windows deployments where to place the amazon-ec2-metadata-mock pods
34 | # pods. By default, this value is empty and every windows node will receive a pod.
35 | windowsNodeSelector: {}
36 |
37 | nodeSelectorTermsOs: ""
38 | nodeSelectorTermsArch: ""
39 |
40 | # podAnnotations define annotations to add to each pod
41 | podAnnotations: {}
42 | linuxAnnotations: {}
43 | windowsAnnotations: {}
44 |
45 | # tolerations specify taints that a pod tolerates so that it can be scheduled to a node with that taint
46 | tolerations: []
47 | linuxTolerations: []
48 | windowsTolerations: []
49 |
50 | # arguments represent CLI args to use when starting amazon-ec2-metadata-mock
51 | arguments: []
52 | linuxArguments: []
53 | windowsArguments: []
54 |
55 | # updateStrategy represents the update strategy for a Deployment
56 | updateStrategy: "RollingUpdate"
57 | linuxUpdateStrategy: ""
58 | windowsUpdateStrategy: ""
59 |
60 | rbac:
61 | # rbac.pspEnabled, if `true` a restricted pod security policy is created and used
62 | pspEnabled: false
63 |
64 | serviceAccount:
65 | # create represents whether a service account should be created
66 | create: true
67 | # name is the name of the service account to use. If name is not set and create is true,
68 | # a name is generated using fullname template
69 | name: "amazon-ec2-metadata-mock-service-account"
70 | annotations: {}
71 |
72 | securityContext:
73 | runAsUserID: "1000"
74 | runAsGroupID: "1000"
75 |
76 | # configMap represents the name of an EXISTING configMap to use
77 | # configMap can be used to pass a config file with the complete set of AEMM configuration overrides, not just limited to AEMM CLI flags. Learn more in README.
78 | configMap: ""
79 |
80 | # configMapFileName represents the name of the file used to create the configMap. Learn more in README.
81 | # supported file extenstions - https://github.com/spf13/viper/blob/master/viper.go#L328
82 | configMapFileName: "aemm-config.json"
83 |
84 | # servicePort represents the port to run the AEMM K8s service on. This can be any port of user's choice.
85 | # note: this port is different from the native AEMM config - aemm.server.port which is not supported when AEMM is run as a K8s service. Learn more in README.
86 | servicePort: "1338"
87 |
88 | serviceName: "amazon-ec2-metadata-mock-service"
89 |
90 | # aemm represents all the CLI flag configuration for Amazon EC2 Metadata Mock (AEMM)
91 | # Null / empty values here means that AEMM will run with defaults configured in the tool
92 | # Refer to the readme for descriptions and defaults - https://github.com/aws/amazon-ec2-metadata-mock/blob/main/helm/amazon-ec2-metadata-mock/README.md
93 | aemm:
94 | server:
95 | hostname: ""
96 | mockDelaySec: 0
97 | mockTriggerTime: ""
98 | mockIPCount: 2
99 | imdsv2: false
100 | rebalanceDelaySec: 0
101 | rebalanceTriggerTime: ""
102 | spot:
103 | action: ""
104 | time: ""
105 | rebalanceRecTime: ""
106 | events:
107 | code: ""
108 | notAfter: ""
109 | notBefore: ""
110 | notBeforeDeadline: ""
111 | state: ""
112 |
113 | # test configuration
114 | test:
115 | image: "centos"
116 | imageTag: "latest"
117 | pullPolicy: "IfNotPresent"
118 |
--------------------------------------------------------------------------------
/helm/aws-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws/amazon-ec2-metadata-mock/6162391291863dd1b2bea75ec7440b86a8fc3a77/helm/aws-logo.png
--------------------------------------------------------------------------------
/pkg/cmd/asglifecycle/asglifecycle.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package asglifecycle
15 |
16 | import (
17 | "errors"
18 | "fmt"
19 | "log"
20 | "strings"
21 |
22 | cmdutil "github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/cmdutil"
23 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
24 | se "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/asglifecycle"
25 |
26 | "github.com/spf13/cobra"
27 | )
28 |
29 | const (
30 | cfgPrefix = "asglifecycle."
31 | )
32 |
33 | var (
34 | c cfg.Config
35 |
36 | // Command represents the CLI command
37 | Command *cobra.Command
38 |
39 | // defaults
40 | defaultCfg = map[string]interface{}{}
41 | )
42 |
43 | func init() {
44 | cobra.OnInitialize(initConfig)
45 | Command = newCmd()
46 | }
47 |
48 | func initConfig() {
49 | cfg.LoadConfigFromDefaults(defaultCfg)
50 | }
51 |
52 | func newCmd() *cobra.Command {
53 | var cmd = &cobra.Command{
54 | Use: "asglifecycle [--code CODE] [--state STATE] [--not-after] [--not-before-deadline]",
55 | Aliases: []string{"asglifecycle", "autoscaling", "asg"},
56 | PreRunE: preRun,
57 | Example: fmt.Sprintf(" %s asglifecycle -h \tasglifecycle help \n %s asglifecycle -t target-lifecycle-state\t\tmocks asg lifecycle target lifecycle states", cmdutil.BinName, cmdutil.BinName),
58 | Run: run,
59 | Short: "Mock EC2 ASG Lifecycle target-lifecycle-state",
60 | Long: "Mock EC2 ASG Lifecycle target-lifecycle-state",
61 | }
62 |
63 | // bind local flags to config
64 | cfg.BindFlagSetWithKeyPrefix(cmd.Flags(), cfgPrefix)
65 | return cmd
66 | }
67 |
68 | // SetConfig sets the local config
69 | func SetConfig(config cfg.Config) {
70 | c = config
71 | }
72 |
73 | func preRun(cmd *cobra.Command, args []string) error {
74 | if cfgErrors := ValidateLocalConfig(); cfgErrors != nil {
75 | return errors.New(strings.Join(cfgErrors, ""))
76 | }
77 | return nil
78 | }
79 |
80 | // ValidateLocalConfig validates all local config and returns a slice of error messages
81 | func ValidateLocalConfig() []string {
82 | // no-op
83 | return nil
84 | }
85 |
86 | func run(cmd *cobra.Command, args []string) {
87 | log.Printf("Initiating %s for EC2 ASG Lifecycle on port %s\n", cmdutil.BinName, c.Server.Port)
88 | cmdutil.PrintFlags(cmd.Flags())
89 | cmdutil.RegisterHandlers(cmd, c)
90 | se.Mock(c)
91 | }
92 |
--------------------------------------------------------------------------------
/pkg/cmd/asglifecycle/asglifecycle_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package asglifecycle
15 |
16 | import (
17 | "bytes"
18 | "fmt"
19 | "testing"
20 |
21 | h "github.com/aws/amazon-ec2-metadata-mock/test"
22 |
23 | "github.com/spf13/pflag"
24 | )
25 |
26 | func TestNewCmdName(t *testing.T) {
27 | expected := "asglifecycle"
28 | actual := newCmd().Name()
29 | h.Assert(t, expected == actual, fmt.Sprintf("Expected the name for asglifecycle command to be %s, but was %s", expected, actual))
30 | }
31 | func TestNewCmdLocalFlags(t *testing.T) {
32 | expectedFlags := []string{}
33 |
34 | cmd := newCmd()
35 | actualFlagSet := cmd.LocalFlags()
36 |
37 | var actualFlags []string
38 | actualFlagSet.VisitAll(func(flag *pflag.Flag) {
39 | actualFlags = append(actualFlags, flag.Name)
40 | })
41 |
42 | h.ItemsMatch(t, expectedFlags, actualFlags)
43 | }
44 |
45 | func TestNewCmdHasPreRunE(t *testing.T) {
46 | pre := newCmd().PreRunE
47 | h.Assert(t, pre != nil, "Expected a non nil PreRunE for the asglifecycle command")
48 | }
49 |
50 | func TestNewCmdHasRun(t *testing.T) {
51 | run := newCmd().Run
52 | h.Assert(t, run != nil, "Expected a non nil Run for the asglifecycle command")
53 | }
54 | func TestNewCmdHasExample(t *testing.T) {
55 | hasExample := newCmd().HasExample()
56 | h.Assert(t, hasExample, "Expected asglifecycle command to have an example, but wasn't found")
57 | }
58 | func TestExecuteHelpExists(t *testing.T) {
59 | cmd := newCmd()
60 | buf := new(bytes.Buffer)
61 | cmd.SetOutput(buf)
62 | cmd.SetArgs([]string{"-h"})
63 | err := cmd.Execute()
64 | h.Ok(t, err)
65 |
66 | output := buf.String()
67 | h.Assert(t, output != "", "Expected help subcommand for asglifecycle, but wasn't found")
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/cmd/cmdutil/cmdutil.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package cmdutil
15 |
16 | import (
17 | "fmt"
18 | "strings"
19 | "time"
20 |
21 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
22 | e "github.com/aws/amazon-ec2-metadata-mock/pkg/error"
23 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/asglifecycle"
24 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/dynamic"
25 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/events"
26 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/handlers"
27 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/imdsv2"
28 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/spot"
29 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/static"
30 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/userdata"
31 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
32 |
33 | "github.com/spf13/cobra"
34 | "github.com/spf13/pflag"
35 | )
36 |
37 | // BinName is the name of this tool's binary
38 | const BinName = "ec2-metadata-mock"
39 |
40 | // handlerPair holds a tuple of a path and its associated handler
41 | type handlerPair struct {
42 | path string
43 | handler server.HandlerType
44 | }
45 |
46 | // Contains finds a string in the given array
47 | func Contains(slice []string, val string) bool {
48 | for _, item := range slice {
49 | if item == val {
50 | return true
51 | }
52 | }
53 | return false
54 | }
55 |
56 | // PrintFlags prints all flags of a command, if set
57 | func PrintFlags(flags *pflag.FlagSet) {
58 | f := make(map[string]interface{})
59 | flags.Visit(func(flag *pflag.Flag) {
60 | f[flag.Name] = flag.Value
61 | })
62 |
63 | if len(f) > 0 {
64 | fmt.Println("\nFlags:")
65 | for key, value := range f {
66 | fmt.Printf("%s: %s\n", key, value)
67 | }
68 | fmt.Println()
69 | }
70 | }
71 |
72 | // ValidateRFC3339TimeFormat validates an input time matches RFC3339 format
73 | func ValidateRFC3339TimeFormat(flagName string, input string) error {
74 | if _, err := time.Parse(time.RFC3339, input); err != nil {
75 | return e.FlagValidationError{
76 | FlagName: flagName,
77 | Allowed: "time in RFC3339 format, e.g. 2020-01-07T01:03:47Z",
78 | InvalidValue: input}
79 | }
80 | return nil
81 | }
82 |
83 | // RegisterHandlers binds paths to handlers for ALL commands
84 | func RegisterHandlers(cmd *cobra.Command, config cfg.Config) {
85 | handlerPairsToRegister := getHandlerPairs(cmd, config)
86 | for _, handlerPair := range handlerPairsToRegister {
87 | if config.Imdsv2Required {
88 | server.HandleFunc(handlerPair.path, imdsv2.ValidateToken(handlerPair.handler))
89 | } else {
90 | server.HandleFunc(handlerPair.path, handlerPair.handler)
91 | }
92 | }
93 |
94 | static.RegisterHandlers(config)
95 | dynamic.RegisterHandlers(config)
96 | userdata.RegisterHandlers(config)
97 |
98 | // paths without explicit handler bindings will fallback to CatchAllHandler
99 | server.HandleFuncPrefix("/", handlers.CatchAllHandler)
100 | }
101 |
102 | // getHandlerPairs returns a slice of {paths, handlers} to register
103 | func getHandlerPairs(cmd *cobra.Command, config cfg.Config) []handlerPair {
104 | // always register these paths
105 | handlerPairs := []handlerPair{
106 | {path: "/", handler: handlers.ListRoutesHandler},
107 | {path: "/latest", handler: handlers.ListRoutesHandler},
108 | {path: static.ServicePath, handler: handlers.ListRoutesHandler},
109 | {path: dynamic.ServicePath, handler: handlers.ListRoutesHandler},
110 | }
111 |
112 | isSpot := strings.Contains(cmd.Name(), "spot")
113 | isEvents := strings.Contains(cmd.Name(), "events")
114 | isASGLifecycle := strings.Contains(cmd.Name(), "asglifecycle")
115 |
116 | subCommandHandlers := map[string][]handlerPair{
117 | "spot": {{path: config.Metadata.Paths.Spot, handler: spot.Handler},
118 | {path: config.Metadata.Paths.SpotTerminationTime, handler: spot.Handler},
119 | {path: config.Metadata.Paths.RebalanceRecTime, handler: spot.Handler}},
120 | "events": {{path: config.Metadata.Paths.Events, handler: events.Handler}},
121 | "asglifecycle": {{path: config.Metadata.Paths.ASGLifecycle, handler: asglifecycle.Handler}},
122 | }
123 |
124 | if isSpot {
125 | handlerPairs = append(handlerPairs, subCommandHandlers["spot"]...)
126 | } else if isEvents {
127 | handlerPairs = append(handlerPairs, subCommandHandlers["events"]...)
128 | } else if isASGLifecycle {
129 | handlerPairs = append(handlerPairs, subCommandHandlers["asglifecycle"]...)
130 | } else {
131 | // root registers all subcommands
132 | for k := range subCommandHandlers {
133 | handlerPairs = append(handlerPairs, subCommandHandlers[k]...)
134 | }
135 | }
136 |
137 | return handlerPairs
138 | }
139 |
--------------------------------------------------------------------------------
/pkg/cmd/events/events_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package events
15 |
16 | import (
17 | "bytes"
18 | "fmt"
19 | "testing"
20 |
21 | h "github.com/aws/amazon-ec2-metadata-mock/test"
22 |
23 | "github.com/spf13/pflag"
24 | )
25 |
26 | func TestNewCmdName(t *testing.T) {
27 | expected := "events"
28 | actual := newCmd().Name()
29 | h.Assert(t, expected == actual, fmt.Sprintf("Expected the name for events command to be %s, but was %s", expected, actual))
30 | }
31 | func TestNewCmdLocalFlags(t *testing.T) {
32 | expectedFlags := []string{"code", "state", "not-before", "not-after", "not-before-deadline"}
33 |
34 | cmd := newCmd()
35 | actualFlagSet := cmd.LocalFlags()
36 |
37 | var actualFlags []string
38 | actualFlagSet.VisitAll(func(flag *pflag.Flag) {
39 | actualFlags = append(actualFlags, flag.Name)
40 | })
41 |
42 | h.ItemsMatch(t, expectedFlags, actualFlags)
43 | }
44 |
45 | func TestNewCmdHasPreRunE(t *testing.T) {
46 | pre := newCmd().PreRunE
47 | h.Assert(t, pre != nil, "Expected a non nil PreRunE for the events command")
48 | }
49 |
50 | func TestNewCmdHasRun(t *testing.T) {
51 | run := newCmd().Run
52 | h.Assert(t, run != nil, "Expected a non nil Run for the events command")
53 | }
54 | func TestNewCmdHasExample(t *testing.T) {
55 | hasExample := newCmd().HasExample()
56 | h.Assert(t, hasExample, "Expected events command to have an example, but wasn't found")
57 | }
58 | func TestExecuteHelpExists(t *testing.T) {
59 | cmd := newCmd()
60 | buf := new(bytes.Buffer)
61 | cmd.SetOutput(buf)
62 | cmd.SetArgs([]string{"-h"})
63 | err := cmd.Execute()
64 | h.Ok(t, err)
65 |
66 | output := buf.String()
67 | h.Assert(t, output != "", "Expected help subcommand for events, but wasn't found")
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/cmd/root/globalflags/globalflags.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | // Package globalflags represents the global flags (to be used with all sub-commands)
15 | package globalflags
16 |
17 | const (
18 | // ConfigFileFlag - config file for cli input parameters in json format
19 | ConfigFileFlag = "config-file"
20 |
21 | // SaveConfigToFileFlag - whether to save processed config from all input sources
22 | SaveConfigToFileFlag = "save-config-to-file"
23 |
24 | // WatchConfigFileFlag - whether to watch the config file for changes
25 | WatchConfigFileFlag = "watch-config-file"
26 |
27 | // MockDelayInSecFlag - spot itn delay in seconds, relative to the application start time
28 | MockDelayInSecFlag = "mock-delay-sec"
29 |
30 | // MockTriggerTimeFlag - spot itn trigger time in RFC3339
31 | MockTriggerTimeFlag = "mock-trigger-time"
32 |
33 | // MockIPCountFlag - the number of nodes in a cluster that can receive Spot ITNs
34 | MockIPCountFlag = "mock-ip-count"
35 |
36 | // HostNameFlag - the HTTP hostname for the mock url
37 | HostNameFlag = "hostname"
38 |
39 | // PortFlag - the HTTP port where the mock runs
40 | PortFlag = "port"
41 |
42 | // Imdsv2Flag - whether to enable IMDSv2 only requiring a session token when submitting requests
43 | Imdsv2Flag = "imdsv2"
44 |
45 | // RebalanceDelayInSecFlag - rebalance rec delay in seconds, relative to the application start time
46 | RebalanceDelayInSecFlag = "rebalance-delay-sec"
47 |
48 | // RebalanceTriggerTimeFlag - rebalance rec trigger time in RFC3339
49 | RebalanceTriggerTimeFlag = "rebalance-trigger-time"
50 |
51 | ASGTerminationDelayInSecFlag = "asg-termination-delay-sec"
52 |
53 | ASGTerminationTriggerTimeFlag = "asg-termination-trigger-time"
54 | )
55 |
56 | // GetTopLevelFlags returns the top level global flags
57 | func GetTopLevelFlags() []string {
58 | return []string{ConfigFileFlag, SaveConfigToFileFlag, WatchConfigFileFlag, MockDelayInSecFlag, MockTriggerTimeFlag, MockIPCountFlag, Imdsv2Flag, RebalanceDelayInSecFlag, RebalanceTriggerTimeFlag, ASGTerminationDelayInSecFlag, ASGTerminationTriggerTimeFlag}
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/cmd/root/root_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package root
15 |
16 | import (
17 | "bytes"
18 | "fmt"
19 | "strings"
20 | "testing"
21 |
22 | h "github.com/aws/amazon-ec2-metadata-mock/test"
23 |
24 | "github.com/spf13/pflag"
25 | )
26 |
27 | func TestNewCmdName(t *testing.T) {
28 | expected := "ec2-metadata-mock"
29 | actual := NewCmd().Name()
30 |
31 | h.Assert(t, expected == actual, fmt.Sprintf("Expected the name for root command to be %s, but was %s", expected, actual))
32 | }
33 | func TestNewCmdFlags(t *testing.T) {
34 | expectedFlags := []string{"config-file", "save-config-to-file", "watch-config-file", "mock-delay-sec", "mock-trigger-time", "mock-ip-count", "hostname", "port", "imdsv2", "rebalance-delay-sec", "rebalance-trigger-time", "asg-termination-delay-sec", "asg-termination-trigger-time"}
35 |
36 | cmd := NewCmd()
37 | actualFlagSet := cmd.PersistentFlags()
38 | var actualFlags []string
39 | actualFlagSet.VisitAll(func(flag *pflag.Flag) {
40 | actualFlags = append(actualFlags, flag.Name)
41 | })
42 |
43 | h.ItemsMatch(t, expectedFlags, actualFlags)
44 | }
45 | func TestNewCmdHasSubcommands(t *testing.T) {
46 | expSubcommandNames := []string{"spot", "events", "asglifecycle"}
47 |
48 | cmd := NewCmd()
49 | actSubcommands := cmd.Commands()
50 | var actSubcommandsNames []string
51 | for _, cmd := range actSubcommands {
52 | n := strings.Split(cmd.Use, " ")[0]
53 | actSubcommandsNames = append(actSubcommandsNames, n)
54 | }
55 |
56 | h.ItemsMatch(t, expSubcommandNames, actSubcommandsNames)
57 | }
58 | func TestNewCmdHasPersistentRunE(t *testing.T) {
59 | ppe := NewCmd().PersistentPreRunE
60 | h.Assert(t, ppe != nil, "Expected a non nil PersistentPreRunE for the root command")
61 | }
62 | func TestNewCmdHasPreRunE(t *testing.T) {
63 | pe := NewCmd().PreRunE
64 | h.Assert(t, pe != nil, "Expected a non nil PreRunE for the root command")
65 | }
66 | func TestNewCmdHasRun(t *testing.T) {
67 | run := NewCmd().Run
68 | h.Assert(t, run != nil, "Expected a non nil Run for the root command")
69 | }
70 | func TestNewCmdHasExample(t *testing.T) {
71 | hasExample := NewCmd().HasExample()
72 | h.Assert(t, hasExample, "Expected root command to have an example, but wasn't found")
73 | }
74 | func TestExecuteHelpExists(t *testing.T) {
75 | cmd := NewCmd()
76 | buf := new(bytes.Buffer)
77 | cmd.SetOutput(buf)
78 | cmd.SetArgs([]string{"-h"})
79 | err := cmd.Execute()
80 | h.Ok(t, err)
81 |
82 | output := buf.String()
83 | h.Assert(t, output != "", "Expected help subcommand for root, but wasn't found")
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/cmd/root/version.txt:
--------------------------------------------------------------------------------
1 | v1.13.0
--------------------------------------------------------------------------------
/pkg/cmd/spot/spot.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package spot
15 |
16 | import (
17 | "errors"
18 | "fmt"
19 | "log"
20 | "strings"
21 |
22 | cmdutil "github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/cmdutil"
23 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
24 | e "github.com/aws/amazon-ec2-metadata-mock/pkg/error"
25 | s "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/spot"
26 |
27 | "github.com/spf13/cobra"
28 | )
29 |
30 | const (
31 | cfgPrefix = "spot."
32 |
33 | // local flags
34 | instanceActionFlagName = "action"
35 | terminationTimeFlagName = "time"
36 | rebalanceRecTimeFlagName = "rebalance-rec-time"
37 |
38 | // instance actions
39 | terminate = "terminate"
40 | hibernate = "hibernate"
41 | stop = "stop"
42 | )
43 |
44 | var (
45 | c cfg.Config
46 |
47 | // Command represents the CLI command
48 | Command *cobra.Command
49 |
50 | // constraints
51 | validInstanceActions = []string{terminate, hibernate, stop}
52 |
53 | // defaults
54 | defaultCfg = map[string]interface{}{
55 | cfgPrefix + instanceActionFlagName: terminate,
56 | }
57 | )
58 |
59 | func init() {
60 | cobra.OnInitialize(initConfig)
61 | Command = newCmd()
62 | }
63 |
64 | func initConfig() {
65 | cfg.LoadConfigFromDefaults(defaultCfg)
66 | }
67 |
68 | func newCmd() *cobra.Command {
69 | var cmd = &cobra.Command{
70 | Use: "spot [--action ACTION]",
71 | Aliases: []string{"spot"},
72 | PreRunE: preRun,
73 | Example: fmt.Sprintf(" %s spot -h \tspot help \n %s spot -d 5 --action terminate\t\tmocks spot interruption only", cmdutil.BinName, cmdutil.BinName),
74 | Run: run,
75 | Short: "Mock EC2 Spot interruption notice",
76 | Long: "Mock EC2 Spot interruption notice",
77 | }
78 |
79 | // local flags
80 | cmd.Flags().StringP(instanceActionFlagName, "a", "", "action in the spot interruption notice (default: terminate)\naction can be one of the following: "+strings.Join(validInstanceActions, ","))
81 | cmd.Flags().StringP(terminationTimeFlagName, "t", "", "termination time specifies the approximate time when the spot instance will receive the shutdown signal in RFC3339 format to execute instance action E.g. 2020-01-07T01:03:47Z (default: request time + 2 minutes in UTC)")
82 | cmd.Flags().StringP(rebalanceRecTimeFlagName, "r", "", "rebalance rec time specifies the approximate time when the rebalance recommendation notification will be emitted in RFC3339 format")
83 |
84 | // bind local flags to config
85 | cfg.BindFlagSetWithKeyPrefix(cmd.Flags(), cfgPrefix)
86 | return cmd
87 | }
88 |
89 | // SetConfig sets the local config
90 | func SetConfig(config cfg.Config) {
91 | c = config
92 | }
93 |
94 | func preRun(cmd *cobra.Command, args []string) error {
95 | if cfgErrors := ValidateLocalConfig(); cfgErrors != nil {
96 | return errors.New(strings.Join(cfgErrors, ""))
97 | }
98 | return nil
99 | }
100 |
101 | // ValidateLocalConfig validates all local config and returns a slice of error messages
102 | func ValidateLocalConfig() []string {
103 | var errStrings []string
104 | c := c.SpotConfig
105 |
106 | // validate instance-action
107 | if ok := cmdutil.Contains(validInstanceActions, c.InstanceAction); !ok {
108 | errStrings = append(errStrings, e.FlagValidationError{
109 | FlagName: instanceActionFlagName,
110 | Allowed: strings.Join(validInstanceActions, ","),
111 | InvalidValue: c.InstanceAction}.Error(),
112 | )
113 | }
114 | // validate time, if override provided
115 | if c.TerminationTime != "" {
116 | if err := cmdutil.ValidateRFC3339TimeFormat(terminationTimeFlagName, c.TerminationTime); err != nil {
117 | errStrings = append(errStrings, err.Error())
118 | }
119 | }
120 | // validate noticeTime, if override provided
121 | if c.RebalanceRecTime != "" {
122 | if err := cmdutil.ValidateRFC3339TimeFormat(rebalanceRecTimeFlagName, c.RebalanceRecTime); err != nil {
123 | errStrings = append(errStrings, err.Error())
124 | }
125 | }
126 | return errStrings
127 | }
128 |
129 | func run(cmd *cobra.Command, args []string) {
130 | log.Printf("Initiating %s for EC2 Spot interruption notice on port %s\n", cmdutil.BinName, c.Server.Port)
131 | cmdutil.PrintFlags(cmd.Flags())
132 | cmdutil.RegisterHandlers(cmd, c)
133 | s.Mock(c)
134 | }
135 |
--------------------------------------------------------------------------------
/pkg/cmd/spot/spot_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package spot
15 |
16 | import (
17 | "bytes"
18 | "fmt"
19 | "testing"
20 |
21 | h "github.com/aws/amazon-ec2-metadata-mock/test"
22 |
23 | "github.com/spf13/pflag"
24 | )
25 |
26 | func TestNewCmdName(t *testing.T) {
27 | expected := "spot"
28 | actual := newCmd().Name()
29 | h.Assert(t, expected == actual, fmt.Sprintf("Expected the name for spot command to be %s, but was %s", expected, actual))
30 | }
31 | func TestNewCmdLocalFlags(t *testing.T) {
32 | expectedFlags := []string{"action", "time", "rebalance-rec-time"}
33 |
34 | cmd := newCmd()
35 | actualFlagSet := cmd.LocalFlags()
36 |
37 | var actualFlags []string
38 | actualFlagSet.VisitAll(func(flag *pflag.Flag) {
39 | actualFlags = append(actualFlags, flag.Name)
40 | })
41 |
42 | h.ItemsMatch(t, expectedFlags, actualFlags)
43 | }
44 | func TestNewCmdHasPreRunE(t *testing.T) {
45 | pre := newCmd().PreRunE
46 | h.Assert(t, pre != nil, "Expected a non nil PreRunE for the spot command")
47 | }
48 | func TestNewCmdHasRun(t *testing.T) {
49 | run := newCmd().Run
50 | h.Assert(t, run != nil, "Expected a non nil Run for the spot command")
51 | }
52 | func TestNewCmdHasExample(t *testing.T) {
53 | hasExample := newCmd().HasExample()
54 | h.Assert(t, hasExample, "Expected spot command to have an example, but wasn't found")
55 | }
56 | func TestExecuteHelpExists(t *testing.T) {
57 | cmd := newCmd()
58 | buf := new(bytes.Buffer)
59 | cmd.SetOutput(buf)
60 | cmd.SetArgs([]string{"-h"})
61 | err := cmd.Execute()
62 | h.Ok(t, err)
63 |
64 | output := buf.String()
65 | h.Assert(t, output != "", "Expected help subcommand for spot, but wasn't found")
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/config/defaults/defaults.go:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package defaults
15 |
16 | import (
17 | // Blank import else compiler complains it's unused
18 | _ "embed"
19 | )
20 |
21 | //go:embed aemm-metadata-default-values.json
22 | var defaultValues []byte
23 |
24 | // GetDefaultValues returns default metadata values populated via aemm-metadata-default-values.json
25 | func GetDefaultValues() []byte {
26 | return defaultValues
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/config/dynamic.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package config
15 |
16 | import (
17 | "encoding/json"
18 |
19 | "github.com/spf13/pflag"
20 | )
21 |
22 | const (
23 | // dynamic keys
24 | instanceIdentityDocument = "instance-identity-document"
25 | )
26 |
27 | var (
28 | dyCfgPrefix = "dynamic."
29 | dyPathsCfgPrefix = dyCfgPrefix + "paths."
30 | dyValuesCfgPrefix = dyCfgPrefix + "values."
31 |
32 | // mapping of dyValue KEYS to dyPath KEYS requiring path substitutions on override
33 | dyValueToPlaceholderPathsKeyMap = map[string][]string{}
34 |
35 | // supported URL paths to run a mock
36 | dyPathsDefaults = map[string]interface{}{}
37 |
38 | // values in mock responses
39 | dyValuesDefaults = map[string]interface{}{}
40 |
41 | // mapping of dynamic value keys to its nested struct
42 | dyNestedValues = map[string]interface{}{}
43 | )
44 |
45 | // GetCfgDnValPrefix returns the prefix to use to access dynamic values in config
46 | func GetCfgDnValPrefix() string {
47 | return dyCfgPrefix + "." + dyValuesCfgPrefix + "."
48 | }
49 |
50 | // GetCfgDnPathsPrefix returns the prefix to use to access dynamic values in config
51 | func GetCfgDnPathsPrefix() string {
52 | return dyCfgPrefix + "." + dyPathsCfgPrefix + "."
53 | }
54 |
55 | // BindDynamicCfg binds a flag that represents a dynamic value to configuration
56 | func BindDynamicCfg(flag *pflag.Flag) {
57 | bindFlagWithKeyPrefix(flag, dyValuesCfgPrefix)
58 | }
59 |
60 | // SetDynamicDefaults sets config defaults for dynamic paths and values
61 | func SetDynamicDefaults(jsonWithDefaults []byte) {
62 | // Unmarshal to map to preserve keys for Paths and Values
63 | var defaultsMap map[string]interface{}
64 | json.Unmarshal(jsonWithDefaults, &defaultsMap)
65 |
66 | dyPaths := defaultsMap["dynamic"].(map[string]interface{})["paths"].(map[string]interface{})
67 | dyValues := defaultsMap["dynamic"].(map[string]interface{})["values"].(map[string]interface{})
68 |
69 | for k, v := range dyPaths {
70 | newKey := dyPathsCfgPrefix + k
71 | // ex: "dynamic.paths.instance-identity-document": "/latest/dynamic/instance-identity/document"
72 | dyPathsDefaults[newKey] = v
73 | }
74 |
75 | for k, v := range dyValues {
76 | newKey := dyValuesCfgPrefix + k
77 | // ex: "dynamic.values.instance-identity-document": {"accountId" ...}
78 | dyValuesDefaults[newKey] = v
79 |
80 | // if dyvalue is a nested struct, then re-unmarshal json data to correct type
81 | if nestedStruct, ok := dyNestedValues[newKey]; ok {
82 | updatedVal, err := unmarshalToNestedStruct(v, nestedStruct)
83 | if err == nil {
84 | dyValuesDefaults[newKey] = updatedVal
85 | }
86 | }
87 | }
88 |
89 | LoadConfigFromDefaults(dyPathsDefaults)
90 | LoadConfigFromDefaults(dyValuesDefaults)
91 | }
92 |
93 | // GetDynamicDefaults returns config defaults for dynamic paths and values
94 | func GetDynamicDefaults() (map[string]interface{}, map[string]interface{}) {
95 | return dyPathsDefaults, dyValuesDefaults
96 | }
97 |
98 | // GetDynamicValueToPlaceholderPathsKeyMap returns collection of dynamic values that are substituted into paths
99 | func GetDynamicValueToPlaceholderPathsKeyMap() map[string][]string {
100 | return dyValueToPlaceholderPathsKeyMap
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/config/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package config
15 |
16 | import (
17 | "github.com/spf13/pflag"
18 | )
19 |
20 | var (
21 | serverCfgPrefix = "server."
22 | serverCfgDefaults = map[string]interface{}{
23 | serverCfgPrefix + "hostname": "0.0.0.0",
24 | serverCfgPrefix + "port": "1338",
25 | }
26 | )
27 |
28 | // BindServerCfg binds a flag that represents a server config to configuration
29 | func BindServerCfg(flag *pflag.Flag) {
30 | bindFlagWithKeyPrefix(flag, serverCfgPrefix)
31 | }
32 |
33 | // SetServerCfgDefaults sets config defaults for server config
34 | func SetServerCfgDefaults() {
35 | LoadConfigFromDefaults(serverCfgDefaults)
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/config/userdata.go:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package config
15 |
16 | import (
17 | "encoding/json"
18 |
19 | "github.com/spf13/pflag"
20 | )
21 |
22 | var (
23 | udCfgPrefix = "userdata."
24 | udPathsCfgPrefix = udCfgPrefix + "paths."
25 | udValuesCfgPrefix = udCfgPrefix + "values."
26 |
27 | // mapping of udValue KEYS to udPath KEYS requiring path substitutions on override
28 | udValueToPlaceholderPathsKeyMap = map[string][]string{}
29 |
30 | // supported URL paths to run a mock
31 | udPathsDefaults = map[string]interface{}{}
32 |
33 | // values in mock responses
34 | udValuesDefaults = map[string]interface{}{}
35 | )
36 |
37 | // GetCfgUdValPrefix returns the prefix to use to access userdata values in config
38 | func GetCfgUdValPrefix() string {
39 | return udCfgPrefix + "." + udValuesCfgPrefix + "."
40 | }
41 |
42 | // GetCfgUdPathsPrefix returns the prefix to use to access userdata values in config
43 | func GetCfgUdPathsPrefix() string {
44 | return udCfgPrefix + "." + udPathsCfgPrefix + "."
45 | }
46 |
47 | // BindUserdataCfg binds a flag that represents a userdata value to configuration
48 | func BindUserdataCfg(flag *pflag.Flag) {
49 | bindFlagWithKeyPrefix(flag, udValuesCfgPrefix)
50 | }
51 |
52 | // SetUserdataDefaults sets config defaults for userdata paths and values
53 | func SetUserdataDefaults(jsonWithDefaults []byte) {
54 | // Unmarshal to map to preserve keys for Paths and Values
55 | var defaultsMap map[string]interface{}
56 | json.Unmarshal(jsonWithDefaults, &defaultsMap)
57 | udPaths := defaultsMap["userdata"].(map[string]interface{})["paths"].(map[string]interface{})
58 |
59 | udValues := defaultsMap["userdata"].(map[string]interface{})["values"].(map[string]interface{})
60 |
61 | for k, v := range udPaths {
62 | newKey := udPathsCfgPrefix + k
63 | // ex: "userdata": "/latest/user-data"
64 | udPathsDefaults[newKey] = v
65 | }
66 |
67 | for k, v := range udValues {
68 | newKey := udValuesCfgPrefix + k
69 | // ex: "userdata": "1234,john,reboot,true|4512,richard,|173,,,"
70 | udValuesDefaults[newKey] = v
71 | }
72 |
73 | LoadConfigFromDefaults(udPathsDefaults)
74 | LoadConfigFromDefaults(udValuesDefaults)
75 | }
76 |
77 | // GetUserdataDefaults returns config defaults for userdata paths and values
78 | func GetUserdataDefaults() (map[string]interface{}, map[string]interface{}) {
79 | return udPathsDefaults, udValuesDefaults
80 | }
81 |
82 | // GetUserdataValueToPlaceholderPathsKeyMap returns collection of userdata values that are substituted into paths
83 | func GetUserdataValueToPlaceholderPathsKeyMap() map[string][]string {
84 | return udValueToPlaceholderPathsKeyMap
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/error/error.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package errors
15 |
16 | import (
17 | "fmt"
18 | )
19 |
20 | // FlagValidationError denotes an error encountered while validating CLI flags
21 | type FlagValidationError struct {
22 | FlagName string
23 | Allowed string
24 | InvalidValue string
25 | }
26 |
27 | // Error returns the formatted flag validation error.
28 | func (ve FlagValidationError) Error() string {
29 | return fmt.Sprintf("Invalid CLI input \"%s\" for flag %s. Allowed value(s): %s.\n", ve.InvalidValue, ve.FlagName, ve.Allowed)
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/mock/asglifecycle/asglifecycle.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package asglifecycle
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "strings"
20 | "time"
21 |
22 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
23 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
24 | )
25 |
26 | const (
27 | targetLifeCycleStatePath = "/latest/meta-data/autoscaling/target-lifecycle-state"
28 | )
29 |
30 | var (
31 | eligibleIPs = make(map[string]bool)
32 | c cfg.Config
33 | asgStartTime int64 = time.Now().Unix()
34 | state = "InService"
35 | )
36 |
37 | func Mock(config cfg.Config) {
38 | SetConfig(config)
39 | server.ListenAndServe(config.Server.HostName, config.Server.Port)
40 | }
41 |
42 | // SetConfig sets the local config
43 | func SetConfig(config cfg.Config) {
44 | c = config
45 | }
46 |
47 | // Handler processes http requests
48 | func Handler(res http.ResponseWriter, req *http.Request) {
49 | if c.MockIPCount >= 0 {
50 | // req.RemoteAddr is formatted as IP:port
51 | requestIP := strings.Split(req.RemoteAddr, ":")[0]
52 | if !eligibleIPs[requestIP] {
53 | if len(eligibleIPs) < c.MockIPCount {
54 | eligibleIPs[requestIP] = true
55 | } else {
56 | log.Printf("Requesting IP %s is not eligible for ASG Lifecycle State because the max number of IPs configured (%d) has been reached.\n", requestIP, c.MockIPCount)
57 | server.ReturnNotFoundResponse(res)
58 | return
59 | }
60 | }
61 | }
62 |
63 | switch req.URL.Path {
64 | case targetLifeCycleStatePath:
65 | handleASGTargetLifecycleState(res, req)
66 | }
67 | }
68 |
69 | func handleASGTargetLifecycleState(res http.ResponseWriter, req *http.Request) {
70 | requestTime := time.Now().Unix()
71 | if c.ASGTerminationTriggerTime != "" {
72 | triggerTime, _ := time.Parse(time.RFC3339, c.ASGTerminationTriggerTime)
73 | delayRemaining := triggerTime.Unix() - requestTime
74 | if delayRemaining <= 0 {
75 | state = "Terminated"
76 | }
77 | } else {
78 | delayInSeconds := c.ASGTerminationDelayInSec
79 | delayRemaining := delayInSeconds - (requestTime - asgStartTime)
80 | if delayRemaining <= 0 {
81 | state = "Terminated"
82 | }
83 | }
84 |
85 | switch req.Method {
86 | case "GET":
87 | server.FormatAndReturnTextResponse(res, state)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/mock/dynamic/dynamic.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package dynamic
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "reflect"
20 |
21 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
22 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/imdsv2"
23 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
24 | )
25 |
26 | var (
27 | supportedPaths = make(map[string]interface{})
28 | response interface{}
29 | jsonTextResponse = map[string]bool{}
30 |
31 | // ServicePath defines the dynamic service path
32 | ServicePath = "/latest/dynamic"
33 | )
34 |
35 | // Handler processes http requests
36 | func Handler(res http.ResponseWriter, req *http.Request) {
37 | log.Println("Received request to mock dynamic metadata:", req.URL.Path)
38 |
39 | if val, ok := supportedPaths[req.URL.Path]; ok {
40 | response = val
41 | } else {
42 | response = "Something went wrong with: " + req.URL.Path
43 | }
44 |
45 | switch response.(type) {
46 | // static metadata values are either string or JSON EXCEPT FOR elastic-inference associations
47 | case string:
48 | server.FormatAndReturnTextResponse(res, response.(string))
49 | default:
50 | if jsonTextResponse[req.URL.Path] {
51 | server.FormatAndReturnJSONTextResponse(res, response)
52 | } else {
53 | server.FormatAndReturnJSONResponse(res, response)
54 | }
55 | }
56 | }
57 |
58 | // RegisterHandlers registers handlers for dynamic paths
59 | func RegisterHandlers(config cfg.Config) {
60 | pathValues := reflect.ValueOf(config.Dynamic.Paths)
61 | dyValues := reflect.ValueOf(config.Dynamic.Values)
62 |
63 | // Iterate over fields in config.Dynamic.Paths to
64 | // determine intersections with config.Dynamic.Values.
65 | // Intersections represent which paths and values to bind.
66 | for i := 0; i < pathValues.NumField(); i++ {
67 | pathFieldName := pathValues.Type().Field(i).Name
68 | dyValueFieldName := dyValues.FieldByName(pathFieldName)
69 | if dyValueFieldName.IsValid() {
70 | path := pathValues.Field(i).Interface().(string)
71 | value := dyValueFieldName.Interface()
72 | if path != "" && value != nil {
73 | // Ex: "/latest/dynamic/instance-identity/document"
74 | supportedPaths[path] = value
75 | if config.Imdsv2Required {
76 | server.HandleFunc(path, imdsv2.ValidateToken(Handler))
77 | } else {
78 | server.HandleFunc(path, Handler)
79 | }
80 | } else {
81 | log.Printf("There was an issue registering path %v with dyValue: %v", path, value)
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/mock/dynamic/types/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package types
15 |
16 | // InstanceIdentityDocument structure for mock json response parsing
17 | type InstanceIdentityDocument struct {
18 | AccountId string `json:"accountId"`
19 | ImageId string `json:"imageId"`
20 | AvailabilityZone string `json:"availabilityZone"`
21 | RamdiskId *string `json:"ramdiskId"`
22 | KernelId *string `json:"kernelId"`
23 | DevpayProductCodes *string `json:"devpayProductCodes"`
24 | MarketplaceProductCodes []string `json:"marketplaceProductCodes"`
25 | Version string `json:"version"`
26 | PrivateIp string `json:"privateIp"`
27 | BillingProducts *string `json:"billingProducts"`
28 | InstanceId string `json:"instanceId"`
29 | PendingTime string `json:"pendingTime"`
30 | Architecture string `json:"architecture"`
31 | InstanceType string `json:"instanceType"`
32 | Region string `json:"region"`
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/mock/events/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package config
15 |
16 | // Config represents the configuration for the mock
17 | type Config struct {
18 | EventCode string `mapstructure:"code"`
19 | EventState string `mapstructure:"state"`
20 | NotBefore string `mapstructure:"not-before"` // The earliest start time for the scheduled event
21 | NotAfter string `mapstructure:"not-after"` // The latest end time for the scheduled event
22 | NotBeforeDeadline string `mapstructure:"not-before-deadline"` // The deadline for starting the event
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/mock/events/events.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package events
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "strings"
20 | "time"
21 |
22 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
23 | t "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/events/internal/types"
24 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
25 | )
26 |
27 | const (
28 | descriptionPrefix = "The instance is scheduled for "
29 | timeLayout = "2 Jan 2006 15:04:05 GMT"
30 | )
31 |
32 | var (
33 | eligibleIPs = make(map[string]bool)
34 | appStartTime int64 = time.Now().Unix()
35 | c cfg.Config
36 | )
37 |
38 | // Mock starts scheduled events mock
39 | func Mock(config cfg.Config) {
40 | SetConfig(config)
41 | server.ListenAndServe(config.Server.HostName, config.Server.Port)
42 | }
43 |
44 | // SetConfig sets the local config
45 | func SetConfig(config cfg.Config) {
46 | c = config
47 | }
48 |
49 | // Handler processes http requests
50 | func Handler(res http.ResponseWriter, req *http.Request) {
51 | log.Printf("RemoteAddr: %s sent request to mock scheduled event: %s\n", req.URL.Path, req.RemoteAddr)
52 |
53 | // specify negative value to disable this feature
54 | if c.MockIPCount >= 0 {
55 | // req.RemoteAddr is formatted as IP:port
56 | requestIP := strings.Split(req.RemoteAddr, ":")[0]
57 | if !eligibleIPs[requestIP] {
58 | if len(eligibleIPs) < c.MockIPCount {
59 | eligibleIPs[requestIP] = true
60 | } else {
61 | log.Printf("Requesting IP %s is not eligible for Scheduled Event because the max number of IPs configured (%d) has been reached.\n", requestIP, c.MockIPCount)
62 | server.ReturnNotFoundResponse(res)
63 | return
64 | }
65 | }
66 | }
67 |
68 | requestTime := time.Now().Unix()
69 |
70 | if c.MockTriggerTime != "" {
71 | triggerTime, _ := time.Parse(time.RFC3339, c.MockTriggerTime)
72 |
73 | delayRemaining := triggerTime.Unix() - requestTime
74 | if delayRemaining > 0 {
75 | log.Printf("MockTriggerTime %s was not reached yet. The mock response will be available in %ds. Returning `notFoundResponse` for now", triggerTime, delayRemaining)
76 | server.ReturnNotFoundResponse(res)
77 | return
78 | }
79 | } else {
80 | delayInSeconds := c.MockDelayInSec
81 | delayRemaining := delayInSeconds - (requestTime - appStartTime)
82 | if delayRemaining > 0 {
83 | log.Printf("Delaying the response by %ds as requested. The mock response will be available in %ds. Returning `notFoundResponse` for now", delayInSeconds, delayRemaining)
84 | server.ReturnNotFoundResponse(res)
85 | return
86 | }
87 | }
88 |
89 | // return mock response after the delay or trigger time has elapsed
90 | server.FormatAndReturnJSONResponse(res, getMetadata())
91 | }
92 |
93 | func getMetadata() []t.Event {
94 | md := c.Metadata.Values
95 | se := c.EventsConfig
96 |
97 | b, _ := time.Parse(time.RFC3339, se.NotBefore)
98 | a, _ := time.Parse(time.RFC3339, se.NotAfter)
99 | bd, _ := time.Parse(time.RFC3339, se.NotBeforeDeadline)
100 | eventResp := t.Event{
101 | Code: se.EventCode,
102 | Description: descriptionPrefix + se.EventCode,
103 | EventID: md.EventID,
104 | State: se.EventState,
105 | NotBefore: b.Format(timeLayout),
106 | NotAfter: a.Format(timeLayout),
107 | NotBeforeDeadline: bd.Format(timeLayout),
108 | }
109 | // supports 1 scheduled event for now
110 | return []t.Event{eventResp}
111 | }
112 |
--------------------------------------------------------------------------------
/pkg/mock/events/internal/types/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package types
15 |
16 | // Event represents the metadata structure for mock json response parsing
17 | type Event struct {
18 | Code string `mapstructure:"code"`
19 | Description string `mapstructure:"description"`
20 | State string `mapstructure:"state"` // State of the scheduled event
21 | EventID string `mapstructure:"event-id" json:"EventId"`
22 | NotBefore string `mapstructure:"not-before"` // The earliest start time for the scheduled event
23 | NotAfter string `mapstructure:"not-after,omitempty"` // The latest end time for the scheduled event
24 | NotBeforeDeadline string `mapstructure:"not-before-deadline,omitempty"` // The deadline for starting the event
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/mock/imdsv2/imdsv2_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package imdsv2
15 |
16 | import (
17 | "fmt"
18 | "io/ioutil"
19 | "net/http"
20 | "net/http/httptest"
21 | "regexp"
22 | "strconv"
23 | "strings"
24 | "testing"
25 | "time"
26 |
27 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
28 | h "github.com/aws/amazon-ec2-metadata-mock/test"
29 | )
30 |
31 | const (
32 | testURL = "http://test-example.com/"
33 | tokenRegex = "^[a-zA-Z0-9+/]{43}="
34 | successMockResponse = "Success!"
35 | )
36 |
37 | func MockHandler(res http.ResponseWriter, req *http.Request) {
38 | server.FormatAndReturnTextResponse(res, successMockResponse)
39 | }
40 |
41 | // Token Generator Tests
42 | func TestGenerateToken(t *testing.T) {
43 | req := httptest.NewRequest("PUT", testURL, nil)
44 | req.Header.Set(tokenTTLHeader, "21500")
45 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
46 | token, ttl := parseGenTokenResp(generateTokenResp)
47 | h.Assert(t, isTokenValid(token), fmt.Sprintf("Expected valid token generation, but was %s", token))
48 | h.Assert(t, ttl == 21500, fmt.Sprintf("Expected Token TTL header to equal requested value, but was %d", ttl))
49 | }
50 | func TestInvalidGenerateTokenRequestGet(t *testing.T) {
51 | req := httptest.NewRequest("GET", testURL, nil)
52 | req.Header.Set(tokenTTLHeader, "21500")
53 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
54 | token, _ := parseGenTokenResp(generateTokenResp)
55 | h.Assert(t, token == "", fmt.Sprintf("Expected no token, but was %s", token))
56 | }
57 | func TestInvalidGenerateTokenInvalidTTL(t *testing.T) {
58 | req := httptest.NewRequest("PUT", testURL, nil)
59 | req.Header.Set(tokenTTLHeader, "0")
60 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
61 | respContent, _ := parseGenTokenResp(generateTokenResp)
62 | h.Assert(t, respContent == server.BadRequestResponse, fmt.Sprintf("Expected 400 -- Bad Request, but was %s", respContent))
63 | }
64 | func TestInvalidGenerateTokenNoTTL(t *testing.T) {
65 | req := httptest.NewRequest("PUT", testURL, nil)
66 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
67 | respContent, _ := parseGenTokenResp(generateTokenResp)
68 | h.Assert(t, respContent == server.BadRequestResponse, fmt.Sprintf("Expected 400 -- Bad Request, but was %s", respContent))
69 | }
70 |
71 | // Token Validator Tests
72 | func TestValidateToken(t *testing.T) {
73 | req := httptest.NewRequest("PUT", testURL, nil)
74 | req.Header.Set(tokenTTLHeader, "21500")
75 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
76 | validTestToken, _ := parseGenTokenResp(generateTokenResp)
77 |
78 | req = httptest.NewRequest("GET", testURL, nil)
79 | req.Header.Set(tokenRequestHeader, validTestToken)
80 | validateTokenResp := executeTestHTTPRequest(req, http.HandlerFunc(ValidateToken(MockHandler)))
81 | respContent, _ := ioutil.ReadAll(validateTokenResp.Body)
82 | h.Assert(t, strings.TrimSpace(string(respContent)) == successMockResponse, fmt.Sprintf("Expected successful token validation, but was %s", respContent))
83 | }
84 | func TestValidateTokenNoToken(t *testing.T) {
85 | req := httptest.NewRequest("GET", testURL, nil)
86 | validateTokenResp := executeTestHTTPRequest(req, http.HandlerFunc(ValidateToken(MockHandler)))
87 | respContent, _ := ioutil.ReadAll(validateTokenResp.Body)
88 | h.Assert(t, strings.TrimSpace(string(respContent)) == server.UnauthorizedResponse, fmt.Sprintf("Expected 401 -- Unauthorized for no token, but was %s", respContent))
89 | }
90 | func TestValidateTokenInvalidToken(t *testing.T) {
91 | invalidTestToken := "ThisTokenIsNotValid!"
92 | req := httptest.NewRequest("GET", testURL, nil)
93 | req.Header.Set(tokenRequestHeader, invalidTestToken)
94 | validateTokenResp := executeTestHTTPRequest(req, http.HandlerFunc(ValidateToken(MockHandler)))
95 | respContent, _ := ioutil.ReadAll(validateTokenResp.Body)
96 | h.Assert(t, strings.TrimSpace(string(respContent)) == server.UnauthorizedResponse, fmt.Sprintf("401 -- Unauthorized for invalid token, but was %s", respContent))
97 | }
98 | func TestValidateTokenExpiredToken(t *testing.T) {
99 | req := httptest.NewRequest("PUT", testURL, nil)
100 | req.Header.Set(tokenTTLHeader, "1")
101 | generateTokenResp := executeTestHTTPRequest(req, GenerateToken)
102 | expiredTestToken, _ := parseGenTokenResp(generateTokenResp)
103 |
104 | time.Sleep(1 * time.Second)
105 |
106 | req = httptest.NewRequest("GET", testURL, nil)
107 | req.Header.Set(tokenRequestHeader, expiredTestToken)
108 | validateTokenResp := executeTestHTTPRequest(req, http.HandlerFunc(ValidateToken(MockHandler)))
109 | respContent, _ := ioutil.ReadAll(validateTokenResp.Body)
110 | h.Assert(t, strings.TrimSpace(string(respContent)) == server.UnauthorizedResponse, fmt.Sprintf("401 -- Unauthorized for expired token, but was %s", respContent))
111 | }
112 |
113 | // Test Helpers
114 | func isTokenValid(token string) bool {
115 | matched, _ := regexp.Match(tokenRegex, []byte(token))
116 | return matched
117 | }
118 |
119 | func executeTestHTTPRequest(req *http.Request, handler http.HandlerFunc) *http.Response {
120 | w := httptest.NewRecorder()
121 | handler.ServeHTTP(w, req)
122 | resp := w.Result()
123 | return resp
124 | }
125 |
126 | func parseGenTokenResp(resp *http.Response) (string, int) {
127 | token, _ := ioutil.ReadAll(resp.Body)
128 | tokenString := strings.TrimSpace(string(token))
129 | ttl := resp.Header.Get(tokenTTLHeader)
130 | ttlInt := -1
131 | if ttl != "" {
132 | ttlInt, _ = strconv.Atoi(ttl)
133 | }
134 | return tokenString, ttlInt
135 | }
136 |
--------------------------------------------------------------------------------
/pkg/mock/imdsv2/tokengenerator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package imdsv2
15 |
16 | import (
17 | "encoding/base64"
18 | "errors"
19 | "log"
20 | "math/rand"
21 | "net/http"
22 | "strconv"
23 | "time"
24 |
25 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
26 | )
27 |
28 | const (
29 | tokenTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
30 | maxTTL = 21600 // 6 hours
31 | )
32 |
33 | var (
34 | generatedTokens = make(map[string]v2Token)
35 | )
36 |
37 | type v2Token struct {
38 | Value string // actual token value
39 | TTL int // token ttl
40 | CreatedAt time.Time // time the token was created
41 | }
42 |
43 | // GenerateToken returns a token with the specified TTL used for IMDSv2 requests
44 | func GenerateToken(res http.ResponseWriter, req *http.Request) {
45 | log.Printf("GenerateToken Received request: %v", req)
46 | // only valid with PUT
47 | if req.Method != http.MethodPut {
48 | return
49 | }
50 | // check that header contains valid ttl
51 | requestedTTL := req.Header.Get(tokenTTLHeader)
52 | validTTL, err := extractValidTTL(requestedTTL)
53 | if err != nil {
54 | log.Printf("Something went wrong with ttl validation: %v with requested TTL: %v", err.Error(), requestedTTL)
55 | server.ReturnBadRequestResponse(res)
56 | return
57 | }
58 |
59 | key := make([]byte, 32)
60 | _, err = rand.Read(key)
61 | if err != nil {
62 | server.FormatAndReturnTextResponse(res, "Something went wrong with token creation")
63 | return
64 | }
65 |
66 | tokenValue := base64.StdEncoding.EncodeToString(key)
67 | token := v2Token{
68 | Value: tokenValue,
69 | TTL: validTTL,
70 | CreatedAt: time.Now(),
71 | }
72 | generatedTokens[token.Value] = token
73 | res.Header().Set(tokenTTLHeader, strconv.Itoa(token.TTL))
74 | server.FormatAndReturnTextResponse(res, token.Value)
75 | }
76 |
77 | func extractValidTTL(reqTTL string) (int, error) {
78 | if reqTTL == "" {
79 | log.Printf("TTL is required. requested TTL: %v", reqTTL)
80 | return 0, errors.New("TTL is nil")
81 | }
82 |
83 | intTTL, err := strconv.Atoi(reqTTL)
84 |
85 | if err != nil {
86 | log.Printf("Something went wrong with ttl conversion. requested TTL: %v", reqTTL)
87 | return 0, err
88 | }
89 | if intTTL <= 0 || intTTL > maxTTL {
90 | return 0, errors.New("TTL needs to be between 0-21600 seconds")
91 | }
92 |
93 | return intTTL, nil
94 | }
95 |
--------------------------------------------------------------------------------
/pkg/mock/imdsv2/tokenvalidator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package imdsv2
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "time"
20 |
21 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
22 | )
23 |
24 | const (
25 | tokenRequestHeader = "X-aws-ec2-metadata-token"
26 | )
27 |
28 | // ValidateToken is a wrapper to validate token before passing request to provided handler
29 | func ValidateToken(pathHandler server.HandlerType) server.HandlerType {
30 | return func(res http.ResponseWriter, req *http.Request) {
31 | log.Printf("ValidateToken Received request: %v", req)
32 | providedToken := req.Header.Get(tokenRequestHeader)
33 | if providedToken == "" {
34 | log.Println("Token required; No token provided.")
35 | server.ReturnUnauthorizedResponse(res)
36 | return
37 | }
38 |
39 | if actualToken, ok := generatedTokens[providedToken]; ok {
40 | accessTime := time.Now()
41 | duration := accessTime.Sub(actualToken.CreatedAt)
42 | if int(duration.Seconds()) >= actualToken.TTL {
43 | log.Println("Token has expired")
44 | delete(generatedTokens, providedToken)
45 | server.ReturnUnauthorizedResponse(res)
46 | return
47 | }
48 | log.Println("Token validated!")
49 | pathHandler(res, req)
50 | } else {
51 | log.Printf("Invalid token provided: %v", providedToken)
52 | server.ReturnUnauthorizedResponse(res)
53 | return
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/mock/root/root.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package root
15 |
16 | import (
17 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
18 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/asglifecycle"
19 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/events"
20 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/spot"
21 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
22 | )
23 |
24 | // Mock serves all subcommand handlers
25 | func Mock(config cfg.Config) {
26 |
27 | // set configs for subcommands
28 | spot.SetConfig(config)
29 | events.SetConfig(config)
30 | asglifecycle.SetConfig(config)
31 |
32 | server.ListenAndServe(config.Server.HostName, config.Server.Port)
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/mock/spot/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package config
15 |
16 | // Config represents the configuration for the mock
17 | type Config struct {
18 | InstanceAction string `mapstructure:"action"`
19 | TerminationTime string `mapstructure:"time"`
20 | RebalanceRecTime string `mapstructure:"rebalance-rec-time"`
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/mock/spot/internal/types/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package types
15 |
16 | // InstanceActionResponse metadata structure for mock json response parsing
17 | type InstanceActionResponse struct {
18 | Action string `json:"action"`
19 | Time string `json:"time"`
20 | }
21 |
22 | // RebalanceRecommendationResponse metadata structure for mock json response parsing
23 | type RebalanceRecommendationResponse struct {
24 | NoticeTime string `json:"noticeTime"`
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/mock/spot/spot.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package spot
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "strings"
20 | "time"
21 |
22 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
23 | t "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/spot/internal/types"
24 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
25 | )
26 |
27 | const (
28 | instanceActionPath = "/latest/meta-data/spot/instance-action"
29 | terminationTimePath = "/latest/meta-data/spot/termination-time"
30 | rebalanceRecPath = "/latest/meta-data/events/recommendations/rebalance"
31 | )
32 |
33 | var (
34 | eligibleIPs = make(map[string]bool)
35 | spotItnStartTime int64 = time.Now().Unix()
36 | c cfg.Config
37 | )
38 |
39 | // Mock starts spot itn mock
40 | func Mock(config cfg.Config) {
41 | SetConfig(config)
42 | server.ListenAndServe(config.Server.HostName, config.Server.Port)
43 | }
44 |
45 | // SetConfig sets the local config
46 | func SetConfig(config cfg.Config) {
47 | c = config
48 | }
49 |
50 | // Handler processes http requests
51 | func Handler(res http.ResponseWriter, req *http.Request) {
52 | // specify negative value to disable this feature
53 | if c.MockIPCount >= 0 {
54 | // req.RemoteAddr is formatted as IP:port
55 | requestIP := strings.Split(req.RemoteAddr, ":")[0]
56 | if !eligibleIPs[requestIP] {
57 | if len(eligibleIPs) < c.MockIPCount {
58 | eligibleIPs[requestIP] = true
59 | } else {
60 | log.Printf("Requesting IP %s is not eligible for Spot ITN or Rebalance Recommendation because the max number of IPs configured (%d) has been reached.\n", requestIP, c.MockIPCount)
61 | server.ReturnNotFoundResponse(res)
62 | return
63 | }
64 | }
65 | }
66 | switch req.URL.Path {
67 | case instanceActionPath, terminationTimePath:
68 | handleSpotITN(res, req)
69 | case rebalanceRecPath:
70 | handleRebalance(res, req)
71 | }
72 | }
73 |
74 | func handleSpotITN(res http.ResponseWriter, req *http.Request) {
75 | requestTime := time.Now().Unix()
76 | if c.MockTriggerTime != "" {
77 | triggerTime, _ := time.Parse(time.RFC3339, c.MockTriggerTime)
78 | delayRemaining := triggerTime.Unix() - requestTime
79 | if delayRemaining > 0 {
80 | log.Printf("MockTriggerTime %s was not reached yet. The spot itn will be available in %ds. Returning `notFoundResponse` for now", triggerTime, delayRemaining)
81 | server.ReturnNotFoundResponse(res)
82 | return
83 | }
84 | } else {
85 | delayInSeconds := c.MockDelayInSec
86 | delayRemaining := delayInSeconds - (requestTime - spotItnStartTime)
87 | if delayRemaining > 0 {
88 | log.Printf("Delaying the response by %ds as requested. The spot itn will be available in %ds. Returning `notFoundResponse` for now", delayInSeconds, delayRemaining)
89 | server.ReturnNotFoundResponse(res)
90 | return
91 | }
92 | }
93 | // default time to requestTime + 2min, unless overridden
94 | mockResponseTime := time.Now().UTC().Add(time.Minute * time.Duration(2)).Format(time.RFC3339)
95 | if c.SpotConfig.TerminationTime != "" {
96 | mockResponseTime = c.SpotConfig.TerminationTime
97 | }
98 | // return mock response after the delay or trigger time has elapsed
99 | switch req.URL.Path {
100 | case instanceActionPath:
101 | server.FormatAndReturnJSONResponse(res, getInstanceActionResponse(mockResponseTime))
102 | case terminationTimePath:
103 | server.FormatAndReturnTextResponse(res, mockResponseTime)
104 | }
105 | }
106 |
107 | func handleRebalance(res http.ResponseWriter, req *http.Request) {
108 | requestTime := time.Now().Unix()
109 | if c.RebalanceTriggerTime != "" {
110 | triggerTime, _ := time.Parse(time.RFC3339, c.RebalanceTriggerTime)
111 | delayRemaining := triggerTime.Unix() - requestTime
112 | if delayRemaining > 0 {
113 | log.Printf("RebalanceTriggerTime %s was not reached yet. The rebalance rec will be available in %ds. Returning `notFoundResponse` for now", triggerTime, delayRemaining)
114 | server.ReturnNotFoundResponse(res)
115 | return
116 | }
117 | } else {
118 | delayInSeconds := c.RebalanceDelayInSec
119 | delayRemaining := delayInSeconds - (requestTime - spotItnStartTime)
120 | if delayRemaining > 0 {
121 | log.Printf("Delaying the response by %ds as requested. The rebalance rec will be available in %ds. Returning `notFoundResponse` for now", delayInSeconds, delayRemaining)
122 | server.ReturnNotFoundResponse(res)
123 | return
124 | }
125 | }
126 | // default time to requestTime, unless overridden
127 | mockResponseTime := time.Now().UTC().Format(time.RFC3339)
128 | if c.SpotConfig.RebalanceRecTime != "" {
129 | mockResponseTime = c.SpotConfig.RebalanceRecTime
130 | }
131 | server.FormatAndReturnJSONResponse(res, t.RebalanceRecommendationResponse{NoticeTime: mockResponseTime})
132 | }
133 |
134 | func getInstanceActionResponse(time string) t.InstanceActionResponse {
135 | return t.InstanceActionResponse{
136 | Action: c.SpotConfig.InstanceAction,
137 | Time: time,
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/pkg/mock/static/static.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package static
15 |
16 | import (
17 | "log"
18 | "net/http"
19 | "reflect"
20 |
21 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
22 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/imdsv2"
23 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
24 | )
25 |
26 | var (
27 | supportedPaths = make(map[string]interface{})
28 | response interface{}
29 | jsonTextResponse = map[string]bool{"/latest/meta-data/elastic-inference/associations/eia-bfa21c7904f64a82a21b9f4540169ce1": true}
30 |
31 | // ServicePath defines the static service path
32 | ServicePath = "/latest/meta-data"
33 | )
34 |
35 | // Handler processes http requests
36 | func Handler(res http.ResponseWriter, req *http.Request) {
37 | log.Println("Received request to mock static metadata:", req.URL.Path)
38 |
39 | if val, ok := supportedPaths[req.URL.Path]; ok {
40 | response = val
41 | } else {
42 | response = "Something went wrong with: " + req.URL.Path
43 | }
44 |
45 | switch response.(type) {
46 | // static metadata values are either string or JSON EXCEPT FOR elastic-inference associations
47 | case string:
48 | server.FormatAndReturnTextResponse(res, response.(string))
49 | default:
50 | if jsonTextResponse[req.URL.Path] {
51 | server.FormatAndReturnJSONTextResponse(res, response)
52 | } else {
53 | server.FormatAndReturnJSONResponse(res, response)
54 | }
55 | }
56 | }
57 |
58 | // RegisterHandlers registers handlers for static paths
59 | func RegisterHandlers(config cfg.Config) {
60 | server.HandleFunc("/latest/api/token", imdsv2.GenerateToken)
61 |
62 | pathValues := reflect.ValueOf(config.Metadata.Paths)
63 | mdValues := reflect.ValueOf(config.Metadata.Values)
64 |
65 | // Iterate over fields in config.Metadata.Paths to
66 | // determine intersections with config.Metadata.Values.
67 | // Intersections represent which paths and values to bind.
68 | for i := 0; i < pathValues.NumField(); i++ {
69 | pathFieldName := pathValues.Type().Field(i).Name
70 | mdValueFieldName := mdValues.FieldByName(pathFieldName)
71 | if mdValueFieldName.IsValid() {
72 | path := pathValues.Field(i).Interface().(string)
73 | value := mdValueFieldName.Interface()
74 | if path != "" && value != nil {
75 | // Ex: "/latest/meta-data/instance-id" : "i-1234567890abcdef0"
76 | supportedPaths[path] = value
77 | if config.Imdsv2Required {
78 | server.HandleFunc(path, imdsv2.ValidateToken(Handler))
79 | } else {
80 | server.HandleFunc(path, Handler)
81 | }
82 | } else {
83 | log.Printf("There was an issue registering path %v with mdValue: %v", path, value)
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/pkg/mock/static/types/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package types
15 |
16 | // IamInformation metadata structure for mock json response parsing
17 | type IamInformation struct {
18 | Code string `json:"Code"`
19 | LastUpdated string `json:"LastUpdated"`
20 | InstanceProfileArn string `json:"InstanceProfileArn"`
21 | InstanceProfileId string `json:"InstanceProfileId"`
22 | }
23 |
24 | // IamSecurityCredentials metadata structure for mock json response parsing
25 | type IamSecurityCredentials struct {
26 | Code string `json:"Code"`
27 | LastUpdated string `json:"LastUpdated"`
28 | Type string `json:"Type"`
29 | AccessKeyId string `json:"AccessKeyId"`
30 | SecretAccessKey string `json:"SecretAccessKey"`
31 | Token string `json:"Token"`
32 | Expiration string `json:"Expiration"`
33 | }
34 |
35 | // ElasticInferenceAccelerator metadata structure for mock json response parsing
36 | type ElasticInferenceAccelerator struct {
37 | Version elasticInferenceAcceleratorMetadata `json:"version_2018_04_12"`
38 | }
39 |
40 | type elasticInferenceAcceleratorMetadata struct {
41 | ElasticInferenceAcceleratorId string `json:"elastic-inference-accelerator-id"`
42 | ElasticInferenceAcceleratorType string `json:"elastic-inference-accelerator-type"`
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/mock/userdata/userdata.go:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package userdata
15 |
16 | import (
17 | "encoding/base64"
18 | "fmt"
19 | "log"
20 | "net/http"
21 | "reflect"
22 |
23 | cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
24 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/imdsv2"
25 | "github.com/aws/amazon-ec2-metadata-mock/pkg/server"
26 | )
27 |
28 | var (
29 | supportedPaths = make(map[string]interface{})
30 | response interface{}
31 | // ServicePath defines the userdata service path
32 | ServicePath = "/latest/user-data"
33 | )
34 |
35 | // Handler processes http requests
36 | func Handler(res http.ResponseWriter, req *http.Request) {
37 | log.Println("Received request to mock userdata:", req.URL.Path)
38 |
39 | if val, ok := supportedPaths[req.URL.Path]; ok {
40 |
41 | response = val
42 | } else {
43 | response = "Something went wrong with: " + req.URL.Path
44 | }
45 | server.FormatAndReturnOctetResponse(res, response.(string))
46 |
47 | }
48 |
49 | // RegisterHandlers registers handlers for userdata paths
50 | func RegisterHandlers(config cfg.Config) {
51 | pathValues := reflect.ValueOf(config.Userdata.Paths)
52 | udValues := reflect.ValueOf(config.Userdata.Values)
53 | // Iterate over fields in config.Userdata.Paths to
54 | // determine intersections with config.Userdata.Values.
55 | // Intersections represent which paths and values to bind.
56 | for i := 0; i < pathValues.NumField(); i++ {
57 | pathFieldName := pathValues.Type().Field(i).Name
58 | udValueFieldName := udValues.FieldByName(pathFieldName)
59 | if udValueFieldName.IsValid() {
60 | path := pathValues.Field(i).Interface().(string)
61 | value := udValueFieldName.Interface()
62 | if path != "" && value != nil {
63 | // Ex: "/latest/meta-data/instance-id" : "i-1234567890abcdef0"
64 | bvalue, err := base64.StdEncoding.DecodeString(config.Userdata.Values.Userdata)
65 | value = string(bvalue)
66 |
67 | if err != nil {
68 | fmt.Println("There was an issue decoding base64 data from config")
69 | panic(err)
70 | }
71 | supportedPaths[path] = value
72 | if config.Imdsv2Required {
73 | server.HandleFunc(path, imdsv2.ValidateToken(Handler))
74 | } else {
75 |
76 | server.HandleFunc(path, Handler)
77 | }
78 | } else {
79 | log.Printf("There was an issue registering path %v with udValue: %v", path, value)
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/pkg/server/httpserver_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package server
15 |
16 | import (
17 | "net/http/httptest"
18 | "testing"
19 |
20 | "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/dynamic/types"
21 | h "github.com/aws/amazon-ec2-metadata-mock/test"
22 | )
23 |
24 | func TestFormatAndReturnJSONResponse(t *testing.T) {
25 | expected := `{
26 | "accountId": "123456789012",
27 | "imageId": "ami-02f471c4f805553d3",
28 | "availabilityZone": "us-east-1a",
29 | "ramdiskId": null,
30 | "kernelId": null,
31 | "devpayProductCodes": null,
32 | "marketplaceProductCodes": ["4i20ezfza3p7xx2kt2g8weu2u","entry"],
33 | "version": "2017-09-30",
34 | "privateIp": "172.31.85.190",
35 | "billingProducts": null,
36 | "instanceId": "i-048bcb15d2686eec7",
37 | "pendingTime": "2022-06-23T06:21:55Z",
38 | "architecture": "x86_64",
39 | "instanceType": "t2.nano",
40 | "region": "us-east-1"
41 | }`
42 |
43 | testDoc := types.InstanceIdentityDocument{
44 | AccountId: "123456789012",
45 | ImageId: "ami-02f471c4f805553d3",
46 | AvailabilityZone: "us-east-1a",
47 | RamdiskId: nil,
48 | KernelId: nil,
49 | DevpayProductCodes: nil,
50 | MarketplaceProductCodes: []string{"4i20ezfza3p7xx2kt2g8weu2u", "entry"},
51 | Version: "2017-09-30",
52 | PrivateIp: "172.31.85.190",
53 | BillingProducts: nil,
54 | InstanceId: "i-048bcb15d2686eec7",
55 | PendingTime: "2022-06-23T06:21:55Z",
56 | Architecture: "x86_64",
57 | InstanceType: "t2.nano",
58 | Region: "us-east-1",
59 | }
60 | rr := httptest.NewRecorder()
61 | FormatAndReturnJSONResponse(rr, testDoc)
62 | actual := rr.Body.String()
63 | h.Assert(t, expected == actual, "FormatAndReturnJSONResponse did not format InstanceIdentityDocument as expected.")
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/server/swapper.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "net/http"
5 | "sync"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | var _ http.Handler = (*swapper)(nil)
11 |
12 | // swapper is an `http.Handler` which allows the user to swap out routers while the server is running.
13 | // This enables us to reset routes as the program is running, allowing for functionality like updating the config.
14 | type swapper struct {
15 | mu sync.Mutex
16 | router *mux.Router
17 | }
18 |
19 | func NewSwapper() *swapper {
20 | return &swapper{
21 | mu: sync.Mutex{},
22 | router: mux.NewRouter(),
23 | }
24 | }
25 |
26 | func (rs *swapper) Reset() {
27 | rs.mu.Lock()
28 | rs.router = mux.NewRouter()
29 | rs.mu.Unlock()
30 | }
31 |
32 | func (rs *swapper) Walk(f func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error) {
33 | rs.mu.Lock()
34 | rs.router.Walk(f)
35 | rs.mu.Unlock()
36 | }
37 |
38 | func (rs *swapper) HandleFuncPrefix(pattern string, requestHandler HandlerType) {
39 | rs.mu.Lock()
40 | rs.router.PathPrefix(pattern).HandlerFunc(requestHandler)
41 | rs.mu.Unlock()
42 | }
43 |
44 | func (rs *swapper) HandleFunc(pattern string, requestHandler HandlerType) {
45 | rs.mu.Lock()
46 | rs.router.HandleFunc(pattern, requestHandler)
47 | rs.mu.Unlock()
48 | }
49 |
50 | func (rs *swapper) Swap(newRouter *mux.Router) {
51 | rs.mu.Lock()
52 | rs.router = newRouter
53 | rs.mu.Unlock()
54 | }
55 |
56 | func (rs *swapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
57 | rs.mu.Lock()
58 | router := rs.router
59 | rs.mu.Unlock()
60 | router.ServeHTTP(w, r)
61 | }
62 |
--------------------------------------------------------------------------------
/scripts/build-binaries:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile
8 | BIN_DIR=$SCRIPTPATH/../build/bin
9 | mkdir -p $BIN_DIR
10 |
11 | VERSION=$(make -s -f $MAKE_FILE_PATH version)
12 | BASE_BIN_NAME=$(make -s -f $MAKE_FILE_PATH binary-name)
13 | PLATFORMS=("linux/amd64")
14 | PASS_THRU_ARGS=""
15 |
16 | USAGE=$(cat << 'EOM'
17 | Usage: build-binaries [-p ]
18 | Builds static binaries for the platform pairs passed in
19 |
20 | Example: build-binaries -p "linux/amd64,linux/arm"
21 | Optional:
22 | -b Base bin name [DEFAULT: output of "make binary-name"]
23 | -p Platform pair list (os/architecture) [DEFAULT: linux/amd64]
24 | -d DIRECT: Set GOPROXY=direct to bypass go proxies
25 | -v VERSION: The application version of the docker image [DEFAULT: output of `make version`]
26 | EOM
27 | )
28 |
29 | # Process our input arguments
30 | while getopts "dp:v:b:" opt; do
31 | case ${opt} in
32 | p ) # Platform Pairs
33 | IFS=',' read -ra PLATFORMS <<< "$OPTARG"
34 | ;;
35 | d ) # sets GOPROXY=direct
36 | PASS_THRU_ARGS="$PASS_THRU_ARGS -d"
37 | ;;
38 | v ) # Image Version
39 | VERSION="$OPTARG"
40 | ;;
41 | b ) # Base bin name
42 | BASE_BIN_NAME="$OPTARG"
43 | ;;
44 | \? )
45 | echo "$USAGE" 1>&2
46 | exit
47 | ;;
48 | esac
49 | done
50 |
51 | for os_arch in "${PLATFORMS[@]}"; do
52 | os=$(echo $os_arch | cut -d'/' -f1)
53 | arch=$(echo $os_arch | cut -d'/' -f2)
54 | container_name="build-$BASE_BIN_NAME-$os-$arch"
55 | repo_name="bin-build"
56 |
57 | if [[ $os == "windows" ]]; then
58 | bin_name="$BASE_BIN_NAME-$os-$arch.exe"
59 | else
60 | bin_name="$BASE_BIN_NAME-$os-$arch"
61 | fi
62 |
63 | docker container rm $container_name || :
64 | $SCRIPTPATH/build-docker-images -p $os_arch -v $VERSION -r $repo_name $PASS_THRU_ARGS
65 | docker container create --rm --name $container_name "$repo_name:$VERSION-$os-$arch"
66 | docker container cp $container_name:/ec2-metadata-mock $BIN_DIR/$bin_name
67 |
68 | if [[ $os == "windows" ]]; then
69 | ## Create zip archive with binary taking into account windows .exe
70 | cp ${BIN_DIR}/${bin_name} ${BIN_DIR}/${BASE_BIN_NAME}.exe
71 | ## Can't reuse bin_name below because it includes .exe
72 | curr_dir=$(pwd)
73 | cd "${BIN_DIR}"
74 | zip -9 -q ${BASE_BIN_NAME}-$os-$arch.zip ${BASE_BIN_NAME}.exe
75 | cd "${curr_dir}"
76 | rm -f ${BIN_DIR}/${BASE_BIN_NAME}.exe
77 | else
78 | ## Create tar.gz archive with binary
79 | cp ${BIN_DIR}/${bin_name} ${BIN_DIR}/${BASE_BIN_NAME}
80 | tar -zcvf ${BIN_DIR}/${bin_name}.tar.gz -C ${BIN_DIR} ${BASE_BIN_NAME}
81 | rm -f ${BIN_DIR}/${BASE_BIN_NAME}
82 | fi
83 | done
--------------------------------------------------------------------------------
/scripts/build-docker-images:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile
8 | DOCKERFILE_PATH=$REPO_ROOT_PATH/Dockerfile
9 |
10 | VERSION=$(make -s -f $MAKE_FILE_PATH version)
11 | PLATFORMS=("linux/amd64")
12 | GOPROXY="https://proxy.golang.org,direct"
13 |
14 |
15 | USAGE=$(cat << 'EOM'
16 | Usage: build-docker-images [-p ]
17 | Builds docker images for the platform pair
18 |
19 | Example: build-docker-images -p "linux/amd64,linux/arm"
20 | Optional:
21 | -p Platform pair list (os/architecture) [DEFAULT: linux/amd64]
22 | -d DIRECT: Set GOPROXY=direct to bypass go proxies
23 | -r IMAGE REPO: set the docker image repo
24 | -v VERSION: The application version of the docker image [DEFAULT: output of `make version`]
25 | EOM
26 | )
27 |
28 | # Process our input arguments
29 | while getopts "dp:r:v:" opt; do
30 | case ${opt} in
31 | p ) # Platform Pairs
32 | IFS=',' read -ra PLATFORMS <<< "$OPTARG"
33 | ;;
34 | d ) # GOPROXY=direct
35 | GOPROXY="direct"
36 | ;;
37 | r ) # Image Repo
38 | IMAGE_REPO="$OPTARG"
39 | ;;
40 | v ) # Image Version
41 | VERSION="$OPTARG"
42 | ;;
43 | \? )
44 | echo "$USAGE" 1>&2
45 | exit
46 | ;;
47 | esac
48 | done
49 |
50 | for os_arch in "${PLATFORMS[@]}"; do
51 | os=$(echo $os_arch | cut -d'/' -f1)
52 | arch=$(echo $os_arch | cut -d'/' -f2)
53 |
54 | img_tag="$IMAGE_REPO:$VERSION-$os-$arch"
55 |
56 | dockerfile="$DOCKERFILE_PATH"
57 | if [[ $os = "windows" ]]; then
58 | # Linux Dockerfile is valid for building Windows binary
59 | if [[ $IMAGE_REPO != "bin-build" ]]; then
60 | dockerfile="${dockerfile}.windows"
61 | fi
62 | fi
63 |
64 | docker build \
65 | --file "${dockerfile}" \
66 | --build-arg GOOS=${os} \
67 | --build-arg GOARCH=${arch} \
68 | --build-arg GOPROXY=${GOPROXY} \
69 | --tag ${img_tag} \
70 | ${REPO_ROOT_PATH}
71 | done
--------------------------------------------------------------------------------
/scripts/create-local-tag-for-release:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script to create a new local tag in preparation for a release
4 | # This script is idempotent i.e. it always fetches remote tags to create the new tag.
5 | # E.g. If the current remote release tag is v1.0.0,
6 | ## 1) running `create-local-tag-for-release -p` will create a new tag v1.0.1
7 | ## 2) immediately running `create-local-tag-for-release -m` will create a new tag v2.0.0
8 |
9 | set -euo pipefail
10 |
11 | REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../; pwd -P )"
12 | MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile
13 | TAG_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]*)?$"
14 |
15 | HELP=$(cat << 'EOM'
16 | Create a new local tag in preparation for a release. This script is idempotent i.e. it always fetches remote tags to create the new tag.
17 |
18 | Usage: create-local-tag-for-release [options]
19 |
20 | Options:
21 | -v new tag / version number. The script relies on the user to specify a valid and accurately incremented tag.
22 | -m increment major version
23 | -i increment minor version
24 | -p increment patch version
25 | -h help
26 |
27 | Examples:
28 | create-local-tag-for-release -v v1.0.0 Create local tag for new version v1.0.0
29 | create-local-tag-for-release -i Create local tag for new version by incrementing minor version only (previous tag=v1.0.0, new tag=v1.1.0)
30 | EOM
31 | )
32 |
33 | MAJOR_INC=false
34 | MINOR_INC=false
35 | PATCH_INC=false
36 | NEW_TAG=""
37 | CURR_REMOTE_RELEASE_TAG=""
38 |
39 | process_args() {
40 | while getopts "hmipv:" opt; do
41 | case ${opt} in
42 | h )
43 | echo -e "$HELP" 1>&2
44 | exit 0
45 | ;;
46 | m )
47 | MAJOR_INC=true
48 | ;;
49 | i )
50 | MINOR_INC=true
51 | ;;
52 | p )
53 | PATCH_INC=true
54 | ;;
55 | v )
56 | NEW_TAG="${OPTARG}"
57 | ;;
58 | \? )
59 | echo "$HELP" 1>&2
60 | exit 0
61 | ;;
62 | esac
63 | done
64 | }
65 |
66 | validate_args() {
67 | if [[ ! -z $NEW_TAG ]]; then
68 | if ! [[ $NEW_TAG =~ $TAG_REGEX ]]; then
69 | echo "❌ Invalid new tag specified $NEW_TAG. Examples: v1.2.3, v1.2.3-dirty"
70 | exit 1
71 | fi
72 |
73 | echo "🥑 Using the new tag specified with -v flag. All other flags, if specified, will be ignored."
74 | echo " NOTE:The script relies on the user to specify a valid and accurately incremented tag."
75 | return
76 | fi
77 |
78 | if ($MAJOR_INC && $MINOR_INC) || ($MAJOR_INC && $PATCH_INC) || ($MINOR_INC && $PATCH_INC); then
79 | echo "❌ Invalid arguments passed. Specify only one of 3 tag parts to increment for the new tag: -m (major) or -i (minor) or -p (patch)."
80 | exit 1
81 | fi
82 |
83 | if $MAJOR_INC || $MINOR_INC || $PATCH_INC; then
84 | return
85 | fi
86 |
87 | echo -e "❌ Invalid arguments passed. Specify atleast one argument.\n$HELP"
88 | exit 1
89 | }
90 |
91 | sync_local_tags_from_remote() {
92 | # setup remote upstream tracking to fetch tags
93 | git remote add the-real-upstream https://github.com/aws/amazon-ec2-metadata-mock.git &> /dev/null || true
94 | git fetch the-real-upstream
95 |
96 | # delete all local tags
97 | git tag -l | xargs git tag -d
98 |
99 | # fetch remote tags
100 | git fetch the-real-upstream --tags
101 |
102 | # record the latest release tag in remote, before creating a new tag
103 | CURR_REMOTE_RELEASE_TAG=$(get_latest_tag)
104 |
105 | # clean up tracking
106 | git remote remove the-real-upstream
107 | }
108 |
109 | create_tag() {
110 | git tag $NEW_TAG
111 | echo -e "\n✅ Created new tag $NEW_TAG (Current latest release tag in remote: v$CURR_REMOTE_RELEASE_TAG)\n"
112 | exit 0
113 | }
114 |
115 | get_latest_tag() {
116 | make -s -f $MAKEFILE_PATH latest-release-tag | cut -b 2-
117 | }
118 |
119 | main() {
120 | process_args "$@"
121 | validate_args
122 |
123 | sync_local_tags_from_remote
124 |
125 | # if new tag is specified, create it
126 | if [[ ! -z $NEW_TAG ]]; then
127 | create_tag
128 | fi
129 |
130 | # increment version
131 | if $MAJOR_INC || $MINOR_INC || $PATCH_INC; then
132 | curr_major_v=$(echo $CURR_REMOTE_RELEASE_TAG | tr '.' '\n' | head -1)
133 | curr_minor_v=$(echo $CURR_REMOTE_RELEASE_TAG | tr '.' '\n' | head -2 | tail -1)
134 | curr_patch_v=$(echo $CURR_REMOTE_RELEASE_TAG | tr '.' '\n' | tail -1)
135 |
136 | if [[ $MAJOR_INC == true ]]; then
137 | new_major_v=$(echo $(($curr_major_v + 1)))
138 | NEW_TAG=$(echo v$new_major_v.0.0)
139 | elif [[ $MINOR_INC == true ]]; then
140 | new_minor_v=$(echo $(($curr_minor_v + 1)))
141 | NEW_TAG=$(echo v$curr_major_v.$new_minor_v.0)
142 | elif [[ $PATCH_INC == true ]]; then
143 | new_patch_v=$(echo $(($curr_patch_v + 1)))
144 | NEW_TAG=$(echo v$curr_major_v.$curr_minor_v.$new_patch_v)
145 | fi
146 | create_tag
147 | fi
148 | }
149 |
150 | main "$@"
--------------------------------------------------------------------------------
/scripts/ecr-public-login:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 | BUILD_DIR=$SCRIPTPATH/../build/
6 | export PATH="${BUILD_DIR}:${PATH}"
7 |
8 | if [[ -z "${ECR_REGISTRY}" ]]; then
9 | echo "The env var ECR_REGISTRY must be set"
10 | exit 1
11 | fi
12 |
13 | function exit_and_fail() {
14 | echo "❌ Failed to login to ECR Public Repo!"
15 | }
16 |
17 | trap exit_and_fail INT TERM ERR
18 |
19 | docker login --username AWS --password="$(aws ecr-public get-login-password --region us-east-1)" "${ECR_REGISTRY}"
20 |
--------------------------------------------------------------------------------
/scripts/ecr-template-for-helm-chart.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Helm Charts for Amazon EC2 Metadata Mock",
3 | "aboutText": "# Helm Chart for Amazon EC2 Metadata Mock\n\nAWS EC2 Instance metadata is data about your instance that you can use to configure or manage the running instance. Instance metadata is divided into categories like hostname, instance id, maintenance events, spot instance action. The instance metadata can be accessed from within the instance. Some instance metadata is available only when an instance is affected by the event.\n\nThis project attempts to bridge the gaps by providing mocks for most of these metadata categories. The mock responses are designed to replicate those from the actual instance metadata service for accurate, local testing.\n\nThis repository contains helm-charts for Amazon EC2 Metadata Mock.\n\nFor more information on this project, see the project repo at [Amazon EC2 Metadata Mock](https://github.com/aws/amazon-ec2-metadata-mock)",
4 | "usageText": "# We can install AEMM using the helm chart from this repository.\n\nWe need to authenticate our helm client to ECR registry and install AEMM chart using helm chart URI, detailed information on how to install helm chart can be found here [HelmChart ReadMe](https://github.com/aws/amazon-ec2-metadata-mock/blob/main/helm/amazon-ec2-metadata-mock/README.md)"
5 | }
--------------------------------------------------------------------------------
/scripts/generate-helm-chart-archives:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script to generate the helm chart archives for charts in REPO/helm
4 |
5 | set -euo pipefail
6 |
7 | REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../ ; pwd -P )"
8 | MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile
9 | VERSION=$(make -s -f $MAKEFILE_PATH version)
10 | HELM_CHART_PATH=$REPO_ROOT_PATH/helm
11 | BUILD_DIR=$REPO_ROOT_PATH/build/k8s-resources/$VERSION
12 | HELM_CHART_ARCHIVES_DIR=$BUILD_DIR/helm-chart-archives
13 | mkdir -p $HELM_CHART_ARCHIVES_DIR
14 |
15 | for c in $HELM_CHART_PATH/*; do
16 | if [ -d $c ]; then
17 | chart_dir=$(echo $c | tr '/' '\n' | tail -1)
18 | echo "Generating Helm archive for $chart_dir"
19 | chart_version=`sed -n 's/version: //p' $c/Chart.yaml`
20 |
21 | archive_name="$chart_dir-$chart_version.tgz"
22 | cd $HELM_CHART_ARCHIVES_DIR
23 | tar -C $HELM_CHART_PATH -zcf $archive_name $chart_dir
24 | fi
25 | done
26 |
27 | echo -e "\nGenerated Helm chart archives in $HELM_CHART_ARCHIVES_DIR:"
28 | for archive in $HELM_CHART_ARCHIVES_DIR/*; do
29 | echo " - $(echo $archive | tr '/' '\n' | tail -1)"
30 | done
31 |
--------------------------------------------------------------------------------
/scripts/generate-k8s-yaml:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | PLATFORM=$(uname | tr '[:upper:]' '[:lower:]')
7 | HELM_VERSION="3.2.0"
8 | NAMESPACE="default"
9 |
10 | MAKEFILEPATH=$SCRIPTPATH/../Makefile
11 | VERSION=$(make -s -f $MAKEFILEPATH version)
12 | BUILD_DIR=$SCRIPTPATH/../build/k8s-resources/$VERSION
13 | INDV_RESOURCES_DIR=$BUILD_DIR/individual-resources
14 | TAR_RESOURCES_FILE=$BUILD_DIR/individual-resources.tar
15 | AGG_RESOURCES_YAML=$BUILD_DIR/all-resources.yaml
16 | mkdir -p $INDV_RESOURCES_DIR
17 |
18 | USAGE=$(cat << 'EOM'
19 | Usage: generate-k8s-yaml [-n ]
20 | Generates the kubernetes yaml resource files from the helm chart
21 | and places them into the build dir.
22 | Example: generate-k8s-yaml -n kube-system
23 | Optional:
24 | -n Kubernetes namespace
25 | EOM
26 | )
27 |
28 | # Process our input arguments
29 | while getopts "n:" opt; do
30 | case ${opt} in
31 | n ) # K8s namespace
32 | NAMESPACE=$OPTARG
33 | ;;
34 | \? )
35 | echoerr "$USAGE" 1>&2
36 | exit
37 | ;;
38 | esac
39 | done
40 |
41 | curl -L https://get.helm.sh/helm-v$HELM_VERSION-$PLATFORM-amd64.tar.gz | tar zxf - -C $BUILD_DIR
42 | mv $BUILD_DIR/$PLATFORM-amd64/helm $BUILD_DIR/.
43 | rm -rf $BUILD_DIR/$PLATFORM-amd64
44 | chmod +x $BUILD_DIR/helm
45 |
46 | $BUILD_DIR/helm template amazon-ec2-metadata-mock \
47 | --no-hooks \
48 | --namespace $NAMESPACE \
49 | $SCRIPTPATH/../helm/amazon-ec2-metadata-mock/ > $AGG_RESOURCES_YAML
50 |
51 | # remove helm annotations from template
52 | cat $AGG_RESOURCES_YAML | grep -v 'helm.sh\|app.kubernetes.io/managed-by: Helm' > $BUILD_DIR/helm_annotations_removed.yaml
53 | mv $BUILD_DIR/helm_annotations_removed.yaml $AGG_RESOURCES_YAML
54 |
55 | $BUILD_DIR/helm template amazon-ec2-metadata-mock \
56 | --no-hooks \
57 | --namespace $NAMESPACE \
58 | --set targetNodeOs="linux windows" \
59 | --output-dir $INDV_RESOURCES_DIR/ \
60 | $SCRIPTPATH/../helm/amazon-ec2-metadata-mock/
61 |
62 | # remove helm annotations from template
63 | for i in $INDV_RESOURCES_DIR/amazon-ec2-metadata-mock/templates/*; do
64 | if [ -f $i ]; then # ignore helm tests
65 | cat $i | grep -v 'helm.sh\|app.kubernetes.io/managed-by: Helm' > $BUILD_DIR/helm_annotations_removed.yaml
66 | mv $BUILD_DIR/helm_annotations_removed.yaml $i
67 | fi
68 | done
69 |
70 | cd $INDV_RESOURCES_DIR/amazon-ec2-metadata-mock/ && tar cvf $TAR_RESOURCES_FILE templates/*
71 | cd $SCRIPTPATH
72 |
73 | echo "Generated amazon-ec2-metadata-mock kubernetes yaml resources files in:"
74 | echo " - $AGG_RESOURCES_YAML"
75 | echo " - $TAR_RESOURCES_FILE"
76 |
77 |
--------------------------------------------------------------------------------
/scripts/helm-login:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 | BUILD_DIR=$SCRIPTPATH/../build/
6 | export PATH="${BUILD_DIR}:${PATH}"
7 |
8 | if [[ -z "${ECR_REGISTRY}" ]]; then
9 | echo "The env var ECR_REGISTRY must be set"
10 | exit 1
11 | fi
12 |
13 | function exit_and_fail() {
14 | echo "❌ Failed to login to ECR Public Repo!"
15 | }
16 |
17 | trap exit_and_fail INT TERM ERR
18 |
19 | export HELM_EXPERIMENTAL_OCI=1
20 | helm registry login --username AWS --password="$(aws ecr-public get-login-password --region us-east-1)" "${ECR_REGISTRY}"
--------------------------------------------------------------------------------
/scripts/install-amazon-ecr-credential-helper:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | usage=$(cat << EOM
6 | Download and install amazon-ecr-credential-helper for Docker client.
7 | usage: $(basename $0) [-h] VERSION
8 | Options:
9 | -h Print help message then exit
10 | Arguments:
11 | VERSION Version number of amazon-ecr-login-helper to download and install (e.g. 0.7.1)
12 | EOM
13 | )
14 |
15 | function display_help {
16 | echo "${usage}" 1<&2
17 | }
18 |
19 | while getopts "h" arg; do
20 | case "${arg}" in
21 | h ) display_help
22 | exit 0
23 | ;;
24 |
25 | * ) display_help
26 | exit 1
27 | ;;
28 | esac
29 | done
30 | shift $((OPTIND-1))
31 |
32 | version="${1:-}"
33 | if [[ -z "${version}" ]]; then
34 | echo "❌ no version given"
35 | display_help
36 | exit 1
37 | fi
38 |
39 | install_path="$(dirname "$(which docker-credential-wincred.exe)")"
40 | curl -Lo "${install_path}/docker-credential-ecr-login.exe" "https://amazon-ecr-credential-helper-releases.s3.us-east-2.amazonaws.com/${version}/windows-amd64/docker-credential-ecr-login.exe"
41 |
42 | # Update Docker to use ecr-login instead of wincred.
43 | modified_config="$(mktemp)"
44 | jq '.credsStore="ecr-login"' ~/.docker/config.json > "${modified_config}"
45 | mv -f "${modified_config}" ~/.docker/config.json
--------------------------------------------------------------------------------
/scripts/push-docker-images:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile
8 |
9 | VERSION=$(make -s -f $MAKE_FILE_PATH version)
10 | PLATFORMS=("linux/amd64")
11 | MANIFEST_IMAGES=()
12 | MANIFEST=""
13 | DOCKER_CLI_CONFIG="$HOME/.docker/config.json"
14 |
15 | USAGE=$(cat << 'EOM'
16 | Usage: push-docker-images [-p ]
17 | Pushes docker images for the platform pairs passed in w/ a container manifest
18 | Example: push-docker-images -p "linux/amd64,linux/arm"
19 | Optional:
20 | -p Platform pair list (os/architecture) [DEFAULT: linux/amd64]
21 | -r IMAGE REPO: set the docker image repo
22 | -v VERSION: The application version of the docker image [DEFAULT: output of `make version`]
23 | -m Create a docker manifest
24 | EOM
25 | )
26 |
27 | # Process our input arguments
28 | while getopts "mp:r:v:" opt; do
29 | case ${opt} in
30 | p ) # Platform Pairs
31 | IFS=',' read -ra PLATFORMS <<< "$OPTARG"
32 | ;;
33 | r ) # Image Repo
34 | IMAGE_REPO="$OPTARG"
35 | ;;
36 | v ) # Image Version
37 | VERSION="$OPTARG"
38 | ;;
39 | m ) # Docker manifest
40 | MANIFEST="true"
41 | ;;
42 | \? )
43 | echo "$USAGE" 1>&2
44 | exit
45 | ;;
46 | esac
47 | done
48 |
49 | if [[ ${#PLATFORMS[@]} -gt 1 && $MANIFEST != "true" ]]; then
50 | echo "Only one platform can be pushed if you do not create a manifest."
51 | echo "Try again with the -m option"
52 | exit 1
53 | fi
54 |
55 | # Existing manifests cannot be updated only overwritten; therefore,
56 | # if manifest exists already, fetch existing platforms so "updated" manifest includes images
57 | # that were there previously
58 | if [[ $MANIFEST == "true" ]]; then
59 | if [[ ! -f $DOCKER_CLI_CONFIG ]]; then
60 | echo '{"experimental":"enabled"}' > $DOCKER_CLI_CONFIG
61 | echo "Created docker config file"
62 | fi
63 | cat <<< "$(jq '.+{"experimental":"enabled"}' $DOCKER_CLI_CONFIG)" > $DOCKER_CLI_CONFIG
64 | echo "Enabled experimental CLI features to execute docker manifest commands"
65 | manifest_exists=$(docker manifest inspect $IMAGE_REPO:$VERSION > /dev/null ; echo $?)
66 | if [[ manifest_exists -eq 0 ]]; then
67 | echo "manifest already exists"
68 | EXISTING_IMAGES=()
69 | while IFS='' read -r line; do
70 | EXISTING_IMAGES+=("$line");
71 | done < <(docker manifest inspect $IMAGE_REPO:$VERSION | jq -r '.manifests[] | "\(.platform.os)-\(.platform.architecture)"')
72 | # treat separate from PLATFORMS because existing images don't need to be tagged and pushed
73 | for os_arch in "${EXISTING_IMAGES[@]}"; do
74 | img_tag="$IMAGE_REPO:$VERSION-$os_arch"
75 | MANIFEST_IMAGES+=("$img_tag")
76 | done
77 | echo "images already in manifest: ${MANIFEST_IMAGES[*]}"
78 | fi
79 | fi
80 |
81 | for os_arch in "${PLATFORMS[@]}"; do
82 | os=$(echo $os_arch | cut -d'/' -f1)
83 | arch=$(echo $os_arch | cut -d'/' -f2)
84 |
85 | img_tag_w_platform="$IMAGE_REPO:$VERSION-$os-$arch"
86 |
87 | if [[ $MANIFEST == "true" ]]; then
88 | img_tag=$img_tag_w_platform
89 | else
90 | img_tag="$IMAGE_REPO:$VERSION"
91 | docker tag $img_tag_w_platform $img_tag
92 | fi
93 | docker push $img_tag
94 | MANIFEST_IMAGES+=("$img_tag")
95 | done
96 |
97 | if [[ $MANIFEST == "true" ]]; then
98 | current_os=$(uname)
99 | # Windows will append '\r' to the end of $img which
100 | # results in docker failing to create the manifest due to invalid reference format.
101 | # However, MacOS does not recognize '\r' as carriage return
102 | # and attempts to remove literal 'r' chars; therefore, made this so portable
103 | for img in "${MANIFEST_IMAGES[@]}"; do
104 | if [[ $current_os == "Darwin" ]]; then
105 | updated_img=$img
106 | else
107 | updated_img=$(echo $img | sed -e 's/\r$//')
108 | fi
109 | echo "creating manifest for $updated_img"
110 | docker manifest create $IMAGE_REPO:$VERSION $updated_img --amend
111 |
112 | os_arch=$(echo ${updated_img//$IMAGE_REPO:$VERSION-/})
113 | os=$(echo $os_arch | cut -d'-' -f1)
114 | arch=$(echo $os_arch | cut -d'-' -f2)
115 |
116 | echo "annotating manifest"
117 | docker manifest annotate $IMAGE_REPO:$VERSION $updated_img --arch $arch --os $os
118 | done
119 |
120 | echo "pushing manifest"
121 | docker manifest push --purge $IMAGE_REPO:$VERSION
122 | fi
--------------------------------------------------------------------------------
/scripts/push-helm-chart:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")";pwd -P )"
5 |
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile
8 | CHART_VERSION=$(make -s -f $MAKE_FILE_PATH chart-version)
9 | HELM_CHART_PATH=$REPO_ROOT_PATH/helm/amazon-ec2-metadata-mock
10 |
11 | USAGE=$(cat << 'EOM'
12 | Usage: push-helm-chart
13 | Pushes helm charts
14 | Optional:
15 | -h HELM CHART REGISTRY: set the helm chart registry
16 | -v CHART VERSION: The chart version [DEFAULT: output of `make chart-version`]
17 | -r HELM CHART REPOSITORY: Set the helm chart repository
18 | EOM
19 | )
20 |
21 | # Process our input arguments
22 | while getopts "r:v:h:" opt; do
23 | case ${opt} in
24 | r ) # Helm Chart Repository
25 | HELM_CHART_REPOSITORY="$OPTARG"
26 | ;;
27 | v ) # Image Version
28 | CHART_VERSION="$OPTARG"
29 | ;;
30 | h ) # Helm Chart Registry
31 | ECR_REGISTRY="$OPTARG"
32 | ;;
33 | \? )
34 | echo "$USAGE" 1>&2
35 | exit
36 | ;;
37 | esac
38 | done
39 |
40 | CHART_EXISTS=$(aws ecr-public describe-images --repository-name "helm/$HELM_CHART_REPOSITORY" --region us-east-1 --query "imageDetails[?contains(imageTags, '$CHART_VERSION')].imageTags[]" --output text)
41 |
42 | if [[ -n "$CHART_EXISTS" ]]; then
43 | echo "chart with version $CHART_VERSION already exists in the repository, skipping pushing of chart..."
44 | exit 0
45 | fi
46 |
47 | echo "chart with version $CHART_VERSION not found in repository, pushing new chart..."
48 | #Package the chart
49 | helm package $HELM_CHART_PATH --destination $REPO_ROOT_PATH/build
50 | #Pushing helm chart
51 | helm push $REPO_ROOT_PATH/build/$HELM_CHART_REPOSITORY-$CHART_VERSION.tgz oci://$ECR_REGISTRY/helm
--------------------------------------------------------------------------------
/scripts/retag-docker-images:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | MAKE_FILE_PATH=$REPO_ROOT_PATH/Makefile
8 |
9 | VERSION=$(make -s -f $MAKE_FILE_PATH version)
10 | PLATFORMS=("linux/amd64")
11 |
12 |
13 | USAGE=$(cat << 'EOM'
14 | Usage: retag-docker-images [-p ]
15 | Tags created docker images with a new prefix
16 |
17 | Example: retag-docker-images -p "linux/amd64,linux/arm" -o -n
18 | Optional:
19 | -p Platform pair list (os/architecture) [DEFAULT: linux/amd64]
20 | -o OLD IMAGE REPO to retag
21 | -n NEW IMAGE REPO to tag with
22 | -v VERSION: The application version of the docker image [DEFAULT: output of `make version`]
23 | EOM
24 | )
25 |
26 | # Process our input arguments
27 | while getopts "p:o:n:v:" opt; do
28 | case ${opt} in
29 | p ) # Platform Pairs
30 | IFS=',' read -ra PLATFORMS <<< "$OPTARG"
31 | ;;
32 | o ) # Old Image Repo
33 | OLD_IMAGE_REPO="$OPTARG"
34 | ;;
35 | n ) # New Image Repo
36 | NEW_IMAGE_REPO="$OPTARG"
37 | ;;
38 | v ) # Image Version
39 | VERSION="$OPTARG"
40 | ;;
41 | \? )
42 | echo "$USAGE" 1>&2
43 | exit
44 | ;;
45 | esac
46 | done
47 |
48 | function exit_and_fail() {
49 | echo "❌ Failed retagging docker images"
50 | }
51 |
52 | trap "exit_and_fail" INT TERM ERR
53 |
54 | for os_arch in "${PLATFORMS[@]}"; do
55 | os=$(echo $os_arch | cut -d'/' -f1)
56 | arch=$(echo $os_arch | cut -d'/' -f2)
57 |
58 | old_img_tag="$OLD_IMAGE_REPO:$VERSION-$os-$arch"
59 | new_img_tag="$NEW_IMAGE_REPO:$VERSION-$os-$arch"
60 |
61 | current_os=$(uname)
62 | # Windows will append '\r' to the end of $img which
63 | # results in docker failing to create the manifest due to invalid reference format.
64 | # However, MacOS does not recognize '\r' as carriage return
65 | # and attempts to remove literal 'r' chars; therefore, made this so portable
66 | if [[ $current_os != "Darwin" ]]; then
67 | old_img_tag=$(echo $old_img_tag | sed -e 's/\r//')
68 | new_img_tag=$(echo $new_img_tag | sed -e 's/\r//')
69 | fi
70 |
71 | docker tag ${old_img_tag} ${new_img_tag}
72 | echo "✅ Successfully retagged docker image $old_img_tag to $new_img_tag"
73 | done
74 |
75 | echo "✅ Done Retagging!"
--------------------------------------------------------------------------------
/scripts/sync-catalog-information-for-helm-chart:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 | REPO_NAME="helm/amazon-ec2-metadata-mock"
6 | REPO_ROOT_PATH=$SCRIPTPATH/../
7 | TEMPLATE_PATH=$REPO_ROOT_PATH/scripts/ecr-template-for-helm-chart.json
8 | CATALOG_DATA=$(cat "$TEMPLATE_PATH")
9 |
10 | if aws ecr-public describe-repositories --region us-east-1 --repository-names "$REPO_NAME" > /dev/null 2>&1; then
11 | echo "The repository $REPO_NAME exists, update it with template..."
12 | aws ecr-public put-repository-catalog-data --region us-east-1 --repository-name "$REPO_NAME" --catalog-data "$CATALOG_DATA"
13 | else
14 | echo "The repository $REPO_NAME does not exist, create it with template..."
15 | aws ecr-public create-repository --region us-east-1 --repository-name "$REPO_NAME" --catalog-data "$CATALOG_DATA"
16 | fi
--------------------------------------------------------------------------------
/scripts/sync-readme-to-ecr-public:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 | REPO_NAME="amazon-ec2-metadata-mock"
6 | #about and usage section char max
7 | MAX_CHAR_COUNT=10240
8 | USAGE_TEXT="See About section"
9 | ADDITIONAL_MSG="...
10 |
11 | **truncated due to char limits**...
12 | A complete version of the ReadMe can be found [here](https://github.com/aws/amazon-ec2-metadata-mock#amazon-ec2-metadata-mock)\""
13 |
14 | # converting to json to insert esc chars, then replace newlines for proper markdown render
15 | raw_about=$(jq -n --arg msg "$(<$SCRIPTPATH/../README.md)" '{"usageText": $msg}' | jq '.usageText' | sed 's/\\n/\
16 | /g')
17 | char_to_trunc="$(($MAX_CHAR_COUNT-${#ADDITIONAL_MSG}))"
18 | raw_truncated="${raw_about:0:$char_to_trunc}"
19 | raw_truncated+="$ADDITIONAL_MSG"
20 | aws ecr-public put-repository-catalog-data --repository-name="${REPO_NAME}" --catalog-data aboutText="${raw_truncated}",usageText="${USAGE_TEXT}" --region us-east-1
21 |
22 | echo "README sync to ecr-public succeeded!"
--------------------------------------------------------------------------------
/scripts/update-versions-for-release:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script to update AEMM and Helm chart versions.
4 | # The following files are updated:
5 | ## - helm charts' Chart.yaml
6 | ## - helm charts' values.yaml
7 | ## - helm charts' README
8 | ## - README.md
9 | ## - version.txt
10 |
11 | # This script is run AFTER creating a new local tag (see `make release-prep`).
12 |
13 | set -euo pipefail
14 |
15 | REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../; pwd -P )"
16 | MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile
17 | LATEST_VERSION=$(make -s -f $MAKEFILE_PATH latest-release-tag | cut -b 2- )
18 | PREVIOUS_VERSION=$(make -s -f $MAKEFILE_PATH previous-release-tag | cut -b 2- )
19 |
20 | # Files to update
21 | REPO_README=$REPO_ROOT_PATH/README.md
22 | APP_VERSION=$REPO_ROOT_PATH/pkg/cmd/root/version.txt
23 | CHART=$REPO_ROOT_PATH/helm/amazon-ec2-metadata-mock/Chart.yaml
24 | CHART_VALUES=$REPO_ROOT_PATH/helm/amazon-ec2-metadata-mock/values.yaml
25 | CHART_README=$REPO_ROOT_PATH/helm/amazon-ec2-metadata-mock/README.md
26 |
27 | FILES=("$REPO_README" "$CHART" "$CHART_README" "$CHART_VALUES" "$APP_VERSION")
28 | FILES_CHANGED=()
29 |
30 | echo -e "🥑 Attempting to update AEMM release version and Helm chart version in preparation for a new release.\n Previous version: $PREVIOUS_VERSION ---> Latest version: $LATEST_VERSION"
31 |
32 | for f in "${FILES[@]}"; do
33 | has_incorrect_version=$(cat $f | grep $PREVIOUS_VERSION)
34 | if [[ ! -z $has_incorrect_version ]]; then
35 | sed -i '' "s/$PREVIOUS_VERSION/$LATEST_VERSION/g" $f
36 | FILES_CHANGED+=("$f")
37 | fi
38 | done
39 |
40 | if [[ ${#FILES_CHANGED[@]} -gt 0 ]]; then
41 | echo -e "\n✅ Updated versions from $PREVIOUS_VERSION to $LATEST_VERSION in files: \n$(echo "${FILES_CHANGED[@]}" | tr ' ' '\n')"
42 | echo -e "\n🥑 To see changes, run \`git diff ${FILES_CHANGED[*]}\`"
43 | else
44 | echo -e "\n✅ All files already use the latest release version $LATEST_VERSION. No files were modified."
45 | fi
--------------------------------------------------------------------------------
/scripts/upload-resources-to-github:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | # Script to upload release assets to Github.
5 | # This script cleans up after itself in cases of parital failures. i.e. either all assets are uploaded or none
6 |
7 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
8 | VERSION=$(make -s -f $SCRIPTPATH/../Makefile version)
9 | BUILD_DIR=$SCRIPTPATH/../build/k8s-resources/$VERSION
10 | BINARY_DIR=$SCRIPTPATH/../build/bin
11 | INDV_K8S_RESOURCES=$BUILD_DIR/individual-resources.tar
12 | AGG_K8S_RESOURCES=$BUILD_DIR/all-resources.yaml
13 | HELM_ARCHIVES_DIR=$BUILD_DIR/helm-chart-archives
14 |
15 | RELEASE_ID=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
16 | https://api.github.com/repos/aws/amazon-ec2-metadata-mock/releases | \
17 | jq --arg VERSION "$VERSION" '.[] | select(.tag_name==$VERSION) | .id')
18 |
19 | [[ -z $TERM ]] || export TERM=linux
20 | RED=$(tput setaf 1)
21 | RESET_FMT=$(tput sgr 0)
22 |
23 | ASSET_IDS_UPLOADED=()
24 |
25 | trap 'handle_errors_and_cleanup $?' EXIT
26 |
27 | handle_errors_and_cleanup() {
28 | if [ $1 -eq "0" ]; then
29 | exit 0
30 | fi
31 |
32 | if [[ ${#ASSET_IDS_UPLOADED[@]} -ne 0 ]]; then
33 | echo -e "\nCleaning up assets uploaded in the current execution of the script"
34 | for asset_id in "${ASSET_IDS_UPLOADED[@]}"; do
35 | echo "Deleting asset $asset_id"
36 | curl -X DELETE \
37 | -H "Authorization: token $GITHUB_TOKEN" \
38 | "https://api.github.com/repos/aws/amazon-ec2-metadata-mock/releases/assets/$asset_id"
39 | done
40 | exit $1
41 | fi
42 | }
43 |
44 | gather_assets_to_upload() {
45 | local resources=("$INDV_K8S_RESOURCES" "$AGG_K8S_RESOURCES")
46 | for archive in $HELM_ARCHIVES_DIR/*; do
47 | resources+=("$archive")
48 | done
49 |
50 | for binary in $BINARY_DIR/*; do
51 | resources+=("$binary")
52 | done
53 | echo "${resources[@]}"
54 | }
55 |
56 | # $1: absolute path to asset
57 | upload_asset() {
58 | resp=$(curl --write-out '%{http_code}' --silent \
59 | -H "Authorization: token $GITHUB_TOKEN" \
60 | -H "Content-Type: $(file -b --mime-type $1)" \
61 | --data-binary @$1 \
62 | "https://uploads.github.com/repos/aws/amazon-ec2-metadata-mock/releases/$RELEASE_ID/assets?name=$(basename $1)")
63 |
64 | response_code=$(echo $resp | sed 's/\(.*\)}//')
65 | response_content=$(echo $resp | sed "s/$response_code//")
66 |
67 | # HTTP success code expected - 201 Created
68 | if [[ $response_code -eq 201 ]]; then
69 | asset_id=$(echo $response_content | jq '.id')
70 | ASSET_IDS_UPLOADED+=("$asset_id")
71 | echo "Created asset ID $asset_id successfully"
72 | else
73 | echo -e "❌ ${RED}Upload failed with response code $response_code and message \n$response_content${RESET_FMT} ❌"
74 | exit 1
75 | fi
76 | }
77 |
78 | ASSETS=$(gather_assets_to_upload)
79 | COUNT=1
80 | echo -e "\nUploading release assets for release id '$RELEASE_ID' to Github"
81 | for asset in $ASSETS; do
82 | name=$(echo $asset | tr '/' '\n' | tail -1)
83 | echo -e "\n $((COUNT++)). $name"
84 | upload_asset $asset
85 | done
--------------------------------------------------------------------------------
/scripts/validators/json-validator:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
6 | # Find all JSON files but ignore any that are also ignored by git (e.g. IDE settings files).
7 | JSON_FILES=$(find \
8 | $SCRIPTPATH/../../ \
9 | -name "*.json" \
10 | -type f \
11 | -not -exec git check-ignore --quiet {} \; \
12 | -print)
13 | FAILED_FILES=()
14 |
15 | if [[ -z `which python` ]] && [[ ! -z `which python3` ]]; then
16 | PY=python3
17 | else
18 | PY=python
19 | fi
20 |
21 | for j in $JSON_FILES; do
22 | echo "validating $j"
23 | $PY -mjson.tool "$j" > /dev/null || FAILED_FILES+=("$j")
24 | done
25 |
26 | if [[ ${#FAILED_FILES[@]} -eq 0 ]]; then
27 | echo "✅ json-validator found no errors!"
28 | else
29 | echo "❌ JSON syntax errors found in these files: ${FAILED_FILES[*]}"
30 | exit 1
31 | fi
32 |
--------------------------------------------------------------------------------
/scripts/validators/release-version-validator:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script to verify that the versions are updated to reflect the latest release tag in:
4 | ## - helm charts' Chart.yaml
5 | ## - helm charts' values.yaml
6 | ## - helm charts' README
7 | ## - README.md
8 | ## - version.txt
9 |
10 | set -euo pipefail
11 |
12 | REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../../; pwd -P )"
13 | MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile
14 | LATEST_VERSION=$(make -s -f $MAKEFILE_PATH latest-release-tag | cut -b 2- )
15 | PREVIOUS_VERSION=$(make -s -f $MAKEFILE_PATH previous-release-tag | cut -b 2- )
16 | UPDATE_NEEDED=false
17 |
18 | print_note() {
19 | if [ $UPDATE_NEEDED == true ]; then
20 | echo "NOTE: Use \`make update-versions-for-release\` to update AEMM and Helm chart versions in preparation for a release."
21 | else
22 | echo "✅ Version ($LATEST_VERSION) was successfully verified in README and Helm chart files ✅"
23 | fi
24 | exit 0
25 | }
26 |
27 | trap 'print_note' EXIT
28 |
29 | # Verify version in README
30 | README_HAS_INCORRECT_VERSION=$(cat $REPO_ROOT_PATH/README.md | grep $PREVIOUS_VERSION)
31 | if [[ ! -z $README_HAS_INCORRECT_VERSION ]]; then
32 | echo "❌ Please update AEMM version in README.md. Expected version v$LATEST_VERSION, but got v$PREVIOUS_VERSION ❌"
33 | UPDATE_NEEDED=true
34 | fi
35 |
36 | # Verify version in version.txt
37 | APP_HAS_INCORRECT_VERSION=$(cat $REPO_ROOT_PATH/pkg/cmd/root/version.txt | grep $PREVIOUS_VERSION)
38 | if [[ ! -z $APP_HAS_INCORRECT_VERSION ]]; then
39 | echo "❌ Please update AEMM version in pkg/cmd/root/version.txt. Expected version v$LATEST_VERSION, but got v$PREVIOUS_VERSION ❌"
40 | UPDATE_NEEDED=true
41 | fi
42 |
43 | # Verify versions in Helm charts
44 | HELM_CHART_PATH=$REPO_ROOT_PATH/helm
45 | for c in $HELM_CHART_PATH/*; do
46 | chart_name=$(echo $c | tr '/' '\n' | tail -1)
47 | if [ -d $c ]; then
48 |
49 | # verify version in values.yaml
50 | values_image_tag=$(sed -n 's/[^\s]tag: //p' $c/values.yaml | tr -d '""' | tr -d ' ')
51 | if [[ $values_image_tag != "$LATEST_VERSION" ]]; then
52 | echo "❌ Please update AEMM version in $chart_name helm chart's values.yaml, image.tag field. Expected version $LATEST_VERSION, but got $values_image_tag ❌"
53 | UPDATE_NEEDED=true
54 | fi
55 |
56 | # verify version in Chart.yaml
57 | chart_version=$(sed -n 's/version: //p' $c/Chart.yaml)
58 | if [[ $chart_version != "$LATEST_VERSION" ]]; then
59 | echo "❌ Please update the chart's version in $chart_name chart's Chart.yaml. Expected version $LATEST_VERSION, but got $chart_version ❌"
60 | UPDATE_NEEDED=true
61 | fi
62 |
63 | # verify version in the chart's README.md
64 | chart_readme_has_incorrect_version=$(cat $c/README.md | grep $PREVIOUS_VERSION)
65 | if [[ ! -z $chart_readme_has_incorrect_version ]]; then
66 | echo "❌ Please update the chart's version in $chart_name chart's README.md. Expected version $LATEST_VERSION, but got $PREVIOUS_VERSION ❌"
67 | UPDATE_NEEDED=true
68 | fi
69 | fi
70 | done
--------------------------------------------------------------------------------
/templates/third-party-licenses.tmpl:
--------------------------------------------------------------------------------
1 | # Third-party Licenses
2 |
3 | {{ range . -}}
4 | - {{ .Name }} {{ .Version }} [{{ .LicenseName }}]({{ .LicenseURL }})
5 | {{ end -}}
--------------------------------------------------------------------------------
/test/e2e/cmd/asglifecycle-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | LIFECYCLE_STATE_TEST_PATH="http://$HOSTNAME:$AEMM_PORT/latest/meta-data/autoscaling/target-lifecycle-state"
6 | TERMINATION_DELAY=10
7 |
8 | function test_asglifecycle_paths() {
9 | pid=$1
10 | tput setaf $MAGENTA
11 | health_check $LIFECYCLE_STATE_TEST_PATH
12 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
13 |
14 | actual_paths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://$HOSTNAME:$AEMM_PORT/latest/meta-data)
15 | expected_paths=$(cat $SCRIPTPATH/golden/asglifecycle/latest/meta-data/index.golden)
16 |
17 | assert_value "$actual_paths" "$expected_paths" "test_asglifecycle_paths"
18 |
19 | clean_up $pid
20 | }
21 |
22 | function test_asg_termination_delay() {
23 | pid=$1
24 | delay_in_sec=$2
25 | tput setaf $MAGENTA
26 | health_check $LIFECYCLE_STATE_TEST_PATH
27 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
28 |
29 | expected_state="InService"
30 | response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $LIFECYCLE_STATE_TEST_PATH)
31 | assert_value "$response" "$expected_state" "test_asglifecycle::asg-pre-delay_response"
32 |
33 | # ensure no impact to asg termination delay functionality
34 | echo "⏲ Waiting on delay ⏲"
35 | sleep $delay_in_sec
36 | expected_state="Terminated"
37 | response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $LIFECYCLE_STATE_TEST_PATH)
38 | assert_value "$response" "$expected_state" "test_asglifecycle::asg-post-delay_response"
39 |
40 | # Confirm response isn't empty
41 | if [[ ! -z $response ]]; then
42 | echo "✅ Verified post-delay response"
43 | else
44 | echo "❌ Failed delay: there should be a lifecycle change to Terminated after delay"
45 | EXIT_CODE_TO_RETURN=1
46 | fi
47 |
48 | clean_up $pid
49 | }
50 |
51 | tput setaf $YELLOW
52 | echo "======================================================================================================"
53 | echo "🥑 Starting asg lifecycle integration tests $METADATA_VERSION"
54 | echo "======================================================================================================"
55 |
56 | start_cmd=$(create_cmd $METADATA_VERSION asglifecycle --port $AEMM_PORT)
57 | $start_cmd &
58 | ASG_PID=$!
59 | test_asglifecycle_paths $ASG_PID
60 |
61 | start_cmd=$(create_cmd $METADATA_VERSION asglifecycle --port $AEMM_PORT --asg-termination-delay-sec $TERMINATION_DELAY)
62 | $start_cmd &
63 | ASG_PID=$!
64 | test_asg_termination_delay $ASG_PID $TERMINATION_DELAY
65 |
66 | exit $EXIT_CODE_TO_RETURN
67 |
68 |
--------------------------------------------------------------------------------
/test/e2e/cmd/dynamic-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | TEST_CONFIG_FILE="$SCRIPTPATH/testdata/aemm-config-integ.json"
6 | DYNAMIC_TEST_PATH="http://$HOSTNAME:$AEMM_PORT/latest/dynamic"
7 | DYNAMIC_IDC_TEST_PATH="$DYNAMIC_TEST_PATH/instance-identity/document"
8 |
9 | DYNAMIC_IDC_ACCOUNT_ID_DEFAULT="0123456789"
10 | DYNAMIC_IDC_ACCOUNT_ID_OVERRIDDEN="9876543210"
11 | DYNAMIC_IDC_INSTANCE_ID_DEFAULT="i-1234567890abcdef0"
12 | DYNAMIC_IDC_INSTANCE_ID_OVERRIDDEN="i-0fedcba0987654321"
13 |
14 | function test_dynamic_paths() {
15 | pid=$1
16 | tput setaf $YELLOW
17 | health_check $DYNAMIC_TEST_PATH
18 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
19 |
20 | actual_paths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $DYNAMIC_TEST_PATH)
21 | expected_paths=$(cat $SCRIPTPATH/golden/dynamic/index.golden)
22 |
23 | assert_value "$actual_paths" "$expected_paths" "test_dynamic_paths"
24 |
25 | clean_up $pid
26 | }
27 |
28 | function test_dynamic_subpath_fws() {
29 | pid=$1
30 | tput setaf $YELLOW
31 | health_check $DYNAMIC_TEST_PATH
32 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
33 |
34 | actual_paths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $DYNAMIC_TEST_PATH/fws)
35 | expected_paths=$(cat $SCRIPTPATH/golden/dynamic/fws.golden)
36 |
37 | assert_value "$actual_paths" "$expected_paths" "test_dynamic_subpath::/latest/dynamic/fws"
38 |
39 | clean_up $pid
40 | }
41 |
42 |
43 | function test_dynamic_subpath_instance-identity() {
44 | pid=$1
45 | tput setaf $YELLOW
46 | health_check $DYNAMIC_TEST_PATH
47 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
48 |
49 | actual_paths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $DYNAMIC_TEST_PATH/instance-identity)
50 | expected_paths=$(cat $SCRIPTPATH/golden/dynamic/instance-identity.golden)
51 |
52 | assert_value "$actual_paths" "$expected_paths" "test_dynamic_subpath::/latest/dynamic/instance-identity"
53 |
54 | clean_up $pid
55 | }
56 |
57 | function test_dynamic_idc_defaults() {
58 | pid=$1
59 | tput setaf $YELLOW
60 | health_check $DYNAMIC_TEST_PATH
61 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
62 |
63 | response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $DYNAMIC_IDC_TEST_PATH)
64 | actual_account_id=$(get_value '"accountId"' "$response")
65 | actual_instance_id=$(get_value '"instanceId"' "$response")
66 |
67 | assert_value "$actual_account_id" $DYNAMIC_IDC_ACCOUNT_ID_DEFAULT 'Default dynamic_idc::accountId val'
68 | assert_value "$actual_instance_id" $DYNAMIC_IDC_INSTANCE_ID_DEFAULT 'Default dynamic_idc::instanceId val'
69 |
70 | clean_up $pid
71 | }
72 |
73 | function test_dynamic_idc_overrides() {
74 | pid=$1
75 | expected_account_id=$2
76 | expected_instance_id=$3
77 | expected_mp_codes=[\""4i20ezfza3p7xx2kt2g8weu2u\""]
78 | tput setaf $YELLOW
79 | health_check $DYNAMIC_TEST_PATH
80 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
81 |
82 | response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $DYNAMIC_IDC_TEST_PATH)
83 | actual_account_id=$(get_value '"accountId"' "$response")
84 | actual_instance_id=$(get_value '"instanceId"' "$response")
85 | # use '--compact-output' to return the value on a single line (i.e. do not pretty print)
86 | actual_marketplace_codes=$(echo "$response" | jq -c '.marketplaceProductCodes')
87 |
88 | assert_value "$actual_account_id" $expected_account_id 'Override dynamic_idc::accountId'
89 | assert_value "$actual_instance_id" $expected_instance_id 'Override dynamic_idc::instanceId'
90 | assert_value "$actual_marketplace_codes" "$expected_mp_codes" 'Override dynamic_idc::marketplaceProductCodes'
91 |
92 | clean_up $pid
93 | }
94 |
95 | tput setaf $YELLOW
96 | echo "======================================================================================================"
97 | echo "🥑 Starting dynamic metadata integration tests $METADATA_VERSION"
98 | echo "======================================================================================================"
99 |
100 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
101 | $start_cmd &
102 | DYNAMIC_PID=$!
103 | test_dynamic_paths $DYNAMIC_PID
104 |
105 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
106 | $start_cmd &
107 | DYNAMIC_PID=$!
108 | test_dynamic_subpath_fws $DYNAMIC_PID
109 |
110 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
111 | $start_cmd &
112 | DYNAMIC_PID=$!
113 | test_dynamic_subpath_instance-identity $DYNAMIC_PID
114 |
115 | $start_cmd &
116 | DYNAMIC_PID=$!
117 | test_dynamic_idc_defaults $DYNAMIC_PID
118 |
119 | # dynamic data overrides
120 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT -c $TEST_CONFIG_FILE)
121 | $start_cmd &
122 | DYNAMIC_PID=$!
123 | test_dynamic_idc_overrides $DYNAMIC_PID $DYNAMIC_IDC_ACCOUNT_ID_OVERRIDDEN $DYNAMIC_IDC_INSTANCE_ID_OVERRIDDEN
124 |
125 | exit $EXIT_CODE_TO_RETURN
126 |
--------------------------------------------------------------------------------
/test/e2e/cmd/handlers-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | SPOT_TEST_PATH="http://$HOSTNAME:$AEMM_PORT/latest/meta-data/spot"
6 | SPOT_TEST_PATH_TRAILING="http://$HOSTNAME:$AEMM_PORT/latest/meta-data/spot/"
7 | SPOT_IA_TEST_PATH_TRAILING="http://$HOSTNAME:$AEMM_PORT/latest/meta-data/spot/instance-action/"
8 | SPOT_TT_TEST_PATH_TRAILING="http://$HOSTNAME:$AEMM_PORT/latest/meta-data/spot/termination-time/"
9 |
10 | function test_subpaths() {
11 | pid=$1
12 | tput setaf $LAVENDER
13 | health_check $SPOT_TEST_PATH
14 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
15 |
16 | actual_subpaths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $SPOT_TEST_PATH)
17 | expected_subpaths=$(cat $SCRIPTPATH/golden/spot/latest/meta-data/spot.golden)
18 |
19 | assert_value "$actual_subpaths" "$expected_subpaths" "test_spot_subpaths"
20 |
21 | clean_up $pid
22 | }
23 |
24 | function test_trailing_slash() {
25 | pid=$1
26 | tput setaf $LAVENDER
27 | health_check $SPOT_TEST_PATH
28 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
29 |
30 | actual_subpaths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $SPOT_TEST_PATH_TRAILING)
31 | expected_subpaths=$(cat $SCRIPTPATH/golden/spot/latest/meta-data/spot.golden)
32 | assert_value "$actual_subpaths" "$expected_subpaths" "test_spot_subpaths_trailing"
33 |
34 |
35 | response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $SPOT_IA_TEST_PATH_TRAILING)
36 | actual_inst_action=$(get_value '"action"' "$response")
37 | actual_ia_time=$(get_value '"time"' "$response")
38 |
39 | assert_value "$actual_inst_action" $SPOT_INSTANCE_ACTION_DEFAULT 'test_spot_trailing::instance_action'
40 | assert_format "$actual_ia_time" $SPOT_DATE_REGEX 'test_spot_trailing::tt_format'
41 |
42 | actual_term_time=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $SPOT_TT_TEST_PATH_TRAILING)
43 | actual_term_time_sec=$(convert_RFC3339_to_sec $actual_term_time)
44 | actual_ia_time_sec=$(convert_RFC3339_to_sec $actual_ia_time)
45 |
46 | # times should be within 5 second range
47 | assert_value_within_range $actual_term_time_sec $actual_ia_time_sec 5
48 |
49 | clean_up $pid
50 | }
51 |
52 | tput setaf $LAVENDER
53 | echo "======================================================================================================"
54 | echo "🥑 Starting handlers integration tests $METADATA_VERSION"
55 | echo "======================================================================================================"
56 |
57 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
58 | $start_cmd &
59 | HANDLERS_PID=$!
60 | test_subpaths $HANDLERS_PID
61 |
62 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
63 | $start_cmd &
64 | HANDLERS_PID=$!
65 | test_trailing_slash $HANDLERS_PID
--------------------------------------------------------------------------------
/test/e2e/cmd/imdsv2-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | function test_imdsv2() {
6 | pid=$1
7 | expected_value=$(cat $2)
8 | token_TTL=$3
9 | test_name=$4
10 | tput setaf $ORANGE
11 | health_check "$HOSTNAME:$AEMM_PORT"
12 |
13 | TOKEN=$(get_v2Token $token_TTL $AEMM_PORT)
14 | sleep 1
15 | actual_value=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" "$HOSTNAME:$AEMM_PORT/latest/meta-data")
16 |
17 | assert_value "$actual_value" "$expected_value" "$test_name"
18 |
19 | clean_up $pid
20 | }
21 |
22 | # run these tests once
23 | if [[ "$METADATA_VERSION" == "v2" ]]; then
24 | tput setaf $ORANGE
25 | echo "======================================================================================================"
26 | echo "🥑 Starting imdsv2 integration tests."
27 | echo "======================================================================================================"
28 |
29 | $SCRIPTPATH/../../build/$BIN --imdsv2 --port $AEMM_PORT &
30 | IMDSV2_PID=$!
31 | test_imdsv2 $IMDSV2_PID "$SCRIPTPATH/golden/default/latest/meta-data/index.golden" "21600" "imdsv2::valid_token"
32 |
33 | $SCRIPTPATH/../../build/$BIN --imdsv2 --port $AEMM_PORT &
34 | IMDSV2_PID=$!
35 | test_imdsv2 $IMDSV2_PID "$SCRIPTPATH/golden/400_bad_request.golden" "0" "imdsv2::invalid_token"
36 |
37 | $SCRIPTPATH/../../build/$BIN --imdsv2 --port $AEMM_PORT &
38 | IMDSV2_PID=$!
39 | test_imdsv2 $IMDSV2_PID "$SCRIPTPATH/golden/401_response.golden" "1" "imdsv2::expired_token"
40 | fi
41 |
42 | exit $EXIT_CODE_TO_RETURN
43 |
--------------------------------------------------------------------------------
/test/e2e/cmd/root-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | ROOT_TEST_PATH="http://$HOSTNAME:$AEMM_PORT"
6 | DELAY_IN_SEC=3
7 |
8 | function test_root() {
9 | pid=$1
10 | query_path=$2
11 | golden_file=$3
12 | test_name=$4
13 |
14 | tput setaf $CYAN
15 | health_check $ROOT_TEST_PATH
16 |
17 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
18 | actual_paths=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $query_path)
19 | expected_paths=$(cat $golden_file)
20 |
21 | assert_value "$actual_paths" "$expected_paths" "test_root::$test_name"
22 |
23 | clean_up $pid
24 | }
25 |
26 | function test_root_delay() {
27 | pid=$1
28 | delay_in_sec=$2
29 | tput setaf $CYAN
30 |
31 | # Give server time to startup
32 | sleep 1
33 | expected_delayed_response=$(cat $SCRIPTPATH/golden/404_response.golden)
34 |
35 | # Subcommand paths are delayed
36 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
37 | actual_response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $ROOT_TEST_PATH/latest/meta-data/spot/instance-action || :)
38 | assert_value "$actual_response" "$expected_delayed_response" "test_root_delay::pre-delay_response"
39 |
40 | # Send request after delay duration
41 | echo "⏲ Waiting on delay ⏲"
42 | sleep $delay_in_sec
43 | actual_response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $ROOT_TEST_PATH/latest/meta-data/spot/instance-action || :)
44 | assert_not_equal "$actual_response" "$expected_delayed_response" "test_root_delay::post-delay_response"
45 |
46 | # Confirm response isn't empty
47 | if [[ ! -z $actual_response ]]; then
48 | echo "✅ Verified post-delay response"
49 | else
50 | echo "❌ Failed delay: there should be a response after delay"
51 | EXIT_CODE_TO_RETURN=1
52 | fi
53 |
54 | clean_up $pid
55 | }
56 |
57 | function test_root_mock_trigger_time() {
58 | pid=$1
59 | delay_in_sec=$2
60 | mock_trigger_time=$3
61 | tput setaf $CYAN
62 |
63 | # Give server time to startup
64 | sleep 1
65 | expected_delayed_response=$(cat $SCRIPTPATH/golden/404_response.golden)
66 |
67 | # Subcommand paths are delayed
68 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
69 | actual_response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $ROOT_TEST_PATH/latest/meta-data/spot/instance-action || :)
70 | assert_value "$actual_response" "$expected_delayed_response" "test_root_mock_trigger_time::pre-trigger_response"
71 |
72 | # Send request after mock_trigger_time
73 | echo "⏲ Waiting on mock_trigger_time ⏲"
74 | sleep $delay_in_sec
75 | actual_response=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $ROOT_TEST_PATH/latest/meta-data/spot/instance-action || :)
76 | assert_not_equal "$actual_response" "$expected_delayed_response" "test_root_mock_trigger_time::post-trigger_response"
77 |
78 | # Confirm response isn't empty
79 | if [[ ! -z $actual_response ]]; then
80 | echo "✅ Verified post-mock_trigger_time response"
81 | else
82 | echo "❌ Failed delay: there should be a response after mock_trigger_time"
83 | EXIT_CODE_TO_RETURN=1
84 | fi
85 |
86 | clean_up $pid
87 | }
88 |
89 | tput setaf $CYAN
90 | echo "======================================================================================================"
91 | echo "🥑 Starting root integration tests $METADATA_VERSION"
92 | echo "======================================================================================================"
93 |
94 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
95 | $start_cmd &
96 | ROOT_PID=$!
97 | test_root $ROOT_PID "$ROOT_TEST_PATH/latest/meta-data" "$SCRIPTPATH/golden/default/latest/meta-data/index.golden" "paths"
98 |
99 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
100 | $start_cmd &
101 | ROOT_PID=$!
102 | test_root $ROOT_PID "$ROOT_TEST_PATH/latest/dynamic" "$SCRIPTPATH/golden/dynamic/index.golden" "dynamic_paths"
103 |
104 | $start_cmd &
105 | ROOT_PID=$!
106 | test_root $ROOT_PID "$ROOT_TEST_PATH" "$SCRIPTPATH/golden/default/index.golden" "versions"
107 |
108 | $start_cmd &
109 | ROOT_PID=$!
110 | test_root $ROOT_PID "$ROOT_TEST_PATH/womp" "$SCRIPTPATH/golden/404_response.golden" "unsupported_paths"
111 |
112 | # Delay should not affect path query
113 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT --mock-delay-sec $DELAY_IN_SEC)
114 | $start_cmd &
115 | ROOT_PID=$!
116 | test_root $ROOT_PID "$ROOT_TEST_PATH/latest/meta-data" "$SCRIPTPATH/golden/default/latest/meta-data/index.golden" "paths_with_delay"
117 |
118 | $start_cmd &
119 | ROOT_PID=$!
120 | test_root_delay $ROOT_PID $DELAY_IN_SEC
121 |
122 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then
123 | mock_trigger_time=$(date --date "${DELAY_IN_SEC} seconds" +%Y-%m-%dT%T%:z)
124 | elif [[ "$OSTYPE" == "darwin"* ]]; then
125 | mock_trigger_time=$(date -v+${DELAY_IN_SEC}S +%Y-%m-%dT%T%z | sed 's@^.\{22\}@&:@')
126 | fi
127 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT --mock-trigger-time "$mock_trigger_time")
128 | $start_cmd &
129 | ROOT_PID=$!
130 | test_root_mock_trigger_time $ROOT_PID $DELAY_IN_SEC "$mock_trigger_time"
131 |
132 | exit $EXIT_CODE_TO_RETURN
133 |
--------------------------------------------------------------------------------
/test/e2e/cmd/static-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | TEST_CONFIG_FILE="$SCRIPTPATH/testdata/aemm-config-integ.json"
6 |
7 | MAC_ADDRESS_DEFAULT="0e:49:61:0f:c3:11"
8 | MAC_ADDRESS_OVERRIDDEN="0e:49:61:0f:c3:77"
9 | PUBLIC_IPV4_DEFAULT="192.0.2.54"
10 | PUBLIC_IPV4_OVERRIDDEN="54.92.157.77"
11 | NETWORK_INTERFACE_ID_DEFAULT="eni-0f95d3625f5c521cc"
12 | IPV4_ASSOCIATION_DEFAULT="192.0.2.54"
13 |
14 | ROOT_PATH="http://$HOSTNAME:$AEMM_PORT"
15 | MAC_ADDRESS_PATH="$ROOT_PATH/latest/meta-data/mac"
16 | NETWORK_INTERFACE_ID_PATH_DEFAULT="$ROOT_PATH/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS_DEFAULT/interface-id"
17 | NETWORK_INTERFACE_ID_PATH_OVERRIDDEN="$ROOT_PATH/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS_OVERRIDDEN/interface-id"
18 | IPV4_ASSOCIATIONS_PATH_DEFAULT="$ROOT_PATH/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS_DEFAULT/ipv4-associations/$PUBLIC_IPV4_DEFAULT"
19 | IPV4_ASSOCIATIONS_PATH_OVERRIDDEN="$ROOT_PATH/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS_OVERRIDDEN/ipv4-associations/$PUBLIC_IPV4_OVERRIDDEN"
20 | SPOT_TEST_PATH="$ROOT_PATH/latest/meta-data/spot/instance-action"
21 | EVENTS_TEST_PATH="$ROOT_PATH/latest/meta-data/events/maintenance/scheduled"
22 | DYNAMIC_TEST_PATH="$ROOT_PATH/latest/dynamic"
23 |
24 | function test_static_metadata_available() {
25 | pid=$1
26 | test_url=$2
27 | test_name=$3
28 | tput setaf $BLUE
29 | health_check $test_url
30 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
31 |
32 | actual_mac_address=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $MAC_ADDRESS_PATH)
33 | assert_value "$actual_mac_address" $MAC_ADDRESS_DEFAULT "mac_address $test_name"
34 |
35 | actual_network_interface_id=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $NETWORK_INTERFACE_ID_PATH_DEFAULT)
36 | assert_value "$actual_network_interface_id" $NETWORK_INTERFACE_ID_DEFAULT "network_interface_id $test_name"
37 |
38 | actual_ipv4_association=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $IPV4_ASSOCIATIONS_PATH_DEFAULT)
39 | assert_value "$actual_ipv4_association" $IPV4_ASSOCIATION_DEFAULT "ipv4_association $test_name"
40 |
41 | clean_up $pid
42 | }
43 |
44 | function test_static_metadata_with_config_file() {
45 | pid=$1
46 | test_url=$2
47 | test_name=$3
48 | tput setaf $BLUE
49 | health_check $test_url
50 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
51 |
52 | updated_mac_address=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $MAC_ADDRESS_PATH)
53 | assert_value "$updated_mac_address" $MAC_ADDRESS_OVERRIDDEN "updated_mac_address $test_name"
54 |
55 | # Default paths should no longer be valid
56 | invalid_network_interface_id=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $NETWORK_INTERFACE_ID_PATH_DEFAULT)
57 | assert_not_equal "$invalid_network_interface_id" $NETWORK_INTERFACE_ID_DEFAULT "invalid_networkId $test_name"
58 | invalid_ipv4_association=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $IPV4_ASSOCIATIONS_PATH_DEFAULT)
59 | assert_not_equal "$invalid_ipv4_association" $IPV4_ASSOCIATION_DEFAULT "invalid_ipv4Association $test_name"
60 |
61 | # Updated paths return expected
62 | actual_network_interface_id=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $NETWORK_INTERFACE_ID_PATH_OVERRIDDEN)
63 | assert_value "$actual_network_interface_id" $NETWORK_INTERFACE_ID_DEFAULT "networkId $test_name"
64 | # Double substitution
65 | actual_ipv4_association=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $IPV4_ASSOCIATIONS_PATH_OVERRIDDEN)
66 | assert_value "$actual_ipv4_association" $IPV4_ASSOCIATION_DEFAULT "ipv4_association $test_name"
67 |
68 | clean_up $pid
69 | }
70 |
71 | tput setaf $BLUE
72 | echo "======================================================================================================"
73 | echo "🥑 Starting static metadata integration tests $METADATA_VERSION"
74 | echo "======================================================================================================"
75 |
76 | # Static metadata should ALWAYS be available
77 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
78 | $start_cmd &
79 | STATIC_PID=$!
80 | test_static_metadata_available $STATIC_PID $ROOT_PATH "static_metadata::root"
81 |
82 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
83 | $start_cmd &
84 | STATIC_PID=$!
85 | test_static_metadata_available $STATIC_PID $DYNAMIC_TEST_PATH "static_metadata::dynamic"
86 |
87 | start_cmd=$(create_cmd $METADATA_VERSION spot -a hibernate --port $AEMM_PORT)
88 | $start_cmd &
89 | STATIC_PID=$!
90 | test_static_metadata_available $STATIC_PID $SPOT_TEST_PATH "static_metadata::spot"
91 |
92 | start_cmd=$(create_cmd $METADATA_VERSION events --code instance-stop --port $AEMM_PORT)
93 | $start_cmd &
94 | STATIC_PID=$!
95 | test_static_metadata_available $STATIC_PID $EVENTS_TEST_PATH "static_metadata::events"
96 |
97 | # Static metadata paths should reflect updated placeholder values
98 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT -c $TEST_CONFIG_FILE)
99 | $start_cmd &
100 | STATIC_PID=$!
101 | test_static_metadata_with_config_file $STATIC_PID $ROOT_PATH "static_metadata::config"
102 |
103 | exit $EXIT_CODE_TO_RETURN
104 |
--------------------------------------------------------------------------------
/test/e2e/cmd/userdata-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | TEST_CONFIG_FILE="$SCRIPTPATH/testdata/aemm-config-integ.json"
6 |
7 | USERDATA_DEFAULT="1234,john,reboot,true"
8 | USERDATA_OVERRIDDEN="1234,john,reboot,true"
9 |
10 | ROOT_PATH="http://$HOSTNAME:$AEMM_PORT"
11 | USERDATA_TEST_PATH="$ROOT_PATH/latest/user-data"
12 |
13 | function test_userdata_defaults() {
14 | pid=$1
15 | test_url=$2
16 | test_name=$3
17 | tput setaf $BLUE
18 | health_check $test_url
19 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
20 |
21 | actual_userdata=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $USERDATA_TEST_PATH)
22 | assert_value "$actual_userdata" $USERDATA_DEFAULT "userdata $test_name"
23 |
24 | clean_up $pid
25 | }
26 |
27 | function test_userdata_overrides() {
28 | pid=$1
29 | test_url=$2
30 | test_name=$3
31 | tput setaf $BLUE
32 | health_check $test_url
33 | TOKEN=$(get_v2Token $MAX_TOKEN_TTL $AEMM_PORT)
34 |
35 | updated_userdata=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" $USERDATA_TEST_PATH)
36 | assert_value "$updated_userdata" $USERDATA_OVERRIDDEN "userdata $test_name"
37 |
38 | clean_up $pid
39 | }
40 |
41 | tput setaf $BLUE
42 | echo "======================================================================================================"
43 | echo "🥑 Starting userdata integration tests $METADATA_VERSION"
44 | echo "======================================================================================================"
45 |
46 |
47 | # userdata data defaults
48 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT)
49 | $start_cmd &
50 | USERDATA_PID=$!
51 | test_userdata_defaults $USERDATA_PID $USERDATA_TEST_PATH $USERDATA_DEFAULT
52 |
53 | # userdata data overrides
54 | start_cmd=$(create_cmd $METADATA_VERSION --port $AEMM_PORT -c $TEST_CONFIG_FILE)
55 | $start_cmd &
56 | USERDATA_PID=$!
57 | test_userdata_overrides $USERDATA_PID $USERDATA_TEST_PATH $USERDATA_OVERRIDDEN
58 |
59 | exit $EXIT_CODE_TO_RETURN
60 |
--------------------------------------------------------------------------------
/test/e2e/golden/400_bad_request.golden:
--------------------------------------------------------------------------------
1 | 400 Bad Request
--------------------------------------------------------------------------------
/test/e2e/golden/400_response.golden:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 400 - Bad Request
6 |
7 |
8 | 400 - Bad Request
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/e2e/golden/401_response.golden:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 401 - Unauthorized
6 |
7 |
8 | 401 - Unauthorized
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/e2e/golden/404_response.golden:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | 404 - Not Found
7 |
8 |
9 | 404 - Not Found
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/e2e/golden/asglifecycle/latest/meta-data/index.golden:
--------------------------------------------------------------------------------
1 | ami-id
2 | ami-launch-index
3 | ami-manifest-path
4 | autoscaling/
5 | block-device-mapping/
6 | elastic-inference/
7 | hostname
8 | iam/
9 | instance-action
10 | instance-id
11 | instance-life-cycle
12 | instance-type
13 | kernel-id
14 | local-hostname
15 | local-ipv4
16 | mac
17 | network/
18 | placement/
19 | product-codes
20 | public-hostname
21 | public-ipv4
22 | public-keys/
23 | ramdisk-id
24 | reservation-id
25 | security-groups
26 | services/
27 | tags/
--------------------------------------------------------------------------------
/test/e2e/golden/default/index.golden:
--------------------------------------------------------------------------------
1 | latest
--------------------------------------------------------------------------------
/test/e2e/golden/default/latest/meta-data/index.golden:
--------------------------------------------------------------------------------
1 | ami-id
2 | ami-launch-index
3 | ami-manifest-path
4 | autoscaling/
5 | block-device-mapping/
6 | elastic-inference/
7 | events/
8 | hostname
9 | iam/
10 | instance-action
11 | instance-id
12 | instance-life-cycle
13 | instance-type
14 | kernel-id
15 | local-hostname
16 | local-ipv4
17 | mac
18 | network/
19 | placement/
20 | product-codes
21 | public-hostname
22 | public-ipv4
23 | public-keys/
24 | ramdisk-id
25 | reservation-id
26 | security-groups
27 | services/
28 | spot/
29 | tags/
--------------------------------------------------------------------------------
/test/e2e/golden/dynamic/fws.golden:
--------------------------------------------------------------------------------
1 | instance-monitoring
--------------------------------------------------------------------------------
/test/e2e/golden/dynamic/index.golden:
--------------------------------------------------------------------------------
1 | fws/
2 | instance-identity/
--------------------------------------------------------------------------------
/test/e2e/golden/dynamic/instance-identity.golden:
--------------------------------------------------------------------------------
1 | document
2 | pkcs7
3 | signature
--------------------------------------------------------------------------------
/test/e2e/golden/events/latest/meta-data/index.golden:
--------------------------------------------------------------------------------
1 | ami-id
2 | ami-launch-index
3 | ami-manifest-path
4 | block-device-mapping/
5 | elastic-inference/
6 | events/
7 | hostname
8 | iam/
9 | instance-action
10 | instance-id
11 | instance-life-cycle
12 | instance-type
13 | kernel-id
14 | local-hostname
15 | local-ipv4
16 | mac
17 | network/
18 | placement/
19 | product-codes
20 | public-hostname
21 | public-ipv4
22 | public-keys/
23 | ramdisk-id
24 | reservation-id
25 | security-groups
26 | services/
27 | tags/
--------------------------------------------------------------------------------
/test/e2e/golden/events/latest/meta-data/network/interfaces/macs/0e_49_61_0f_c3_11/index.golden:
--------------------------------------------------------------------------------
1 | device-number
2 | interface-id
3 | ipv4-associations/
4 | ipv6s
5 | local-hostname
6 | local-ipv4s
7 | mac
8 | network-card-index
9 | owner-id
10 | public-hostname
11 | public-ipv4s
12 | security-group-ids
13 | security-groups
14 | subnet-id
15 | subnet-ipv4-cidr-block
16 | subnet-ipv6-cidr-blocks
17 | vpc-id
18 | vpc-ipv4-cidr-block
19 | vpc-ipv4-cidr-blocks
20 | vpc-ipv6-cidr-blocks
--------------------------------------------------------------------------------
/test/e2e/golden/spot/latest/meta-data/index.golden:
--------------------------------------------------------------------------------
1 | ami-id
2 | ami-launch-index
3 | ami-manifest-path
4 | block-device-mapping/
5 | elastic-inference/
6 | events/
7 | hostname
8 | iam/
9 | instance-action
10 | instance-id
11 | instance-life-cycle
12 | instance-type
13 | kernel-id
14 | local-hostname
15 | local-ipv4
16 | mac
17 | network/
18 | placement/
19 | product-codes
20 | public-hostname
21 | public-ipv4
22 | public-keys/
23 | ramdisk-id
24 | reservation-id
25 | security-groups
26 | services/
27 | spot/
28 | tags/
--------------------------------------------------------------------------------
/test/e2e/golden/spot/latest/meta-data/spot.golden:
--------------------------------------------------------------------------------
1 | instance-action
2 | termination-time
--------------------------------------------------------------------------------
/test/e2e/testdata/aemm-config-integ.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "paths": {
4 | "ipv4-associations": "/latest/meta-data/network/interfaces/macs/0e:49:61:0f:c3:77/ipv4-associations/192.0.2.54"
5 | },
6 | "values": {
7 | "mac": "0e:49:61:0f:c3:77",
8 | "public-ipv4": "54.92.157.77"
9 | }
10 | },
11 | "spot": {
12 | "action": "terminate",
13 | "time": "2020-01-07T01:03:47Z"
14 | },
15 | "dynamic": {
16 | "values": {
17 | "instance-identity-document": {
18 | "accountId": "9876543210",
19 | "imageId": "ami-0b69ea66ff7391e80",
20 | "availabilityZone": "us-east-1f",
21 | "ramdiskId": null,
22 | "kernelId": null,
23 | "devpayProductCodes": null,
24 | "marketplaceProductCodes": ["4i20ezfza3p7xx2kt2g8weu2u"],
25 | "version": "2017-09-30",
26 | "privateIp": "10.0.7.10",
27 | "billingProducts": null,
28 | "instanceId": "i-0fedcba0987654321",
29 | "pendingTime": "2019-10-31T07:02:24Z",
30 | "architecture": "x86_64",
31 | "instanceType": "m4.xlarge",
32 | "region": "us-east-1" }
33 | }
34 | },
35 | "userdata": {
36 | "paths": {
37 | "userdata": "/latest/user-data"
38 | },
39 | "values": {
40 | "userdata": "MTIzNCxqb2huLHJlYm9vdCx0cnVlCg=="
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/helm/ct.yaml:
--------------------------------------------------------------------------------
1 | chart-dirs:
2 | - helm
3 | all: true
4 | check-version-increment: false
5 | validate-chart-schema: false
6 | validate-maintainers: true
7 | validate-yaml: true
8 | debug: false
9 | helm-extra-args: "--timeout 600s --namespace default --kubeconfig /root/.kube/config"
10 |
--------------------------------------------------------------------------------
/test/helm/kind-config.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: kind.x-k8s.io/v1alpha4
3 | nodes:
4 | - role: control-plane
5 | - role: worker
--------------------------------------------------------------------------------
/test/helm/mock-ip-count-test/mock-ip-test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | SCRIPTPATH="$(
6 | cd "$(dirname "$0")"
7 | pwd -P
8 | )"
9 | EXIT_CODE_TO_RETURN=0
10 | STATUS_CHECK_CYCLES=30
11 | STATUS_CHECK_SLEEP=2
12 |
13 | function get_status() {
14 | pod=$1
15 | status=$(kubectl describe pod $pod | grep "Status:")
16 | status=${status//"Status:"/}
17 | status=$(echo $status | xargs)
18 | echo $status
19 | }
20 |
21 | function assert_value() {
22 | # assert actual == expected
23 | if [[ $1 == "$2" ]]; then
24 | echo "✅ Verified $3"
25 | else
26 | echo "❌ Failed $3 verification. Actual: $1 Expected: $2"
27 | EXIT_CODE_TO_RETURN=1
28 | fi
29 | }
30 |
31 | function clean_up() {
32 | kubectl delete pods/test-pod pods/test-pod-404
33 | }
34 |
35 | function test() {
36 | mock_ip_count=$1
37 | expected_test_pod_status=$2
38 | expected_test_pod_404_status=$3
39 | echo "executing mock-ip-count test with mockIPCount=$mock_ip_count"
40 |
41 | helm upgrade --install "$CLUSTER_NAME-aemm" \
42 | $AEMM_HELM_REPO \
43 | --wait \
44 | --namespace default \
45 | --values $AEMM_HELM_REPO/ci/local-image-values.yaml \
46 | --set aemm.spot.time="1994-05-15T00:00:00Z" \
47 | --set aemm.mockIPCount=$mock_ip_count
48 |
49 | # Deploy pods
50 | kubectl apply -f "$SCRIPTPATH/test-pod.yaml"
51 | sleep 1
52 | kubectl apply -f "$SCRIPTPATH/test-pod-404.yaml"
53 |
54 | # Proceed with copying only after pod is Running
55 | for i in `seq 1 $STATUS_CHECK_CYCLES`; do
56 | test_pod_404_status=$(get_status test-pod-404)
57 | if [[ "$test_pod_404_status" == "Running" ]]; then
58 | echo "✅ Verified test-pod-404 is created and running"
59 | break
60 | fi
61 | echo "test-pod-404 still provisioning with status: $test_pod_404_status . status check $i/$STATUS_CHECK_CYCLES, sleeping for $STATUS_CHECK_SLEEP seconds"
62 | sleep $STATUS_CHECK_SLEEP
63 | done
64 |
65 | echo "copying 404_response.golden to 404-pod"
66 | kubectl cp "$SCRIPTPATH/../../e2e/golden/404_response.golden" test-pod-404:/tmp/404_response.golden
67 |
68 | # Query status until tests succeed or fail
69 | for i in `seq 1 $STATUS_CHECK_CYCLES`; do
70 | test_pod_status=$(get_status test-pod)
71 | test_pod_404_status=$(get_status test-pod-404)
72 | if [[ "$test_pod_status" != "Running" ]] && [[ "$test_pod_404_status" != "Running" ]]; then
73 | echo "✅ Verified both pods are no longer Running"
74 | break
75 | fi
76 | echo "Pods are still Running. test-pod status: $test_pod_status test-pod-404 status: $test_pod_404_status"
77 | echo "status check $i/$STATUS_CHECK_CYCLES, sleeping for $STATUS_CHECK_SLEEP seconds"
78 | sleep $STATUS_CHECK_SLEEP
79 | done
80 |
81 | assert_value "$test_pod_status" "$expected_test_pod_status" "test-pod $expected_test_pod_status w/mockIPCount=$mock_ip_count"
82 | assert_value "$test_pod_404_status" "$expected_test_pod_404_status" "test-pod-404 $expected_test_pod_404_status w/mockIPCount=$mock_ip_count"
83 |
84 | clean_up
85 | }
86 |
87 | echo "======================================================================================================"
88 | echo "🥑 Starting AEMM mock-ip-count test"
89 | echo "======================================================================================================"
90 |
91 | test 1 "Succeeded" "Succeeded" # 404 returned after ONE IP Spot request
92 | test 2 "Succeeded" "Failed" # 404 returned after TWO IP Spot requests
93 |
94 | exit $EXIT_CODE_TO_RETURN
--------------------------------------------------------------------------------
/test/helm/mock-ip-count-test/test-pod-404.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: "test-pod-404"
5 | spec:
6 | restartPolicy: Never
7 | containers:
8 | - name: mock-ip-test
9 | imagePullPolicy: "IfNotPresent"
10 | image: "quay.io/centos/centos:latest"
11 | command:
12 | - "bash"
13 | - "-c"
14 | - |
15 | SERVICE_NAME="AMAZON_EC2_METADATA_MOCK_SERVICE"
16 | HOST_VAR=$(echo "${SERVICE_NAME}_SERVICE_HOST")
17 | PORT_VAR=$(echo "${SERVICE_NAME}_SERVICE_PORT")
18 | sleep 5
19 | ACTUAL=$(curl http://${!HOST_VAR}:${!PORT_VAR}/latest/meta-data/spot/termination-time)
20 | EXPECTED=$(cat /tmp/404_response.golden)
21 | [[ "$ACTUAL" == "$EXPECTED" ]] && exit 0 || exit 1
22 |
--------------------------------------------------------------------------------
/test/helm/mock-ip-count-test/test-pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: "test-pod"
5 | spec:
6 | restartPolicy: Never
7 | containers:
8 | - name: mock-ip-test
9 | imagePullPolicy: "IfNotPresent"
10 | image: "quay.io/centos/centos:latest"
11 | command:
12 | - "bash"
13 | - "-c"
14 | - |
15 | SERVICE_NAME="AMAZON_EC2_METADATA_MOCK_SERVICE"
16 | HOST_VAR=$(echo "${SERVICE_NAME}_SERVICE_HOST")
17 | PORT_VAR=$(echo "${SERVICE_NAME}_SERVICE_PORT")
18 | sleep 3
19 | ACTUAL=$(curl http://${!HOST_VAR}:${!PORT_VAR}/latest/meta-data/spot/termination-time)
20 | EXPECTED="1994-05-15T00:00:00Z"
21 | [[ "$ACTUAL" == "$EXPECTED" ]] && exit 0 || exit 1
--------------------------------------------------------------------------------
/test/helpers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may
4 | // not use this file except in compliance with the License. A copy of the
5 | // License is located at
6 | //
7 | // http://aws.amazon.com/apache2.0/
8 | //
9 | // or in the "license" file accompanying this file. This file is distributed
10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 | // express or implied. See the License for the specific language governing
12 | // permissions and limitations under the License.
13 |
14 | package test
15 |
16 | import (
17 | "fmt"
18 | "path/filepath"
19 | "runtime"
20 | "testing"
21 | )
22 |
23 | // ItemsMatch fails the test if the items in exp and act slices dont match.
24 | // A nil argument is equivalent to an empty slice.
25 | func ItemsMatch(tb testing.TB, exp, act []string) {
26 | if len(exp) != len(act) {
27 | tb.Errorf(fmt.Sprintf("Expected %d items in slice, but was %d", len(exp), len(act)))
28 | }
29 | for _, v := range exp {
30 | if !Contains(act, v) {
31 | tb.Errorf(fmt.Sprintf("Expected to find item %s in slice, but was not found", v))
32 | }
33 | }
34 | }
35 |
36 | // Contains returns a bool indicating whether the slice contains the given val
37 | func Contains(slice []string, val string) bool {
38 | for _, item := range slice {
39 | if item == val {
40 | return true
41 | }
42 | }
43 | return false
44 | }
45 |
46 | // Assert fails the test if the condition is false.
47 | func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
48 | if !condition {
49 | _, file, line, _ := runtime.Caller(1)
50 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
51 | tb.FailNow()
52 | }
53 | }
54 |
55 | // Ok fails the test if an err is not nil.
56 | func Ok(tb testing.TB, err error) {
57 | if err != nil {
58 | _, file, line, _ := runtime.Caller(1)
59 | fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
60 | tb.FailNow()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/test/readme-test/run-readme-spellcheck:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
5 |
6 | function exit_and_fail() {
7 | echo "❌ Test Failed! Found a markdown file with spelling errors."
8 | exit 1
9 | }
10 | trap exit_and_fail INT ERR TERM
11 |
12 | docker build -t misspell -f $SCRIPTPATH/spellcheck-Dockerfile $SCRIPTPATH/
13 | docker run -i --rm -v $SCRIPTPATH/../../:/app misspell /bin/bash -c 'find /app/ -type f -name "*.md" -not -path "build" | grep -v "/build/" | xargs misspell -error -debug'
14 | echo "✅ Markdown file spell check passed!"
--------------------------------------------------------------------------------
/test/readme-test/spellcheck-Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.17
2 |
3 | RUN go install github.com/client9/misspell/cmd/misspell@v0.3.4
4 |
5 | CMD [ "/go/bin/misspell" ]
6 |
--------------------------------------------------------------------------------
/test/shellcheck/run-shellcheck:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
6 | BUILD_DIR="${SCRIPTPATH}/../../build"
7 |
8 | KERNEL=$(uname -s | tr '[:upper:]' '[:lower:]')
9 | SHELLCHECK_VERSION="0.7.1"
10 |
11 | function exit_and_fail() {
12 | echo "❌ Test Failed! Found a shell script with errors."
13 | exit 1
14 | }
15 | trap exit_and_fail INT ERR TERM
16 |
17 | curl -Lo ${BUILD_DIR}/shellcheck.tar.xz "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.${KERNEL}.x86_64.tar.xz"
18 | tar -C ${BUILD_DIR} -xvf "${BUILD_DIR}/shellcheck.tar.xz"
19 | export PATH="${BUILD_DIR}/shellcheck-v${SHELLCHECK_VERSION}:$PATH"
20 |
21 | shell_files=()
22 | while IFS='' read -r line; do
23 | shell_files+=("$line");
24 | done < <(grep -Rnl --exclude-dir=build --exclude-dir=docs -e '#!.*/bin/bash' -e '#!.*/usr/bin/env bash' ${SCRIPTPATH}/../../)
25 | shellcheck -S warning "${shell_files[@]}"
26 |
27 | echo "✅ All shell scripts look good! 😎"
--------------------------------------------------------------------------------