Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6bb3674
chore: add scaffolding to do multiple deployments in a loop
bvtujo Sep 14, 2023
dc8492c
test: add unit test for loop behavior
bvtujo Sep 14, 2023
61655d3
enable multiservice flag
bvtujo Sep 15, 2023
7606cd1
chore: clean up no-ops
bvtujo Sep 18, 2023
7db2a14
Merge branch 'mainline' into chore/multiple-svc-deployments
bvtujo Sep 18, 2023
be8b45c
Update internal/pkg/cli/flag.go
bvtujo Sep 19, 2023
03dcd85
chore: remove all flag to satisfy linter and fix failing test
bvtujo Sep 19, 2023
e923b97
Merge branch 'mainline' into chore/multiple-svc-deployments
bvtujo Sep 20, 2023
ddb33a7
chore: address feedback
bvtujo Sep 20, 2023
beb6ad5
Merge branch 'mainline' into chore/multiple-svc-deployments
bvtujo Sep 20, 2023
8091859
fix: use established method to check wl type
bvtujo Sep 20, 2023
2e7f9e6
chore: change flag to string slice
bvtujo Sep 21, 2023
7658a12
chore: add generic filter function
bvtujo Sep 21, 2023
d020fce
feat: enable --all flag and deployment order tags
bvtujo Sep 25, 2023
dc1abe5
docs: update deploy docstring
bvtujo Sep 25, 2023
c7ecb9b
chore: adjust flag description
bvtujo Sep 25, 2023
9bb2230
fix: several bugfixes
bvtujo Sep 26, 2023
d9fee93
fix: don't prompt for workload initialization
bvtujo Sep 28, 2023
8f97bf5
fix: don't error out on no infra changes
bvtujo Sep 28, 2023
44f82eb
chore: address feedback
bvtujo Sep 28, 2023
e2033b0
Merge branch 'mainline' into feat/enable-multiselect
bvtujo Oct 2, 2023
64fd6d2
chore: remove unsafe array index
bvtujo Oct 2, 2023
cf94dfb
chore: embed actionCommand into struct
bvtujo Oct 2, 2023
d9732fe
chore: use priority queue for deployment order; add unit tests
bvtujo Oct 2, 2023
9183d7e
chore: add new test case for local workloads only
bvtujo Oct 2, 2023
0e59042
fix: update behavior for unspecified initWkld flag when some workload…
bvtujo Oct 2, 2023
8217f47
nit: fix error in rendering deployment order
bvtujo Oct 2, 2023
e4edaa9
fix: don't panic when there's an empty changeset
bvtujo Oct 3, 2023
be30e6f
chore: duplicate FilterItemsByStrings and FilterOutItems
bvtujo Oct 3, 2023
53998c7
chore: move pq to new package
bvtujo Oct 3, 2023
bc01562
chore: fix unit test
bvtujo Oct 3, 2023
17fcd6d
chore: staticcheck
bvtujo Oct 3, 2023
f4e66b0
chore: example to Lesser struct
bvtujo Oct 3, 2023
8beec42
chore: address nits
bvtujo Oct 3, 2023
5a53631
chore: fix staticcheck (unused variable)
bvtujo Oct 3, 2023
44e97b8
chore: make initwkld a bool, not *bool, and update description
bvtujo Oct 3, 2023
eafbce3
chore: don't export funcs; rewrite docstrings
bvtujo Oct 4, 2023
a01ad23
chore: nits
bvtujo Oct 4, 2023
efb00e9
Merge branch 'mainline' into feat/enable-multiselect
mergify[bot] Oct 4, 2023
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
410 changes: 344 additions & 66 deletions internal/pkg/cli/deploy.go

Large diffs are not rendered by default.

426 changes: 318 additions & 108 deletions internal/pkg/cli/deploy_test.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions internal/pkg/cli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ const (
svcFlagDescription = "Name of the service."
jobFlagDescription = "Name of the job."
workloadFlagDescription = "Name of the service or job."
workloadsFlagDescription = "Names of the service or jobs to deploy, with an optional priority tag."
nameFlagDescription = "Name of the service, job, or task group."
yesFlagDescription = "Skips confirmation prompt."
resourceTagsFlagDescription = `Optional. Labels with a key and value separated by commas.
Expand All @@ -280,8 +281,8 @@ rollback in case of deployment failure.
We do not recommend using this flag for a
production environment.`
forceEnvDeployFlagDescription = "Optional. Force update the environment stack template."
yesInitWorkloadFlagDescription = "Optional. Initialize a workload before deploying it."
allWorkloadsFlagDescription = "Optional. Deploy all workloads with manifests in the current Copilot workspace."
yesInitWorkloadFlagDescription = "Optional. When specified with --all, initialize all local workloads before deployment."
allWorkloadsFlagDescription = "Optional. Deploy all workloads with manifests in the current Copilot workspace."
detachFlagDescription = "Optional. Skip displaying CloudFormation deployment progress."

// Operational.
Expand Down
1 change: 1 addition & 0 deletions internal/pkg/cli/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ type wsSelector interface {
Service(prompt, help string) (string, error)
Job(prompt, help string) (string, error)
Workload(msg, help string) (string, error)
Workloads(msg, help string) ([]string, error)
}

type staticSourceSelector interface {
Expand Down
15 changes: 15 additions & 0 deletions internal/pkg/cli/mocks/mock_interfaces.go

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

86 changes: 86 additions & 0 deletions internal/pkg/queue/queue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Package queue provides a generic priority queue.
package queue

import "container/heap"

// Lesser is an interface to enable generic structs to be elements of a priority queue.
// Any struct can become a priority queue element by defining the LessThan method
// and initializing a new PriorityQueue.
//
// (s myStruct) LessThan(other myStruct) bool
// q := NewPriorityQueue[myStruct]()
type Lesser[T any] interface {
LessThan(T) bool
}

// PriorityQueue implements a priority queue.
type PriorityQueue[T Lesser[T]] struct {
pq pq[T]
}

// Push adds a new element to the queue and puts it in the correct place.
func (p *PriorityQueue[T]) Push(x T) {
heap.Push(&p.pq, x)
}

// Pop removes the top element of the queue and restructures it in log(n) time.
func (p *PriorityQueue[T]) Pop() (*T, bool) {
if p.pq.Len() == 0 {
return nil, false
}
v := heap.Pop(&p.pq).(T)
return &v, true
}

// Len returns the length of the queue.
func (p *PriorityQueue[T]) Len() int {
return p.pq.Len()
}

type pq[T Lesser[T]] []T

// NewPriorityQueue returns an empty priority queue.
func NewPriorityQueue[T Lesser[T]]() *PriorityQueue[T] {
var arr pq[T] = make([]T, 0)
heap.Init(&arr)
return &PriorityQueue[T]{
pq: arr,
}
}

// Len returns the length of the data structure.
func (p *pq[T]) Len() int { return len(*p) }

// Less returns whether element i is less than element j, using the generic type's LessThan function.
func (p *pq[T]) Less(i, j int) bool { return (*p)[i].LessThan((*p)[j]) }

// Swap swaps the positions of two elements in the priority queue.
func (p *pq[T]) Swap(i, j int) {
(*p)[i], (*p)[j] = (*p)[j], (*p)[i]
}

// Push appends a new element to the back of the underlying array.
func (p *pq[T]) Push(x any) {
*p = append(*p, x.(T))
}

// Pop removes the last element from the array.
func (p *pq[T]) Pop() any {
old := *p
n := len(old)
res := old[n-1]
*p = old[:n-1]
return res
}

// Compile-time check that the PriorityQueue type works on a generic type.
type comparableInt int

func (c comparableInt) LessThan(a comparableInt) bool {
return c < a
}

var _ heap.Interface = (*pq[comparableInt])(nil)
61 changes: 61 additions & 0 deletions internal/pkg/queue/queue_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package queue

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestPriorityQueue(t *testing.T) {
t.Run("sorts numbers using comparableInt", func(t *testing.T) {
nums := []comparableInt{1, 6, 2, 10, 1000, -5}
want := []comparableInt{-5, 1, 2, 6, 10, 1000}
pq := NewPriorityQueue[comparableInt]()
for _, n := range nums {
pq.Push(n)
}
require.Equal(t, len(nums), pq.Len())
out := make([]comparableInt, 0, pq.Len())
for pq.Len() > 0 {
v, ok := pq.Pop()
require.Equal(t, true, ok)
out = append(out, *v)
}
require.Equal(t, want, out)
})

t.Run("sorts arbitrary structs by length of member", func(t *testing.T) {
items := []arbitraryStruct{
{"aaaaaa"}, {"x"}, {"aaa"},
}
want := []arbitraryStruct{
{"x"}, {"aaa"}, {"aaaaaa"},
}
pq := NewPriorityQueue[arbitraryStruct]()
for _, n := range items {
pq.Push(n)
}
require.Equal(t, len(items), pq.Len())
out := make([]arbitraryStruct, 0, pq.Len())
for pq.Len() > 0 {
v, ok := pq.Pop()
require.Equal(t, true, ok)
out = append(out, *v)
}
require.Equal(t, want, out)
})

t.Run("can't pop from empty priority queue", func(t *testing.T) {
pq := NewPriorityQueue[arbitraryStruct]()
v, ok := pq.Pop()
require.Nil(t, v)
require.Equal(t, false, ok)
})
}

type arbitraryStruct struct {
fieldA string
}

func (a arbitraryStruct) LessThan(b arbitraryStruct) bool { return len(a.fieldA) < len(b.fieldA) }
81 changes: 41 additions & 40 deletions internal/pkg/term/selector/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ func (s *LocalWorkloadSelector) getWorkloadSelectOptions(workloadType string) ([
return nil, fmt.Errorf("no %s found in workspace", pluralNounString)
}
// Get the list of un-initialized workloads that are present in the workspace and add them to options.
unInitializedLocalWorkloads := filterOutStrings(wsWlNames, initializedLocalWorkloads)
unInitializedLocalWorkloads := filterOutItems(wsWlNames, initializedLocalWorkloads, func(a string) string { return a })
for _, wl := range unInitializedLocalWorkloads {
options = append(options, prompt.Option{
Value: wl,
Expand Down Expand Up @@ -885,32 +885,24 @@ func (s *LocalWorkloadSelector) Workload(msg, help string) (wl string, err error
return selectedWlName, nil
}

func filterStrings(allStrings []string, wantedStrings []string) []string {
isWanted := make(map[string]bool)
for _, name := range wantedStrings {
isWanted[name] = true
// filterOutItems is a generic function to return the subset of allItems which does not include the items specified in
// unwantedItems. stringFunc is a function which maps the unwantedItem type T to a string value. For example, one can
// convert a struct of type *config.Workload to a string by passing
//
// func(w *config.Workload) string { return w.Name }
//
// as the stringFunc parameter.
func filterOutItems[T any](allItems []string, unwantedItems []T, stringFunc func(T) string) []string {
isUnwanted := make(map[string]bool)
for _, item := range unwantedItems {
isUnwanted[stringFunc(item)] = true
}
var filtered []string
for _, wl := range allStrings {
if _, ok := isWanted[wl]; !ok {
for _, str := range allItems {
if isUnwanted[str] {
continue
}
filtered = append(filtered, wl)
}
return filtered
}

func filterOutStrings(allStrings []string, unwantedStrings []string) []string {
isUnWanted := make(map[string]bool)
for _, name := range unwantedStrings {
isUnWanted[name] = true
}
var filtered []string
for _, wl := range allStrings {
if isUnWanted[wl] {
continue
}
filtered = append(filtered, wl)
filtered = append(filtered, str)
}
return filtered
}
Expand Down Expand Up @@ -945,27 +937,36 @@ func (s *LocalEnvironmentSelector) LocalEnvironment(msg, help string) (string, e
}

func filterEnvsByName(envs []*config.Environment, wantedNames []string) []string {
// TODO: refactor this and `filterWlsByName` when generic supports using common struct fields: https://github.com/golang/go/issues/48522
isWanted := make(map[string]bool)
for _, name := range wantedNames {
isWanted[name] = true
}
var filtered []string
for _, wl := range envs {
if _, ok := isWanted[wl.Name]; !ok {
continue
}
filtered = append(filtered, wl.Name)
}
return filtered
return filterItemsByStrings(wantedNames, envs, func(e *config.Environment) string { return e.Name })
}

func filterWlsByName(wls []*config.Workload, wantedNames []string) []string {
stringWls := make([]string, len(wls))
for i := range wls {
stringWls[i] = wls[i].Name
return filterItemsByStrings(wantedNames, wls, func(w *config.Workload) string { return w.Name })
}

// filterItemsByStrings is a generic function to return the subset of wantedStrings that exists in possibleItems.
// stringFunc is a method to convert the generic item type (T) to a string; for example, one can convert a struct of type
// *config.Workload to a string by passing
//
// func(w *config.Workload) string { return w.Name }.
//
// Likewise, filterItemsByStrings can work on a list of strings by returning the unmodified item:
//
// filterItemsByStrings(wantedStrings, stringSlice2, func(s string) string { return s })
//
// It returns a list of strings (items whose stringFunc() exists in the list of wantedStrings).
func filterItemsByStrings[T any](wantedStrings []string, possibleItems []T, stringFunc func(T) string) []string {
m := make(map[string]bool)
for _, item := range wantedStrings {
m[item] = true
}
res := make([]string, 0, len(wantedStrings))
for _, item := range possibleItems {
if m[stringFunc(item)] {
res = append(res, stringFunc(item))
}
}
return filterStrings(stringWls, wantedNames)
return res
}

// WsPipeline fetches all the pipelines in a workspace and prompts the user to select one.
Expand Down