From 8d4f1dd2ebd881caefb91494c01f9e33f2e9d0d4 Mon Sep 17 00:00:00 2001 From: Integralist Date: Mon, 15 Apr 2024 16:53:32 +0100 Subject: [PATCH 1/7] fix(compute/deploy): check compute product entitlement when creating service --- pkg/api/undocumented/undocumented.go | 24 +- pkg/commands/compute/deploy.go | 168 +++----- pkg/commands/compute/deploy_test.go | 547 ++++----------------------- pkg/errors/remediation_error.go | 6 +- pkg/mock/client.go | 6 +- 5 files changed, 148 insertions(+), 603 deletions(-) diff --git a/pkg/api/undocumented/undocumented.go b/pkg/api/undocumented/undocumented.go index e224cd219..9b8ff460a 100644 --- a/pkg/api/undocumented/undocumented.go +++ b/pkg/api/undocumented/undocumented.go @@ -17,12 +17,30 @@ import ( "github.com/fastly/cli/pkg/useragent" ) -// EdgeComputeTrial is the API endpoint for activating a compute trial. -const EdgeComputeTrial = "/customer/%s/edge-compute-trial" +// EntitledProductCheck is the API endpoint for checking whether a user already +// has paid access to the specified product. +const EntitledProductCheck = "/entitled-products/%s" + +// EntitledProductMessageCompute is shown to a user who doesn't yet have paid +// access to the Compute product. +const EntitledProductMessageCompute = "You are creating a Compute trial service which is subject to our terms and conditions as they apply to product trials" + +// ProductCompute is the ID for the Compute product. +const ProductCompute = "compute" // RequestTimeout is the timeout for the API network request. const RequestTimeout = 5 * time.Second +// EntitledProductResponse represents the API response for requesting a +// customer's entitlement data. +type EntitledProductResponse struct { + AccessLevel string `json:"access_level"` + CustomerID string `json:"customer_id"` + HasAccess bool `json:"has_access"` + HasPermToDisable bool `json:"has_permission_to_disable"` + HasPermToEnable bool `json:"has_permission_to_enable"` +} + // APIError models a custom error for undocumented API calls. type APIError struct { Err error @@ -101,7 +119,7 @@ func Call(opts CallOptions) (data []byte, err error) { Remediation: fsterr.NetworkRemediation, } } - return data, NewError(err, 0) + return data, NewError(err, res.StatusCode) } defer res.Body.Close() // #nosec G307 diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index f11426f11..d5ec685d6 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -1,6 +1,7 @@ package compute import ( + "encoding/json" "errors" "fmt" "io" @@ -163,7 +164,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { text.Break(out) } - fnActivateTrial, serviceID, err := c.Setup(out) + serviceID, err := c.Setup(out) if err != nil { return err } @@ -190,7 +191,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { var serviceVersion *fastly.Version if noExistingService { - serviceID, serviceVersion, err = c.NewService(manifestFilename, fnActivateTrial, spinner, in, out) + serviceID, serviceVersion, err = c.NewService(manifestFilename, spinner, in, out) if err != nil { return err } @@ -351,14 +352,7 @@ func validStatusCodeRange(status int) bool { // - Acquire the Service ID/Version. // - Validate there is a package to deploy. // - Determine if a trial needs to be activated on the user's account. -func (c *DeployCommand) Setup(out io.Writer) (fnActivateTrial Activator, serviceID string, err error) { - defaultActivator := func(_ string) error { return nil } - - token, s := c.Globals.Token() - if s == lookup.SourceUndefined { - return defaultActivator, "", fsterr.ErrNoToken - } - +func (c *DeployCommand) Setup(out io.Writer) (serviceID string, err error) { // IMPORTANT: We don't handle the error when looking up the Service ID. // This is because later in the Exec() flow we might create a 'new' service. serviceID, source, flag, err := argparser.ServiceID(c.ServiceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog) @@ -369,7 +363,7 @@ func (c *DeployCommand) Setup(out io.Writer) (fnActivateTrial Activator, service if c.PackagePath == "" { projectName, source := c.Globals.Manifest.Name() if source == manifest.SourceUndefined { - return defaultActivator, serviceID, fsterr.ErrReadingManifest + return serviceID, fsterr.ErrReadingManifest } c.PackagePath = filepath.Join("pkg", fmt.Sprintf("%s.tar.gz", sanitize.BaseName(projectName))) } @@ -379,13 +373,10 @@ func (c *DeployCommand) Setup(out io.Writer) (fnActivateTrial Activator, service c.Globals.ErrLog.AddWithContext(err, map[string]any{ "Package path": c.PackagePath, }) - return defaultActivator, serviceID, err + return serviceID, err } - endpoint, _ := c.Globals.APIEndpoint() - fnActivateTrial = preconfigureActivateTrial(endpoint, token, c.Globals.HTTPClient, c.Globals.Env.DebugMode) - - return fnActivateTrial, serviceID, err + return serviceID, err } // validatePackage checks the package and returns its path, which can change @@ -487,44 +478,8 @@ func packageSize(path string) (size int64, err error) { return fi.Size(), nil } -// Activator represents a function that calls an undocumented API endpoint for -// activating a Compute free trial on the given customer account. -// -// It is preconfigured with the Fastly API endpoint, a user token and a simple -// HTTP Client. -// -// This design allows us to pass an Activator rather than passing multiple -// unrelated arguments through several nested functions. -type Activator func(customerID string) error - -// preconfigureActivateTrial activates a free trial on the customer account. -func preconfigureActivateTrial(endpoint, token string, httpClient api.HTTPClient, debugMode string) Activator { - debug, _ := strconv.ParseBool(debugMode) - return func(customerID string) error { - _, err := undocumented.Call(undocumented.CallOptions{ - APIEndpoint: endpoint, - HTTPClient: httpClient, - Method: http.MethodPost, - Path: fmt.Sprintf(undocumented.EdgeComputeTrial, customerID), - Token: token, - Debug: debug, - }) - if err != nil { - apiErr, ok := err.(undocumented.APIError) - if !ok { - return err - } - // 409 Conflict == The Compute trial has already been created. - if apiErr.StatusCode != http.StatusConflict { - return fmt.Errorf("%w: %d %s", err, apiErr.StatusCode, http.StatusText(apiErr.StatusCode)) - } - } - return nil - } -} - // NewService handles creating a new service when no Service ID is found. -func (c *DeployCommand) NewService(manifestFilename string, fnActivateTrial Activator, spinner text.Spinner, in io.Reader, out io.Writer) (string, *fastly.Version, error) { +func (c *DeployCommand) NewService(manifestFilename string, spinner text.Spinner, in io.Reader, out io.Writer) (string, *fastly.Version, error) { var ( err error serviceID string @@ -571,7 +526,7 @@ func (c *DeployCommand) NewService(manifestFilename string, fnActivateTrial Acti // There is no service and so we'll do a one time creation of the service // // NOTE: we're shadowing the `serviceID` and `serviceVersion` variables. - serviceID, serviceVersion, err = createService(c.Globals, serviceName, fnActivateTrial, spinner, out) + serviceID, serviceVersion, err = createService(c.Globals, serviceName, spinner, out) if err != nil { c.Globals.ErrLog.AddWithContext(err, map[string]any{ "Service name": serviceName, @@ -601,16 +556,7 @@ func (c *DeployCommand) NewService(manifestFilename string, fnActivateTrial Acti } // createService creates a service to associate with the compute package. -// -// NOTE: If the creation of the service fails because the user has not -// activated a free trial, then we'll trigger the trial for their account. -func createService( - g *global.Data, - serviceName string, - fnActivateTrial Activator, - spinner text.Spinner, - out io.Writer, -) (serviceID string, serviceVersion *fastly.Version, err error) { +func createService(g *global.Data, serviceName string, spinner text.Spinner, out io.Writer) (serviceID string, serviceVersion *fastly.Version, err error) { f := g.Flags apiClient := g.APIClient errLog := g.ErrLog @@ -619,6 +565,51 @@ func createService( text.Break(out) } + // Before we create the service, we first check if the user has either paid + // access to the Compute product or is already on a trial (i.e. `has_access` + // will be `true`). If `has_access` is `false`, then we'll display a message + // to explain that the service we're about to create will be part of a trial + // access to the Compute product. The `has_access` will be `true` once the + // service is created and the user creates a new service using the CLI (as + // this means we don't keep showing them the 'trial' message unnecessarily). + // The API will internally handle the service trial activation (if needed). + apiEndpoint, _ := g.APIEndpoint() + token, s := g.Token() + if s == lookup.SourceUndefined { + return "", nil, fsterr.ErrNoToken + } + debug, _ := strconv.ParseBool(g.Env.DebugMode) + data, err := undocumented.Call(undocumented.CallOptions{ + APIEndpoint: apiEndpoint, + HTTPClient: g.HTTPClient, + Method: http.MethodPost, + Path: fmt.Sprintf(undocumented.EntitledProductCheck, undocumented.ProductCompute), + Token: token, + Debug: debug, + }) + if err != nil { + if apiErr, ok := err.(undocumented.APIError); ok { + err = fmt.Errorf("%w: %d %s", err, apiErr.StatusCode, http.StatusText(apiErr.StatusCode)) + } + err = fmt.Errorf("error checking entitlement to the Compute product: %w", err) + return "", nil, fsterr.RemediationError{ + Inner: err, + Remediation: fsterr.ComputeAccessRemediation, + } + } + + var epr undocumented.EntitledProductResponse + if err := json.Unmarshal(data, &epr); err != nil { + return "", nil, fsterr.RemediationError{ + Inner: err, + Remediation: fsterr.ComputeAccessRemediation, + } + } + + if !epr.HasAccess { + text.Info(out, "\n"+undocumented.EntitledProductMessageCompute+"\n\n") + } + err = spinner.Start() if err != nil { return "", nil, err @@ -631,60 +622,15 @@ func createService( Type: fastly.ToPointer("wasm"), }) if err != nil { - if strings.Contains(err.Error(), trialNotActivated) { - user, err := apiClient.GetCurrentUser() - if err != nil { - err = fmt.Errorf("unable to identify user associated with the given token: %w", err) - spinner.StopFailMessage(msg) - spinErr := spinner.StopFail() - if spinErr != nil { - return "", nil, fmt.Errorf(text.SpinnerErrWrapper, spinErr, err) - } - return serviceID, serviceVersion, fsterr.RemediationError{ - Inner: err, - Remediation: "To ensure you have access to the Compute platform we need your Customer ID. " + fsterr.AuthRemediation, - } - } - - customerID := fastly.ToValue(user.CustomerID) - err = fnActivateTrial(customerID) - if err != nil { - err = fmt.Errorf("error creating service: you do not have the Compute free trial enabled on your Fastly account") - spinner.StopFailMessage(msg) - spinErr := spinner.StopFail() - if spinErr != nil { - return "", nil, fmt.Errorf(text.SpinnerErrWrapper, spinErr, err) - } - return serviceID, serviceVersion, fsterr.RemediationError{ - Inner: err, - Remediation: fsterr.ComputeTrialRemediation, - } - } - - errLog.AddWithContext(err, map[string]any{ - "Service Name": serviceName, - "Customer ID": customerID, - }) - - spinner.StopFailMessage(msg) - err = spinner.StopFail() - if err != nil { - return "", nil, err - } - - return createService(g, serviceName, fnActivateTrial, spinner, out) - } - spinner.StopFailMessage(msg) spinErr := spinner.StopFail() if spinErr != nil { return "", nil, spinErr } - errLog.AddWithContext(err, map[string]any{ "Service Name": serviceName, }) - return serviceID, serviceVersion, fmt.Errorf("error creating service: %w", err) + return "", nil, fmt.Errorf("error creating service: %w", err) } spinner.StopMessage(msg) @@ -1211,7 +1157,7 @@ func (c *DeployCommand) ExistingServiceVersion(serviceID string, out io.Writer) }) return serviceVersion, fsterr.RemediationError{ Inner: fmt.Errorf("invalid service type: %s", serviceType), - Remediation: "Ensure the provided Service ID is associated with a 'Wasm' Fastly Service and not a 'VCL' Fastly service. " + fsterr.ComputeTrialRemediation, + Remediation: "Ensure the provided Service ID is associated with a 'Wasm' Fastly Service and not a 'VCL' Fastly service. " + fsterr.ComputeAccessRemediation, } } diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index f60b6fa49..df8c74906 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -1,11 +1,10 @@ package compute_test import ( - "context" + "errors" "fmt" "io" "net/http" - "net/url" "os" "path/filepath" "strings" @@ -16,7 +15,7 @@ import ( "github.com/fastly/cli/pkg/app" "github.com/fastly/cli/pkg/commands/compute" - "github.com/fastly/cli/pkg/errors" + fsterr "github.com/fastly/cli/pkg/errors" "github.com/fastly/cli/pkg/global" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/mock" @@ -94,10 +93,8 @@ func TestDeploy(t *testing.T) { args []string dontWantOutput []string // There are two times the HTTPClient is used. - // The first is if we need to activate a free trial. + // The first is if we need to bypass the entitlement API call. // The second is when we ping for service availability. - // In this test case the free trial activation isn't used. - // So we only define a single HTTP client call for service availability. httpClientRes []*http.Response httpClientErr []error manifest string @@ -113,7 +110,7 @@ func TestDeploy(t *testing.T) { name: "no fastly.toml manifest", args: args("compute deploy --token 123"), wantError: "error reading fastly.toml", - wantRemediationError: errors.ComputeInitRemediation, + wantRemediationError: fsterr.ComputeInitRemediation, noManifest: true, }, { @@ -123,7 +120,30 @@ func TestDeploy(t *testing.T) { // // Additionally it validates that the specified path (files generated by // the testutil.NewEnv()) cause no issues. - name: "path with no service ID", + name: "success with no service ID", + args: args("compute deploy --token 123 -v --package pkg/package.tar.gz"), + api: mock.API{ + ActivateVersionFn: activateVersionOk, + CreateBackendFn: createBackendOK, + CreateDomainFn: createDomainOK, + CreateServiceFn: createServiceOK, + GetPackageFn: getPackageOk, + ListDomainsFn: listDomainsOk, + UpdatePackageFn: updatePackageOk, + }, + stdin: []string{ + "Y", // when prompted to create a new service + }, + wantOutput: []string{ + "Deployed package (service 12345, version 1)", + }, + }, + { + // This test validates a new service creation for someone either without + // paid access to the Compute product, or who are already using the trial + // and so they don't need to be reminded each time that their service is + // part of a trial. + name: "success with no service ID and showing trial message", args: args("compute deploy --token 123 -v --package pkg/package.tar.gz"), api: mock.API{ ActivateVersionFn: activateVersionOk, @@ -136,7 +156,7 @@ func TestDeploy(t *testing.T) { }, httpClientRes: []*http.Response{ { - Body: io.NopCloser(strings.NewReader("success")), + Body: io.NopCloser(strings.NewReader(`{"has_access":false}`)), Status: http.StatusText(http.StatusOK), StatusCode: http.StatusOK, }, @@ -148,9 +168,29 @@ func TestDeploy(t *testing.T) { "Y", // when prompted to create a new service }, wantOutput: []string{ + "INFO: You are creating a Compute trial service", "Deployed package (service 12345, version 1)", }, }, + { + // This test validates what happens when the entitlement check fails. + name: "error checking compute entitlement", + args: args("compute deploy --token 123 -v --package pkg/package.tar.gz"), + httpClientRes: []*http.Response{ + { + Body: nil, + Status: http.StatusText(http.StatusBadRequest), + StatusCode: http.StatusBadRequest, + }, + }, + httpClientErr: []error{ + errors.New("whoops"), + }, + stdin: []string{ + "Y", // when prompted to create a new service + }, + wantError: "error checking entitlement to the Compute product: whoops: 400 Bad Request", + }, // Same validation as above with the exception that we use the default path // parsing logic (i.e. we don't explicitly pass a path via `-p` flag). { @@ -165,16 +205,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, stdin: []string{ "Y", // when prompted to create a new service }, @@ -220,7 +250,7 @@ func TestDeploy(t *testing.T) { args: args("compute deploy --package pkg/package.tar.gz --token 123"), reduceSizeLimit: true, wantError: "package size is too large", - wantRemediationError: errors.PackageSizeRemediation, + wantRemediationError: fsterr.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 @@ -263,113 +293,6 @@ func TestDeploy(t *testing.T) { }, wantError: fmt.Sprintf("error creating service: %s", testutil.Err.Error()), }, - // The following test mocks the service creation to fail with a specific - // error value that will result in the code trying to activate a free trial - // for the customer's account. - // - // Specifically this test will fail the initial API call to get the - // customer's details and so we expect it to return that error (as we can't - // activate a free trial without knowing the customer ID). - { - name: "service create error due to no trial activated and error getting user", - args: args("compute deploy --token 123"), - api: mock.API{ - CreateServiceFn: createServiceErrorNoTrial, - DeleteServiceFn: deleteServiceOK, - GetCurrentUserFn: getCurrentUserError, - }, - stdin: []string{ - "Y", // when prompted to create a new service - }, - wantError: fmt.Sprintf("unable to identify user associated with the given token: %s", testutil.Err.Error()), - wantOutput: []string{ - "Creating service", - }, - }, - // The following test mocks the HTTP client to return a 400 Bad Request, - // which is then coerced into a generic 'no free trial' error. - { - name: "service create error due to no trial activated and error activating trial", - args: args("compute deploy --token 123"), - api: mock.API{ - CreateServiceFn: createServiceErrorNoTrial, - DeleteServiceFn: deleteServiceOK, - GetCurrentUserFn: getCurrentUser, - }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader(testutil.Err.Error())), - Status: http.StatusText(http.StatusBadRequest), - StatusCode: http.StatusBadRequest, - }, - }, - httpClientErr: []error{ - nil, - }, - stdin: []string{ - "Y", // when prompted to create a new service - }, - wantError: "error creating service: you do not have the Compute free trial enabled on your Fastly account", - wantRemediationError: errors.ComputeTrialRemediation, - wantOutput: []string{ - "Creating service", - }, - }, - // The following test mocks the HTTP client to return a timeout error, - // which is then coerced into a generic 'no free trial' error. - { - name: "service create error due to no trial activated and activating trial timeout", - args: args("compute deploy --token 123"), - api: mock.API{ - CreateServiceFn: createServiceErrorNoTrial, - DeleteServiceFn: deleteServiceOK, - GetCurrentUserFn: getCurrentUser, - }, - httpClientRes: []*http.Response{ - nil, - }, - httpClientErr: []error{ - &url.Error{Err: context.DeadlineExceeded}, - }, - stdin: []string{ - "Y", // when prompted to create a new service - }, - wantError: "error creating service: you do not have the Compute free trial enabled on your Fastly account", - wantRemediationError: errors.ComputeTrialRemediation, - wantOutput: []string{ - "Creating service", - }, - }, - // The following test mocks the HTTP client to return successfully when - // trying to activate the free trial. - { - name: "service create success", - args: args("compute deploy --token 123"), - api: mock.API{ - ActivateVersionFn: activateVersionOk, - CreateBackendFn: createBackendOK, - CreateServiceFn: createServiceOK, - GetPackageFn: getPackageOk, - ListDomainsFn: listDomainsOk, - UpdatePackageFn: updatePackageOk, - }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, - stdin: []string{ - "Y", // when prompted to create a new service - }, - wantOutput: []string{ - "Creating service", - }, - }, // The following test doesn't provide a Service ID by either a flag nor the // manifest, so this will result in the deploy script attempting to create // a new service. We mock the service creation to be successful while we @@ -500,16 +423,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -532,16 +445,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -568,16 +471,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, noManifest: true, wantOutput: []string{ "Using fastly.toml within --package archive:", @@ -602,16 +495,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -631,16 +514,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -660,16 +533,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -690,16 +553,6 @@ func TestDeploy(t *testing.T) { UpdatePackageFn: updatePackageOk, UpdateVersionFn: updateVersionOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "Uploading package", "Activating service", @@ -723,16 +576,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -778,16 +621,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -830,16 +663,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -883,16 +706,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -941,16 +754,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "SUCCESS: Deployed package (service 12345, version 1)", }, @@ -970,16 +773,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, stdin: []string{ "Y", // when prompted to create a new service "foobar", // when prompted for service name @@ -1011,16 +804,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, stdin: []string{ "Y", // when prompted to create a new service "foobar", // when prompted for service name @@ -1056,16 +839,6 @@ func TestDeploy(t *testing.T) { ListDomainsFn: listDomainsOk, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, stdin: []string{ "Y", // when prompted to create a new service "foobar", // when prompted for service name @@ -1095,16 +868,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, wantOutput: []string{ "SUCCESS: Deployed package (service 12345, version 1)", }, @@ -1132,16 +895,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1184,16 +937,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1241,16 +984,6 @@ func TestDeploy(t *testing.T) { UpdateConfigStoreItemFn: updateConfigStoreItemOK, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1303,16 +1036,6 @@ func TestDeploy(t *testing.T) { UpdateConfigStoreItemFn: updateConfigStoreItemOK, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1365,16 +1088,6 @@ func TestDeploy(t *testing.T) { UpdateConfigStoreItemFn: updateConfigStoreItemOK, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1420,16 +1133,6 @@ func TestDeploy(t *testing.T) { UpdateConfigStoreItemFn: updateConfigStoreItemOK, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1477,16 +1180,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1525,16 +1218,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1574,16 +1257,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1624,16 +1297,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1671,16 +1334,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1728,16 +1381,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1790,16 +1433,6 @@ func TestDeploy(t *testing.T) { ListKVStoresFn: listKVStoresEmpty, ListVersionsFn: testutil.ListVersions, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1839,16 +1472,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1894,16 +1517,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -1952,16 +1565,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -2007,16 +1610,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -2073,16 +1666,6 @@ func TestDeploy(t *testing.T) { ListVersionsFn: testutil.ListVersions, UpdatePackageFn: updatePackageOk, }, - httpClientRes: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader("success")), - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - }, - }, - httpClientErr: []error{ - nil, - }, manifest: ` name = "package" manifest_version = 2 @@ -2159,6 +1742,20 @@ func TestDeploy(t *testing.T) { if testcase.httpClientRes != nil || testcase.httpClientErr != nil { opts.HTTPClient = mock.HTMLClient(testcase.httpClientRes, testcase.httpClientErr) + } else { + // Default to mocking Compute entitlement check to be successful. + opts.HTTPClient = mock.HTMLClient( + []*http.Response{ + { + Body: io.NopCloser(strings.NewReader(`{"has_access":true}`)), + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + }, + []error{ + nil, + }, + ) } if testcase.reduceSizeLimit { @@ -2251,22 +1848,6 @@ func createServiceError(*fastly.CreateServiceInput) (*fastly.Service, error) { return nil, testutil.Err } -// NOTE: We don't return testutil.Err but a very specific error message so that -// the Deploy logic will drop into a nested logic block. -func createServiceErrorNoTrial(*fastly.CreateServiceInput) (*fastly.Service, error) { - return nil, fmt.Errorf("Valid values for 'type' are: 'vcl'") -} - -func getCurrentUser() (*fastly.User, error) { - return &fastly.User{ - CustomerID: fastly.ToPointer("abc"), - }, nil -} - -func getCurrentUserError() (*fastly.User, error) { - return nil, testutil.Err -} - func deleteServiceOK(_ *fastly.DeleteServiceInput) error { return nil } diff --git a/pkg/errors/remediation_error.go b/pkg/errors/remediation_error.go index 14e990993..200e9ae37 100644 --- a/pkg/errors/remediation_error.go +++ b/pkg/errors/remediation_error.go @@ -150,9 +150,9 @@ var ComputeBuildRemediation = strings.Join([]string{ "See more at https://www.fastly.com/documentation/reference/compute/fastly-toml", }, " ") -// ComputeTrialRemediation suggests contacting customer manager to enable the -// free trial feature flag. -var ComputeTrialRemediation = "For more help with this error see fastly.help/cli/ecp-feature" +// ComputeAccessRemediation directs users to an official help page when there are +// issues checking the entitlement to the Compute product. +var ComputeAccessRemediation = "For more help with this error see https://www.fastly.com/documentation/help/cli/ecp-feature/ and contact https://support.fastly.com/" // ProfileRemediation suggests no profiles exist. var ProfileRemediation = "Run `fastly profile create ` to create a profile, or `fastly profile list` to view available profiles (at least one profile should be set as 'default')." diff --git a/pkg/mock/client.go b/pkg/mock/client.go index fd714cf84..94f4ca8bc 100644 --- a/pkg/mock/client.go +++ b/pkg/mock/client.go @@ -32,7 +32,7 @@ type HTTPClient struct { // Get mocks a HTTP Client Get request. func (c HTTPClient) Get(p string, _ *fastly.RequestOptions) (*http.Response, error) { - fmt.Printf("p: %#v\n", p) + fmt.Printf("(c HTTPClient) Get(p): %#v\n", p) // IMPORTANT: Have to increment on defer as index is already 0 by this point. // This is opposite to the Do() method which is -1 at the time it's called. defer func() { c.Index++ }() @@ -41,8 +41,8 @@ func (c HTTPClient) Get(p string, _ *fastly.RequestOptions) (*http.Response, err // Do mocks a HTTP Client Do operation. func (c HTTPClient) Do(r *http.Request) (*http.Response, error) { - fmt.Printf("r.URL: %#v\n", r.URL.String()) - fmt.Printf("r: %#v\n", r) + fmt.Printf("(c HTTPClient) Do(r *http.Request): r.URL: %#v\n", r.URL.String()) + fmt.Printf("(c HTTPClient) Do(r *http.Request): r: %#v\n", r) c.Index++ return c.Responses[c.Index], c.Errors[c.Index] } From 354dd09d01a9eb2d0a912faa03f7c6660dad2021 Mon Sep 17 00:00:00 2001 From: Integralist Date: Mon, 15 Apr 2024 17:17:52 +0100 Subject: [PATCH 2/7] fix(undocumented): set default status code --- pkg/api/undocumented/undocumented.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/api/undocumented/undocumented.go b/pkg/api/undocumented/undocumented.go index 9b8ff460a..d442994ad 100644 --- a/pkg/api/undocumented/undocumented.go +++ b/pkg/api/undocumented/undocumented.go @@ -119,7 +119,11 @@ func Call(opts CallOptions) (data []byte, err error) { Remediation: fsterr.NetworkRemediation, } } - return data, NewError(err, res.StatusCode) + statusCode := http.StatusInternalServerError + if res != nil { + statusCode = res.StatusCode + } + return data, NewError(err, statusCode) } defer res.Body.Close() // #nosec G307 From e614e275521c7972fc0c03a1acc97bd9596874bd Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 17 Apr 2024 17:44:52 +0100 Subject: [PATCH 3/7] refactor(undocumented): updated legal notice --- pkg/api/undocumented/undocumented.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/undocumented/undocumented.go b/pkg/api/undocumented/undocumented.go index d442994ad..7c9a5707d 100644 --- a/pkg/api/undocumented/undocumented.go +++ b/pkg/api/undocumented/undocumented.go @@ -23,7 +23,7 @@ const EntitledProductCheck = "/entitled-products/%s" // EntitledProductMessageCompute is shown to a user who doesn't yet have paid // access to the Compute product. -const EntitledProductMessageCompute = "You are creating a Compute trial service which is subject to our terms and conditions as they apply to product trials" +const EntitledProductMessageCompute = "By creating this Compute service, you acknowledge that the service is a trial service for evaluation purposes subject to Fastly’s terms of service (www.fastly.com/terms)." // ProductCompute is the ID for the Compute product. const ProductCompute = "compute" From 665cb4e6dfba7e54e5165062938393571dd03e16 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 17 Apr 2024 17:51:34 +0100 Subject: [PATCH 4/7] refactor(compute/deploy): prompt user to continue --- pkg/commands/compute/deploy.go | 11 +++++++++-- pkg/errors/errors.go | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index d5ec685d6..dc00f7fdd 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -526,7 +526,7 @@ func (c *DeployCommand) NewService(manifestFilename string, spinner text.Spinner // There is no service and so we'll do a one time creation of the service // // NOTE: we're shadowing the `serviceID` and `serviceVersion` variables. - serviceID, serviceVersion, err = createService(c.Globals, serviceName, spinner, out) + serviceID, serviceVersion, err = createService(c.Globals, serviceName, spinner, in, out) if err != nil { c.Globals.ErrLog.AddWithContext(err, map[string]any{ "Service name": serviceName, @@ -556,7 +556,7 @@ func (c *DeployCommand) NewService(manifestFilename string, spinner text.Spinner } // createService creates a service to associate with the compute package. -func createService(g *global.Data, serviceName string, spinner text.Spinner, out io.Writer) (serviceID string, serviceVersion *fastly.Version, err error) { +func createService(g *global.Data, serviceName string, spinner text.Spinner, in io.Reader, out io.Writer) (serviceID string, serviceVersion *fastly.Version, err error) { f := g.Flags apiClient := g.APIClient errLog := g.ErrLog @@ -608,6 +608,13 @@ func createService(g *global.Data, serviceName string, spinner text.Spinner, out if !epr.HasAccess { text.Info(out, "\n"+undocumented.EntitledProductMessageCompute+"\n\n") + cont, err := text.AskYesNo(out, "Are you sure you want to continue? [y/N]: ", in) + if err != nil { + return "", nil, err + } + if !cont { + return "", nil, fsterr.ErrComputeTrialStopped + } } err = spinner.Start() diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 091005bac..3d096968f 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -171,3 +171,7 @@ var ErrInvalidEnableDisableFlagCombo = RemediationError{ Inner: fmt.Errorf("invalid flag combination: --enable and --disable"), Remediation: "Use either --enable or --disable, not both.", } + +// ErrComputeTrialStopped means the user declined to go ahead and create a trial +// account to deploy their Compute application. +var ErrComputeTrialStopped = errors.New("deploy stopped by user") From a48ed82e535475a542b38a53d28cf0c089690680 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 17 Apr 2024 19:45:15 +0100 Subject: [PATCH 5/7] fix(compute/deploy): tests --- pkg/commands/compute/deploy_test.go | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index df8c74906..ac9a50b34 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -166,12 +166,47 @@ func TestDeploy(t *testing.T) { }, stdin: []string{ "Y", // when prompted to create a new service + "", // when prompted for a service name use the default + "Y", // when prompted to approve the trial account setup + "", // this is so we generate a backend name using a built-in formula + "", // this stops prompting for backends }, wantOutput: []string{ - "INFO: You are creating a Compute trial service", + "INFO: By creating this Compute service,", + "you acknowledge that the service is a trial service", "Deployed package (service 12345, version 1)", }, }, + { + // This test is the same as above but instead we don't configure a Y + // response to the prompt asking if they want to start the trial account. + // So this leads to an error. + name: "error when declining trial account setup", + args: args("compute deploy --token 123 -v --package pkg/package.tar.gz"), + api: mock.API{ + ActivateVersionFn: activateVersionOk, + CreateBackendFn: createBackendOK, + CreateDomainFn: createDomainOK, + CreateServiceFn: createServiceOK, + GetPackageFn: getPackageOk, + ListDomainsFn: listDomainsOk, + UpdatePackageFn: updatePackageOk, + }, + httpClientRes: []*http.Response{ + { + Body: io.NopCloser(strings.NewReader(`{"has_access":false}`)), + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + }, + httpClientErr: []error{ + nil, + }, + stdin: []string{ + "Y", // when prompted to create a new service + }, + wantError: "deploy stopped by user", + }, { // This test validates what happens when the entitlement check fails. name: "error checking compute entitlement", From 5539575bcdb7c58d9c9f405b215789efe186287b Mon Sep 17 00:00:00 2001 From: Integralist Date: Thu, 18 Apr 2024 10:16:38 +0100 Subject: [PATCH 6/7] fix(compute/deploy): use correct API method --- pkg/commands/compute/deploy.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index dc00f7fdd..09cb4389b 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -582,7 +582,7 @@ func createService(g *global.Data, serviceName string, spinner text.Spinner, in data, err := undocumented.Call(undocumented.CallOptions{ APIEndpoint: apiEndpoint, HTTPClient: g.HTTPClient, - Method: http.MethodPost, + Method: http.MethodGet, Path: fmt.Sprintf(undocumented.EntitledProductCheck, undocumented.ProductCompute), Token: token, Debug: debug, @@ -607,7 +607,7 @@ func createService(g *global.Data, serviceName string, spinner text.Spinner, in } if !epr.HasAccess { - text.Info(out, "\n"+undocumented.EntitledProductMessageCompute+"\n\n") + text.Info(out, undocumented.EntitledProductMessageCompute+"\n\n") cont, err := text.AskYesNo(out, "Are you sure you want to continue? [y/N]: ", in) if err != nil { return "", nil, err @@ -615,6 +615,7 @@ func createService(g *global.Data, serviceName string, spinner text.Spinner, in if !cont { return "", nil, fsterr.ErrComputeTrialStopped } + text.Break(out) } err = spinner.Start() From ce02ae65fcb7049a0ce0def4249869e7bd831ac4 Mon Sep 17 00:00:00 2001 From: Mark McDonnell Date: Tue, 25 Jun 2024 16:46:13 +0100 Subject: [PATCH 7/7] refactor(compute/deploy): update terminology Co-authored-by: Kevin P. Fleming --- pkg/commands/compute/deploy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 09cb4389b..e4e3e2ac6 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -1165,7 +1165,7 @@ func (c *DeployCommand) ExistingServiceVersion(serviceID string, out io.Writer) }) return serviceVersion, fsterr.RemediationError{ Inner: fmt.Errorf("invalid service type: %s", serviceType), - Remediation: "Ensure the provided Service ID is associated with a 'Wasm' Fastly Service and not a 'VCL' Fastly service. " + fsterr.ComputeAccessRemediation, + Remediation: "Ensure the provided Service ID is associated with a 'Compute' Fastly Service and not a 'CDN' Fastly service. " + fsterr.ComputeAccessRemediation, } }