Skip to content
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Bug Fixes:

- fix(compute/deploy): remove compute trial activation code because trials no longer exist ([#1730](https://github.com/fastly/cli/pull/1730))

### Enhancements:

### Dependencies:
Expand Down
3 changes: 0 additions & 3 deletions pkg/api/undocumented/undocumented.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import (
"github.com/fastly/cli/pkg/useragent"
)

// EdgeComputeTrial is the API endpoint for activating a compute trial.
const EdgeComputeTrial = "/customer/%s/edge-compute-trial"

// RequestTimeout is the timeout for the API network request.
const RequestTimeout = 5 * time.Second

Expand Down
115 changes: 11 additions & 104 deletions pkg/commands/compute/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
Expand All @@ -21,7 +20,6 @@ import (
"github.com/fastly/go-fastly/v14/fastly"

"github.com/fastly/cli/pkg/api"
"github.com/fastly/cli/pkg/api/undocumented"
"github.com/fastly/cli/pkg/argparser"
"github.com/fastly/cli/pkg/commands/compute/setup"
"github.com/fastly/cli/pkg/debug"
Expand All @@ -36,7 +34,6 @@ import (

const (
manageServiceBaseURL = "https://manage.fastly.com/configure/services/"
trialNotActivated = "Valid values for 'type' are: 'vcl'"
)

// ErrPackageUnchanged is an error that indicates the package hasn't changed.
Expand Down Expand Up @@ -178,7 +175,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
}
Expand All @@ -205,7 +202,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
}
Expand Down Expand Up @@ -368,13 +365,10 @@ func validStatusCodeRange(status int) bool {
// - Check if there is an API token missing.
// - 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()
func (c *DeployCommand) Setup(out io.Writer) (serviceID string, err error) {
_, s := c.Globals.Token()
if s == lookup.SourceUndefined {
return defaultActivator, "", fsterr.ErrNoToken()
return "", fsterr.ErrNoToken()
}

// IMPORTANT: We don't handle the error when looking up the Service ID.
Expand All @@ -387,7 +381,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)))
}
Expand All @@ -397,13 +391,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
Expand Down Expand Up @@ -505,44 +496,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
Expand Down Expand Up @@ -589,7 +544,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,
Expand Down Expand Up @@ -619,13 +574,9 @@ 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) {
Expand All @@ -649,50 +600,6 @@ func createService(
Type: fastly.ToPointer("wasm"),
})
if err != nil {
if strings.Contains(err.Error(), trialNotActivated) {
user, err := apiClient.GetCurrentUser(context.TODO())
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 {
Expand Down Expand Up @@ -1253,7 +1160,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.",
}
}

Expand Down
107 changes: 3 additions & 104 deletions pkg/commands/compute/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -90,14 +89,9 @@ func TestDeploy(t *testing.T) {
originalPackageSizeLimit := compute.MaxPackageSize
args := testutil.SplitArgs
scenarios := []struct {
api mock.API
args []string
dontWantOutput []string
// There are two times the HTTPClient is used.
// The first is if we need to activate a free trial.
// 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.
api mock.API
args []string
dontWantOutput []string
httpClientRes []*http.Response
httpClientErr []error
manifest string
Expand Down Expand Up @@ -270,85 +264,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"),
Expand Down Expand Up @@ -2354,22 +2269,6 @@ func createServiceError(_ context.Context, _ *fastly.CreateServiceInput) (*fastl
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(_ context.Context, _ *fastly.CreateServiceInput) (*fastly.Service, error) {
return nil, fmt.Errorf("Valid values for 'type' are: 'vcl'")
}

func getCurrentUser(_ context.Context) (*fastly.User, error) {
return &fastly.User{
CustomerID: fastly.ToPointer("abc"),
}, nil
}

func getCurrentUserError(_ context.Context) (*fastly.User, error) {
return nil, testutil.Err
}

func deleteServiceOK(_ context.Context, _ *fastly.DeleteServiceInput) error {
return nil
}
Expand Down
4 changes: 0 additions & 4 deletions pkg/errors/remediation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,6 @@ 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"

// ProfileRemediation suggests running auth commands.
func ProfileRemediation() string {
if env.AuthCommandDisabled() {
Expand Down
Loading