Skip to content
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.18

require (
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/aws/aws-sdk-go v1.44.126
github.com/aws/aws-sdk-go v1.44.127
github.com/briandowns/spinner v1.19.0
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.44.126 h1:7HQJw2DNiwpxqMe2H7odGNT2rhO4SRrUe5/8dYXl0Jk=
github.com/aws/aws-sdk-go v1.44.126/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0=
github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
Expand Down
13 changes: 13 additions & 0 deletions internal/pkg/aws/apprunner/apprunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type api interface {
ResumeService(input *apprunner.ResumeServiceInput) (*apprunner.ResumeServiceOutput, error)
StartDeployment(input *apprunner.StartDeploymentInput) (*apprunner.StartDeploymentOutput, error)
DescribeObservabilityConfiguration(input *apprunner.DescribeObservabilityConfigurationInput) (*apprunner.DescribeObservabilityConfigurationOutput, error)
DescribeVpcIngressConnection(input *apprunner.DescribeVpcIngressConnectionInput) (*apprunner.DescribeVpcIngressConnectionOutput, error)
}

// AppRunner wraps an AWS AppRunner client.
Expand Down Expand Up @@ -213,6 +214,18 @@ func (a *AppRunner) WaitForOperation(operationId, svcARN string) error {
}
}

// PrivateURL returns the url associated with a VPC Ingress Connection.
func (a *AppRunner) PrivateURL(vicARN string) (string, error) {
resp, err := a.client.DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{
VpcIngressConnectionArn: aws.String(vicARN),
})
if err != nil {
return "", fmt.Errorf("describe vpc ingress connection %q: %w", vicARN, err)
}

return aws.StringValue(resp.VpcIngressConnection.DomainName), nil
}

// ParseServiceName returns the service name.
// For example: arn:aws:apprunner:us-west-2:1234567890:service/my-service/fc1098ac269245959ba78fd58bdd4bf
// will return my-service
Expand Down
50 changes: 50 additions & 0 deletions internal/pkg/aws/apprunner/apprunner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,56 @@ func TestAppRunner_DescribeOperation(t *testing.T) {
}
}

func TestAppRunner_PrivateURL(t *testing.T) {
const mockARN = "mockVicArn"
tests := map[string]struct {
mockAppRunnerClient func(m *mocks.Mockapi)
expectedErr string
expectedURL string
}{
"error if error from sdk": {
mockAppRunnerClient: func(m *mocks.Mockapi) {
m.EXPECT().DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{
VpcIngressConnectionArn: aws.String(mockARN),
}).Return(nil, errors.New("some error"))
},
expectedErr: `describe vpc ingress connection "mockVicArn": some error`,
},
"success": {
mockAppRunnerClient: func(m *mocks.Mockapi) {
m.EXPECT().DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{
VpcIngressConnectionArn: aws.String(mockARN),
}).Return(&apprunner.DescribeVpcIngressConnectionOutput{
VpcIngressConnection: &apprunner.VpcIngressConnection{
DomainName: aws.String("example.com"),
},
}, nil)
},
expectedURL: "example.com",
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockAppRunnerClient := mocks.NewMockapi(ctrl)
tc.mockAppRunnerClient(mockAppRunnerClient)

service := AppRunner{
client: mockAppRunnerClient,
}

url, err := service.PrivateURL(mockARN)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
}
require.Equal(t, tc.expectedURL, url)
})
}
}

func TestAppRunner_PauseService(t *testing.T) {
const (
mockOperationId = "mock-operation"
Expand Down
15 changes: 15 additions & 0 deletions internal/pkg/aws/apprunner/mocks/mock_apprunner.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions internal/pkg/cli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/aws/copilot-cli/internal/pkg/manifest"
"github.com/dustin/go-humanize/english"
)

// Long flag names.
Expand Down Expand Up @@ -64,6 +65,8 @@ const (
noSubscriptionFlag = "no-subscribe"
subscribeTopicsFlag = "subscribe-topics"

ingressTypeFlag = "ingress-type"

storageTypeFlag = "storage-type"
storagePartitionKeyFlag = "partition-key"
storageSortKeyFlag = "sort-key"
Expand Down Expand Up @@ -200,6 +203,9 @@ Mutually exclusive with the -%s ,--%s and --%s flags.`, nameFlagShort, nameFlag,

repoURLFlagDescription = fmt.Sprintf(`The repository URL to trigger your pipeline.
Supported providers are: %s.`, strings.Join(manifest.PipelineProviders, ", "))

ingressTypeFlagDescription = fmt.Sprintf(`Required for a Request-Driven Web Service. Allowed source of traffic to your service.
Must be one of %s`, english.OxfordWordSeries(rdwsIngressOptions, "or"))
)

const (
Expand Down
55 changes: 54 additions & 1 deletion internal/pkg/cli/svc_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"fmt"
"os"
"strconv"
"strings"

"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/copilot-cli/internal/pkg/aws/identity"
"github.com/dustin/go-humanize/english"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/copilot-cli/internal/pkg/docker/dockerfile"
Expand Down Expand Up @@ -48,7 +50,7 @@ const (

var (
fmtSvcInitSvcTypePrompt = "Which %s best represents your service's architecture?"
svcInitSvcTypeHelpPrompt = fmt.Sprintf(`A %s is an internet-facing HTTP server managed by AWS App Runner that scales based on incoming requests.
svcInitSvcTypeHelpPrompt = fmt.Sprintf(`A %s is an internet-facing or private HTTP server managed by AWS App Runner that scales based on incoming requests.
To learn more see: https://git.io/JEEfb

A %s is an internet-facing HTTP server managed by Amazon ECS on AWS Fargate behind a load balancer.
Expand Down Expand Up @@ -82,9 +84,23 @@ You should set this to the port which your Dockerfile uses to communicate with t
svcInitPublisherHelpPrompt = `A publisher is an existing SNS Topic to which a service publishes messages.
These messages can be consumed by the Worker Service.`

svcInitIngressTypePrompt = "Would you like to accept traffic from your environment or the internet?"
svcInitIngressTypeHelpPrompt = `"Environment" will configure your service as private.
"Internet" will configure your service as public.`

wkldInitImagePrompt = fmt.Sprintf("What's the %s ([registry/]repository[:tag|@digest]) of the image to use?", color.Emphasize("location"))
)

const (
ingressTypeEnvironment = "Environment"
ingressTypeInternet = "Internet"
)

var rdwsIngressOptions = []string{
ingressTypeEnvironment,
ingressTypeInternet,
}

var serviceTypeHints = map[string]string{
manifest.RequestDrivenWebServiceType: "App Runner",
manifest.LoadBalancedWebServiceType: "Internet to ECS on Fargate",
Expand All @@ -100,6 +116,7 @@ type initWkldVars struct {
image string
subscriptions []string
noSubscribe bool
ingressType string
}

type initSvcVars struct {
Expand Down Expand Up @@ -215,6 +232,9 @@ func (o *initSvcOpts) Validate() error {
if err := validateSubscribe(o.noSubscribe, o.subscriptions); err != nil {
return err
}
if err := o.validateIngressType(); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -253,6 +273,9 @@ func (o *initSvcOpts) Ask() error {
if err := o.validateSvc(); err != nil {
return err
}
if err := o.askIngressType(); err != nil {
return err
}
shouldSkipAsking, err := o.shouldSkipAsking()
if err != nil {
return err
Expand Down Expand Up @@ -313,6 +336,7 @@ func (o *initSvcOpts) Execute() error {
},
Port: o.port,
HealthCheck: hc,
Private: strings.EqualFold(o.ingressType, ingressTypeEnvironment),
})
if err != nil {
return err
Expand Down Expand Up @@ -389,6 +413,34 @@ func (o *initSvcOpts) askSvcName() error {
return nil
}

func (o *initSvcOpts) askIngressType() error {
if o.wkldType != manifest.RequestDrivenWebServiceType || o.ingressType != "" {
return nil
}

var opts []prompt.Option
for _, typ := range rdwsIngressOptions {
opts = append(opts, prompt.Option{Value: typ})
}

t, err := o.prompt.SelectOption(svcInitIngressTypePrompt, svcInitIngressTypeHelpPrompt, opts, prompt.WithFinalMessage("Reachable from:"))
if err != nil {
return fmt.Errorf("select ingress type: %w", err)
}
o.ingressType = t
return nil
}

func (o *initSvcOpts) validateIngressType() error {
if o.wkldType != manifest.RequestDrivenWebServiceType {
return nil
}
if strings.EqualFold(o.ingressType, "internet") || strings.EqualFold(o.ingressType, "environment") {
return nil
}
return fmt.Errorf("invalid ingress type %q: must be one of %s", o.ingressType, english.OxfordWordSeries(rdwsIngressOptions, "or"))
}

func (o *initSvcOpts) askImage() error {
if o.image != "" {
return nil
Expand Down Expand Up @@ -712,6 +764,7 @@ This command is also run as part of "copilot init".`,
cmd.Flags().Uint16Var(&vars.port, svcPortFlag, 0, svcPortFlagDescription)
cmd.Flags().StringArrayVar(&vars.subscriptions, subscribeTopicsFlag, []string{}, subscribeTopicsFlagDescription)
cmd.Flags().BoolVar(&vars.noSubscribe, noSubscriptionFlag, false, noSubscriptionFlagDescription)
cmd.Flags().StringVar(&vars.ingressType, ingressTypeFlag, "", ingressTypeFlagDescription)

return cmd
}
Loading