mirror of
https://github.com/docker/compose.git
synced 2025-07-23 13:45:00 +02:00
Create CloudFormation template with parameters
so we don't need AWS API to resolve IDs and can run conversion offline Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
69a7ef0763
commit
1fdac494f3
@ -49,7 +49,7 @@ func ConvertCommand(dockerCli command.Cli, projectOpts *compose.ProjectOptions)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
template, err := client.Convert(context.Background(), project)
|
template, err := client.Convert(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -305,6 +305,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
||||||
|
@ -5,6 +5,5 @@ package amazon
|
|||||||
type API interface {
|
type API interface {
|
||||||
downAPI
|
downAPI
|
||||||
upAPI
|
upAPI
|
||||||
convertAPI
|
|
||||||
secretsAPI
|
secretsAPI
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package amazon
|
package amazon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -19,20 +17,31 @@ import (
|
|||||||
"github.com/docker/ecs-plugin/pkg/compose"
|
"github.com/docker/ecs-plugin/pkg/compose"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudformation.Template, error) {
|
func (c client) Convert(project *compose.Project) (*cloudformation.Template, error) {
|
||||||
template := cloudformation.NewTemplate()
|
template := cloudformation.NewTemplate()
|
||||||
vpc, err := c.GetVPC(ctx, project)
|
template.Parameters["VPCId"] = cloudformation.Parameter{
|
||||||
if err != nil {
|
Type: "AWS::EC2::VPC::Id",
|
||||||
return nil, err
|
Description: "ID of the VPC",
|
||||||
}
|
}
|
||||||
|
|
||||||
subnets, err := c.api.GetSubNets(ctx, vpc)
|
/*
|
||||||
if err != nil {
|
FIXME can't set subnets: Ref("SubnetIds") see https://github.com/awslabs/goformation/issues/282
|
||||||
return nil, err
|
template.Parameters["SubnetIds"] = cloudformation.Parameter{
|
||||||
|
Type: "List<AWS::EC2::Subnet::Id>",
|
||||||
|
Description: "The list of SubnetIds, for at least two Availability Zones in the region in your VPC",
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
template.Parameters["Subnet1Id"] = cloudformation.Parameter{
|
||||||
|
Type: "AWS::EC2::Subnet::Id",
|
||||||
|
Description: "SubnetId,for Availability Zone 1 in the region in your VPC",
|
||||||
|
}
|
||||||
|
template.Parameters["Subnet2Id"] = cloudformation.Parameter{
|
||||||
|
Type: "AWS::EC2::Subnet::Id",
|
||||||
|
Description: "SubnetId,for Availability Zone 1 in the region in your VPC",
|
||||||
}
|
}
|
||||||
|
|
||||||
for net := range project.Networks {
|
for net := range project.Networks {
|
||||||
name, resource := convertNetwork(project, net, vpc)
|
name, resource := convertNetwork(project, net, cloudformation.Ref("VPCId"))
|
||||||
template.Resources[name] = resource
|
template.Resources[name] = resource
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +54,7 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
|
|||||||
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
||||||
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
||||||
Name: fmt.Sprintf("%s.local", project.Name),
|
Name: fmt.Sprintf("%s.local", project.Name),
|
||||||
Vpc: vpc,
|
Vpc: cloudformation.Ref("VPCId"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
@ -55,7 +64,7 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name)
|
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name)
|
||||||
policy, err := c.getPolicy(ctx, definition)
|
policy, err := c.getPolicy(definition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -115,7 +124,10 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
|
|||||||
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
||||||
AssignPublicIp: ecsapi.AssignPublicIpEnabled,
|
AssignPublicIp: ecsapi.AssignPublicIpEnabled,
|
||||||
SecurityGroups: serviceSecurityGroups,
|
SecurityGroups: serviceSecurityGroups,
|
||||||
Subnets: subnets,
|
Subnets: []string{
|
||||||
|
cloudformation.Ref("Subnet1Id"),
|
||||||
|
cloudformation.Ref("Subnet2Id"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
|
SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
|
||||||
@ -171,29 +183,7 @@ func networkResourceName(project *compose.Project, network string) string {
|
|||||||
return fmt.Sprintf("%s%sNetwork", project.Name, strings.Title(network))
|
return fmt.Sprintf("%s%sNetwork", project.Name, strings.Title(network))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, error) {
|
func (c client) getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
|
||||||
//check compose file for the default external network
|
|
||||||
if net, ok := project.Networks["default"]; ok {
|
|
||||||
if net.External.External {
|
|
||||||
vpc := net.Name
|
|
||||||
ok, err := c.api.VpcExists(ctx, vpc)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return "", errors.New("Vpc does not exist: " + vpc)
|
|
||||||
}
|
|
||||||
return vpc, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultVPC, err := c.api.GetDefaultVPC(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return defaultVPC, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c client) getPolicy(ctx context.Context, taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
|
|
||||||
|
|
||||||
arns := []string{}
|
arns := []string{}
|
||||||
for _, container := range taskDef.ContainerDefinitions {
|
for _, container := range taskDef.ContainerDefinitions {
|
||||||
@ -212,17 +202,10 @@ func (c client) getPolicy(ctx context.Context, taskDef *ecs.TaskDefinition) (*Po
|
|||||||
Statement: []PolicyStatement{
|
Statement: []PolicyStatement{
|
||||||
{
|
{
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
Action: []string{"secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt"},
|
Action: []string{ActionGetSecretValue, ActionGetParameters, ActionDecrypt},
|
||||||
Resource: arns,
|
Resource: arns,
|
||||||
}},
|
}},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type convertAPI interface {
|
|
||||||
GetDefaultVPC(ctx context.Context) (string, error)
|
|
||||||
VpcExists(ctx context.Context, vpcID string) (bool, error)
|
|
||||||
GetSubNets(ctx context.Context, vpcID string) ([]string, error)
|
|
||||||
GetRoleArn(ctx context.Context, name string) (string, error)
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package amazon
|
package amazon
|
||||||
|
|
||||||
const ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
const (
|
||||||
|
ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
||||||
|
|
||||||
|
ActionGetSecretValue = "secretsmanager:GetSecretValue"
|
||||||
|
ActionGetParameters = "ssm:GetParameters"
|
||||||
|
ActionDecrypt = "kms:Decrypt"
|
||||||
|
)
|
||||||
|
|
||||||
var assumeRolePolicyDocument = PolicyDocument{
|
var assumeRolePolicyDocument = PolicyDocument{
|
||||||
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
||||||
|
@ -6,12 +6,11 @@ package mock
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
|
cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
|
||||||
cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
|
cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
|
||||||
docker "github.com/docker/ecs-plugin/pkg/docker"
|
docker "github.com/docker/ecs-plugin/pkg/docker"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAPI is a mock of API interface
|
// MockAPI is a mock of API interface
|
||||||
@ -77,23 +76,23 @@ func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 docker.Secret) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateSecret indicates an expected call of CreateSecret
|
// CreateSecret indicates an expected call of CreateSecret
|
||||||
func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 docker.Secret) *gomock.Call {
|
func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStack mocks base method
|
// CreateStack mocks base method
|
||||||
func (m *MockAPI) CreateStack(arg0 context.Context, arg1 string, arg2 *cloudformation0.Template) error {
|
func (m *MockAPI) CreateStack(arg0 context.Context, arg1 string, arg2 *cloudformation0.Template, arg3 map[string]string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2, arg3)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStack indicates an expected call of CreateStack
|
// CreateStack indicates an expected call of CreateStack
|
||||||
func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCluster mocks base method
|
// DeleteCluster mocks base method
|
||||||
@ -168,21 +167,6 @@ func (mr *MockAPIMockRecorder) GetDefaultVPC(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultVPC", reflect.TypeOf((*MockAPI)(nil).GetDefaultVPC), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultVPC", reflect.TypeOf((*MockAPI)(nil).GetDefaultVPC), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoleArn mocks base method
|
|
||||||
func (m *MockAPI) GetRoleArn(arg0 context.Context, arg1 string) (string, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetRoleArn", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoleArn indicates an expected call of GetRoleArn
|
|
||||||
func (mr *MockAPIMockRecorder) GetRoleArn(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoleArn", reflect.TypeOf((*MockAPI)(nil).GetRoleArn), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStackID mocks base method
|
// GetStackID mocks base method
|
||||||
func (m *MockAPI) GetStackID(arg0 context.Context, arg1 string) (string, error) {
|
func (m *MockAPI) GetStackID(arg0 context.Context, arg1 string) (string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -153,17 +153,27 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
|
|||||||
return len(stacks.Stacks) > 0, nil
|
return len(stacks.Stacks) > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template) error {
|
func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template, parameters map[string]string) error {
|
||||||
logrus.Debug("Create CloudFormation stack")
|
logrus.Debug("Create CloudFormation stack")
|
||||||
json, err := template.JSON()
|
json, err := template.JSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
param := []*cloudformation.Parameter{}
|
||||||
|
for name, value := range parameters {
|
||||||
|
param = append(param, &cloudformation.Parameter{
|
||||||
|
ParameterKey: aws.String(name),
|
||||||
|
ParameterValue: aws.String(value),
|
||||||
|
UsePreviousValue: aws.Bool(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
_, err = s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
|
_, err = s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
|
||||||
OnFailure: aws.String("DELETE"),
|
OnFailure: aws.String("DELETE"),
|
||||||
StackName: aws.String(name),
|
StackName: aws.String(name),
|
||||||
TemplateBody: aws.String(string(json)),
|
TemplateBody: aws.String(string(json)),
|
||||||
|
Parameters: param,
|
||||||
TimeoutInMinutes: aws.Int64(10),
|
TimeoutInMinutes: aws.Int64(10),
|
||||||
Capabilities: []*string{
|
Capabilities: []*string{
|
||||||
aws.String(cloudformation.CapabilityCapabilityIam),
|
aws.String(cloudformation.CapabilityCapabilityIam),
|
||||||
|
@ -29,12 +29,28 @@ func (c *client) ComposeUp(ctx context.Context, project *compose.Project) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
template, err := c.Convert(ctx, project)
|
template, err := c.Convert(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.api.CreateStack(ctx, project.Name, template)
|
vpc, err := c.GetVPC(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subNets, err := c.api.GetSubNets(ctx, vpc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := map[string]string{
|
||||||
|
"VPCId": vpc,
|
||||||
|
"Subnet1Id": subNets[0],
|
||||||
|
"Subnet2Id": subNets[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.api.CreateStack(ctx, project.Name, template, parameters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -42,10 +58,36 @@ func (c *client) ComposeUp(ctx context.Context, project *compose.Project) error
|
|||||||
return c.WaitStackCompletion(ctx, project.Name, StackCreate)
|
return c.WaitStackCompletion(ctx, project.Name, StackCreate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, error) {
|
||||||
|
//check compose file for the default external network
|
||||||
|
if net, ok := project.Networks["default"]; ok {
|
||||||
|
if net.External.External {
|
||||||
|
vpc := net.Name
|
||||||
|
ok, err := c.api.VpcExists(ctx, vpc)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("VPC does not exist: %s", vpc)
|
||||||
|
}
|
||||||
|
return vpc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultVPC, err := c.api.GetDefaultVPC(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return defaultVPC, nil
|
||||||
|
}
|
||||||
|
|
||||||
type upAPI interface {
|
type upAPI interface {
|
||||||
waitAPI
|
waitAPI
|
||||||
|
GetDefaultVPC(ctx context.Context) (string, error)
|
||||||
|
VpcExists(ctx context.Context, vpcID string) (bool, error)
|
||||||
|
GetSubNets(ctx context.Context, vpcID string) ([]string, error)
|
||||||
|
|
||||||
ClusterExists(ctx context.Context, name string) (bool, error)
|
ClusterExists(ctx context.Context, name string) (bool, error)
|
||||||
CreateCluster(ctx context.Context, name string) (string, error)
|
CreateCluster(ctx context.Context, name string) (string, error)
|
||||||
StackExists(ctx context.Context, name string) (bool, error)
|
StackExists(ctx context.Context, name string) (bool, error)
|
||||||
CreateStack(ctx context.Context, name string, template *cloudformation.Template) error
|
CreateStack(ctx context.Context, name string, template *cloudformation.Template, parameters map[string]string) error
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type API interface {
|
type API interface {
|
||||||
Convert(ctx context.Context, project *Project) (*cloudformation.Template, error)
|
Convert(project *Project) (*cloudformation.Template, error)
|
||||||
ComposeUp(ctx context.Context, project *Project) error
|
ComposeUp(ctx context.Context, project *Project) error
|
||||||
ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error
|
ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user