Skip to content

Commit

Permalink
feat: support retrieving the ec2 name through imds (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
purpleclay authored Sep 7, 2022
1 parent 78d80c2 commit 2ea10d3
Show file tree
Hide file tree
Showing 27 changed files with 1,254 additions and 229 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.19

- name: Cache Go
uses: actions/cache@v3
Expand All @@ -69,6 +69,7 @@ jobs:
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Test
run: task test

Expand All @@ -81,11 +82,14 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: task integration-test

# Temporary workaround until the golangci-lint GitHub action supports 1.19
- name: Install golangci-lint
if: matrix.os == 'ubuntu-latest'
run: go install github.com/golangci/golangci-lint/cmd/[email protected]

- name: Lint Code
if: matrix.os == 'ubuntu-latest'
uses: golangci/[email protected]
with:
skip-go-installation: true
run: golangci-lint run --version --verbose --out-format=github-actions

- name: misspell
if: matrix.os == 'ubuntu-latest'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.19

- uses: actions/cache@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ tasks:
test:
desc: Run the tests
cmds:
- go test -tags=!integration -race -vet=off -p 1 -covermode=atomic -coverprofile=unittest.out ./...
- go test -short -race -vet=off -p 1 -covermode=atomic -coverprofile=unittest.out ./...

integration-test:
desc: Run the integration tests
cmds:
- go test -run=TestIntegration -tags=integration -race -vet=off -p 1 -covermode=atomic -coverprofile=integrationtest.out ./...
- go test -run=Integration -race -vet=off -p 1 -covermode=atomic -coverprofile=integrationtest.out ./...

lint:
desc: Lint the code using golangci
Expand Down
7 changes: 4 additions & 3 deletions cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
opts := completionOptions{}

cmd := &cobra.Command{
Use: "completion",
Short: "Generate completion script for your target shell",
Long: "Generate a dns53 completion script for either the bash, zsh or fish shells",
Use: "completion",
Short: "Generate completion script for your target shell",
Long: "Generate a dns53 completion script for either the bash, zsh or fish shells",
SilenceUsage: true,
}

bash := &cobra.Command{
Expand Down
119 changes: 119 additions & 0 deletions cmd/imds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
Copyright (c) 2022 Purple Clay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package cmd

import (
"context"
"errors"
"io"
"strings"

awsimds "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/purpleclay/dns53/internal/ec2"
"github.com/purpleclay/dns53/internal/imds"
"github.com/spf13/cobra"
)

// Custom type used to toggle any setting "on" or "off"
type toggleSetting string

const (
toggleSettingOn toggleSetting = "on"
toggleSettingOff toggleSetting = "off"
)

func (t *toggleSetting) String() string {
return string(*t)
}

func (t *toggleSetting) Set(v string) error {
setting := strings.ToLower(v)

switch setting {
case "on", "off":
*t = toggleSetting(setting)
return nil
default:
return errors.New(`supported values are "on" or "off" (case-insensitive)`)
}
}

func (t *toggleSetting) Type() string {
return "string"
}

type imdsOptions struct {
InstanceMetadataTags toggleSetting
}

func newIMDSCommand(out io.Writer) *cobra.Command {
opt := imdsOptions{}

imdsCmd := &cobra.Command{
Use: "imds",
Short: "Toggle EC2 IMDS features",
Args: cobra.NoArgs,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := awsConfig(globalOpts)
if err != nil {
return err
}

if opt.InstanceMetadataTags == "" {
return nil
}

return toggleMetadataTags(awsec2.NewFromConfig(cfg), awsimds.NewFromConfig(cfg), opt.InstanceMetadataTags)
},
}

f := imdsCmd.Flags()
f.Var(&opt.InstanceMetadataTags, "instance-metadata-tags", "toggle the inclusion of EC2 instance tags within IMDS (on|off)")

imdsCmd.MarkFlagRequired("--instance-metadata-tags")
return imdsCmd
}

func toggleMetadataTags(ec2API ec2.ClientAPI, imdsAPI imds.ClientAPI, setting toggleSetting) error {
ec2Client := ec2.NewFromAPI(ec2API)
imdsClient := imds.NewFromAPI(imdsAPI)

metadata, err := imdsClient.InstanceMetadata(context.Background())
if err != nil {
return err
}

var toggle ec2.InstanceMetadataToggle

switch setting {
case toggleSettingOn:
toggle = ec2.InstanceMetadataToggleEnabled
case toggleSettingOff:
toggle = ec2.InstanceMetadataToggleDisabled
}

return ec2Client.ToggleInstanceMetadataTags(context.Background(), metadata.InstanceID, toggle)
}
116 changes: 116 additions & 0 deletions cmd/imds_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright (c) 2022 Purple Clay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package cmd

import (
"testing"

"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/purpleclay/dns53/internal/ec2/ec2mock"
"github.com/purpleclay/dns53/internal/imds/imdsstub"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestToggleSettingString(t *testing.T) {
toggle := toggleSetting("on")
assert.Equal(t, "on", toggle.String())
}

func TestToggleSettingSet(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "LowercaseOn",
input: "on",
expected: "on",
},
{
name: "LowercaseOff",
input: "off",
expected: "off",
},
{
name: "MixedCaseOn",
input: "oN",
expected: "on",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var setting toggleSetting

err := setting.Set(tt.input)
require.NoError(t, err)

require.Equal(t, tt.expected, string(setting))
})
}
}

func TestToggleSettingSetError(t *testing.T) {
var setting toggleSetting

err := setting.Set("not-supported")
assert.EqualError(t, err, `supported values are "on" or "off" (case-insensitive)`)
}

func TestToggleSettingType(t *testing.T) {
toggle := toggleSetting("on")
assert.Equal(t, "string", toggle.Type())
}

func TestToggleMetadataTags(t *testing.T) {
tests := []struct {
name string
toggle toggleSetting
expected string
}{
{
name: "On",
toggle: toggleSettingOn,
expected: "enabled",
},
{
name: "Off",
toggle: toggleSettingOff,
expected: "disabled",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockEC2 := ec2mock.New(t)
mockEC2.On("ModifyInstanceMetadataOptions", mock.Anything, mock.MatchedBy(func(req *ec2.ModifyInstanceMetadataOptionsInput) bool {
return req.InstanceMetadataTags == types.InstanceMetadataTagsState(tt.expected)
}), mock.Anything).Return(&ec2.ModifyInstanceMetadataOptionsOutput{}, nil)

err := toggleMetadataTags(mockEC2, imdsstub.New(t), tt.toggle)
assert.NoError(t, err)
})
}
}
2 changes: 2 additions & 0 deletions cmd/man.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func newManPagesCmd(out io.Writer) *cobra.Command {
Short: "Generate man pages for dns53",
DisableFlagsInUseLine: true,
Hidden: true,
SilenceUsage: true,
SilenceErrors: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
mp, err := mcobra.NewManPage(1, cmd.Root())
Expand Down
30 changes: 8 additions & 22 deletions pkg/imds/metadata_test.go → cmd/man_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build integration

/*
Copyright (c) 2022 Purple Clay
Expand All @@ -22,35 +20,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package imds_test
package cmd

import (
"context"
"bytes"
"testing"

"github.com/aws/aws-sdk-go-v2/config"
awsimds "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/purpleclay/dns53/pkg/imds"
aemm "github.com/purpleclay/testcontainers-aemm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestIntegration_InstanceMetadata(t *testing.T) {
ctx := context.Background()
container := aemm.MustStart(ctx)
defer container.Terminate(ctx)

cfg, err := config.LoadDefaultConfig(ctx, config.WithEC2IMDSEndpoint(container.URL))
require.NoError(t, err)
func TestManPages(t *testing.T) {
var buf bytes.Buffer
cmd := newManPagesCmd(&buf)

client := imds.NewFromAPI(awsimds.NewFromConfig(cfg))
metadata, err := client.InstanceMetadata(ctx)
err := cmd.Execute()
require.NoError(t, err)

assert.Equal(t, aemm.ValueLocalIPv4, metadata.IPv4)
assert.Equal(t, aemm.ValuePlacementRegion, metadata.Region)
assert.Equal(t, aemm.ValueNetworkInterfaces0VPCID, metadata.VPC)
assert.Equal(t, aemm.ValuePlacementAvailabilityZone, metadata.AZ)
assert.Equal(t, aemm.ValueInstanceID, metadata.InstanceID)
assert.NotEmpty(t, buf.String())
assert.Contains(t, buf.String(), ".TH MAN 1")
}
Loading

0 comments on commit 2ea10d3

Please sign in to comment.