Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cage recreate #70

Merged
merged 11 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[*.go]
indent_size = 4
indent_size = 2
indent_style = tab
[Shakefile]
indent_size = 2
indent_style = tab
indent_style = tab
10 changes: 7 additions & 3 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ jobs:
git diff --exit-code
- run: go build
- run: go build github.com/loilo-inc/canarycage/cli/cage
- run: go test -coverprofile=coverage.txt -covermode=count
- name: Upload Coverage
run: bash <(curl -s https://codecov.io/bash)
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
fail_ci_if_error: true
files: coverage.txt
41 changes: 29 additions & 12 deletions cage.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//go:generate go run github.com/golang/mock/mockgen -source $GOFILE -destination ../mocks/mock_$GOPACKAGE/$GOFILE -package mock_$GOPACKAGE
package cage

import (
"context"
"time"

"github.com/loilo-inc/canarycage/awsiface"
)
Expand All @@ -10,27 +12,42 @@ type Cage interface {
Up(ctx context.Context) (*UpResult, error)
Run(ctx context.Context, input *RunInput) (*RunResult, error)
RollOut(ctx context.Context) (*RollOutResult, error)
Recreate(ctx context.Context) (*RecreateResult, error)
}

type Time interface {
Now() time.Time
NewTimer(time.Duration) *time.Timer
}

type cage struct {
env *Envars
ecs awsiface.EcsClient
alb awsiface.AlbClient
ec2 awsiface.Ec2Client
Env *Envars
Ecs awsiface.EcsClient
Alb awsiface.AlbClient
Ec2 awsiface.Ec2Client
Time Time
MaxWait time.Duration
}

type Input struct {
Env *Envars
ECS awsiface.EcsClient
ALB awsiface.AlbClient
EC2 awsiface.Ec2Client
Env *Envars
ECS awsiface.EcsClient
ALB awsiface.AlbClient
EC2 awsiface.Ec2Client
Time Time
MaxWait time.Duration
}

func NewCage(input *Input) Cage {
if input.Time == nil {
input.Time = &timeImpl{}
}
return &cage{
env: input.Env,
ecs: input.ECS,
alb: input.ALB,
ec2: input.EC2,
Env: input.Env,
Ecs: input.ECS,
Alb: input.ALB,
Ec2: input.EC2,
Time: input.Time,
MaxWait: 5 * time.Minute,
}
}
94 changes: 88 additions & 6 deletions cli/cage/commands/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,101 @@ package commands

import (
"context"
"io"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
cage "github.com/loilo-inc/canarycage"
"github.com/loilo-inc/canarycage/cli/cage/prompt"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)

type CageCommands interface {
Up() *cli.Command
RollOut() *cli.Command
Run() *cli.Command
Commands(
envars *cage.Envars,
) []*cli.Command
}

type cageCommands struct {
ctx context.Context
Prompt *prompt.Prompter
cageCliProvier cageCliProvier
}

func NewCageCommands(
stdin io.Reader,
cageCliProvier cageCliProvier,
) CageCommands {
return &cageCommands{
Prompt: prompt.NewPrompter(stdin),
cageCliProvier: cageCliProvier,
}
}

type cageCliProvier = func(envars *cage.Envars) (cage.Cage, error)

func (c *cageCommands) Commands(envars *cage.Envars) []*cli.Command {
return []*cli.Command{
c.Up(envars),
c.RollOut(envars),
c.Run(envars),
c.Recreate(envars),
}
}

func DefalutCageCliProvider(envars *cage.Envars) (cage.Cage, error) {
conf, err := config.LoadDefaultConfig(
context.Background(),
config.WithRegion(envars.Region))
if err != nil {
return nil, xerrors.Errorf("failed to load aws config: %w", err)
}
cagecli := cage.NewCage(&cage.Input{
Env: envars,
ECS: ecs.NewFromConfig(conf),
EC2: ec2.NewFromConfig(conf),
ALB: elasticloadbalancingv2.NewFromConfig(conf),
})
return cagecli, nil
}

func (c *cageCommands) requireArgs(
ctx *cli.Context,
minArgs int,
maxArgs int,
) (dir string, rest []string, err error) {
if ctx.NArg() < minArgs {
return "", nil, xerrors.Errorf("invalid number of arguments. expected at least %d", minArgs)
} else if ctx.NArg() > maxArgs {
return "", nil, xerrors.Errorf("invalid number of arguments. expected at most %d", maxArgs)
}
dir = ctx.Args().First()
rest = ctx.Args().Tail()
return
}

func NewCageCommands(ctx context.Context) CageCommands {
return &cageCommands{ctx: ctx}
func (c *cageCommands) setupCage(
envars *cage.Envars,
dir string,
) (cage.Cage, error) {
td, svc, err := cage.LoadDefinitionsFromFiles(dir)
if err != nil {
return nil, err
}
cage.MergeEnvars(envars, &cage.Envars{
Cluster: *svc.Cluster,
Service: *svc.ServiceName,
TaskDefinitionInput: td,
ServiceDefinitionInput: svc,
})
if err := cage.EnsureEnvars(envars); err != nil {
return nil, err
}
cagecli, err := c.cageCliProvier(envars)
if err != nil {
return nil, err
}
return cagecli, nil
}
112 changes: 112 additions & 0 deletions cli/cage/commands/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package commands_test

import (
"fmt"
"strings"
"testing"

"github.com/golang/mock/gomock"
cage "github.com/loilo-inc/canarycage"
"github.com/loilo-inc/canarycage/cli/cage/commands"
"github.com/loilo-inc/canarycage/mocks/mock_cage"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)

func TestCommands(t *testing.T) {
region := "ap-notheast-1"
cluster := "cluster"
service := "service"
stdinService := fmt.Sprintf("%s\n%s\n%s\n%s\n", region, cluster, service, "yes")
stdinTask := fmt.Sprintf("%s\n%s\n%s\n", region, cluster, "yes")
setup := func(t *testing.T, input string) (*cli.App, *mock_cage.MockCage) {
ctrl := gomock.NewController(t)
stdin := strings.NewReader(input)
cagecli := mock_cage.NewMockCage(ctrl)
app := cli.NewApp()
app.Commands = commands.NewCageCommands(stdin, func(envars *cage.Envars) (cage.Cage, error) {
return cagecli, nil
}).Commands(&cage.Envars{CI: input == ""})
return app, cagecli
}
t.Run("rollout", func(t *testing.T) {
t.Run("basic", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().RollOut(gomock.Any()).Return(&cage.RollOutResult{}, nil)
err := app.Run([]string{"cage", "rollout", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("basic/ci", func(t *testing.T) {
app, cagecli := setup(t, "")
cagecli.EXPECT().RollOut(gomock.Any()).Return(&cage.RollOutResult{}, nil)
err := app.Run([]string{"cage", "rollout", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("error", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().RollOut(gomock.Any()).Return(&cage.RollOutResult{}, fmt.Errorf("error"))
err := app.Run([]string{"cage", "rollout", "--region", "ap-notheast-1", "../../../fixtures"})
assert.EqualError(t, err, "error")
})
})
t.Run("recreate", func(t *testing.T) {
t.Run("basic", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().Recreate(gomock.Any()).Return(&cage.RecreateResult{}, nil)
err := app.Run([]string{"cage", "recreate", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("basic/ci", func(t *testing.T) {
app, cagecli := setup(t, "")
cagecli.EXPECT().Recreate(gomock.Any()).Return(&cage.RecreateResult{}, nil)
err := app.Run([]string{"cage", "recreate", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("error", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().Recreate(gomock.Any()).Return(nil, fmt.Errorf("error"))
err := app.Run([]string{"cage", "recreate", "--region", "ap-notheast-1", "../../../fixtures"})
assert.EqualError(t, err, "error")
})
})
t.Run("up", func(t *testing.T) {
t.Run("basic", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().Up(gomock.Any()).Return(&cage.UpResult{}, nil)
err := app.Run([]string{"cage", "up", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("basic/ci", func(t *testing.T) {
app, cagecli := setup(t, "")
cagecli.EXPECT().Up(gomock.Any()).Return(&cage.UpResult{}, nil)
err := app.Run([]string{"cage", "up", "--region", "ap-notheast-1", "../../../fixtures"})
assert.NoError(t, err)
})
t.Run("error", func(t *testing.T) {
app, cagecli := setup(t, stdinService)
cagecli.EXPECT().Up(gomock.Any()).Return(nil, fmt.Errorf("error"))
err := app.Run([]string{"cage", "up", "--region", "ap-notheast-1", "../../../fixtures"})
assert.EqualError(t, err, "error")
})
})
t.Run("run", func(t *testing.T) {
t.Run("basic", func(t *testing.T) {
app, cagecli := setup(t, stdinTask)
cagecli.EXPECT().Run(gomock.Any(), gomock.Any()).Return(&cage.RunResult{}, nil)
err := app.Run([]string{"cage", "run", "--region", "ap-notheast-1", "../../../fixtures", "container", "exec"})
assert.NoError(t, err)
})
t.Run("basic/ci", func(t *testing.T) {
app, cagecli := setup(t, "")
cagecli.EXPECT().Run(gomock.Any(), gomock.Any()).Return(&cage.RunResult{}, nil)
err := app.Run([]string{"cage", "run", "--region", "ap-notheast-1", "../../../fixtures", "container", "exec"})
assert.NoError(t, err)
})
t.Run("error", func(t *testing.T) {
app, cagecli := setup(t, stdinTask)
cagecli.EXPECT().Run(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error"))
err := app.Run([]string{"cage", "run", "--region", "ap-notheast-1", "../../../fixtures", "container", "exec"})
assert.EqualError(t, err, "error")
})
})
}
3 changes: 3 additions & 0 deletions cli/cage/commands/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package commands

type CommandsExport = cageCommands
44 changes: 2 additions & 42 deletions cli/cage/commands/flags.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package commands

import (
"context"

"github.com/apex/log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/loilo-inc/canarycage"
cage "github.com/loilo-inc/canarycage"
"github.com/urfave/cli/v2"
)

Expand All @@ -15,6 +11,7 @@ func RegionFlag(dest *string) *cli.StringFlag {
EnvVars: []string{cage.RegionKey},
Usage: "aws region for ecs. if not specified, try to load from aws sessions automatically",
Destination: dest,
Required: true,
}
}
func ClusterFlag(dest *string) *cli.StringFlag {
Expand Down Expand Up @@ -51,40 +48,3 @@ func CanaryTaskIdleDurationFlag(dest *int) *cli.IntFlag {
Value: 10,
}
}

func (c *cageCommands) aggregateEnvars(
ctx *cli.Context,
envars *cage.Envars,
) {
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
log.Fatalf(err.Error())
}

if envars.Region != "" {
log.Infof("🗺 region was set: %s", envars.Region)
}

if cfg.Region != "" {
log.Infof("🗺 region was loaded from default config: %s", cfg.Region)
} else {
log.Fatalf("🙄 region must specified by --region flag or aws session")
}

if ctx.NArg() > 0 {
dir := ctx.Args().Get(0)
td, svc, err := cage.LoadDefinitionsFromFiles(dir)
if err != nil {
log.Fatalf(err.Error())
}
cage.MergeEnvars(envars, &cage.Envars{
Cluster: *svc.Cluster,
Service: *svc.ServiceName,
TaskDefinitionInput: td,
ServiceDefinitionInput: svc,
})
}
if err := cage.EnsureEnvars(envars); err != nil {
log.Fatalf(err.Error())
}
}
Loading
Loading