From da59098b63c1232b33e55091aa0f9bb300ee88e3 Mon Sep 17 00:00:00 2001 From: Mark McDonnell Date: Thu, 2 Sep 2021 13:58:34 +0100 Subject: [PATCH] validate package size doesn't exceed limit (#380) * add package size validation * add test to validate package size error messaging * fix test from timing out by resetting package variable --- pkg/commands/compute/deploy.go | 30 ++++++++++++++++++++++ pkg/commands/compute/deploy_test.go | 39 ++++++++++++++++++++++------- pkg/errors/remediation_error.go | 7 ++++++ 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index fedc641ff..49e5444d1 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -29,6 +29,10 @@ const ( manageServiceBaseURL = "https://manage.fastly.com/configure/services/" ) +// PackageSizeLimit describes the package size limit in bytes (currently 50mb) +// https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits +var PackageSizeLimit int64 = 50000000 + // DeployCommand deploys an artifact previously produced by build. type DeployCommand struct { cmd.Base @@ -317,6 +321,9 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { // validatePackage short-circuits the deploy command if the user hasn't first // built a package to be deployed. +// +// NOTE: It also validates if the package size exceeds limit: +// https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits func validatePackage(data manifest.Data, pathFlag string, errLog errors.LogInterface) (pkgName, pkgPath string, err error) { pkgName, source := data.Name() pkgPath, err = packagePath(pathFlag, pkgName, source) @@ -328,9 +335,23 @@ func validatePackage(data manifest.Data, pathFlag string, errLog errors.LogInter }) return pkgName, pkgPath, err } + pkgSize, err := packageSize(pkgPath) + if err != nil { + errLog.AddWithContext(err, map[string]interface{}{ + "Package path": pkgPath, + }) + return pkgName, pkgPath, err + } + if pkgSize > PackageSizeLimit { + return pkgName, pkgPath, errors.RemediationError{ + Inner: fmt.Errorf("package size is too large (%d bytes)", pkgSize), + Remediation: errors.PackageSizeRemediation, + } + } if err := validate(pkgPath); err != nil { errLog.AddWithContext(err, map[string]interface{}{ "Package path": pkgPath, + "Package size": pkgSize, }) return pkgName, pkgPath, err } @@ -355,6 +376,15 @@ func packagePath(path string, name string, source manifest.Source) (string, erro return path, nil } +// packageSize returns the size of the .tar.gz package. +func packageSize(path string) (size int64, err error) { + fi, err := os.Stat(path) + if err != nil { + return size, err + } + return fi.Size(), nil +} + // manageNoServiceIDFlow handles creating a new service when no Service ID is found. func manageNoServiceIDFlow( acceptDefaults bool, diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index 1f0722b07..59ca6d14b 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -11,7 +11,9 @@ import ( "time" "github.com/fastly/cli/pkg/app" + "github.com/fastly/cli/pkg/commands/compute" "github.com/fastly/cli/pkg/commands/compute/manifest" + "github.com/fastly/cli/pkg/errors" "github.com/fastly/cli/pkg/mock" "github.com/fastly/cli/pkg/testutil" "github.com/fastly/go-fastly/v3/fastly" @@ -72,17 +74,20 @@ func TestDeploy(t *testing.T) { } defer os.Chdir(pwd) + originalPackageSizeLimit := compute.PackageSizeLimit args := testutil.Args for _, testcase := range []struct { - api mock.API - args []string - dontWantOutput []string - manifest string - name string - noManifest bool - stdin []string - wantError string - wantOutput []string + api mock.API + args []string + dontWantOutput []string + manifest string + name string + noManifest bool + reduceSizeLimit bool + stdin []string + wantError string + wantRemediationError string + wantOutput []string }{ { name: "no token", @@ -182,6 +187,13 @@ func TestDeploy(t *testing.T) { }, wantError: fmt.Sprintf("error fetching service backends: %s", testutil.Err.Error()), }, + { + name: "package size too large", + args: args("compute deploy -p pkg/package.tar.gz --token 123"), + reduceSizeLimit: true, + wantError: "package size is too large", + wantRemediationError: errors.PackageSizeRemediation, + }, // The following test doesn't just validate the package API error behaviour // but as a side effect it validates that when deleting the created // service, the Service ID is also cleared out from the manifest. @@ -900,6 +912,14 @@ func TestDeploy(t *testing.T) { opts := testutil.NewRunOpts(testcase.args, &stdout) opts.APIClient = mock.APIClient(testcase.api) + if testcase.reduceSizeLimit { + compute.PackageSizeLimit = 1000000 // 1mb (our test package should above this) + } else { + // As multiple test scenarios run within a single environment instance + // we need to ensure each scenario resets the package variable. + compute.PackageSizeLimit = originalPackageSizeLimit + } + if len(testcase.stdin) > 1 { // To handle multiple prompt input from the user we need to do some // coordination around io pipes to mimic the required user behaviour. @@ -950,6 +970,7 @@ func TestDeploy(t *testing.T) { t.Log(stdout.String()) testutil.AssertErrorContains(t, err, testcase.wantError) + testutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError) for _, s := range testcase.wantOutput { testutil.AssertStringContains(t, stdout.String(), s) diff --git a/pkg/errors/remediation_error.go b/pkg/errors/remediation_error.go index 99f3cddf8..1858c2ef1 100644 --- a/pkg/errors/remediation_error.go +++ b/pkg/errors/remediation_error.go @@ -96,3 +96,10 @@ var AutoCloneRemediation = strings.Join([]string{ var IDRemediation = strings.Join([]string{ "Please provide one via the --id flag", }, " ") + +// PackageSizeRemediation suggests checking the resources documentation for the +// current package size limit. +var PackageSizeRemediation = strings.Join([]string{ + "Please check our Compute@Edge resource limits:", + "https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits", +}, " ")