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

Interactive init #5

Merged
merged 10 commits into from
Mar 11, 2020
1 change: 1 addition & 0 deletions .github/workflows/pr_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
run: make test
shell: bash
env:
TEST_COMPUTE_INIT: true
TEST_COMPUTE_BUILD: true
scan:
runs-on: ubuntu-latest
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/blang/semver v3.5.1+incompatible
github.com/fastly/go-fastly v1.3.0
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
github.com/fastly/go-fastly v1.7.0
github.com/fatih/color v1.7.0
github.com/frankban/quicktest v1.5.0 // indirect
github.com/google/go-cmp v0.3.1
github.com/google/go-github/v28 v28.1.1
github.com/google/jsonapi v0.0.0-20181016150055-d0428f63eb51 // indirect
github.com/google/jsonapi v0.0.0-20200226002910-c8283f632fb7 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/kennygrant/sanitize v1.2.4
github.com/mattn/go-colorable v0.1.4 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyG
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fastly/go-fastly v1.3.0 h1:halI6tkEmn5JIeW888P2bNGprGP7OYot+o1akPXvPyY=
github.com/fastly/go-fastly v1.3.0/go.mod h1:cBtWXhszIFx9xpzgm9L/3PW3Ixszo153xJBEHJghGUk=
github.com/fastly/go-fastly v1.7.0 h1:IiDMXzoSSJZjQP5VIjv+BU4WMoo3DeMGIy2xm9BDuL8=
github.com/fastly/go-fastly v1.7.0/go.mod h1:cBtWXhszIFx9xpzgm9L/3PW3Ixszo153xJBEHJghGUk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
Expand All @@ -52,6 +56,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/jsonapi v0.0.0-20170708005851-46d3ced04344/go.mod h1:XSx4m2SziAqk9DXY9nz659easTq4q6TyrpYd9tHSm0g=
github.com/google/jsonapi v0.0.0-20181016150055-d0428f63eb51 h1:k+U8IQj6kj659R+Ahq6YsK03GdUo8qQdTsq5HBzfQwM=
github.com/google/jsonapi v0.0.0-20181016150055-d0428f63eb51/go.mod h1:XSx4m2SziAqk9DXY9nz659easTq4q6TyrpYd9tHSm0g=
github.com/google/jsonapi v0.0.0-20200226002910-c8283f632fb7 h1:aQ4kMXDAmP9IRIZHcSKB2orXHGwGiSxH4PX1BzKHR50=
github.com/google/jsonapi v0.0.0-20200226002910-c8283f632fb7/go.mod h1:XSx4m2SziAqk9DXY9nz659easTq4q6TyrpYd9tHSm0g=
github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ type Interface interface {
GetBigQuery(*fastly.GetBigQueryInput) (*fastly.BigQuery, error)
UpdateBigQuery(*fastly.UpdateBigQueryInput) (*fastly.BigQuery, error)
DeleteBigQuery(*fastly.DeleteBigQueryInput) error

GetUser(*fastly.GetUserInput) (*fastly.User, error)
}

// Interface assertion, to catch mismatches early.
Expand Down
20 changes: 11 additions & 9 deletions pkg/app/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,17 @@ COMMANDS
compute init [<flags>]
Initialize a new Compute@Edge package locally

-n, --name=NAME Name of package, defaulting to directory name
of the --path destination
-f, --from="https://github.com/fastly/fastly-template-rust-default"
Git repository containing package template
-p, --path=PATH Destination to write the new package,
defaulting to the current directory
-s, --service-id=SERVICE-ID Optional Fastly service ID written to the
package manifest, where this package will be
deployed
-n, --name=NAME Name of package, defaulting to directory name
of the --path destination
-d, --description=DESCRIPTION Description of the package
-a, --author=AUTHOR Author of the package
-f, --from=FROM Git repository containing package template
-p, --path=PATH Destination to write the new package,
defaulting to the current directory
--domain=DOMAIN The name of the domain associated to the
package
--backend=BACKEND A hostname, IPv4, or IPv6 address for the
package backend

compute build [<flags>]
Build a Compute@Edge package locally
Expand Down
2 changes: 1 addition & 1 deletion pkg/common/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func CopyFile(src, dst string) (err error) {
if err != nil {
return fmt.Errorf("error reading source file: %w", err)
}
defer in.Close()
defer in.Close() // #nosec G307
phamann marked this conversation as resolved.
Show resolved Hide resolved

// Create destination file for writing.
out, err := os.Create(dst)
Expand Down
66 changes: 66 additions & 0 deletions pkg/common/undo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package common

import (
"fmt"
"io"
)

// UndoFn is a function with no arguments which returns an error or nil.
type UndoFn func() error

// UndoStack models a simple undo stack which consumers can use to store undo
// stateful functions, such as a function to teardown API state if something
// goes wrong during procedural commands, for example deleting a Fastly service
// after it's been created.
type UndoStack struct {
states []UndoFn
}

// NewUndoStack constructs a new UndoStack.
func NewUndoStack() *UndoStack {
s := make([]UndoFn, 0, 1)
stack := &UndoStack{
states: s,
}
return stack
}

// Pop method pops last added UndoFn element oof the stack and returns it.
// If stack is empty Pop() returns nil.
func (s *UndoStack) Pop() UndoFn {
n := len(s.states)
if n == 0 {
return nil
}
v := s.states[n-1]
s.states = s.states[:n-1]
return v
}

// Push method pushes an Undoer element onto the UndoStack.
func (s *UndoStack) Push(elem UndoFn) {
s.states = append(s.states, elem)
}

// Len method returns the number of elements in the UndoStack.
func (s *UndoStack) Len() int {
return len(s.states)
}
phamann marked this conversation as resolved.
Show resolved Hide resolved

// RunIfError unwinds the stack if a non-nil error is passed, by serially
// calling each UndoFn function state in FIFO order. If any UndoFn returns an
// error, it gets logged to the provided writer. Should be deferrerd, such as:
//
// undoStack := common.NewUndoStack()
// defer undoStack.RunIfError(w, err)
//
func (s *UndoStack) RunIfError(w io.Writer, err error) {
if err == nil {
return
}
for i := len(s.states) - 1; i >= 0; i-- {
if err := s.states[i](); err != nil {
fmt.Fprintf(w, "%w", err)
}
}
}
180 changes: 167 additions & 13 deletions pkg/compute/compute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,86 @@ func TestInit(t *testing.T) {
for _, testcase := range []struct {
name string
args []string
configFile config.File
api mock.API
wantFiles []string
unwantedFiles []string
stdin string
wantError string
wantOutput []string
manifestIncludes string
}{
{
name: "unkown repository",
args: []string{"compute", "init", "--from", "https://example.com/template"},
name: "no token",
args: []string{"compute", "init"},
wantError: "no token provided",
},
{
name: "unkown repository",
args: []string{"compute", "init", "--from", "https://example.com/template"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendOK,
DeleteServiceFn: deleteServiceOK,
DeleteBackendFn: deleteBackendOK,
DeleteDomainFn: deleteDomainOK,
},
wantError: "error fetching package template: repository not found",
},
{
name: "with name",
args: []string{"compute", "init", "--name", "test"},
name: "create service error",
args: []string{"compute", "init"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceError,
},
wantError: "error creating service: fixture error",
},
{
name: "create domain error",
args: []string{"compute", "init"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainError,
DeleteServiceFn: deleteServiceOK,
},
wantError: "error creating domain: fixture error",
},
{
name: "create backend error",
args: []string{"compute", "init"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendError,
DeleteServiceFn: deleteServiceOK,
DeleteDomainFn: deleteDomainOK,
},
wantError: "error creating backend: fixture error",
},
{
name: "with name",
args: []string{"compute", "init", "--name", "test"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendOK,
},
wantOutput: []string{
"Initializing...",
"Fetching package template...",
Expand All @@ -55,20 +121,54 @@ func TestInit(t *testing.T) {
manifestIncludes: `name = "test"`,
},
{
name: "with service",
args: []string{"compute", "init", "--service-id", "test"},
name: "with description",
args: []string{"compute", "init", "--description", "test"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendOK,
},
wantOutput: []string{
"Initializing...",
"Fetching package template...",
"Updating package manifest..",
},
manifestIncludes: `description = "test"`,
},
{
name: "with author",
args: []string{"compute", "init", "--author", "test@example.com"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendOK,
},
wantOutput: []string{
"Initializing...",
"Fetching package template...",
"Updating package manifest..",
},
manifestIncludes: `service_id = "test"`,
manifestIncludes: `authors = ["test@example.com"]`,
},
{
name: "default",
args: []string{"compute", "init"},
name: "default",
args: []string{"compute", "init"},
configFile: config.File{Token: "123"},
api: mock.API{
GetTokenSelfFn: tokenOK,
GetUserFn: getUserOk,
CreateServiceFn: createServiceOK,
CreateDomainFn: createDomainOK,
CreateBackendFn: createBackendOK,
},
wantFiles: []string{
"cargo.toml",
"Cargo.toml",
"fastly.toml",
"src/main.rs",
},
Expand Down Expand Up @@ -108,12 +208,12 @@ func TestInit(t *testing.T) {
var (
args = testcase.args
env = config.Environment{}
file = config.File{}
file = testcase.configFile
appConfigFile = "/dev/null"
clientFactory = mock.APIClient(mock.API{})
clientFactory = mock.APIClient(testcase.api)
httpClient = http.DefaultClient
versioner update.Versioner = nil
in io.Reader = nil
in io.Reader = bytes.NewBufferString(testcase.stdin)
buf bytes.Buffer
out io.Writer = common.NewSyncWriter(&buf)
)
Expand Down Expand Up @@ -836,6 +936,60 @@ func copyFile(t *testing.T, fromFilename, toFilename string) {

var errTest = errors.New("fixture error")

func tokenOK() (*fastly.Token, error) { return &fastly.Token{}, nil }

func getUserOk(i *fastly.GetUserInput) (*fastly.User, error) {
return &fastly.User{Login: "test@example.com"}, nil
}

func createServiceOK(i *fastly.CreateServiceInput) (*fastly.Service, error) {
return &fastly.Service{
ID: "12345",
Name: i.Name,
Type: i.Type,
}, nil
}

func createServiceError(*fastly.CreateServiceInput) (*fastly.Service, error) {
return nil, errTest
}

func deleteServiceOK(i *fastly.DeleteServiceInput) error {
return nil
}

func createDomainOK(i *fastly.CreateDomainInput) (*fastly.Domain, error) {
return &fastly.Domain{
ServiceID: i.Service,
Version: i.Version,
Name: i.Name,
}, nil
}

func createDomainError(i *fastly.CreateDomainInput) (*fastly.Domain, error) {
return nil, errTest
}

func deleteDomainOK(i *fastly.DeleteDomainInput) error {
return nil
}

func createBackendOK(i *fastly.CreateBackendInput) (*fastly.Backend, error) {
return &fastly.Backend{
ServiceID: i.Service,
Version: i.Version,
Name: i.Name,
}, nil
}

func createBackendError(i *fastly.CreateBackendInput) (*fastly.Backend, error) {
return nil, errTest
}

func deleteBackendOK(i *fastly.DeleteBackendInput) error {
return nil
}

func latestVersionInactiveOk(i *fastly.LatestVersionInput) (*fastly.Version, error) {
return &fastly.Version{ServiceID: i.Service, Number: 1, Active: false}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/compute/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (c *Client) UpdatePackage(serviceID string, v int, path string) error {
if err != nil {
return fmt.Errorf("error reading package: %w", err)
}
defer file.Close()
defer file.Close() // #nosec G307

var body bytes.Buffer
w := multipart.NewWriter(&body)
Expand Down
Loading