From 91f6b5846daf3ef7f617ee5fd6ae17993af8ea7d Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 16:04:12 +0000 Subject: [PATCH 1/9] feat(compute/deploy): check service availability --- pkg/commands/compute/deploy.go | 98 ++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 5580599b8..c93dc77a1 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -12,6 +12,7 @@ import ( "path/filepath" "sort" "strings" + "time" "github.com/fastly/cli/pkg/api" "github.com/fastly/cli/pkg/api/undocumented" @@ -159,10 +160,20 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { return err } + domain, err := getServiceDomain(c.Globals.APIClient, serviceID, serviceVersion.Number) + if err != nil { + return err + } + + serviceURL := fmt.Sprintf("https://%s", domain) + + if err := checkingServiceAvailability(serviceURL, spinner); err != nil { + return err + } + text.Break(out) text.Description(out, "Manage this service at", fmt.Sprintf("%s%s", manageServiceBaseURL, serviceID)) - - displayDomain(c.Globals.APIClient, serviceID, serviceVersion.Number, out) + text.Description(out, "View this service at", serviceURL) text.Success(out, "Deployed package (service %s, version %v)", serviceID, serviceVersion.Number) return nil @@ -785,21 +796,6 @@ func pkgUpload(spinner text.Spinner, client api.Interface, serviceID string, ver return spinner.Stop() } -// displayDomain displays a domain from those available in the service. -func displayDomain(apiClient api.Interface, serviceID string, serviceVersion int, out io.Writer) { - latestDomains, err := apiClient.ListDomains(&fastly.ListDomainsInput{ - ServiceID: serviceID, - ServiceVersion: serviceVersion, - }) - if err == nil { - name := latestDomains[0].Name - if segs := strings.Split(name, "*."); len(segs) > 1 { - name = segs[1] - } - text.Description(out, "View this service at", fmt.Sprintf("https://%s", name)) - } -} - func constructSetupObjects( newService bool, serviceID string, @@ -1115,3 +1111,71 @@ func processService(c *DeployCommand, serviceID string, serviceVersion int, spin spinner.StopMessage(msg) return spinner.Stop() } + +func getServiceDomain(apiClient api.Interface, serviceID string, serviceVersion int) (string, error) { + latestDomains, err := apiClient.ListDomains(&fastly.ListDomainsInput{ + ServiceID: serviceID, + ServiceVersion: serviceVersion, + }) + if err != nil { + return "", err + } + name := latestDomains[0].Name + if segs := strings.Split(name, "*."); len(segs) > 1 { + name = segs[1] + } + return name, nil +} + +// checkingServiceAvailability pings the service URL until either there is a 200 +// OK or if the configured timeout is exceeded. +func checkingServiceAvailability(serviceURL string, spinner text.Spinner) error { + err := spinner.Start() + if err != nil { + return err + } + msg := "Checking service availability" + spinner.Message(msg + "...") + + timeout := time.After(2 * time.Minute) + ticker := time.NewTicker(1 * time.Second) + defer func() { ticker.Stop() }() + + // Keep trying until we're timed out, got a result or got an error + for { + select { + case <-timeout: + spinner.StopFailMessage(msg) + spinErr := spinner.StopFail() + if spinErr != nil { + return spinErr + } + return errors.New("service unavailable") + case <-ticker.C: + ok, err := pingServiceURL(serviceURL) + if err != nil { + spinner.StopFailMessage(msg) + spinErr := spinner.StopFail() + if spinErr != nil { + return spinErr + } + return err + } else if ok { + spinner.StopMessage(msg) + return spinner.Stop() + } + // Service not available, and no error, so jump back to top of loop + } + } +} + +func pingServiceURL(serviceURL string) (ok bool, err error) { + resp, err := http.Get(serviceURL) + if err != nil { + return false, err + } + if resp.StatusCode == http.StatusOK { + return true, nil + } + return false, nil +} From d053857278bffb418738709b879fe34b602cb924 Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 16:04:42 +0000 Subject: [PATCH 2/9] fix(compute/deploy): only display info log if there are setup items --- pkg/commands/compute/deploy.go | 2 +- pkg/manifest/manifest.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index c93dc77a1..e496b8bb2 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -974,7 +974,7 @@ func processSetupCreation( out io.Writer, ) error { // NOTE: We need to output this message immediately to avoid breaking prompt. - if newService { + if newService && c.Manifest.File.Setup.Defined() { text.Info(out, "Processing of the fastly.toml [setup] configuration happens only when there is no existing service. Once a service is created, any further changes to the service or its resources must be made manually.") text.Break(out) } diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index f81728165..d45da7462 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -224,6 +224,26 @@ type Setup struct { ObjectStores map[string]*SetupObjectStore `toml:"object_stores,omitempty"` } +// Defined indicates if there is any [setup] configuration in the manifest. +func (s Setup) Defined() bool { + var defined bool + + if len(s.Backends) > 0 { + defined = true + } + if len(s.Dictionaries) > 0 { + defined = true + } + if len(s.Loggers) > 0 { + defined = true + } + if len(s.ObjectStores) > 0 { + defined = true + } + + return defined +} + // SetupBackend represents a '[setup.backends.]' instance. type SetupBackend struct { Address string `toml:"address,omitempty"` From f861987ba938496183681e96d7a07270d0c25c56 Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 16:05:07 +0000 Subject: [PATCH 3/9] fix(compute/setup): spinner ellipsis should be removed after start --- pkg/commands/compute/setup/backend.go | 4 ++-- pkg/commands/compute/setup/dictionary.go | 8 ++++---- pkg/commands/compute/setup/domain.go | 4 ++-- pkg/commands/compute/setup/object_store.go | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/commands/compute/setup/backend.go b/pkg/commands/compute/setup/backend.go index 09a386921..6b2220860 100644 --- a/pkg/commands/compute/setup/backend.go +++ b/pkg/commands/compute/setup/backend.go @@ -66,14 +66,14 @@ func (b *Backends) Create() error { // Avoids range-loop variable issue (i.e. var is reused across iterations). bk := bk - msg := fmt.Sprintf("Creating backend '%s' (host: %s, port: %d)...", bk.Name, bk.Address, bk.Port) + msg := fmt.Sprintf("Creating backend '%s' (host: %s, port: %d)", bk.Name, bk.Address, bk.Port) if !b.isOriginless() { err := b.Spinner.Start() if err != nil { return err } - b.Spinner.Message(msg) + b.Spinner.Message(msg + "...") } opts := &fastly.CreateBackendInput{ diff --git a/pkg/commands/compute/setup/dictionary.go b/pkg/commands/compute/setup/dictionary.go index 1a00c28fe..ce5642c6a 100644 --- a/pkg/commands/compute/setup/dictionary.go +++ b/pkg/commands/compute/setup/dictionary.go @@ -119,8 +119,8 @@ func (d *Dictionaries) Create() error { if err != nil { return err } - msg := fmt.Sprintf("Creating dictionary '%s'...", dictionary.Name) - d.Spinner.Message(msg) + msg := fmt.Sprintf("Creating dictionary '%s'", dictionary.Name) + d.Spinner.Message(msg + "...") dict, err := d.APIClient.CreateDictionary(&fastly.CreateDictionaryInput{ ServiceID: d.ServiceID, @@ -148,8 +148,8 @@ func (d *Dictionaries) Create() error { if err != nil { return err } - msg := fmt.Sprintf("Creating dictionary item '%s'...", item.Key) - d.Spinner.Message(msg) + msg := fmt.Sprintf("Creating dictionary item '%s'", item.Key) + d.Spinner.Message(msg + "...") _, err = d.APIClient.CreateDictionaryItem(&fastly.CreateDictionaryItemInput{ ServiceID: d.ServiceID, diff --git a/pkg/commands/compute/setup/domain.go b/pkg/commands/compute/setup/domain.go index 2b06e7a14..a63e20d7d 100644 --- a/pkg/commands/compute/setup/domain.go +++ b/pkg/commands/compute/setup/domain.go @@ -153,8 +153,8 @@ func (d *Domains) createDomain(name string, attempt int) error { if err != nil { return err } - msg := fmt.Sprintf("Creating domain '%s'...", name) - d.Spinner.Message(msg) + msg := fmt.Sprintf("Creating domain '%s'", name) + d.Spinner.Message(msg + "...") _, err = d.APIClient.CreateDomain(&fastly.CreateDomainInput{ ServiceID: d.ServiceID, diff --git a/pkg/commands/compute/setup/object_store.go b/pkg/commands/compute/setup/object_store.go index 61b3b795a..2a9085a74 100644 --- a/pkg/commands/compute/setup/object_store.go +++ b/pkg/commands/compute/setup/object_store.go @@ -117,8 +117,8 @@ func (o *ObjectStores) Create() error { if err != nil { return err } - msg := fmt.Sprintf("Creating object store '%s'...", objectStore.Name) - o.Spinner.Message(msg) + msg := fmt.Sprintf("Creating object store '%s'", objectStore.Name) + o.Spinner.Message(msg + "...") store, err := o.APIClient.CreateObjectStore(&fastly.CreateObjectStoreInput{ Name: objectStore.Name, From 1da77a9432178c2d9e9a8b83c5de86987223d1b3 Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 17:12:31 +0000 Subject: [PATCH 4/9] fix(lint): ignore gosec issue --- pkg/commands/compute/deploy.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index e496b8bb2..0f2fa0d17 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -1170,6 +1170,10 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner) error } func pingServiceURL(serviceURL string) (ok bool, err error) { + // gosec flagged this: + // G107 (CWE-88): Potential HTTP request made with variable url + // Disabling as we trust the source of the variable. + // #nosec resp, err := http.Get(serviceURL) if err != nil { return false, err From 2466d2b116fd278d4fe15ee744993e13def284c8 Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 20:08:32 +0000 Subject: [PATCH 5/9] tests(compute/deploy): mock the HTTP client --- pkg/commands/compute/deploy.go | 15 +- pkg/commands/compute/deploy_test.go | 372 ++++++++++++++++++++++++++-- pkg/mock/client.go | 16 +- 3 files changed, 371 insertions(+), 32 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 0f2fa0d17..446ee51a5 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -167,7 +167,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { serviceURL := fmt.Sprintf("https://%s", domain) - if err := checkingServiceAvailability(serviceURL, spinner); err != nil { + if err := checkingServiceAvailability(serviceURL, spinner, c.Globals.HTTPClient); err != nil { return err } @@ -1129,7 +1129,7 @@ func getServiceDomain(apiClient api.Interface, serviceID string, serviceVersion // checkingServiceAvailability pings the service URL until either there is a 200 // OK or if the configured timeout is exceeded. -func checkingServiceAvailability(serviceURL string, spinner text.Spinner) error { +func checkingServiceAvailability(serviceURL string, spinner text.Spinner, httpClient api.HTTPClient) error { err := spinner.Start() if err != nil { return err @@ -1152,7 +1152,7 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner) error } return errors.New("service unavailable") case <-ticker.C: - ok, err := pingServiceURL(serviceURL) + ok, err := pingServiceURL(serviceURL, httpClient) if err != nil { spinner.StopFailMessage(msg) spinErr := spinner.StopFail() @@ -1169,12 +1169,17 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner) error } } -func pingServiceURL(serviceURL string) (ok bool, err error) { +func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, err error) { + req, err := http.NewRequest("GET", serviceURL, nil) + if err != nil { + return false, err + } + // gosec flagged this: // G107 (CWE-88): Potential HTTP request made with variable url // Disabling as we trust the source of the variable. // #nosec - resp, err := http.Get(serviceURL) + resp, err := httpClient.Do(req) if err != nil { return false, err } diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index 6611141ce..3865e85c5 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -80,11 +80,16 @@ func TestDeploy(t *testing.T) { originalPackageSizeLimit := compute.PackageSizeLimit args := testutil.Args scenarios := []struct { - api mock.API - args []string - dontWantOutput []string - httpClientRes *http.Response - httpClientErr error + 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. + httpClientRes []*http.Response + httpClientErr []error manifest string name string noManifest bool @@ -124,6 +129,16 @@ 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 }, @@ -145,6 +160,16 @@ 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 }, @@ -261,12 +286,16 @@ func TestDeploy(t *testing.T) { CreateServiceFn: createServiceErrorNoTrial, GetCurrentUserFn: getCurrentUser, }, - httpClientRes: &http.Response{ - Body: io.NopCloser(strings.NewReader(testutil.Err.Error())), - Status: http.StatusText(http.StatusBadRequest), - StatusCode: http.StatusBadRequest, + httpClientRes: []*http.Response{ + { + Body: io.NopCloser(strings.NewReader(testutil.Err.Error())), + Status: http.StatusText(http.StatusBadRequest), + StatusCode: http.StatusBadRequest, + }, + }, + httpClientErr: []error{ + nil, }, - httpClientErr: nil, stdin: []string{ "Y", // when prompted to create a new service }, @@ -285,8 +314,12 @@ func TestDeploy(t *testing.T) { CreateServiceFn: createServiceErrorNoTrial, GetCurrentUserFn: getCurrentUser, }, - httpClientRes: nil, - httpClientErr: &url.Error{Err: context.DeadlineExceeded}, + httpClientRes: []*http.Response{ + nil, + }, + httpClientErr: []error{ + &url.Error{Err: context.DeadlineExceeded}, + }, stdin: []string{ "Y", // when prompted to create a new service }, @@ -309,12 +342,16 @@ 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, + httpClientRes: []*http.Response{ + { + Body: io.NopCloser(strings.NewReader("success")), + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + }, + }, + httpClientErr: []error{ + nil, }, - httpClientErr: nil, stdin: []string{ "Y", // when prompted to create a new service }, @@ -452,6 +489,16 @@ 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 version", @@ -474,6 +521,16 @@ 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 version", @@ -500,6 +557,16 @@ 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:", @@ -524,6 +591,16 @@ 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 version", @@ -543,6 +620,16 @@ 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 version", @@ -562,6 +649,16 @@ 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 version", @@ -582,6 +679,16 @@ 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 version", @@ -600,10 +707,21 @@ func TestDeploy(t *testing.T) { CreateBackendFn: createBackendOK, CreateDomainFn: createDomainOK, CreateServiceFn: createServiceOK, + DeleteServiceFn: deleteServiceOK, 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, + }, manifest: ` name = "package" manifest_version = 2 @@ -644,10 +762,21 @@ func TestDeploy(t *testing.T) { CreateBackendFn: createBackendOK, CreateDomainFn: createDomainOK, CreateServiceFn: createServiceOK, + DeleteServiceFn: deleteServiceOK, 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, + }, manifest: ` name = "package" manifest_version = 2 @@ -685,10 +814,21 @@ func TestDeploy(t *testing.T) { CreateBackendFn: createBackendOK, CreateDomainFn: createDomainOK, CreateServiceFn: createServiceOK, + DeleteServiceFn: deleteServiceOK, 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, + }, manifest: ` name = "package" manifest_version = 2 @@ -732,6 +872,16 @@ 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 @@ -780,6 +930,16 @@ 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)", }, @@ -799,6 +959,16 @@ 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 @@ -830,10 +1000,20 @@ 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 - "fastly.com", + "Y", // when prompted to create a new service + "foobar", // when prompted for service name + "fastly.com", // when prompted for a backend "443", "", // this is so we generate a backend name using a built-in formula "google.com", @@ -865,6 +1045,16 @@ 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 @@ -894,6 +1084,16 @@ 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)", }, @@ -921,6 +1121,16 @@ 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 @@ -963,6 +1173,16 @@ 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 @@ -1008,6 +1228,16 @@ 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 @@ -1057,6 +1287,16 @@ 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 @@ -1100,6 +1340,16 @@ 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 @@ -1147,6 +1397,16 @@ 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 @@ -1185,6 +1445,16 @@ 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 @@ -1224,6 +1494,16 @@ 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 @@ -1264,6 +1544,16 @@ 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 @@ -1301,6 +1591,16 @@ 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 @@ -1347,6 +1647,16 @@ 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 @@ -1394,6 +1704,16 @@ 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 @@ -1438,6 +1758,16 @@ 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 @@ -1555,7 +1885,7 @@ func TestDeploy(t *testing.T) { select { case <-done: // Wait for app.Run() to finish - case <-time.After(time.Second): + case <-time.After(5 * time.Second): t.Fatalf("unexpected timeout waiting for mocked prompt inputs to be processed") } } else { diff --git a/pkg/mock/client.go b/pkg/mock/client.go index 7518b4725..39c009ea0 100644 --- a/pkg/mock/client.go +++ b/pkg/mock/client.go @@ -15,19 +15,23 @@ func APIClient(a API) func(string, string) (api.Interface, error) { } type mockHTTPClient struct { - res *http.Response - err error + // index keeps track of which response/error to return + index int + res []*http.Response + err []error } func (c mockHTTPClient) Do(_ *http.Request) (*http.Response, error) { - return c.res, c.err + c.index++ + return c.res[c.index], c.err[c.index] } // HTMLClient returns a mock HTTP Client that returns a stubbed response or // error. -func HTMLClient(res *http.Response, err error) api.HTTPClient { +func HTMLClient(res []*http.Response, err []error) api.HTTPClient { return mockHTTPClient{ - res: res, - err: err, + index: -1, + res: res, + err: err, } } From 699e342bd44ca3803bd73734aa70e77ee7f3dfc6 Mon Sep 17 00:00:00 2001 From: Integralist Date: Tue, 7 Mar 2023 20:32:26 +0000 Subject: [PATCH 6/9] fix(compute/deploy): check for non-5xx --- pkg/commands/compute/deploy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 446ee51a5..f4d41e191 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -1169,6 +1169,8 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner, httpCl } } +// pingServiceURL indicates if the service returned a non-5xx response, which +// should help signify if the service is generally available. func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, err error) { req, err := http.NewRequest("GET", serviceURL, nil) if err != nil { @@ -1183,7 +1185,7 @@ func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, err if err != nil { return false, err } - if resp.StatusCode == http.StatusOK { + if resp.StatusCode < http.StatusInternalServerError { return true, nil } return false, nil From 8d75746da3893a47e20decb784902b397e5d67c7 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 8 Mar 2023 11:18:36 +0000 Subject: [PATCH 7/9] fix(compute/deploy): better remediation message --- pkg/commands/compute/deploy.go | 69 ++++++++++++++++++---------- pkg/commands/compute/publish.go | 3 -- pkg/commands/compute/setup/domain.go | 5 ++ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index f4d41e191..142c763eb 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -127,7 +127,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { if err := processSetupConfig( newService, domains, backends, dictionaries, loggers, objectStores, - serviceID, serviceVersion.Number, c, out, + serviceID, serviceVersion.Number, c, ); err != nil { return err } @@ -167,10 +167,15 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { serviceURL := fmt.Sprintf("https://%s", domain) - if err := checkingServiceAvailability(serviceURL, spinner, c.Globals.HTTPClient); err != nil { + var status int + if status, err = checkingServiceAvailability(serviceURL, spinner, c.Globals.HTTPClient); err != nil { return err } + if status >= http.StatusBadRequest { + text.Info(out, "The service path `/` responded with a non-successful status code (%d). Please check your application code if this is an unexpected status code.", status) + } + text.Break(out) text.Description(out, "Manage this service at", fmt.Sprintf("%s%s", manageServiceBaseURL, serviceID)) text.Description(out, "View this service at", serviceURL) @@ -520,7 +525,6 @@ func manageNoServiceIDFlow( } } - text.Break(out) return serviceID, serviceVersion, nil } @@ -905,7 +909,6 @@ func processSetupConfig( serviceID string, serviceVersion int, c *DeployCommand, - out io.Writer, ) (err error) { if domains.Missing() { err = domains.Configure() @@ -956,8 +959,6 @@ func processSetupConfig( } } - text.Break(out) - return nil } @@ -1129,10 +1130,14 @@ func getServiceDomain(apiClient api.Interface, serviceID string, serviceVersion // checkingServiceAvailability pings the service URL until either there is a 200 // OK or if the configured timeout is exceeded. -func checkingServiceAvailability(serviceURL string, spinner text.Spinner, httpClient api.HTTPClient) error { - err := spinner.Start() +func checkingServiceAvailability( + serviceURL string, + spinner text.Spinner, + httpClient api.HTTPClient, +) (status int, err error) { + err = spinner.Start() if err != nil { - return err + return 0, err } msg := "Checking service availability" spinner.Message(msg + "...") @@ -1141,28 +1146,46 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner, httpCl ticker := time.NewTicker(1 * time.Second) defer func() { ticker.Stop() }() + remediation := "The service has been successfully deployed and activated, but the service 'availability' check %s (last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than expected to deploy across our global range of servers. Please continue to check the service URL and if still unavailable please contact Fastly support." + // Keep trying until we're timed out, got a result or got an error for { select { case <-timeout: - spinner.StopFailMessage(msg) + returnedStatus := fmt.Sprintf(" (status: %d)", status) + spinner.StopFailMessage(msg + returnedStatus) spinErr := spinner.StopFail() if spinErr != nil { - return spinErr + return status, spinErr + } + return status, fsterr.RemediationError{ + Inner: errors.New("service unavailable"), + Remediation: fmt.Sprintf(remediation, "timed out", status), } - return errors.New("service unavailable") case <-ticker.C: - ok, err := pingServiceURL(serviceURL, httpClient) + var ( + ok bool + err error + ) + // We overwrite the `status` variable in the parent scope (defined in the + // return arguments list) so it can be used as part of both the timeout + // and success scenarios. + ok, status, err = pingServiceURL(serviceURL, httpClient) if err != nil { - spinner.StopFailMessage(msg) + returnedStatus := fmt.Sprintf(" (status: %d)", status) + spinner.StopFailMessage(msg + returnedStatus) spinErr := spinner.StopFail() if spinErr != nil { - return spinErr + return status, spinErr + } + return status, fsterr.RemediationError{ + Inner: err, + Remediation: fmt.Sprintf(remediation, "failed", status), } - return err } else if ok { - spinner.StopMessage(msg) - return spinner.Stop() + returnedStatus := fmt.Sprintf(" (status: %d)", status) + spinner.StopMessage(msg + returnedStatus) + return status, spinner.Stop() } // Service not available, and no error, so jump back to top of loop } @@ -1171,10 +1194,10 @@ func checkingServiceAvailability(serviceURL string, spinner text.Spinner, httpCl // pingServiceURL indicates if the service returned a non-5xx response, which // should help signify if the service is generally available. -func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, err error) { +func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, status int, err error) { req, err := http.NewRequest("GET", serviceURL, nil) if err != nil { - return false, err + return false, 0, err } // gosec flagged this: @@ -1183,10 +1206,10 @@ func pingServiceURL(serviceURL string, httpClient api.HTTPClient) (ok bool, err // #nosec resp, err := httpClient.Do(req) if err != nil { - return false, err + return false, 0, err } if resp.StatusCode < http.StatusInternalServerError { - return true, nil + return true, resp.StatusCode, nil } - return false, nil + return false, resp.StatusCode, nil } diff --git a/pkg/commands/compute/publish.go b/pkg/commands/compute/publish.go index ae275122d..bfb19f5f4 100644 --- a/pkg/commands/compute/publish.go +++ b/pkg/commands/compute/publish.go @@ -6,7 +6,6 @@ import ( "github.com/fastly/cli/pkg/cmd" "github.com/fastly/cli/pkg/global" "github.com/fastly/cli/pkg/manifest" - "github.com/fastly/cli/pkg/text" ) // PublishCommand produces and deploys an artifact from files on the local disk. @@ -97,8 +96,6 @@ func (c *PublishCommand) Exec(in io.Reader, out io.Writer) (err error) { return err } - text.Break(out) - // Reset the fields on the DeployCommand based on PublishCommand values. if c.pkg.WasSet { c.deploy.Package = c.pkg.Value diff --git a/pkg/commands/compute/setup/domain.go b/pkg/commands/compute/setup/domain.go index a63e20d7d..fe7837ce2 100644 --- a/pkg/commands/compute/setup/domain.go +++ b/pkg/commands/compute/setup/domain.go @@ -66,6 +66,7 @@ func (d *Domains) Configure() error { err error ) if !d.AcceptDefaults && !d.NonInteractive { + text.Break(d.Stdout) domain, err = text.Input(d.Stdout, text.BoldYellow(fmt.Sprintf("Domain: [%s] ", defaultDomain)), d.Stdin, d.validateDomain) if err != nil { return fmt.Errorf("error reading input %w", err) @@ -149,6 +150,10 @@ func (d *Domains) validateDomain(input string) error { } func (d *Domains) createDomain(name string, attempt int) error { + if !d.AcceptDefaults && !d.NonInteractive { + text.Break(d.Stdout) + } + err := d.Spinner.Start() if err != nil { return err From 10d18942dc7adc500f53e132ad598fa6d3648ccd Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 8 Mar 2023 14:16:12 +0000 Subject: [PATCH 8/9] fix(compute/deploy): add status check overrides --- pkg/app/run.go | 10 +++--- pkg/commands/compute/deploy.go | 56 +++++++++++++++++++++++---------- pkg/commands/compute/publish.go | 28 ++++++++++++++--- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/pkg/app/run.go b/pkg/app/run.go index dbaa65a04..49ef151f7 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -79,12 +79,10 @@ func Run(opts RunOpts) error { // the globalFlags map in pkg/app/usage.go which is used for usage rendering. // You should also update `IsGlobalFlagsOnly` in ../cmd/cmd.go // - // NOTE: Global flags, unlike command flags, must be unique. This means BOTH - // the long flag and the short flag identifiers must be unique. If you try to - // reuse an identifier (long or short), then kingpin will trigger a runtime - // panic 🎉 - // - // NOTE: Short flags CAN be safely reused across commands. + // NOTE: Global flags (long and short) MUST be unique. + // A subcommand can't define a flag that is already global. + // Kingpin will otherwise trigger a runtime panic 🎉 + // Interestingly, short flags can be reused but only across subcommands. tokenHelp := fmt.Sprintf("Fastly API token (or via %s)", env.Token) app.Flag("accept-defaults", "Accept default options for all interactive prompts apart from Yes/No confirmations").Short('d').BoolVar(&g.Flags.AcceptDefaults) app.Flag("auto-yes", "Answer yes automatically to all Yes/No confirmations. This may suppress security warnings").Short('y').BoolVar(&g.Flags.AutoYes) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 142c763eb..18b95d364 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -44,12 +44,16 @@ type DeployCommand struct { // NOTE: these are public so that the "publish" composite command can set the // values appropriately before calling the Exec() function. - Comment cmd.OptionalString - Domain string - Manifest manifest.Data - Package string - ServiceName cmd.OptionalServiceNameID - ServiceVersion cmd.OptionalServiceVersion + Comment cmd.OptionalString + Domain string + Manifest manifest.Data + Package string + ServiceName cmd.OptionalServiceNameID + ServiceVersion cmd.OptionalServiceVersion + StatusCheckCode int + StatusCheckOff bool + StatusCheckPath string + StatusCheckTimeout int } // NewDeployCommand returns a usable command registered under the parent. @@ -82,6 +86,10 @@ func NewDeployCommand(parent cmd.Registerer, g *global.Data, m manifest.Data) *D c.CmdClause.Flag("comment", "Human-readable comment").Action(c.Comment.Set).StringVar(&c.Comment.Value) c.CmdClause.Flag("domain", "The name of the domain associated to the package").StringVar(&c.Domain) c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').StringVar(&c.Package) + c.CmdClause.Flag("status-check-code", "Set the expected status response for the service availability check").IntVar(&c.StatusCheckCode) + c.CmdClause.Flag("status-check-off", "Disable the service availability check").BoolVar(&c.StatusCheckOff) + c.CmdClause.Flag("status-check-path", "Specify the URL path for the service availability check").Default("/").StringVar(&c.StatusCheckPath) + c.CmdClause.Flag("status-check-timeout", "Set a timeout (in seconds) for the service availability check").Default("120").IntVar(&c.StatusCheckTimeout) return &c } @@ -167,13 +175,29 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { serviceURL := fmt.Sprintf("https://%s", domain) - var status int - if status, err = checkingServiceAvailability(serviceURL, spinner, c.Globals.HTTPClient); err != nil { - return err + if c.Globals.Verbose() { + text.Info(out, "Checking service availability for: %s", serviceURL+c.StatusCheckPath) } - if status >= http.StatusBadRequest { - text.Info(out, "The service path `/` responded with a non-successful status code (%d). Please check your application code if this is an unexpected status code.", status) + if !c.StatusCheckOff { + var status int + if status, err = checkingServiceAvailability(serviceURL+c.StatusCheckPath, spinner, c); err != nil { + if re, ok := err.(fsterr.RemediationError); ok { + text.Warning(out, re.Remediation) + } + } + + // Because the service availability can return an error (which we ignore), + // then we need to check for the 'no error' scenarios. + if err == nil { + if c.StatusCheckCode > 0 && status != c.StatusCheckCode { + // If the user set a specific status code expectation... + text.Warning(out, "The service path `%s` responded with a status code (%d) that didn't match what was expected (%d).", c.StatusCheckPath, status, c.StatusCheckCode) + } else if c.StatusCheckCode == 0 && status >= http.StatusBadRequest { + // If no status code was specified, and the actual status response was an error... + text.Info(out, "The service path `%s` responded with a non-successful status code (%d). Please check your application code if this is an unexpected response.", c.StatusCheckPath, status) + } + } } text.Break(out) @@ -1133,7 +1157,7 @@ func getServiceDomain(apiClient api.Interface, serviceID string, serviceVersion func checkingServiceAvailability( serviceURL string, spinner text.Spinner, - httpClient api.HTTPClient, + c *DeployCommand, ) (status int, err error) { err = spinner.Start() if err != nil { @@ -1142,11 +1166,11 @@ func checkingServiceAvailability( msg := "Checking service availability" spinner.Message(msg + "...") - timeout := time.After(2 * time.Minute) + timeout := time.After(time.Duration(c.StatusCheckTimeout) * time.Second) ticker := time.NewTicker(1 * time.Second) defer func() { ticker.Stop() }() - remediation := "The service has been successfully deployed and activated, but the service 'availability' check %s (last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than expected to deploy across our global range of servers. Please continue to check the service URL and if still unavailable please contact Fastly support." + remediation := "The service has been successfully deployed and activated, but our service 'availability' check %s (last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than usual to deploy across our global network. Please continue to check the service URL and if still unavailable please contact Fastly support." // Keep trying until we're timed out, got a result or got an error for { @@ -1159,7 +1183,7 @@ func checkingServiceAvailability( return status, spinErr } return status, fsterr.RemediationError{ - Inner: errors.New("service unavailable"), + Inner: errors.New("service not yet available"), Remediation: fmt.Sprintf(remediation, "timed out", status), } case <-ticker.C: @@ -1170,7 +1194,7 @@ func checkingServiceAvailability( // We overwrite the `status` variable in the parent scope (defined in the // return arguments list) so it can be used as part of both the timeout // and success scenarios. - ok, status, err = pingServiceURL(serviceURL, httpClient) + ok, status, err = pingServiceURL(serviceURL, c.Globals.HTTPClient) if err != nil { returnedStatus := fmt.Sprintf(" (status: %d)", status) spinner.StopFailMessage(msg + returnedStatus) diff --git a/pkg/commands/compute/publish.go b/pkg/commands/compute/publish.go index bfb19f5f4..0f5f5656e 100644 --- a/pkg/commands/compute/publish.go +++ b/pkg/commands/compute/publish.go @@ -22,11 +22,15 @@ type PublishCommand struct { timeout cmd.OptionalInt // Deploy fields - comment cmd.OptionalString - domain cmd.OptionalString - pkg cmd.OptionalString - serviceName cmd.OptionalServiceNameID - serviceVersion cmd.OptionalServiceVersion + comment cmd.OptionalString + domain cmd.OptionalString + pkg cmd.OptionalString + serviceName cmd.OptionalServiceNameID + serviceVersion cmd.OptionalServiceVersion + statusCheckCode int + statusCheckOff bool + statusCheckPath string + statusCheckTimeout int } // NewPublishCommand returns a usable command registered under the parent. @@ -56,6 +60,10 @@ func NewPublishCommand(parent cmd.Registerer, g *global.Data, build *BuildComman Description: cmd.FlagServiceDesc, Dst: &c.serviceName.Value, }) + c.CmdClause.Flag("status-check-code", "Set the expected status response for the service availability check to the root path").IntVar(&c.statusCheckCode) + c.CmdClause.Flag("status-check-off", "Disable the service availability check").BoolVar(&c.statusCheckOff) + c.CmdClause.Flag("status-check-path", "Specify the URL path for the service availability check").Default("/").StringVar(&c.statusCheckPath) + c.CmdClause.Flag("status-check-timeout", "Set a timeout (in seconds) for the service availability check").Default("120").IntVar(&c.statusCheckTimeout) c.RegisterFlag(cmd.StringFlagOpts{ Name: cmd.FlagVersionName, Description: cmd.FlagVersionDesc, @@ -113,6 +121,16 @@ func (c *PublishCommand) Exec(in io.Reader, out io.Writer) (err error) { c.deploy.Comment = c.comment } c.deploy.Manifest = c.manifest + if c.statusCheckCode > 0 { + c.deploy.StatusCheckCode = c.statusCheckCode + } + if c.statusCheckOff { + c.deploy.StatusCheckOff = c.statusCheckOff + } + if c.statusCheckTimeout > 0 { + c.deploy.StatusCheckTimeout = c.statusCheckTimeout + } + c.deploy.StatusCheckPath = c.statusCheckPath err = c.deploy.Exec(in, out) if err != nil { From b448a441bd77f1b112cce01060c23f21938b24ed Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 8 Mar 2023 14:45:08 +0000 Subject: [PATCH 9/9] refactor(compute/deploy): tweak messaging --- pkg/commands/compute/deploy.go | 6 ++-- pkg/commands/compute/deploy_test.go | 50 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 18b95d364..ab17ebba3 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -1112,7 +1112,7 @@ func processService(c *DeployCommand, serviceID string, serviceVersion int, spin if err != nil { return err } - msg := "Activating version" + msg := fmt.Sprintf("Activating service (version %d)", serviceVersion) spinner.Message(msg + "...") _, err = c.Globals.APIClient.ActivateVersion(&fastly.ActivateVersionInput{ @@ -1164,13 +1164,13 @@ func checkingServiceAvailability( return 0, err } msg := "Checking service availability" - spinner.Message(msg + "...") + spinner.Message(msg + " (app is being deployed across Fastly's global network)...") timeout := time.After(time.Duration(c.StatusCheckTimeout) * time.Second) ticker := time.NewTicker(1 * time.Second) defer func() { ticker.Stop() }() - remediation := "The service has been successfully deployed and activated, but our service 'availability' check %s (last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than usual to deploy across our global network. Please continue to check the service URL and if still unavailable please contact Fastly support." + remediation := "The service has been successfully deployed and activated, but the service 'availability' check %s (last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than usual to deploy across our global network. Please continue to check the service URL and if still unavailable please contact Fastly support." // Keep trying until we're timed out, got a result or got an error for { diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index 3865e85c5..e421e311a 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -456,7 +456,7 @@ func TestDeploy(t *testing.T) { wantError: "error activating version: test error", wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", }, }, // The following test validates that if a package contains code that has @@ -501,7 +501,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Manage this service at:", "https://manage.fastly.com/configure/services/123", "View this service at:", @@ -533,7 +533,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Manage this service at:", "https://manage.fastly.com/configure/services/123", "View this service at:", @@ -571,7 +571,7 @@ func TestDeploy(t *testing.T) { wantOutput: []string{ "Using fastly.toml within --package archive:", "Uploading package", - "Activating version", + "Activating service", "Manage this service at:", "https://manage.fastly.com/configure/services/123", "View this service at:", @@ -603,7 +603,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Deployed package (service 123, version 3)", }, }, @@ -632,7 +632,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Deployed package (service 123, version 4)", }, }, @@ -661,7 +661,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Deployed package (service 123, version 4)", }, }, @@ -691,7 +691,7 @@ func TestDeploy(t *testing.T) { }, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "Deployed package (service 123, version 4)", }, }, @@ -748,7 +748,7 @@ func TestDeploy(t *testing.T) { "Creating backend 'backend_name' (host: developer.fastly.com, port: 443)", "Creating backend 'other_backend_name' (host: httpbin.org, port: 443)", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -799,7 +799,7 @@ func TestDeploy(t *testing.T) { "Creating backend 'foo_backend' (host: developer.fastly.com, port: 80)", "Creating backend 'bar_backend' (host: httpbin.org, port: 80)", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, dontWantOutput: []string{ @@ -851,7 +851,7 @@ func TestDeploy(t *testing.T) { "Creating backend 'foo_backend' (host: 127.0.0.1, port: 80)", "Creating backend 'bar_backend' (host: 127.0.0.1, port: 80)", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, dontWantOutput: []string{ @@ -901,7 +901,7 @@ func TestDeploy(t *testing.T) { "Creating backend 'backend_name' (host: developer.fastly.com, port: 443)", "Creating backend 'other_backend_name' (host: httpbin.org, port: 443)", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, dontWantOutput: []string{ @@ -1151,7 +1151,7 @@ func TestDeploy(t *testing.T) { `, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 123, version 4)", }, dontWantOutput: []string{ @@ -1199,7 +1199,7 @@ func TestDeploy(t *testing.T) { `, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 123, version 4)", }, dontWantOutput: []string{ @@ -1266,7 +1266,7 @@ func TestDeploy(t *testing.T) { "Creating dictionary item 'foo'", "Creating dictionary item 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1319,7 +1319,7 @@ func TestDeploy(t *testing.T) { "Creating dictionary item 'foo'", "Creating dictionary item 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1370,7 +1370,7 @@ func TestDeploy(t *testing.T) { "Creating dictionary item 'foo'", "Creating dictionary item 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, // The following are predefined values for the `description` and `value` @@ -1417,7 +1417,7 @@ func TestDeploy(t *testing.T) { `, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 123, version 4)", }, dontWantOutput: []string{ @@ -1473,7 +1473,7 @@ func TestDeploy(t *testing.T) { "Refer to the help documentation for each provider (if no provider shown, then select your own):", "fastly logging create --help", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1520,7 +1520,7 @@ func TestDeploy(t *testing.T) { "Refer to the help documentation for each provider (if no provider shown, then select your own):", "fastly logging create --help", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, dontWantOutput: []string{ @@ -1572,7 +1572,7 @@ func TestDeploy(t *testing.T) { "Refer to the help documentation for each provider (if no provider shown, then select your own):", "fastly logging create --help", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1617,7 +1617,7 @@ func TestDeploy(t *testing.T) { `, wantOutput: []string{ "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 123, version 4)", }, dontWantOutput: []string{ @@ -1682,7 +1682,7 @@ func TestDeploy(t *testing.T) { "Creating object store key 'foo'", "Creating object store key 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1736,7 +1736,7 @@ func TestDeploy(t *testing.T) { "Creating object store key 'foo'", "Creating object store key 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, }, @@ -1788,7 +1788,7 @@ func TestDeploy(t *testing.T) { "Creating object store key 'foo'", "Creating object store key 'bar'", "Uploading package", - "Activating version", + "Activating service", "SUCCESS: Deployed package (service 12345, version 1)", }, // The following are predefined values for the `description` and `value`