Repository: aws/amazon-ecs-cli-v2
Pull request #360 (2019-11-15 05:45:33)

Pipeline update
<!-- Provide summary of changes -->

This PR DOES:
- deploy the pipeline.yml

this PR DOES NOT:
- deploy a pipeline that actually works

Fast follow: Ensure role name does not exceed 64 characters, but submitting this to get early feedback. 

<!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" -->

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.


kohidave: This is so rad. Thank you so much!

Patch:
diff --git a/internal/pkg/cli/deploy.go b/internal/pkg/cli/deploy.go
index c6a428af..8bc0087f 100644
--- a/internal/pkg/cli/deploy.go
+++ b/internal/pkg/cli/deploy.go
@@ -8,11 +8,19 @@ import (
 	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/deploy"
 )
 
+// Interfaces for deploying resources through CloudFormation. Facilitates mocking.
 type environmentDeployer interface {
 	DeployEnvironment(env *deploy.CreateEnvironmentInput) error
 	StreamEnvironmentCreation(env *deploy.CreateEnvironmentInput) (<-chan []deploy.ResourceEvent, <-chan deploy.CreateEnvironmentResponse)
 }
 
+type pipelineDeployer interface {
+	DeployPipeline(env *deploy.CreatePipelineInput) error
+	AddPipelineResourcesToProject(project *archer.Project, region string) error
+	projectResourcesGetter
+	// TODO: Add StreamPipelineCreation method
+}
+
 type projectDeployer interface {
 	DeployProject(in *deploy.CreateProjectInput) error
 	AddAppToProject(project *archer.Project, appName string) error
@@ -27,4 +35,5 @@ type projectResourcesGetter interface {
 type deployer interface {
 	environmentDeployer
 	projectDeployer
+	pipelineDeployer
 }
diff --git a/internal/pkg/cli/flag.go b/internal/pkg/cli/flag.go
index 9d4cf334..237405da 100644
--- a/internal/pkg/cli/flag.go
+++ b/internal/pkg/cli/flag.go
@@ -24,6 +24,7 @@ const (
 	enableCDFlag          = "enable-cd"
 	envsFlag              = "environments"
 	domainNameFlag        = "domain"
+	pipelineFileFlag      = "file"
 )
 
 // Short flag names.
@@ -39,6 +40,7 @@ const (
 	githubRepoFlagShort        = "r"
 	githubAccessTokenFlagShort = "t"
 	envsFlagShort              = "e"
+	pipelineFileFlagShort      = "f"
 )
 
 // Descriptions for flags.
@@ -60,4 +62,6 @@ const (
 	enableCDFlagDescription          = "Enables automatic deployment to production environment."
 	pipelineEnvsFlagDescription      = "Environments to add to the pipeline."
 	domainNameFlagDescription        = "Optional. Your existing custom domain name."
+	pipelineFileFlagDescription      = "Name of YAML file used to update the pipeline."
+	deployFlagDescription            = "Trigger a deployment of your application(s) to any new stage in your pipeline."
 )
diff --git a/internal/pkg/cli/mocks/mock_deploy.go b/internal/pkg/cli/mocks/mock_deploy.go
index 5c430f64..6e72ad5b 100644
--- a/internal/pkg/cli/mocks/mock_deploy.go
+++ b/internal/pkg/cli/mocks/mock_deploy.go
@@ -36,7 +36,6 @@ func (m *MockenvironmentDeployer) EXPECT() *MockenvironmentDeployerMockRecorder
 
 // DeployEnvironment mocks base method
 func (m *MockenvironmentDeployer) DeployEnvironment(env *deploy.CreateEnvironmentInput) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeployEnvironment", env)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -44,13 +43,11 @@ func (m *MockenvironmentDeployer) DeployEnvironment(env *deploy.CreateEnvironmen
 
 // DeployEnvironment indicates an expected call of DeployEnvironment
 func (mr *MockenvironmentDeployerMockRecorder) DeployEnvironment(env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployEnvironment", reflect.TypeOf((*MockenvironmentDeployer)(nil).DeployEnvironment), env)
 }
 
 // StreamEnvironmentCreation mocks base method
 func (m *MockenvironmentDeployer) StreamEnvironmentCreation(env *deploy.CreateEnvironmentInput) (<-chan []deploy.ResourceEvent, <-chan deploy.CreateEnvironmentResponse) {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "StreamEnvironmentCreation", env)
 	ret0, _ := ret[0].(<-chan []deploy.ResourceEvent)
 	ret1, _ := ret[1].(<-chan deploy.CreateEnvironmentResponse)
@@ -59,10 +56,82 @@ func (m *MockenvironmentDeployer) StreamEnvironmentCreation(env *deploy.CreateEn
 
 // StreamEnvironmentCreation indicates an expected call of StreamEnvironmentCreation
 func (mr *MockenvironmentDeployerMockRecorder) StreamEnvironmentCreation(env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamEnvironmentCreation", reflect.TypeOf((*MockenvironmentDeployer)(nil).StreamEnvironmentCreation), env)
 }
 
+// MockpipelineDeployer is a mock of pipelineDeployer interface
+type MockpipelineDeployer struct {
+	ctrl     *gomock.Controller
+	recorder *MockpipelineDeployerMockRecorder
+}
+
+// MockpipelineDeployerMockRecorder is the mock recorder for MockpipelineDeployer
+type MockpipelineDeployerMockRecorder struct {
+	mock *MockpipelineDeployer
+}
+
+// NewMockpipelineDeployer creates a new mock instance
+func NewMockpipelineDeployer(ctrl *gomock.Controller) *MockpipelineDeployer {
+	mock := &MockpipelineDeployer{ctrl: ctrl}
+	mock.recorder = &MockpipelineDeployerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockpipelineDeployer) EXPECT() *MockpipelineDeployerMockRecorder {
+	return m.recorder
+}
+
+// DeployPipeline mocks base method
+func (m *MockpipelineDeployer) DeployPipeline(env *deploy.CreatePipelineInput) error {
+	ret := m.ctrl.Call(m, "DeployPipeline", env)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeployPipeline indicates an expected call of DeployPipeline
+func (mr *MockpipelineDeployerMockRecorder) DeployPipeline(env interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployPipeline", reflect.TypeOf((*MockpipelineDeployer)(nil).DeployPipeline), env)
+}
+
+// AddPipelineResourcesToProject mocks base method
+func (m *MockpipelineDeployer) AddPipelineResourcesToProject(project *archer.Project, region string) error {
+	ret := m.ctrl.Call(m, "AddPipelineResourcesToProject", project, region)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AddPipelineResourcesToProject indicates an expected call of AddPipelineResourcesToProject
+func (mr *MockpipelineDeployerMockRecorder) AddPipelineResourcesToProject(project, region interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPipelineResourcesToProject", reflect.TypeOf((*MockpipelineDeployer)(nil).AddPipelineResourcesToProject), project, region)
+}
+
+// GetProjectResourcesByRegion mocks base method
+func (m *MockpipelineDeployer) GetProjectResourcesByRegion(project *archer.Project, region string) (*archer.ProjectRegionalResources, error) {
+	ret := m.ctrl.Call(m, "GetProjectResourcesByRegion", project, region)
+	ret0, _ := ret[0].(*archer.ProjectRegionalResources)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetProjectResourcesByRegion indicates an expected call of GetProjectResourcesByRegion
+func (mr *MockpipelineDeployerMockRecorder) GetProjectResourcesByRegion(project, region interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectResourcesByRegion", reflect.TypeOf((*MockpipelineDeployer)(nil).GetProjectResourcesByRegion), project, region)
+}
+
+// GetRegionalProjectResources mocks base method
+func (m *MockpipelineDeployer) GetRegionalProjectResources(project *archer.Project) ([]*archer.ProjectRegionalResources, error) {
+	ret := m.ctrl.Call(m, "GetRegionalProjectResources", project)
+	ret0, _ := ret[0].([]*archer.ProjectRegionalResources)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetRegionalProjectResources indicates an expected call of GetRegionalProjectResources
+func (mr *MockpipelineDeployerMockRecorder) GetRegionalProjectResources(project interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRegionalProjectResources", reflect.TypeOf((*MockpipelineDeployer)(nil).GetRegionalProjectResources), project)
+}
+
 // MockprojectDeployer is a mock of projectDeployer interface
 type MockprojectDeployer struct {
 	ctrl     *gomock.Controller
@@ -88,7 +157,6 @@ func (m *MockprojectDeployer) EXPECT() *MockprojectDeployerMockRecorder {
 
 // DeployProject mocks base method
 func (m *MockprojectDeployer) DeployProject(in *deploy.CreateProjectInput) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeployProject", in)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -96,13 +164,11 @@ func (m *MockprojectDeployer) DeployProject(in *deploy.CreateProjectInput) error
 
 // DeployProject indicates an expected call of DeployProject
 func (mr *MockprojectDeployerMockRecorder) DeployProject(in interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployProject", reflect.TypeOf((*MockprojectDeployer)(nil).DeployProject), in)
 }
 
 // AddAppToProject mocks base method
 func (m *MockprojectDeployer) AddAppToProject(project *archer.Project, appName string) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "AddAppToProject", project, appName)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -110,13 +176,11 @@ func (m *MockprojectDeployer) AddAppToProject(project *archer.Project, appName s
 
 // AddAppToProject indicates an expected call of AddAppToProject
 func (mr *MockprojectDeployerMockRecorder) AddAppToProject(project, appName interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAppToProject", reflect.TypeOf((*MockprojectDeployer)(nil).AddAppToProject), project, appName)
 }
 
 // AddEnvToProject mocks base method
 func (m *MockprojectDeployer) AddEnvToProject(project *archer.Project, env *archer.Environment) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "AddEnvToProject", project, env)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -124,7 +188,6 @@ func (m *MockprojectDeployer) AddEnvToProject(project *archer.Project, env *arch
 
 // AddEnvToProject indicates an expected call of AddEnvToProject
 func (mr *MockprojectDeployerMockRecorder) AddEnvToProject(project, env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEnvToProject", reflect.TypeOf((*MockprojectDeployer)(nil).AddEnvToProject), project, env)
 }
 
@@ -153,7 +216,6 @@ func (m *MockprojectResourcesGetter) EXPECT() *MockprojectResourcesGetterMockRec
 
 // GetProjectResourcesByRegion mocks base method
 func (m *MockprojectResourcesGetter) GetProjectResourcesByRegion(project *archer.Project, region string) (*archer.ProjectRegionalResources, error) {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetProjectResourcesByRegion", project, region)
 	ret0, _ := ret[0].(*archer.ProjectRegionalResources)
 	ret1, _ := ret[1].(error)
@@ -162,13 +224,11 @@ func (m *MockprojectResourcesGetter) GetProjectResourcesByRegion(project *archer
 
 // GetProjectResourcesByRegion indicates an expected call of GetProjectResourcesByRegion
 func (mr *MockprojectResourcesGetterMockRecorder) GetProjectResourcesByRegion(project, region interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectResourcesByRegion", reflect.TypeOf((*MockprojectResourcesGetter)(nil).GetProjectResourcesByRegion), project, region)
 }
 
 // GetRegionalProjectResources mocks base method
 func (m *MockprojectResourcesGetter) GetRegionalProjectResources(project *archer.Project) ([]*archer.ProjectRegionalResources, error) {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetRegionalProjectResources", project)
 	ret0, _ := ret[0].([]*archer.ProjectRegionalResources)
 	ret1, _ := ret[1].(error)
@@ -177,7 +237,6 @@ func (m *MockprojectResourcesGetter) GetRegionalProjectResources(project *archer
 
 // GetRegionalProjectResources indicates an expected call of GetRegionalProjectResources
 func (mr *MockprojectResourcesGetterMockRecorder) GetRegionalProjectResources(project interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRegionalProjectResources", reflect.TypeOf((*MockprojectResourcesGetter)(nil).GetRegionalProjectResources), project)
 }
 
@@ -206,7 +265,6 @@ func (m *Mockdeployer) EXPECT() *MockdeployerMockRecorder {
 
 // DeployEnvironment mocks base method
 func (m *Mockdeployer) DeployEnvironment(env *deploy.CreateEnvironmentInput) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeployEnvironment", env)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -214,13 +272,11 @@ func (m *Mockdeployer) DeployEnvironment(env *deploy.CreateEnvironmentInput) err
 
 // DeployEnvironment indicates an expected call of DeployEnvironment
 func (mr *MockdeployerMockRecorder) DeployEnvironment(env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployEnvironment", reflect.TypeOf((*Mockdeployer)(nil).DeployEnvironment), env)
 }
 
 // StreamEnvironmentCreation mocks base method
 func (m *Mockdeployer) StreamEnvironmentCreation(env *deploy.CreateEnvironmentInput) (<-chan []deploy.ResourceEvent, <-chan deploy.CreateEnvironmentResponse) {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "StreamEnvironmentCreation", env)
 	ret0, _ := ret[0].(<-chan []deploy.ResourceEvent)
 	ret1, _ := ret[1].(<-chan deploy.CreateEnvironmentResponse)
@@ -229,13 +285,11 @@ func (m *Mockdeployer) StreamEnvironmentCreation(env *deploy.CreateEnvironmentIn
 
 // StreamEnvironmentCreation indicates an expected call of StreamEnvironmentCreation
 func (mr *MockdeployerMockRecorder) StreamEnvironmentCreation(env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamEnvironmentCreation", reflect.TypeOf((*Mockdeployer)(nil).StreamEnvironmentCreation), env)
 }
 
 // DeployProject mocks base method
 func (m *Mockdeployer) DeployProject(in *deploy.CreateProjectInput) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeployProject", in)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -243,13 +297,11 @@ func (m *Mockdeployer) DeployProject(in *deploy.CreateProjectInput) error {
 
 // DeployProject indicates an expected call of DeployProject
 func (mr *MockdeployerMockRecorder) DeployProject(in interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployProject", reflect.TypeOf((*Mockdeployer)(nil).DeployProject), in)
 }
 
 // AddAppToProject mocks base method
 func (m *Mockdeployer) AddAppToProject(project *archer.Project, appName string) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "AddAppToProject", project, appName)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -257,13 +309,11 @@ func (m *Mockdeployer) AddAppToProject(project *archer.Project, appName string)
 
 // AddAppToProject indicates an expected call of AddAppToProject
 func (mr *MockdeployerMockRecorder) AddAppToProject(project, appName interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAppToProject", reflect.TypeOf((*Mockdeployer)(nil).AddAppToProject), project, appName)
 }
 
 // AddEnvToProject mocks base method
 func (m *Mockdeployer) AddEnvToProject(project *archer.Project, env *archer.Environment) error {
-	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "AddEnvToProject", project, env)
 	ret0, _ := ret[0].(error)
 	return ret0
@@ -271,6 +321,55 @@ func (m *Mockdeployer) AddEnvToProject(project *archer.Project, env *archer.Envi
 
 // AddEnvToProject indicates an expected call of AddEnvToProject
 func (mr *MockdeployerMockRecorder) AddEnvToProject(project, env interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEnvToProject", reflect.TypeOf((*Mockdeployer)(nil).AddEnvToProject), project, env)
 }
+
+// DeployPipeline mocks base method
+func (m *Mockdeployer) DeployPipeline(env *deploy.CreatePipelineInput) error {
+	ret := m.ctrl.Call(m, "DeployPipeline", env)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeployPipeline indicates an expected call of DeployPipeline
+func (mr *MockdeployerMockRecorder) DeployPipeline(env interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeployPipeline", reflect.TypeOf((*Mockdeployer)(nil).DeployPipeline), env)
+}
+
+// AddPipelineResourcesToProject mocks base method
+func (m *Mockdeployer) AddPipelineResourcesToProject(project *archer.Project, region string) error {
+	ret := m.ctrl.Call(m, "AddPipelineResourcesToProject", project, region)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AddPipelineResourcesToProject indicates an expected call of AddPipelineResourcesToProject
+func (mr *MockdeployerMockRecorder) AddPipelineResourcesToProject(project, region interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPipelineResourcesToProject", reflect.TypeOf((*Mockdeployer)(nil).AddPipelineResourcesToProject), project, region)
+}
+
+// GetProjectResourcesByRegion mocks base method
+func (m *Mockdeployer) GetProjectResourcesByRegion(project *archer.Project, region string) (*archer.ProjectRegionalResources, error) {
+	ret := m.ctrl.Call(m, "GetProjectResourcesByRegion", project, region)
+	ret0, _ := ret[0].(*archer.ProjectRegionalResources)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetProjectResourcesByRegion indicates an expected call of GetProjectResourcesByRegion
+func (mr *MockdeployerMockRecorder) GetProjectResourcesByRegion(project, region interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectResourcesByRegion", reflect.TypeOf((*Mockdeployer)(nil).GetProjectResourcesByRegion), project, region)
+}
+
+// GetRegionalProjectResources mocks base method
+func (m *Mockdeployer) GetRegionalProjectResources(project *archer.Project) ([]*archer.ProjectRegionalResources, error) {
+	ret := m.ctrl.Call(m, "GetRegionalProjectResources", project)
+	ret0, _ := ret[0].([]*archer.ProjectRegionalResources)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetRegionalProjectResources indicates an expected call of GetRegionalProjectResources
+func (mr *MockdeployerMockRecorder) GetRegionalProjectResources(project interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRegionalProjectResources", reflect.TypeOf((*Mockdeployer)(nil).GetRegionalProjectResources), project)
+}
diff --git a/internal/pkg/cli/pipeline.go b/internal/pkg/cli/pipeline.go
index 4ddc3be6..54fd829c 100644
--- a/internal/pkg/cli/pipeline.go
+++ b/internal/pkg/cli/pipeline.go
@@ -21,6 +21,7 @@ func BuildPipelineCmd() *cobra.Command {
 	}
 
 	cmd.AddCommand(BuildPipelineInitCmd())
+	cmd.AddCommand(BuildPipelineUpdateCmd())
 
 	cmd.SetUsageTemplate(template.Usage)
 	cmd.Annotations = map[string]string{
diff --git a/internal/pkg/cli/pipeline_init.go b/internal/pkg/cli/pipeline_init.go
index 165caf57..050891f2 100644
--- a/internal/pkg/cli/pipeline_init.go
+++ b/internal/pkg/cli/pipeline_init.go
@@ -326,7 +326,7 @@ func relPath(fullPath string) string {
 func (opts *InitPipelineOpts) selectGitHubRepo() error {
 	repo, err := opts.prompt.Get(
 		pipelineEnterGitHubRepoPrompt,
-		fmt.Sprintf(`The GitHub repository linked to your workspace. Pushing to this repository will trigger your pipeline build stage. Please enter full repository URL, e.g. https://github.com/myCompany/myRepo`),
+		fmt.Sprintf(`The GitHub repository linked to your workspace. Pushing to this repository will trigger your pipeline build stage. Please enter full repository URL, e.g. "https://github.com/myCompany/myRepo", or the owner/rep, e.g. "myCompany/myRepo"`),
 		validateGitHubRepo,
 	)
 
diff --git a/internal/pkg/cli/pipeline_update.go b/internal/pkg/cli/pipeline_update.go
new file mode 100644
index 00000000..6723a13d
--- /dev/null
+++ b/internal/pkg/cli/pipeline_update.go
@@ -0,0 +1,245 @@
+// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package cli
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/archer"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/aws/identity"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/aws/session"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/deploy"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/deploy/cloudformation"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/manifest"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/store"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/term/color"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/term/log"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/workspace"
+
+	"github.com/aws/aws-sdk-go/aws"
+
+	termprogress "github.com/aws/amazon-ecs-cli-v2/internal/pkg/term/progress"
+	"github.com/spf13/cobra"
+)
+
+const (
+	fmtAddPipelineResourcesStart    = "Adding pipeline resources to your project: %s"
+	fmtAddPipelineResourcesComplete = "Successfully added pipeline resources to your project: %s"
+
+	fmtUpdatePipelineFailed   = "Failed to accept changes for pipeline: %s."
+	fmtUpdatePipelineStart    = "Proposing infrastructure changes for the pipeline: %s"
+	fmtUpdatePipelineComplete = "Successfully updated pipeline: %s"
+)
+
+var errNoPipelineFile = errors.New("There was no pipeline manifest found in your workspace. Please run `archer pipeline init` to create an pipeline.")
+
+// UpdatePipelineOpts holds the configuration needed to create or update a pipeline
+type UpdatePipelineOpts struct {
+	// Fields with matching flags.
+	PipelineFile string
+	// Deploy bool
+
+	pipelineDeployer pipelineDeployer
+	project          *archer.Project
+	prog             progress
+	region           string
+	envStore         archer.EnvironmentStore
+	account          string
+	ws               archer.Workspace
+
+	*GlobalOpts
+}
+
+func NewUpdatePipelineOpts() *UpdatePipelineOpts {
+	return &UpdatePipelineOpts{
+		GlobalOpts: NewGlobalOpts(),
+		prog:       termprogress.NewSpinner(),
+	}
+}
+
+// func (opts *UpdatePipelineOpts) Ask() error {
+// 	if opts.PipelineFile() == "" {
+// 		return errNoPipelineFile
+// 	}
+
+// 	return nil
+// }
+
+// Validate returns an error if the flag values passed by the user are invalid.
+func (opts *UpdatePipelineOpts) Validate() error {
+	if opts.PipelineFile == "" {
+		return errNoPipelineFile
+	}
+
+	return nil
+}
+
+func (opts *UpdatePipelineOpts) convertStages(manifestStages []manifest.PipelineStage) ([]deploy.PipelineStage, error) {
+	var stages []deploy.PipelineStage
+	apps, err := opts.ws.AppNames()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, stage := range manifestStages {
+		env, err := opts.envStore.GetEnvironment(opts.project.Name, stage.Name)
+		if err != nil {
+			return nil, err
+		}
+
+		pipelineStage := deploy.PipelineStage{
+			LocalApplications: apps,
+			AssociatedEnvironment: &deploy.AssociatedEnvironment{
+				Name:      stage.Name,
+				Region:    env.Region,
+				AccountID: env.AccountID,
+				Prod:      env.Prod,
+			},
+		}
+		stages = append(stages, pipelineStage)
+	}
+
+	return stages, nil
+}
+
+func (opts *UpdatePipelineOpts) getArtifactBuckets() ([]deploy.ArtifactBucket, error) {
+	regionalResources, err := opts.pipelineDeployer.GetRegionalProjectResources(opts.project)
+	if err != nil {
+		return nil, err
+	}
+
+	var buckets []deploy.ArtifactBucket
+	for _, resource := range regionalResources {
+		bucket := deploy.ArtifactBucket{
+			BucketName: resource.S3Bucket,
+			KeyArn:     resource.KMSKeyARN,
+		}
+		buckets = append(buckets, bucket)
+	}
+
+	return buckets, nil
+}
+
+func (opts *UpdatePipelineOpts) Execute() error {
+	// bootstrap pipeline resources
+	opts.prog.Start(fmt.Sprintf(fmtAddPipelineResourcesStart, color.HighlightUserInput(opts.project.Name)))
+	err := opts.pipelineDeployer.AddPipelineResourcesToProject(opts.project, opts.region)
+	if err != nil {
+		return nil
+	}
+	opts.prog.Stop(log.Ssuccessf(fmtAddPipelineResourcesComplete, color.HighlightUserInput(opts.project.Name)))
+
+	// read pipeline manifest
+	data, err := opts.ws.ReadFile(workspace.PipelineFileName)
+	if err != nil {
+		return nil
+	}
+	pipeline, err := manifest.UnmarshalPipeline(data)
+	// TODO nil check source?
+	source := &deploy.Source{
+		ProviderName: pipeline.Source.ProviderName,
+		Properties:   pipeline.Source.Properties,
+	}
+
+	// convert environments to deployment stages
+	stages, err := opts.convertStages(pipeline.Stages)
+	if err != nil {
+		return nil
+	}
+
+	// get cross-regional resources
+	artifactBuckets, err := opts.getArtifactBuckets()
+	if err != nil {
+		return nil
+	}
+
+	deployPipelineInput := &deploy.CreatePipelineInput{
+		ProjectName:     opts.ProjectName(),
+		Name:            pipeline.Name,
+		Source:          source,
+		Stages:          stages,
+		ArtifactBuckets: artifactBuckets,
+	}
+
+	// deploy pipeline
+	opts.prog.Start(fmt.Sprintf(fmtUpdatePipelineStart, color.HighlightUserInput(opts.PipelineFile)))
+
+	if err := opts.pipelineDeployer.DeployPipeline(deployPipelineInput); err != nil {
+		// TODO if pipeline already exists, still update?
+		opts.prog.Stop(log.Serrorf(fmtUpdatePipelineFailed, color.HighlightUserInput(opts.PipelineFile)))
+		return fmt.Errorf("deploy pipeline: %w", err)
+	}
+	// TODO stream events
+
+	opts.prog.Stop(log.Ssuccessf(fmtUpdatePipelineComplete, color.HighlightUserInput(opts.PipelineFile))) // change to pipeline name?
+
+	return nil
+}
+
+// BuildPipelineUpdateCmd build the command for deploying a new pipeline or updating an existing pipeline.
+func BuildPipelineUpdateCmd() *cobra.Command {
+	opts := NewUpdatePipelineOpts()
+	cmd := &cobra.Command{
+		Use:   "update",
+		Short: "Deploys a pipeline for applications in your workspace.",
+		Long:  `Deploys a pipeline for the applications in your workspace, using the environments associated with the applications.`,
+		Example: `
+  Deploy an updated pipeline for the applications in your workspace:
+  /code $ archer pipeline update`,
+
+		PreRunE: func(cmd *cobra.Command, args []string) error {
+			store, err := store.New()
+			if err != nil {
+				return fmt.Errorf("couldn't connect to project datastore: %w", err)
+			}
+			opts.envStore = store
+
+			project, err := store.GetProject(opts.ProjectName())
+			if err != nil {
+				return fmt.Errorf("get project %s: %w", opts.ProjectName(), err)
+			}
+			opts.project = project
+
+			defaultSession, err := session.Default()
+			if err != nil {
+				return err
+			}
+			opts.pipelineDeployer = cloudformation.New(defaultSession)
+
+			region := aws.StringValue(defaultSession.Config.Region)
+			opts.region = region
+
+			identityService := identity.New(defaultSession)
+			caller, err := identityService.Get()
+			if err != nil {
+				return nil
+			}
+			account := caller.Account
+			opts.account = account
+
+			ws, err := workspace.New()
+			if err != nil {
+				return err
+			}
+			opts.ws = ws
+
+			return opts.Validate()
+		},
+
+		RunE: func(cmd *cobra.Command, args []string) error {
+			// if err := opts.Ask(); err != nil {
+			// 	return err
+			// }
+			// if err := opts.Validate(); err != nil {
+			// 	return err
+			// }
+			return opts.Execute()
+		},
+	}
+	cmd.Flags().StringVarP(&opts.PipelineFile, pipelineFileFlag, pipelineFileFlagShort, workspace.PipelineFileName, pipelineFileFlagDescription)
+	// cmd.Flags().BoolVar(&opts.Deploy, deployFlag, false, deployFlagDescription)
+
+	return cmd
+}
diff --git a/internal/pkg/cli/validate.go b/internal/pkg/cli/validate.go
index b5dc9142..bb277451 100644
--- a/internal/pkg/cli/validate.go
+++ b/internal/pkg/cli/validate.go
@@ -20,7 +20,7 @@ var (
 	errInvalidGitHubRepo = errors.New("value must be a valid GitHub repository, e.g. https://github.com/myCompany/myRepo")
 )
 
-var githubRepoExp = regexp.MustCompile(`https:\/\/github\.com\/(?P<owner>.+)\/(?P<repo>.+)`)
+var githubRepoExp = regexp.MustCompile(`(https:\/\/github\.com\/|)(?P<owner>.+)\/(?P<repo>.+)`)
 
 func validateProjectName(val interface{}) error {
 	if err := basicNameValidation(val); err != nil {
diff --git a/internal/pkg/deploy/cloudformation/pipeline.go b/internal/pkg/deploy/cloudformation/pipeline.go
new file mode 100644
index 00000000..adbcaec3
--- /dev/null
+++ b/internal/pkg/deploy/cloudformation/pipeline.go
@@ -0,0 +1,22 @@
+// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Package cloudformation provides functionality to deploy archer resources with AWS CloudFormation.
+// This file defines API for deploying a pipeline.
+package cloudformation
+
+import (
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/deploy"
+	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/deploy/cloudformation/stack"
+)
+
+// DeployPipeline sets up everything required for a pipeline with cross-region stages.
+// These resources include things that are regional, rather than scoped to a particular
+// environment, such as ECR Repos, CodePipeline KMS keys & S3 buckets.
+// We deploy pipeline resources through StackSets - that way we can have one
+// template that we update and all regional stacks are updated.
+func (cf CloudFormation) DeployPipeline(in *deploy.CreatePipelineInput) error {
+	pipelineConfig := stack.NewPipelineStackConfig(in)
+
+	return cf.deploy(pipelineConfig)
+}
diff --git a/internal/pkg/deploy/cloudformation/stack/pipeline.go b/internal/pkg/deploy/cloudformation/stack/pipeline.go
index f1075c3e..896d193e 100644
--- a/internal/pkg/deploy/cloudformation/stack/pipeline.go
+++ b/internal/pkg/deploy/cloudformation/stack/pipeline.go
@@ -21,12 +21,14 @@ type pipelineStackConfig struct {
 	*deploy.CreatePipelineInput
 }
 
-func newPipelineStackConfig(in *deploy.CreatePipelineInput) *pipelineStackConfig {
+func NewPipelineStackConfig(in *deploy.CreatePipelineInput) *pipelineStackConfig {
 	return &pipelineStackConfig{
 		CreatePipelineInput: in,
 	}
 }
 
+// TODO https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html
+// Role names limited to 64 chars bc UGH
 func (p *pipelineStackConfig) StackName() string {
 	return p.ProjectName + "-" + p.Name
 }
@@ -46,7 +48,7 @@ func (p *pipelineStackConfig) Template() (string, error) {
 	}
 	var buf bytes.Buffer
 	if err := tpl.Execute(&buf, p); err != nil {
-		return "", fmt.Errorf("execute CloudFormation template for project %s, pipeline %s, error: %w",
+		return "", fmt.Errorf("execute CloudFormation template for project: %s, pipeline: %s, error: %w",
 			p.ProjectName, p.Name, err)
 	}
 	return buf.String(), nil
diff --git a/internal/pkg/deploy/pipeline.go b/internal/pkg/deploy/pipeline.go
index 162029b5..bdc84cf8 100644
--- a/internal/pkg/deploy/pipeline.go
+++ b/internal/pkg/deploy/pipeline.go
@@ -8,7 +8,7 @@ package deploy
 import (
 	"errors"
 	"fmt"
-	"strings"
+	"regexp"
 
 	"github.com/aws/aws-sdk-go/aws/arn"
 
@@ -16,17 +16,28 @@ import (
 	"github.com/aws/amazon-ecs-cli-v2/internal/pkg/manifest"
 )
 
+// NOTE: this is duplicated from validate.go
+var githubRepoExp = regexp.MustCompile(`(https:\/\/github\.com\/|)(?P<owner>.+)\/(?P<repo>.+)`)
+
+const (
+	fmtInvalidGitHubRepo = "unable to locate the repository from the properties: %+v"
+)
+
 // CreatePipelineInput represents the fields required to deploy a pipeline.
 type CreatePipelineInput struct {
 	// Name of the project this pipeline belongs to
 	ProjectName string
+
 	// Name of the pipeline
 	Name string
+
 	// The source code provider for this pipeline
 	Source *Source
+
 	// The stages of the pipeline. The order of stages in this list
 	// will be the order we deploy to
 	Stages []PipelineStage
+
 	// A list of artifact buckets and corresponding KMS keys that will
 	// be used in this pipeline.
 	ArtifactBuckets []ArtifactBucket
@@ -36,7 +47,8 @@ type CreatePipelineInput struct {
 // intermediate artifacts produced by the pipeline.
 type ArtifactBucket struct {
 	// The ARN of the S3 bucket.
-	BucketArn string
+	BucketName string
+
 	// The ARN of the KMS key used to en/decrypt artifacts stored in this bucket.
 	KeyArn string
 }
@@ -48,21 +60,11 @@ func (a *ArtifactBucket) Region() (string, error) {
 	parsedArn, err := arn.Parse(a.KeyArn)
 	if err != nil {
 		return "", fmt.Errorf("failed to parse region out of key ARN: %s, error: %w",
-			a.BucketArn, err)
+			a.BucketName, err)
 	}
 	return parsedArn.Region, nil
 }
 
-// BucketName parses out the name of the bucket from its ARN.
-func (a *ArtifactBucket) BucketName() (string, error) {
-	parsedArn, err := arn.Parse(a.BucketArn)
-	if err != nil {
-		return "", fmt.Errorf("failed to parse the name of the bucket out of bucket ARN: %s, error: %w",
-			a.BucketArn, err)
-	}
-	return parsedArn.Resource, nil
-}
-
 // Source defines the source of the artifacts to be built and deployed.
 type Source struct {
 	// The name of the source code provider. For example, "GitHub"
@@ -111,16 +113,24 @@ func (s *Source) parseOwnerAndRepo() (*ownerAndRepo, error) {
 	}
 	ownerAndRepoStr, ok := ownerAndRepoI.(string)
 	if !ok {
-		return nil, fmt.Errorf("unable to locate the repository from the properties: %+v", ownerAndRepoI)
+		return nil, fmt.Errorf(fmtInvalidGitHubRepo, ownerAndRepoI)
+	}
+
+	match := githubRepoExp.FindStringSubmatch(ownerAndRepoStr)
+	if len(match) == 0 {
+		return nil, fmt.Errorf(fmtInvalidGitHubRepo, ownerAndRepoStr)
 	}
 
-	result := strings.Split(ownerAndRepoStr, "/")
-	if len(result) != 2 {
-		return nil, fmt.Errorf("unable to locate the repository from the properties: %s", ownerAndRepoStr)
+	matches := make(map[string]string)
+	for i, name := range githubRepoExp.SubexpNames() {
+		if i != 0 && name != "" {
+			matches[name] = match[i]
+		}
 	}
+
 	return &ownerAndRepo{
-		owner: result[0],
-		repo:  result[1],
+		owner: matches["owner"],
+		repo:  matches["repo"],
 	}, nil
 }
 
@@ -172,10 +182,13 @@ type AssociatedEnvironment struct {
 	// Name of the environment, must be unique within a project.
 	// This is also the name of the pipeline stage.
 	Name string
+
 	// The region this environment is stored in.
 	Region string
+
 	// AccountID of the account this environment is stored in.
 	AccountID string
+
 	// Whether or not this environment is a production environment.
 	Prod bool
 }
diff --git a/internal/pkg/manifest/pipeline.go b/internal/pkg/manifest/pipeline.go
index 752ed66a..b248a326 100644
--- a/internal/pkg/manifest/pipeline.go
+++ b/internal/pkg/manifest/pipeline.go
@@ -86,6 +86,7 @@ type PipelineManifest struct {
 	Version PipelineSchemaMajorVersion `yaml:"version"`
 	Source  *Source                    `yaml:"source"`
 	Stages  []PipelineStage            `yaml:"stages"`
+	// ArtifactBuckets?
 }
 
 // Source defines the source of the artifacts to be built and deployed.
diff --git a/templates/cicd/pipeline_cfn.yml b/templates/cicd/pipeline_cfn.yml
index bbed90b2..be99b4fd 100644
--- a/templates/cicd/pipeline_cfn.yml
+++ b/templates/cicd/pipeline_cfn.yml
@@ -49,8 +49,8 @@ Resources:
             # that is in the same region as the pipeline.
             # Loop through all the artifact buckets created in the stackset
             Resource:{{range .ArtifactBuckets}}
-              - {{.BucketArn}}
-              - !Join ['', ['{{.BucketArn}}', '/*']]{{end}}
+              - {{.BucketName}}
+              - !Join ['', ['{{.BucketName}}', '/*']]{{end}}
           -
             Effect: Allow
             Action:
@@ -191,8 +191,8 @@ Resources:
               - s3:GetObject
               - s3:ListBucket
             Resource:{{range .ArtifactBuckets}}
-              - {{.BucketArn}}
-              - !Join ['', ['{{.BucketArn}}', '/*']]{{end}}
+              - {{.BucketName}}
+              - !Join ['', ['{{.BucketName}}', '/*']]{{end}}
           -
             Effect: Allow
             Action:
@@ -304,4 +304,4 @@ Resources:
                 ProjectName: !Ref IntegTestProject{{logicalIDSafe $stage.Name}}
               RunOrder: 3
               InputArtifacts:
-                - Name: SCCheckoutArtifact{{end}}{{end}}
\ No newline at end of file
+                - Name: SCCheckoutArtifact{{end}}{{end}}


Test patch:
diff --git a/internal/pkg/cli/validate_test.go b/internal/pkg/cli/validate_test.go
index 26000014..6b08c7ed 100644
--- a/internal/pkg/cli/validate_test.go
+++ b/internal/pkg/cli/validate_test.go
@@ -144,3 +144,32 @@ func TestIsCorrectFormat(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateGitHubRepo(t *testing.T) {
+	testCases := map[string]struct {
+		input string
+		err error
+	}{
+		"full url": {
+			input: "https://github.com/badgoose/chaos",
+			err:  nil,
+		},
+		"owner and repo only": {
+			input: "badgoose/chaos",
+			err:  nil,
+		},
+		"invalid repo": {
+			input: "THEGOOSE",
+			err: errInvalidGitHubRepo,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			got := validateGitHubRepo(tc.input)
+
+			require.True(t, errors.Is(got, tc.err))
+		})
+	}
+}
+
diff --git a/internal/pkg/deploy/cloudformation/project_test.go b/internal/pkg/deploy/cloudformation/project_test.go
index 1396f37b..dbf846b6 100644
--- a/internal/pkg/deploy/cloudformation/project_test.go
+++ b/internal/pkg/deploy/cloudformation/project_test.go
@@ -499,7 +499,7 @@ func TestAddPipelineResourcesToProject(t *testing.T) {
 		},
 	}
 
-	actual := getRegionFromClient
+	actual := getRegionFromClient // FIXME refactor using defer func
 	for name, tc := range testCases {
 		t.Run(name, func(t *testing.T) {
 			getRegionFromClient = tc.getRegionFromClient
diff --git a/internal/pkg/deploy/cloudformation/stack/pipeline_test.go b/internal/pkg/deploy/cloudformation/stack/pipeline_test.go
index e09e2b59..cabd59d1 100644
--- a/internal/pkg/deploy/cloudformation/stack/pipeline_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/pipeline_test.go
@@ -24,7 +24,7 @@ const (
 )
 
 func TestPipelineParameters(t *testing.T) {
-	pipeline := newPipelineStackConfig(
+	pipeline := NewPipelineStackConfig(
 		mockCreatePipelineInput(),
 	)
 
@@ -32,7 +32,7 @@ func TestPipelineParameters(t *testing.T) {
 }
 
 func TestPipelineTags(t *testing.T) {
-	pipeline := newPipelineStackConfig(
+	pipeline := NewPipelineStackConfig(
 		mockCreatePipelineInput(),
 	)
 
@@ -46,7 +46,7 @@ func TestPipelineTags(t *testing.T) {
 }
 
 func TestPipelineStackName(t *testing.T) {
-	pipeline := newPipelineStackConfig(
+	pipeline := NewPipelineStackConfig(
 		mockCreatePipelineInput(),
 	)
 
@@ -58,7 +58,7 @@ func TestPipelineTemplateRendering(t *testing.T) {
 	expectedTemplate, err := ioutil.ReadFile("../testdata/rendered_pipeline_cfn_template.yml")
 	require.NoError(t, err, "expected template can not be read")
 
-	pipeline := newPipelineStackConfig(
+	pipeline := NewPipelineStackConfig(
 		mockCreatePipelineInput(),
 	)
 	tmpl, err := pipeline.Template()
@@ -99,16 +99,16 @@ func mockCreatePipelineInput() *deploy.CreatePipelineInput {
 		},
 		ArtifactBuckets: []deploy.ArtifactBucket{
 			{
-				BucketArn: "arn:aws:s3:::chicken-us-east-1",
+				BucketName: "chicken-us-east-1",
 				KeyArn:    fmt.Sprintf("arn:aws:kms:us-east-1:%s:key/30131d3f-c30f-4d49-beaa-cf4bfc07f34e", toolsAccountID),
 			},
 			{
-				BucketArn: "arn:aws:s3:::chicken-us-west-2",
+				BucketName: "chicken-us-west-2",
 				KeyArn:    fmt.Sprintf("arn:aws:kms:us-west-2:%s:key/80de5f7f-422d-4dff-8f4d-01f6ec5715bc", toolsAccountID),
 			},
 			// assume the pipeline is hosted in a region that does not contain any archer environment
 			{
-				BucketArn: "arn:aws:s3:::chicken-us-west-1",
+				BucketName: "chicken-us-west-1",
 				KeyArn:    fmt.Sprintf("arn:aws:kms:us-west-1:%s:key/75668c57-ec4b-4d0c-b880-8dc3fa78f6d1", toolsAccountID),
 			},
 		},
diff --git a/internal/pkg/deploy/cloudformation/testdata/rendered_pipeline_cfn_template.yml b/internal/pkg/deploy/cloudformation/testdata/rendered_pipeline_cfn_template.yml
index 88bb8b5e..1b466113 100755
--- a/internal/pkg/deploy/cloudformation/testdata/rendered_pipeline_cfn_template.yml
+++ b/internal/pkg/deploy/cloudformation/testdata/rendered_pipeline_cfn_template.yml
@@ -49,12 +49,12 @@ Resources:
             # that is in the same region as the pipeline.
             # Loop through all the artifact buckets created in the stackset
             Resource:
-              - arn:aws:s3:::chicken-us-east-1
-              - !Join ['', ['arn:aws:s3:::chicken-us-east-1', '/*']]
-              - arn:aws:s3:::chicken-us-west-2
-              - !Join ['', ['arn:aws:s3:::chicken-us-west-2', '/*']]
-              - arn:aws:s3:::chicken-us-west-1
-              - !Join ['', ['arn:aws:s3:::chicken-us-west-1', '/*']]
+              - chicken-us-east-1
+              - !Join ['', ['chicken-us-east-1', '/*']]
+              - chicken-us-west-2
+              - !Join ['', ['chicken-us-west-2', '/*']]
+              - chicken-us-west-1
+              - !Join ['', ['chicken-us-west-1', '/*']]
           -
             Effect: Allow
             Action:
@@ -220,12 +220,12 @@ Resources:
               - s3:GetObject
               - s3:ListBucket
             Resource:
-              - arn:aws:s3:::chicken-us-east-1
-              - !Join ['', ['arn:aws:s3:::chicken-us-east-1', '/*']]
-              - arn:aws:s3:::chicken-us-west-2
-              - !Join ['', ['arn:aws:s3:::chicken-us-west-2', '/*']]
-              - arn:aws:s3:::chicken-us-west-1
-              - !Join ['', ['arn:aws:s3:::chicken-us-west-1', '/*']]
+              - chicken-us-east-1
+              - !Join ['', ['chicken-us-east-1', '/*']]
+              - chicken-us-west-2
+              - !Join ['', ['chicken-us-west-2', '/*']]
+              - chicken-us-west-1
+              - !Join ['', ['chicken-us-west-1', '/*']]
           -
             Effect: Allow
             Action:
@@ -445,4 +445,4 @@ Resources:
                 ProjectName: !Ref IntegTestProjectprodDASHcanDASHfly
               RunOrder: 3
               InputArtifacts:
-                - Name: SCCheckoutArtifact
\ No newline at end of file
+                - Name: SCCheckoutArtifact
diff --git a/internal/pkg/deploy/pipeline_test.go b/internal/pkg/deploy/pipeline_test.go
index 69e25a32..559ec325 100644
--- a/internal/pkg/deploy/pipeline_test.go
+++ b/internal/pkg/deploy/pipeline_test.go
@@ -52,6 +52,17 @@ func TestParseOwnerAndRepo(t *testing.T) {
 			expectedOwner:  "chicken",
 			expectedRepo:   "wings",
 		},
+		"valid full repository name": {
+			src: &Source{
+				ProviderName: "GitHub",
+				Properties: map[string]interface{}{
+					"repository": "https://github.com/badgoose/chaOS",
+				},
+			},
+			expectedErrMsg: nil,
+			expectedOwner:  "badgoose",
+			expectedRepo:   "chaOS",
+		},
 	}
 
 	for name, tc := range testCases {