Skip to content

Commit

Permalink
Use original structs of ECS for pipectl init
Browse files Browse the repository at this point in the history
  • Loading branch information
t-kikuc committed Dec 26, 2023
1 parent d3a549d commit 016f930
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 77 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ require (
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
Expand Down
31 changes: 7 additions & 24 deletions pkg/app/pipectl/cmd/initialize/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,17 @@ import (
)

type genericECSApplicationSpec struct {
Name string `yaml:"name"`
Input genericECSDeploymentInput `yaml:"input"`
Description string `yaml:"description,omitempty"`
}

// genericECSDeploymentInput represents an generic input for config.ECSDeploymentInput.
type genericECSDeploymentInput struct {
ServiceDefinitionFile string `yaml:"serviceDefinitionFile"`
TaskDefinitionFile string `yaml:"taskDefinitionFile"`
TargetGroups genericECSTargetGroups `yaml:"targetGroups,omitempty"`
}

type genericECSTargetGroups struct {
Primary genericECSTargetGroup `yaml:"primary,omitempty"`
Canary genericECSTargetGroup `yaml:"canary,omitempty"`
}

type genericECSTargetGroup struct {
TargetGroupArn string `yaml:"targetGroupArn"`
ContainerName string `yaml:"containerName"`
ContainerPort int `yaml:"containerPort"`
Name string `json:"name"`
Input config.ECSDeploymentInput `json:"input"`
Description string `json:"description,omitempty"`
}

func generateECSConfig(in io.Reader) (*genericConfig, error) {
spec, e := generateECSSpec(in)
if e != nil {
return nil, e
}

return &genericConfig{
Kind: config.KindECSApp,
APIVersion: config.VersionV1Beta1,
Expand All @@ -73,11 +56,11 @@ func generateECSSpec(in io.Reader) (*genericECSApplicationSpec, error) {

cfg := &genericECSApplicationSpec{
Name: appName,
Input: genericECSDeploymentInput{
Input: config.ECSDeploymentInput{
ServiceDefinitionFile: serviceDefFile,
TaskDefinitionFile: taskDefFile,
TargetGroups: genericECSTargetGroups{
Primary: genericECSTargetGroup{
TargetGroups: config.ECSTargetGroups{
Primary: &config.ECSTargetGroup{
TargetGroupArn: targetGroupArn,
ContainerName: containerName,
ContainerPort: containerPort,
Expand Down
11 changes: 6 additions & 5 deletions pkg/app/pipectl/cmd/initialize/ecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import (
"bytes"
"testing"

"github.com/goccy/go-yaml"
"github.com/pipe-cd/pipecd/pkg/config"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

func TestGenerateECSConfig(t *testing.T) {
Expand All @@ -31,7 +31,7 @@ func TestGenerateECSConfig(t *testing.T) {
expectedErr error
}{
{
name: "correct config for ECSApp",
name: "valid config for ECSApp",
inputs: []string{
"myApp",
"service-definition.json",
Expand All @@ -45,11 +45,11 @@ func TestGenerateECSConfig(t *testing.T) {
APIVersion: config.VersionV1Beta1,
ApplicationSpec: &genericECSApplicationSpec{
Name: "myApp",
Input: genericECSDeploymentInput{
Input: config.ECSDeploymentInput{
ServiceDefinitionFile: "service-definition.json",
TaskDefinitionFile: "task-definition.json",
TargetGroups: genericECSTargetGroups{
Primary: genericECSTargetGroup{
TargetGroups: config.ECSTargetGroups{
Primary: &config.ECSTargetGroup{
TargetGroupArn: "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/xxx/xxx",
ContainerName: "test-container",
ContainerPort: 80,
Expand All @@ -65,6 +65,7 @@ func TestGenerateECSConfig(t *testing.T) {

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// Mock user's input.
in := bytes.NewBufferString("")
for _, word := range tc.inputs {
in.WriteString(word + "\n")
Expand Down
65 changes: 31 additions & 34 deletions pkg/app/pipectl/cmd/initialize/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import (
"io"
"os"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

// "github.com/go-yaml/yaml"
// "sigs.k8s.io/yaml"

"github.com/pipe-cd/pipecd/pkg/cli"
"github.com/pipe-cd/pipecd/pkg/config"
Expand All @@ -34,14 +31,11 @@ type command struct {
someTextOption string
}

// Use genericConfigs in order to
// - keep the order as we want
// - use only simple fields, without attatching `omitempty` to all fields
// - enable modifying the original configs isolately from init command
// Use genericConfigs in order to simplify using the spec.
type genericConfig struct {
APIVersion string `yaml:"apiVersion"`
Kind config.Kind `yaml:"kind"`
ApplicationSpec interface{} `yaml:"spec"`
APIVersion string `json:"apiVersion"`
Kind config.Kind `json:"kind"`
ApplicationSpec interface{} `json:"spec"`
}

func NewCommand() *cobra.Command {
Expand All @@ -50,9 +44,9 @@ func NewCommand() *cobra.Command {
}
cmd := &cobra.Command{
Use: "init",
Short: "Create a app.pipecd.yaml easily (interactively)",
Short: "Generate a app.pipecd.yaml easily and interactively",
Example: ` pipectl init`,
Long: "Create a app.pipecd.yaml easily, interactively selecting options.",
Long: "Generate a app.pipecd.yaml easily, interactively selecting options.",
RunE: cli.WithContext(c.run),
}

Expand Down Expand Up @@ -89,38 +83,43 @@ func generateConfig(ctx context.Context, input cli.Input, in io.Reader) error {
return err
}

fmt.Println("### The config model was successfully generated.")
fmt.Println("### The config model was successfully generated. Move on to exporting. ###")

targetPath := promptString("Path to save the generated config (if not specified, it goes to stdout) : ", in)
if len(targetPath) == 0 {
// If the target path is not specified, print the config to stdout.
// If the target path is not specified, print to stdout.
printConfig(cfgBytes)
} else {
if _, err := os.Stat(targetPath); err == nil {
// If the file exists, ask if overwrite it.
overwrite := promptStringRequired(fmt.Sprintf("The file %s already exists. Overwrite it? [y/n] : ", targetPath), in)
if overwrite == "y" || overwrite == "Y" {
exportConfig(cfgBytes, targetPath)
} else {
fmt.Println("Cancelled exporting the config.")
printConfig(cfgBytes)
}
} else {
// If the file does not exist, simply write to the new file, including validating the path.
exportConfig(cfgBytes, targetPath)
}
exportConfig(cfgBytes, targetPath, in)
}

return nil
}

// Write the config to the specified path file.
func exportConfig(configBytes []byte, path string) {
// Write the config to the specified path.
func exportConfig(configBytes []byte, path string, in io.Reader) {
if fInfo, err := os.Stat(path); err == nil {
if fInfo.IsDir() {
fmt.Printf("The path %s is a directory. Please specify a file path.\n", path)
printConfig(configBytes)
return
}

// If the file exists, ask if overwrite it.
overwrite := promptStringRequired(fmt.Sprintf("The file %s already exists. Overwrite it? [y/n] : ", path), in)
if overwrite != "y" && overwrite != "Y" {
fmt.Println("Cancelled exporting the config.")
printConfig(configBytes)
return
}
}

// If the file does not exist or overwrite, write to the path, including validating.
fmt.Printf("Start exporting the config to %s\n", path)
err := os.WriteFile(path, configBytes, 0644)
if err != nil {
fmt.Printf("Failed to export the config to %s: %v\n", path, err)
// If failed, print the config to prevent losing it.
// If failed, print the config to avoid losing it.
printConfig(configBytes)
} else {
fmt.Printf("Successfully exported the config to %s\n", path)
Expand Down Expand Up @@ -153,13 +152,11 @@ func promptInt(message string, in io.Reader) (int, error) {

// Read a string value from stdin, and validate it is not empty.
func promptStringRequired(message string, in io.Reader) string {
// Limit for avoiding infinite loops in tests.
for i := 0; i < 30; i++ {
for {
in := promptString(message, in)
if in != "" {
return in
}
fmt.Printf("[WARN] This field is required. \n")
}
return "[not specified]"
}
28 changes: 14 additions & 14 deletions pkg/config/application_ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,32 @@ func (s *ECSApplicationSpec) Validate() error {

type ECSDeploymentInput struct {
// The Amazon Resource Name (ARN) that identifies the cluster.
ClusterArn string `json:"clusterArn"`
ClusterArn string `json:"clusterArn,omitempty"`
// The launch type on which to run your task.
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_types.html
// Default is FARGATE
LaunchType string `json:"launchType" default:"FARGATE"`
LaunchType string `json:"launchType,omitempty" default:"FARGATE"`
// VpcConfiguration ECSVpcConfiguration `json:"awsvpcConfiguration"`
AwsVpcConfiguration ECSVpcConfiguration `json:"awsvpcConfiguration"`
AwsVpcConfiguration ECSVpcConfiguration `json:"awsvpcConfiguration,omitempty" default:""`
// The name of service definition file placing in application directory.
ServiceDefinitionFile string `json:"serviceDefinitionFile"`
// The name of task definition file placing in application directory.
// Default is taskdef.json
TaskDefinitionFile string `json:"taskDefinitionFile" default:"taskdef.json"`
// ECSTargetGroups
TargetGroups ECSTargetGroups `json:"targetGroups"`
TargetGroups ECSTargetGroups `json:"targetGroups,omitempty"`
// Automatically reverts all changes from all stages when one of them failed.
// Default is true.
AutoRollback *bool `json:"autoRollback,omitempty" default:"true"`
// Run standalone task during deployment.
// Default is true.
RunStandaloneTask *bool `json:"runStandaloneTask" default:"true"`
RunStandaloneTask *bool `json:"runStandaloneTask,omitempty" default:"true"`
// How the ECS service is accessed.
// Possible values are:
// - ELB - The service is accessed via ELB and target groups.
// - SERVICE_DISCOVERY - The service is accessed via ECS Service Discovery.
// Default is ELB.
AccessType string `json:"accessType" default:"ELB"`
AccessType string `json:"accessType,omitempty" default:"ELB"`
}

func (in *ECSDeploymentInput) IsStandaloneTask() bool {
Expand All @@ -84,20 +84,20 @@ func (in *ECSDeploymentInput) IsAccessedViaELB() bool {
}

type ECSVpcConfiguration struct {
Subnets []string `json:"subnets"`
AssignPublicIP string `json:"assignPublicIp"`
SecurityGroups []string `json:"securityGroups"`
Subnets []string `json:"subnets,omitempty"`
AssignPublicIP string `json:"assignPublicIp,omitempty"`
SecurityGroups []string `json:"securityGroups,omitempty"`
}

type ECSTargetGroups struct {
Primary *ECSTargetGroup `json:"primary"`
Canary *ECSTargetGroup `json:"canary"`
Primary *ECSTargetGroup `json:"primary,omitempty"`
Canary *ECSTargetGroup `json:"canary,omitempty"`
}

type ECSTargetGroup struct {
TargetGroupArn string `json:"targetGroupArn"`
ContainerName string `json:"containerName"`
ContainerPort int `json:"containerPort"`
TargetGroupArn string `json:"targetGroupArn,omitempty"`
ContainerName string `json:"containerName,omitempty"`
ContainerPort int `json:"containerPort,omitempty"`
}

// ECSSyncStageOptions contains all configurable values for a ECS_SYNC stage.
Expand Down

0 comments on commit 016f930

Please sign in to comment.