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

Verify fastly crate version during compute build. #67

Merged
merged 6 commits into from
May 12, 2020
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 pkg/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func Run(args []string, env config.Environment, file config.File, configFilePath

computeRoot := compute.NewRootCommand(app, &globals)
computeInit := compute.NewInitCommand(computeRoot.CmdClause, &globals)
computeBuild := compute.NewBuildCommand(computeRoot.CmdClause, &globals)
computeBuild := compute.NewBuildCommand(computeRoot.CmdClause, httpClient, &globals)
computeDeploy := compute.NewDeployCommand(computeRoot.CmdClause, httpClient, &globals)
computeUpdate := compute.NewUpdateCommand(computeRoot.CmdClause, httpClient, &globals)
computeValidate := compute.NewValidateCommand(computeRoot.CmdClause, &globals)
Expand Down
28 changes: 12 additions & 16 deletions pkg/compute/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"

"github.com/fastly/cli/pkg/api"
"github.com/fastly/cli/pkg/common"
"github.com/fastly/cli/pkg/compute/manifest"
"github.com/fastly/cli/pkg/config"
Expand All @@ -20,16 +21,6 @@ type Toolchain interface {
Build(out io.Writer, verbose bool) error
}

// GetToolchain returns a Toolchain for the provided language.
func GetToolchain(l string) (Toolchain, error) {
switch l {
case "rust":
return &Rust{}, nil
default:
return nil, fmt.Errorf("unsupported language %s", l)
}
}

func createPackageArchive(files []string, destination string) error {
tar := archiver.NewTarGz()
tar.OverwriteExisting = true //
Expand All @@ -47,14 +38,16 @@ func createPackageArchive(files []string, destination string) error {
// BuildCommand produces a deployable artifact from files on the local disk.
type BuildCommand struct {
common.Base
name string
lang string
client api.HTTPClient
name string
lang string
}

// NewBuildCommand returns a usable command registered under the parent.
func NewBuildCommand(parent common.Registerer, globals *config.Data) *BuildCommand {
func NewBuildCommand(parent common.Registerer, client api.HTTPClient, globals *config.Data) *BuildCommand {
var c BuildCommand
c.Globals = globals
c.client = client
c.CmdClause = parent.Command("build", "Build a Compute@Edge package locally")
c.CmdClause.Flag("name", "Package name").StringVar(&c.name)
c.CmdClause.Flag("language", "Language type").StringVar(&c.lang)
Expand Down Expand Up @@ -108,9 +101,12 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) {
}
name = sanitize.BaseName(name)

toolchain, err := GetToolchain(lang)
if err != nil {
return err
var toolchain Toolchain
switch lang {
case "rust":
toolchain = &Rust{c.client}
default:
return fmt.Errorf("unsupported language %s", lang)
}

progress.Step(fmt.Sprintf("Verifying local %s toolchain...", lang))
Expand Down
155 changes: 132 additions & 23 deletions pkg/compute/compute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/fastly/cli/pkg/api"
Expand Down Expand Up @@ -274,39 +275,75 @@ func TestBuild(t *testing.T) {
}

for _, testcase := range []struct {
name string
args []string
manifest string
wantError string
wantOutputContains string
name string
args []string
fastlyManifest string
cargoManifest string
cargoLock string
client api.HTTPClient
wantError string
wantRemediationError string
wantOutputContains string
}{
{
name: "no fastly.toml manifest",
args: []string{"compute", "build"},
client: versionClient{[]string{"0.0.0"}},
wantError: "error reading package manifest: open fastly.toml:", // actual message differs on Windows
},
{
name: "empty language",
args: []string{"compute", "build"},
manifest: "name = \"test\"\n",
wantError: "language cannot be empty, please provide a language",
name: "empty language",
args: []string{"compute", "build"},
fastlyManifest: "name = \"test\"\n",
client: versionClient{[]string{"0.0.0"}},
wantError: "language cannot be empty, please provide a language",
},
{
name: "empty name",
args: []string{"compute", "build"},
manifest: "language = \"rust\"\n",
wantError: "name cannot be empty, please provide a name",
name: "empty name",
args: []string{"compute", "build"},
fastlyManifest: "language = \"rust\"\n",
client: versionClient{[]string{"0.0.0"}},
wantError: "name cannot be empty, please provide a name",
},
{
name: "unknown language",
args: []string{"compute", "build"},
manifest: "name = \"test\"\nlanguage = \"javascript\"\n",
wantError: "unsupported language javascript",
name: "unknown language",
args: []string{"compute", "build"},
fastlyManifest: "name = \"test\"\nlanguage = \"javascript\"\n",
client: versionClient{[]string{"0.0.0"}},
wantError: "unsupported language javascript",
},
{
name: "fastly crate not found",
args: []string{"compute", "build"},
fastlyManifest: "name = \"test\"\nlanguage = \"rust\"\n",
cargoLock: " ",
client: versionClient{[]string{"0.4.0"}},
wantError: "fastly crate not found",
wantRemediationError: "fastly = \"^0.4.0\"",
},
{
name: "fastly crate out-of-date but within minor range",
args: []string{"compute", "build"},
fastlyManifest: "name = \"test\"\nlanguage = \"rust\"\n",
cargoLock: "[[package]]\nname = \"fastly\"\nversion = \"0.3.2\"",
client: versionClient{[]string{"0.3.3"}},
wantError: "fastly crate not up-to-date",
wantRemediationError: "cargo update -p fastly",
},
{
name: "fastly crate out-of-date but lower than minor range",
args: []string{"compute", "build"},
fastlyManifest: "name = \"test\"\nlanguage = \"rust\"\n",
cargoLock: "[[package]]\nname = \"fastly\"\nversion = \"0.3.2\"",
client: versionClient{[]string{"0.4.0"}},
wantError: "fastly crate not up-to-date",
wantRemediationError: "fastly = \"^0.4.0\"",
},
{
name: "success",
args: []string{"compute", "build"},
manifest: "name = \"test\"\nlanguage = \"rust\"\n",
fastlyManifest: "name = \"test\"\nlanguage = \"rust\"\n",
client: versionClient{[]string{"0.0.0"}},
wantOutputContains: "Built rust package test",
},
} {
Expand All @@ -320,7 +357,7 @@ func TestBuild(t *testing.T) {

// Create our build environment in a temp dir.
// Defer a call to clean it up.
rootdir := makeBuildEnvironment(t, testcase.manifest)
rootdir := makeBuildEnvironment(t, testcase.fastlyManifest, testcase.cargoManifest, testcase.cargoLock)
defer os.RemoveAll(rootdir)

// Before running the test, chdir into the build environment.
Expand All @@ -337,14 +374,15 @@ func TestBuild(t *testing.T) {
file = config.File{}
appConfigFile = "/dev/null"
clientFactory = mock.APIClient(mock.API{})
httpClient = http.DefaultClient
httpClient = testcase.client
versioner update.Versioner = nil
in io.Reader = nil
buf bytes.Buffer
out io.Writer = common.NewSyncWriter(&buf)
)
err = app.Run(args, env, file, appConfigFile, clientFactory, httpClient, versioner, in, out)
testutil.AssertErrorContains(t, err, testcase.wantError)
testutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)
if testcase.wantOutputContains != "" {
testutil.AssertStringContains(t, buf.String(), testcase.wantOutputContains)
}
Expand Down Expand Up @@ -965,6 +1003,44 @@ func TestGetIdealPackage(t *testing.T) {
}
}

func TestGetLatestCrateVersion(t *testing.T) {
for _, testcase := range []struct {
name string
inputClient api.HTTPClient
wantVersion string
wantError string
}{
{
name: "http error",
inputClient: &errorClient{errTest},
wantError: "fixture error",
},
{
name: "no valid versions",
inputClient: &versionClient{[]string{}},
wantError: "no valid crate versions found",
},
{
name: "unsorted",
inputClient: &versionClient{[]string{"0.5.23", "0.1.0", "1.2.3", "0.7.3"}},
wantVersion: "1.2.3",
},
{
name: "reverse chronological",
inputClient: &versionClient{[]string{"1.2.3", "0.8.3", "0.3.2"}},
wantVersion: "1.2.3",
},
} {
t.Run(testcase.name, func(t *testing.T) {
v, err := compute.GetLatestCrateVersion(testcase.inputClient, "fastly")
testutil.AssertErrorContains(t, err, testcase.wantError)
if v != testcase.wantVersion {
t.Errorf("wanted version %s, got %s", testcase.wantVersion, v)
}
})
}
}

func makeInitEnvironment(t *testing.T) (rootdir string) {
t.Helper()

Expand All @@ -986,7 +1062,7 @@ func makeInitEnvironment(t *testing.T) (rootdir string) {
return rootdir
}

func makeBuildEnvironment(t *testing.T, manifestContent string) (rootdir string) {
func makeBuildEnvironment(t *testing.T, fastlyManifestContent, cargoManifestContent, cargoLockContent string) (rootdir string) {
t.Helper()

p := make([]byte, 8)
Expand Down Expand Up @@ -1014,9 +1090,23 @@ func makeBuildEnvironment(t *testing.T, manifestContent string) (rootdir string)
copyFile(t, fromFilename, toFilename)
}

if manifestContent != "" {
if fastlyManifestContent != "" {
filename := filepath.Join(rootdir, compute.ManifestFilename)
if err := ioutil.WriteFile(filename, []byte(manifestContent), 0777); err != nil {
if err := ioutil.WriteFile(filename, []byte(fastlyManifestContent), 0777); err != nil {
t.Fatal(err)
}
}

if cargoManifestContent != "" {
filename := filepath.Join(rootdir, "Cargo.toml")
if err := ioutil.WriteFile(filename, []byte(cargoManifestContent), 0777); err != nil {
t.Fatal(err)
}
}

if cargoLockContent != "" {
filename := filepath.Join(rootdir, "Cargo.lock")
if err := ioutil.WriteFile(filename, []byte(cargoLockContent), 0777); err != nil {
t.Fatal(err)
}
}
Expand Down Expand Up @@ -1230,3 +1320,22 @@ func (c codeClient) Do(*http.Request) (*http.Response, error) {
rec.WriteHeader(c.code)
return rec.Result(), nil
}

type versionClient struct {
versions []string
}

func (v versionClient) Do(*http.Request) (*http.Response, error) {
rec := httptest.NewRecorder()

var versions []string
for _, vv := range v.versions {
versions = append(versions, fmt.Sprintf(`{"num":"%s"}`, vv))
}

_, err := rec.Write([]byte(fmt.Sprintf(`{"versions":[%s]}`, strings.Join(versions, ","))))
if err != nil {
return nil, err
}
return rec.Result(), nil
}
Loading