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(cicd): allow CodeCommit as pipeline source #1808

Merged
merged 25 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dfdfdbd
chore: tiny refactors + add conditional to cfn template
huanjani Dec 31, 2020
27bffa0
build: refactor conversion of info b/w manifest and deploy (owner/rep…
huanjani Jan 4, 2021
d49df8e
fix: move gh secret condition to method from template; minor refactoring
huanjani Jan 4, 2021
766458a
build: get to functional state for cc; need to make more flexible
huanjani Jan 5, 2021
0da9c86
Merge remote-tracking branch 'archer/mainline' into pipeline-update
huanjani Jan 5, 2021
1ccacc5
build: enable both GH and CC as sources
huanjani Jan 5, 2021
4b5f15a
build: specify cc allowed actions, add unit tests
huanjani Jan 6, 2021
3bcddf0
fix: cap 'URL'
huanjani Jan 6, 2021
1bb4c60
chore: update docs
huanjani Jan 6, 2021
2228cfe
fix: move vars closer to their usage
huanjani Jan 7, 2021
bebc056
chore: add regex-passing examples
huanjani Jan 7, 2021
89de541
fix: remove replicated code
huanjani Jan 7, 2021
10957e7
fix: tweak wording in docs
huanjani Jan 7, 2021
1992889
chore: split source into two types, use go templates instead of cfn c…
huanjani Jan 7, 2021
c80e798
fix: change go template consecutive 'if's to 'if-else'
huanjani Jan 8, 2021
529671d
chore: use switch to assign source type
huanjani Jan 8, 2021
5816806
Merge remote-tracking branch 'archer/mainline' into pipeline-update
huanjani Jan 11, 2021
7a4aff9
address feedback
huanjani Jan 12, 2021
a2f80a4
fix: stringify more safely; catch session err
huanjani Jan 12, 2021
905d37d
fix: address feedback
huanjani Jan 13, 2021
28f5da1
fix: remove TODO
huanjani Jan 13, 2021
d7c4600
Update internal/pkg/deploy/pipeline.go
huanjani Jan 13, 2021
c875b98
fix: update ecs-cli-v2 to copilot
huanjani Jan 13, 2021
07eec32
chore: use local variables
huanjani Jan 14, 2021
040d5dc
Merge branch 'mainline' into pipeline-update
mergify[bot] Jan 14, 2021
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
14 changes: 6 additions & 8 deletions internal/pkg/cli/pipeline_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const (
fmtGHPipelineName = "pipeline-%s-%s-%s"
fmtCCPipelineName = "pipeline-%s-%s"
fmtGHRepoURL = "https://%s/%s/%s"
fmtCCRepoURL = "https://%s.console.%s/codesuite/codecommit/repositories/%s"
fmtCCRepoURL = "https://%s.console.%s/codesuite/codecommit/repositories/%s/browse"
)

var (
Expand Down Expand Up @@ -147,10 +147,8 @@ func (o *initPipelineOpts) Validate() error {
if o.appName == "" {
return errNoAppInWorkspace
}
if o.appName != "" {
if _, err := o.store.GetApplication(o.appName); err != nil {
return err
}
if _, err := o.store.GetApplication(o.appName); err != nil {
return err
}

if o.repoURL != "" {
Expand Down Expand Up @@ -561,16 +559,16 @@ func (o *initPipelineOpts) pipelineName() (string, error) {
func (o *initPipelineOpts) pipelineProvider() (manifest.Provider, error) {
if o.provider == ghProviderName {
config := &manifest.GitHubProperties{
OwnerAndRepository: fmt.Sprintf(fmtGHRepoURL, githubURL, o.githubOwner, o.repoName),
RepositoryURL: fmt.Sprintf(fmtGHRepoURL, githubURL, o.githubOwner, o.repoName),
Branch: o.repoBranch,
GithubSecretIdKeyName: o.secret,
}
return manifest.NewProvider(config)
}
if o.provider == ccProviderName {
config := &manifest.CodeCommitProperties{
Repository: fmt.Sprintf(fmtCCRepoURL, o.ccRegion, awsURL, o.repoName),
Branch: o.repoBranch,
RepositoryURL: fmt.Sprintf(fmtCCRepoURL, o.ccRegion, awsURL, o.repoName),
Branch: o.repoBranch,
}
return manifest.NewProvider(config)
}
Expand Down
3 changes: 2 additions & 1 deletion internal/pkg/cli/pipeline_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const (

type updatePipelineVars struct {
appName string
pipelineName string
skipConfirmation bool
}

Expand All @@ -56,6 +55,8 @@ type updatePipelineOpts struct {
region string
envStore environmentStore
ws wsPipelineReader

pipelineName string
}

func newUpdatePipelineOpts(vars updatePipelineVars) (*updatePipelineOpts, error) {
Expand Down
5 changes: 3 additions & 2 deletions internal/pkg/cli/pipeline_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,7 @@ stages:

opts := &updatePipelineOpts{
updatePipelineVars: updatePipelineVars{
pipelineName: tc.inPipelineName,
appName: tc.inAppName,
appName: tc.inAppName,
},
pipelineDeployer: mockPipelineDeployer,
ws: mockWorkspace,
Expand All @@ -611,6 +610,8 @@ stages:
envStore: mockEnvStore,
prog: mockProgress,
prompt: mockPrompt,

pipelineName: tc.inPipelineName,
}

// WHEN
Expand Down
89 changes: 49 additions & 40 deletions internal/pkg/deploy/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import (
)

// NOTE: this is duplicated from validate.go
var githubRepoExp = regexp.MustCompile(`(https:\/\/github\.com\/|)(?P<owner>.+)\/(?P<repo>.+)`)
var ghRepoExp = regexp.MustCompile(`(https:\/\/github\.com\/|)(?P<owner>.+)\/(?P<repo>.+)`)

// NOTE: 'region' is not currently parsed out as a Source property, but this enables that possibility.
var ccRepoExp = regexp.MustCompile(`(https:\/\/(?P<region>.+)(.console.aws.amazon.com\/codesuite\/codecommit\/repositories\/)(?P<repo>.+)(\/browse))`)

const (
fmtInvalidGitHubRepo = "unable to locate the repository from the properties: %+v"
fmtInvalidRepo = "unable to locate the repository URL from the properties: %+v"
)

// CreatePipelineInput represents the fields required to deploy a pipeline.
Expand Down Expand Up @@ -82,78 +85,84 @@ type Source struct {
// Secrets manager, which stores the GitHub Personal Access token if the
// provider is "GitHub". Otherwise, it returns the detected provider.
func (s *Source) GitHubPersonalAccessTokenSecretID() (string, error) {
// TODO type check if properties are GitHubProperties?
secretID, exists := s.Properties[manifest.GithubSecretIdKeyName]
if !exists {
return "", errors.New("the GitHub token secretID is not configured")
}

id, ok := secretID.(string)
if !ok {
return "", fmt.Errorf("unable to locate the GitHub token secretID from %v", secretID)
}
id := "N/A"
var ok bool
if s.ProviderName == manifest.GithubProviderName {
secretID, exists := s.Properties[manifest.GithubSecretIdKeyName]
if !exists {
return "", errors.New("the GitHub token secretID is not configured")
}

if s.ProviderName != manifest.GithubProviderName {
return fmt.Sprintf("Non-GitHub provider detected: %s", s.ProviderName), nil
id, ok = secretID.(string)
if !ok {
return "", fmt.Errorf("unable to locate the GitHub token secretID from %v", secretID)
}
}

return id, nil
}

type ownerAndRepo struct {
owner string
repo string
}

func (s *Source) parseOwnerAndRepo() (*ownerAndRepo, error) {
if s.ProviderName != manifest.GithubProviderName {
return nil, fmt.Errorf("invalid provider: %s", s.ProviderName)
// parseOwnerAndRepo parses the owner (if GitHub is the provider) and repo name from the repo URL.
func (s *Source) parseOwnerAndRepo(provider string) (string, string, error) {
var repoExp *regexp.Regexp
if provider == manifest.GithubProviderName {
repoExp = ghRepoExp
}
if provider == manifest.CodeCommitProviderName {
repoExp = ccRepoExp
}
ownerAndRepoI, exists := s.Properties["repository"]
url, exists := s.Properties["repository"]
if !exists {
return nil, fmt.Errorf("unable to locate the repository from the properties: %+v", s.Properties)
return "", "", fmt.Errorf("unable to locate the repository from the properties: %+v", s.Properties)
}
ownerAndRepoStr, ok := ownerAndRepoI.(string)
urlStr, ok := url.(string)
if !ok {
return nil, fmt.Errorf(fmtInvalidGitHubRepo, ownerAndRepoI)
return "", "", fmt.Errorf(fmtInvalidRepo, url)
}

match := githubRepoExp.FindStringSubmatch(ownerAndRepoStr)
match := repoExp.FindStringSubmatch(urlStr)
if len(match) == 0 {
return nil, fmt.Errorf(fmtInvalidGitHubRepo, ownerAndRepoStr)
return "", "", fmt.Errorf(fmtInvalidRepo, urlStr)
}

matches := make(map[string]string)
for i, name := range githubRepoExp.SubexpNames() {
for i, name := range repoExp.SubexpNames() {
if i != 0 && name != "" {
matches[name] = match[i]
}
}

return &ownerAndRepo{
owner: matches["owner"],
repo: matches["repo"],
}, nil
owner := ""
if provider == manifest.GithubProviderName {
owner = matches["owner"]
}
return owner, matches["repo"], nil
}

// Repository returns the repository portion. For example,
// given "aws/amazon-ecs-cli-v2", this function returns "amazon-ecs-cli-v2".
func (s *Source) Repository() (string, error) {
oAndR, err := s.parseOwnerAndRepo()
if s.ProviderName != manifest.GithubProviderName && s.ProviderName != manifest.CodeCommitProviderName {
return "", fmt.Errorf("invalid provider: %s", s.ProviderName)
}
_, repo, err := s.parseOwnerAndRepo(s.ProviderName)
if err != nil {
return "", err
}
return oAndR.repo, nil
return repo, nil
}

// Owner returns the repository owner portion. For example,
// given "aws/amazon-ecs-cli-v2", this function returns "aws".
func (s *Source) Owner() (string, error) {
oAndR, err := s.parseOwnerAndRepo()
if err != nil {
return "", err
owner := "N/A"
var err error
if s.ProviderName == manifest.GithubProviderName {
owner, _, err = s.parseOwnerAndRepo(s.ProviderName)
if err != nil {
return "", err
}
}
return oAndR.owner, nil
return owner, nil
}

// PipelineStage represents configuration for each deployment stage
Expand Down
30 changes: 17 additions & 13 deletions internal/pkg/deploy/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ func TestParseOwnerAndRepo(t *testing.T) {
expectedOwner string
expectedRepo string
}{
"unsupported source provider": {
src: &Source{
ProviderName: "chicken",
Properties: map[string]interface{}{},
},
expectedErrMsg: aws.String("invalid provider: chicken"),
},
"missing repository property": {
src: &Source{
ProviderName: "GitHub",
Expand All @@ -38,9 +31,9 @@ func TestParseOwnerAndRepo(t *testing.T) {
"repository": "invalid",
},
},
expectedErrMsg: aws.String("unable to locate the repository from the properties"),
expectedErrMsg: aws.String("unable to locate the repository URL from the properties"),
},
"valid repository property": {
"valid GH repository property": {
src: &Source{
ProviderName: "GitHub",
Properties: map[string]interface{}{
Expand All @@ -51,7 +44,18 @@ func TestParseOwnerAndRepo(t *testing.T) {
expectedOwner: "chicken",
expectedRepo: "wings",
},
"valid full repository name": {
"valid full CC repository name": {
src: &Source{
ProviderName: "CodeCommit",
Properties: map[string]interface{}{
"repository": "https://us-west-2.console.aws.amazon.com/codesuite/codecommit/repositories/wings/browse",
},
},
expectedErrMsg: nil,
expectedOwner: "",
expectedRepo: "wings",
},
"valid full GH repository name": {
src: &Source{
ProviderName: "GitHub",
Properties: map[string]interface{}{
Expand All @@ -66,13 +70,13 @@ func TestParseOwnerAndRepo(t *testing.T) {

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
oAndR, err := tc.src.parseOwnerAndRepo()
owner, repo, err := tc.src.parseOwnerAndRepo(tc.src.ProviderName)
if tc.expectedErrMsg != nil {
require.Contains(t, err.Error(), *tc.expectedErrMsg)
} else {
require.NoError(t, err, "expected error")
require.Equal(t, tc.expectedOwner, oAndR.owner, "mismatched owner")
require.Equal(t, tc.expectedRepo, oAndR.repo, "mismatched repo")
require.Equal(t, tc.expectedOwner, owner, "mismatched owner")
require.Equal(t, tc.expectedRepo, repo, "mismatched repo")
}
})
}
Expand Down
8 changes: 3 additions & 5 deletions internal/pkg/manifest/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,16 @@ func (p *codecommitProvider) Properties() map[string]interface{} {
type GitHubProperties struct {
// use tag from https://godoc.org/github.com/fatih/structs#example-Map--Tags
// to specify the name of the field in the output properties

// An example for OwnerAndRepository would be: "aws/copilot"
OwnerAndRepository string `structs:"repository" yaml:"repository"`
RepositoryURL string `structs:"repository" yaml:"repository"`
Branch string `structs:"branch" yaml:"branch"`
GithubSecretIdKeyName string `structs:"access_token_secret" yaml:"access_token_secret"`
}

// CodeCommitProperties contains information for configuring a CodeCommit
// source provider.
type CodeCommitProperties struct {
Repository string `structs:"repository" yaml:"repository"`
Branch string `structs:"branch" yaml:"branch"`
RepositoryURL string `structs:"repository" yaml:"repository"`
Branch string `structs:"branch" yaml:"branch"`
}

// NewProvider creates a source provider based on the type of
Expand Down
27 changes: 17 additions & 10 deletions internal/pkg/manifest/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import (
)

const (
defaultBranch = "main"
defaultGHBranch = "main"
defaultCCBranch = "master"
)

func TestNewProvider(t *testing.T) {
Expand All @@ -28,8 +29,14 @@ func TestNewProvider(t *testing.T) {
}{
"successfully create GitHub provider": {
providerConfig: &GitHubProperties{
OwnerAndRepository: "aws/amazon-ecs-cli-v2",
Branch: defaultBranch,
RepositoryURL: "aws/amazon-ecs-cli-v2",
Branch: defaultGHBranch,
},
},
"successfully create CodeCommit provider": {
providerConfig: &CodeCommitProperties{
RepositoryURL: "https://us-west-2.console.aws.amazon.com/codesuite/codecommit/repositories/wings/browse",
Branch: defaultCCBranch,
},
},
}
Expand Down Expand Up @@ -61,8 +68,8 @@ func TestNewPipelineManifest(t *testing.T) {
"errors out when no stage provided": {
provider: func() Provider {
p, err := NewProvider(&GitHubProperties{
OwnerAndRepository: "aws/amazon-ecs-cli-v2",
Branch: defaultBranch,
RepositoryURL: "aws/amazon-ecs-cli-v2",
Branch: defaultGHBranch,
})
require.NoError(t, err, "failed to create provider")
return p
Expand All @@ -73,8 +80,8 @@ func TestNewPipelineManifest(t *testing.T) {
"happy case with non-default stages": {
provider: func() Provider {
p, err := NewProvider(&GitHubProperties{
OwnerAndRepository: "aws/amazon-ecs-cli-v2",
Branch: defaultBranch,
RepositoryURL: "aws/amazon-ecs-cli-v2",
Branch: defaultGHBranch,
})
require.NoError(t, err, "failed to create provider")
return p
Expand All @@ -95,8 +102,8 @@ func TestNewPipelineManifest(t *testing.T) {
Source: &Source{
ProviderName: "GitHub",
Properties: structs.Map(GitHubProperties{
OwnerAndRepository: "aws/amazon-ecs-cli-v2",
Branch: defaultBranch,
RepositoryURL: "aws/amazon-ecs-cli-v2",
Branch: defaultGHBranch,
}),
},
Stages: []PipelineStage{
Expand Down Expand Up @@ -239,7 +246,7 @@ stages:
Properties: map[string]interface{}{
"access_token_secret": "github-token-badgoose-backend",
"repository": "aws/somethingCool",
"branch": defaultBranch,
"branch": defaultGHBranch,
},
},
Stages: []PipelineStage{
Expand Down
5 changes: 3 additions & 2 deletions site/content/docs/commands/pipeline-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ $ copilot pipeline init [flags]

## What are the flags?
```bash
-a, --app string Name of the application.
-e, --environments strings Environments to add to the pipeline.
-b, --git-branch string Branch used to trigger your pipeline.
-t, --github-access-token string GitHub personal access token for your repository.
-u, --github-url string GitHub repository URL for your service.
-u, --url string The repository URL to trigger your pipeline.
-h, --help help for init
```

## Examples
Create a pipeline for the services in your workspace.
```bash
$ copilot pipeline init \
--github-url https://github.com/gitHubUserName/myFrontendApp.git \
--url https://github.com/gitHubUserName/myFrontendApp.git \
--github-access-token file://myGitHubToken \
--environments "test,prod"
```
Loading