From b294e4c9145893b0433794a425ccfa3589595029 Mon Sep 17 00:00:00 2001 From: Mark McDonnell Date: Thu, 29 Sep 2022 13:41:20 +0100 Subject: [PATCH] Move the internal build scripts to the fastly.toml manifest (#640) * bump go-fastly * remove deprecated toolchain_version * reword Scripts description * add ToolchainValidator type * fix merge conflict 1 * fix tests * go 1.18 * fix conflict of regions in run_test * fix conflict with run_test output needing to be deleted * remove other redundant fields * skip windows running tests that use a subprocess * pass manifest reference so file can be updated with default build script * bad rebase put this back in * add missing public type comment * suggested edits * fix tests * notifier for patched manifest * address Andrew's requested changes * fix some bugs after manual testing a deployment * fix test to work in ci where there is didn't rust version * fix test for windows platform * rename variable * document how examples are added to devhub * rename method --- .fastly/config.toml | 3 - .github/workflows/pr_test.yml | 4 +- .github/workflows/tag_release.yml | 2 +- DOCUMENTATION.md | 7 + README.md | 1 + go.mod | 4 +- go.sum | 4 +- pkg/app/run_test.go | 4943 ----------------- pkg/app/usage.go | 4 + pkg/commands/compute/build.go | 102 +- pkg/commands/compute/build_test.go | 407 +- pkg/commands/compute/compute_mocks_test.go | 34 - pkg/commands/compute/compute_test.go | 164 - pkg/commands/compute/deploy.go | 68 +- pkg/commands/compute/deploy_test.go | 11 +- pkg/commands/compute/init.go | 114 +- pkg/commands/compute/init_test.go | 13 +- pkg/commands/compute/language.go | 30 +- .../compute/language_assemblyscript.go | 187 +- pkg/commands/compute/language_go.go | 429 +- pkg/commands/compute/language_javascript.go | 416 +- pkg/commands/compute/language_other.go | 89 +- pkg/commands/compute/language_rust.go | 801 +-- pkg/commands/compute/language_toolchain.go | 649 ++- pkg/commands/compute/publish.go | 8 - pkg/commands/compute/serve.go | 5 - pkg/commands/compute/testdata/build/go/go.mod | 2 + .../compute/testdata/build/rust/Cargo.toml | 2 +- pkg/config/config.go | 14 - .../config-incompatible-config-version.toml | 5 +- pkg/config/testdata/config.toml | 3 - pkg/config/testdata/static/config.toml | 3 - pkg/errors/errors.go | 2 +- pkg/errors/remediation_error.go | 2 +- pkg/manifest/manifest.go | 243 +- 35 files changed, 1634 insertions(+), 7141 deletions(-) create mode 100644 DOCUMENTATION.md diff --git a/.fastly/config.toml b/.fastly/config.toml index e2bee0aac..ee66084ca 100644 --- a/.fastly/config.toml +++ b/.fastly/config.toml @@ -13,11 +13,8 @@ ttl = "5m" toolchain_constraint = ">= 1.17 < 1.19" # upper limit should be temporary (just while we figure out any rough edges) [language.rust] - toolchain_version = "1.56.1" toolchain_constraint = ">= 1.56.1" wasm_wasi_target = "wasm32-wasi" - fastly_sys_constraint = ">= 0.3.3" - rustup_constraint = ">= 1.23.0" [viceroy] ttl = "24h" diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 1a83120eb..df77ee719 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -15,7 +15,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.19.x + go-version: 1.18.x - name: "Restore golang bin cache" id: go-bin-cache uses: actions/cache@v2 @@ -60,7 +60,7 @@ jobs: strategy: matrix: tinygo-version: [0.24.0] - go-version: [1.19.x] + go-version: [1.18.x] node-version: [12] rust-toolchain: [stable] platform: [ubuntu-latest, macos-latest, windows-latest] diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index f3eb1614e..70418cb94 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -15,7 +15,7 @@ jobs: - name: "Install Go" uses: actions/setup-go@v2 with: - go-version: '1.19.x' + go-version: '1.18.x' - name: "Set GOHOSTOS and GOHOSTARCH" run: echo "GOHOSTOS=$(go env GOHOSTOS)" >> $GITHUB_ENV && echo "GOHOSTARCH=$(go env GOHOSTARCH)" >> $GITHUB_ENV - name: "Download latest app config" diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 000000000..952c69363 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,7 @@ +## Documentation + +The help output from the Fastly CLI is consumed by the Fastly Developer Hub to produce online documentation: https://developer.fastly.com/reference/cli/ + +Part of the documentation is to provide additional usage examples and links to APIs used by the CLI commands (example: https://developer.fastly.com/reference/cli/backend/create/#examples). + +These examples and API references are defined in [`pkg/app/metadata.json`](./pkg/app/metadata.json). diff --git a/README.md b/README.md index 66fed6b7a..1d43d4499 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [Commands](https://developer.fastly.com/reference/cli/#command-groups) - [Development](DEVELOP.md) - [Testing](TESTING.md) +- [Documentation](DOCUMENTATION.md) ## Contributing diff --git a/go.mod b/go.mod index ded567d9f..9b0edaa50 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/fastly/cli -go 1.19 +go 1.18 require ( github.com/Masterminds/semver/v3 v3.1.1 @@ -8,7 +8,7 @@ require ( github.com/bep/debounce v1.2.0 github.com/blang/semver v3.5.1+incompatible github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 - github.com/fastly/go-fastly/v6 v6.5.1 + github.com/fastly/go-fastly/v6 v6.5.2 github.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible github.com/fatih/color v1.13.0 github.com/frankban/quicktest v1.13.1 // indirect diff --git a/go.sum b/go.sum index b7195ec08..c634a3807 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.m github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fastly/go-fastly/v6 v6.5.1 h1:l/E99Kj1V+o3om74lIibAdZRONC0YZnYgWFPrTEVWDA= -github.com/fastly/go-fastly/v6 v6.5.1/go.mod h1:NrIbx45etTFv35rgfRe+eQY+8kA47arWABIkOaQ+roY= +github.com/fastly/go-fastly/v6 v6.5.2 h1:oeHoRoddStqHn1Ukyz+oVxPjvYDBARNr+hP6+HAt4TY= +github.com/fastly/go-fastly/v6 v6.5.2/go.mod h1:NrIbx45etTFv35rgfRe+eQY+8kA47arWABIkOaQ+roY= github.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible h1:FhrXlfhgGCS+uc6YwyiFUt04alnjpoX7vgDKJxS6Qbk= github.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible/go.mod h1:U8UynVoU1SQaqD2I4ZqgYd5lx3A1ipQYn4aSt2Y5h6c= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= diff --git a/pkg/app/run_test.go b/pkg/app/run_test.go index a78005706..4f49daa54 100644 --- a/pkg/app/run_test.go +++ b/pkg/app/run_test.go @@ -13,53 +13,6 @@ import ( "github.com/fastly/cli/pkg/testutil" ) -func TestApplication(t *testing.T) { - args := testutil.Args - scenarios := []testutil.TestScenario{ - { - Name: "no args", - Args: nil, - WantError: helpDefault + "\nERROR: error parsing arguments: command not specified.\n", - }, - { - Name: "help flag only", - Args: args("--help"), - WantError: helpDefault + "\nERROR: error parsing arguments: command not specified.\n", - }, - { - Name: "help argument only", - Args: args("help"), - WantError: fullFatHelpDefault, - }, - { - Name: "help service", - Args: args("help service"), - WantError: helpService, - }, - } - // These tests should only verify the app.Run helper wires things up - // correctly, and check behaviors that can't be associated with a specific - // command or subcommand. Commands should be tested in their packages, - // leveraging the app.Run helper as appropriate. - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(testcase.Name, func(t *testing.T) { - var ( - stdout bytes.Buffer - stderr bytes.Buffer - ) - - opts := testutil.NewRunOpts(testcase.Args, &stdout) - err := app.Run(opts) - if err != nil { - errors.Deduce(err).Print(&stderr) - } - - testutil.AssertString(t, testcase.WantError, stripTrailingSpace(stderr.String())) - }) - } -} - func TestShellCompletion(t *testing.T) { args := testutil.Args scenarios := []testutil.TestScenario{ @@ -184,4899 +137,3 @@ func stripTrailingSpace(str string) string { } return buf.String() } - -var helpDefault = strings.TrimSpace(` -USAGE - fastly [] [ ...] - -A tool to interact with the Fastly API - -GLOBAL FLAGS - --help Show context-sensitive help. - -d, --accept-defaults Accept default options for all interactive prompts - apart from Yes/No confirmations - -y, --auto-yes Answer yes automatically to all Yes/No confirmations. - This may suppress security warnings - -i, --non-interactive Do not prompt for user input - suitable for CI - processes. Equivalent to --accept-defaults and - --auto-yes - -o, --profile=PROFILE Switch account profile for single command execution - (see also: 'fastly profile switch') - -t, --token=TOKEN Fastly API token (or via FASTLY_API_TOKEN) - -v, --verbose Verbose logging - -COMMANDS - help Show help. - acl Manipulate Fastly ACLs (Access Control Lists) - acl-entry Manipulate Fastly ACL (Access Control List) entries - auth-token Manage API tokens for Fastly service users - backend Manipulate Fastly service version backends - compute Manage Compute@Edge packages - config Display the Fastly CLI configuration - dictionary Manipulate Fastly edge dictionaries - dictionary-item Manipulate Fastly edge dictionary items - domain Manipulate Fastly service version domains - healthcheck Manipulate Fastly service version healthchecks - ip-list List Fastly's public IPs - log-tail Tail Compute@Edge logs - logging Manipulate Fastly service version logging endpoints - pops List Fastly datacenters - profile Manage user profiles - purge Invalidate objects in the Fastly cache - service Manipulate Fastly services - service-version Manipulate Fastly service versions - stats View historical and realtime statistics for a Fastly service - tls-config Apply configuration options for each TLS enabled domain - tls-custom Manage custom keys and certs used to enable TLS - tls-platform Manage large numbers of TLS certificates - tls-subscription Generate TLS certificates procured and renewed by Fastly - update Update the CLI to the latest version - user Manipulate users of the Fastly API and web interface - vcl Manipulate Fastly service version VCL - version Display version information for the Fastly CLI - whoami Get information about the currently authenticated account - -SEE ALSO - https://developer.fastly.com/reference/cli/ -`) + "\n\n" - -var helpService = strings.TrimSpace(` -USAGE - fastly [] service - -GLOBAL FLAGS - --help Show context-sensitive help. - -d, --accept-defaults Accept default options for all interactive prompts - apart from Yes/No confirmations - -y, --auto-yes Answer yes automatically to all Yes/No confirmations. - This may suppress security warnings - -i, --non-interactive Do not prompt for user input - suitable for CI - processes. Equivalent to --accept-defaults and - --auto-yes - -o, --profile=PROFILE Switch account profile for single command execution - (see also: 'fastly profile switch') - -t, --token=TOKEN Fastly API token (or via FASTLY_API_TOKEN) - -v, --verbose Verbose logging - -SUBCOMMANDS - - service create --name=NAME [] - Create a Fastly service - - -n, --name=NAME Service name - --type=vcl Service type. Can be one of "wasm" or "vcl", defaults - to "vcl". - --comment=COMMENT Human-readable comment - - service delete [] - Delete a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - -f, --force Force deletion of an active service - - service describe [] - Show detailed information about a Fastly service - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - service list [] - List Fastly services - - --direction=ascend Direction in which to sort results - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort="created" Field on which to sort - - service search --name=NAME - Search for a Fastly service by name - - -n, --name=NAME Service name - - service update [] - Update a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - -n, --name=NAME Service name - --comment=COMMENT Human-readable comment - -SEE ALSO - https://developer.fastly.com/reference/cli/service/ - -`) + "\n\n" - -var fullFatHelpDefault = strings.TrimSpace(` -USAGE - fastly [] - -A tool to interact with the Fastly API - -GLOBAL FLAGS - --help Show context-sensitive help. - -d, --accept-defaults Accept default options for all interactive prompts - apart from Yes/No confirmations - -y, --auto-yes Answer yes automatically to all Yes/No confirmations. - This may suppress security warnings - -i, --non-interactive Do not prompt for user input - suitable for CI - processes. Equivalent to --accept-defaults and - --auto-yes - -o, --profile=PROFILE Switch account profile for single command execution - (see also: 'fastly profile switch') - -t, --token=TOKEN Fastly API token (or via FASTLY_API_TOKEN) - -v, --verbose Verbose logging - -COMMANDS - help [ ...] - Show help. - - - acl create --name=NAME --version=VERSION [] - Create a new ACL attached to the specified service version - - --name=NAME Name for the ACL. Must start with an - alphanumeric character and contain only - alphanumeric characters, underscores, - and whitespace - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl delete --name=NAME --version=VERSION [] - Delete an ACL from the specified service version - - --name=NAME The name of the ACL to delete - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl describe --name=NAME --version=VERSION [] - Retrieve a single ACL by name for the version and service - - --name=NAME The name of the ACL - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl list --version=VERSION [] - List ACLs - - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl update --name=NAME --new-name=NEW-NAME --version=VERSION [] - Update an ACL for a particular service and version - - --name=NAME The name of the ACL to update - --new-name=NEW-NAME The new name of the ACL - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl-entry create --acl-id=ACL-ID --ip=IP [] - Add an ACL entry to an ACL - - --acl-id=ACL-ID Alphanumeric string identifying a ACL - --ip=IP An IP address - --comment=COMMENT A freeform descriptive note - --negated Whether to negate the match - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --subnet=SUBNET Number of bits for the subnet mask applied to - the IP address - - acl-entry delete --acl-id=ACL-ID --id=ID [] - Delete an ACL entry from a specified ACL - - --acl-id=ACL-ID Alphanumeric string identifying a ACL - --id=ID Alphanumeric string identifying an ACL Entry - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl-entry describe --acl-id=ACL-ID --id=ID [] - Retrieve a single ACL entry - - --acl-id=ACL-ID Alphanumeric string identifying a ACL - --id=ID Alphanumeric string identifying an ACL Entry - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - acl-entry list --acl-id=ACL-ID [] - List ACLs - - --acl-id=ACL-ID Alphanumeric string identifying a ACL - --direction=ascend Direction in which to sort results - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --sort="created" Field on which to sort - - acl-entry update --acl-id=ACL-ID [] - Update an ACL entry for a specified ACL - - --acl-id=ACL-ID Alphanumeric string identifying a ACL - --comment=COMMENT A freeform descriptive note - --file=FILE Batch update json passed as file path or - content, e.g. $(< batch.json) - --id=ID Alphanumeric string identifying an ACL Entry - --ip=IP An IP address - --negated Whether to negate the match - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --subnet=SUBNET Number of bits for the subnet mask applied to - the IP address - - auth-token create --password=PASSWORD [] - Create an API token - - --password=PASSWORD User password corresponding with --token or - $FASTLY_API_TOKEN - --expires=EXPIRES Time-stamp (UTC) of when the token will expire - --name=NAME Name of the token - --scope=SCOPE ... Authorization scope (repeat flag per scope) - --services=SERVICES ... A comma-separated list of alphanumeric strings - identifying services (default: access to all - services) - - auth-token delete [] - Revoke an API token - - --current Revoke the token used to authenticate the request - --file=FILE Revoke tokens in bulk from a newline delimited list of tokens - --id=ID Alphanumeric string identifying a token - - auth-token describe [] - Get the current API token - - -j, --json Render output as JSON - - auth-token list [] - List API tokens - - --customer-id=CUSTOMER-ID Alphanumeric string identifying the customer - (falls back to FASTLY_CUSTOMER_ID) - -j, --json Render output as JSON - - backend create --version=VERSION --name=NAME --address=ADDRESS [] - Create a backend on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Backend name - --address=ADDRESS A hostname, IPv4, or IPv6 address for the - backend - --comment=COMMENT A descriptive note - --port=PORT Port number of the address - --override-host=OVERRIDE-HOST - The hostname to override the Host header - --connect-timeout=CONNECT-TIMEOUT - How long to wait for a timeout in - milliseconds - --max-conn=MAX-CONN Maximum number of connections - --first-byte-timeout=FIRST-BYTE-TIMEOUT - How long to wait for the first bytes in - milliseconds - --between-bytes-timeout=BETWEEN-BYTES-TIMEOUT - How long to wait between bytes in - milliseconds - --auto-loadbalance Whether or not this backend should be - automatically load balanced - --weight=WEIGHT Weight used to load balance this backend - against others - --request-condition=REQUEST-CONDITION - Condition, which if met, will select this - backend during a request - --healthcheck=HEALTHCHECK The name of the healthcheck to use with this - backend - --shield=SHIELD The shield POP designated to reduce inbound - load on this origin by serving the cached - data to the rest of the network - --use-ssl Whether or not to use SSL to reach the - backend - --ssl-check-cert Be strict on checking SSL certs - --ssl-ca-cert=SSL-CA-CERT CA certificate attached to origin - --ssl-client-cert=SSL-CLIENT-CERT - Client certificate attached to origin - --ssl-client-key=SSL-CLIENT-KEY - Client key attached to origin - --ssl-cert-hostname=SSL-CERT-HOSTNAME - Overrides ssl_hostname, but only for cert - verification. Does not affect SNI at all. - --ssl-sni-hostname=SSL-SNI-HOSTNAME - Overrides ssl_hostname, but only for SNI - in the handshake. Does not affect cert - validation at all. - --min-tls-version=MIN-TLS-VERSION - Minimum allowed TLS version on SSL - connections to this backend - --max-tls-version=MAX-TLS-VERSION - Maximum allowed TLS version on SSL - connections to this backend - --ssl-ciphers=SSL-CIPHERS List of OpenSSL ciphers - (https://www.openssl.org/docs/man1.0.2/man1/ciphers) - - backend delete --version=VERSION --name=NAME [] - Delete a backend on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Backend name - - backend describe --version=VERSION --name=NAME [] - Show detailed information about a backend on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME Name of backend - - backend list --version=VERSION [] - List backends on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - backend update --version=VERSION --name=NAME [] - Update a backend on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME backend name - --new-name=NEW-NAME New backend name - --comment=COMMENT A descriptive note - --address=ADDRESS A hostname, IPv4, or IPv6 address for the - backend - --port=PORT Port number of the address - --override-host=OVERRIDE-HOST - The hostname to override the Host header - --connect-timeout=CONNECT-TIMEOUT - How long to wait for a timeout in - milliseconds - --max-conn=MAX-CONN Maximum number of connections - --first-byte-timeout=FIRST-BYTE-TIMEOUT - How long to wait for the first bytes in - milliseconds - --between-bytes-timeout=BETWEEN-BYTES-TIMEOUT - How long to wait between bytes in - milliseconds - --auto-loadbalance Whether or not this backend should be - automatically load balanced - --weight=WEIGHT Weight used to load balance this backend - against others - --request-condition=REQUEST-CONDITION - condition, which if met, will select this - backend during a request - --healthcheck=HEALTHCHECK The name of the healthcheck to use with this - backend - --shield=SHIELD The shield POP designated to reduce inbound - load on this origin by serving the cached - data to the rest of the network - --use-ssl Whether or not to use SSL to reach the - backend - --ssl-check-cert Be strict on checking SSL certs - --ssl-ca-cert=SSL-CA-CERT CA certificate attached to origin - --ssl-client-cert=SSL-CLIENT-CERT - Client certificate attached to origin - --ssl-client-key=SSL-CLIENT-KEY - Client key attached to origin - --ssl-cert-hostname=SSL-CERT-HOSTNAME - Overrides ssl_hostname, but only for cert - verification. Does not affect SNI at all. - --ssl-sni-hostname=SSL-SNI-HOSTNAME - Overrides ssl_hostname, but only for SNI - in the handshake. Does not affect cert - validation at all. - --min-tls-version=MIN-TLS-VERSION - Minimum allowed TLS version on SSL - connections to this backend - --max-tls-version=MAX-TLS-VERSION - Maximum allowed TLS version on SSL - connections to this backend - --ssl-ciphers=SSL-CIPHERS List of OpenSSL ciphers - (https://www.openssl.org/docs/man1.0.2/man1/ciphers) - - compute build [] - Build a Compute@Edge package locally - - --include-source Include source code in built package - --language=LANGUAGE Language type - --name=NAME Package name - --skip-verification Skip verification steps and force build - --timeout=TIMEOUT Timeout, in seconds, for the build compilation step - - compute deploy [] - Deploy a package to a Fastly Compute@Edge service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --comment=COMMENT Human-readable comment - --domain=DOMAIN The name of the domain associated to the - package - --name=NAME Package name - -p, --package=PACKAGE Path to a package tar.gz - - compute init [] - Initialize a new Compute@Edge package locally - - -n, --name=NAME Name of package, falls back to --directory - --description=DESCRIPTION Description of the package - -p, --directory=DIRECTORY Destination to write the new package, - defaulting to the current directory - -a, --author=AUTHOR ... Author(s) of the package - -l, --language=LANGUAGE Language of the package - -f, --from=FROM Local project directory, or Git repository - URL, or URL referencing a .zip/.tar.gz file, - containing a package template - --force Skip non-empty directory verification step - and force new project creation - - compute pack --wasm-binary=WASM-BINARY - Package a pre-compiled Wasm binary for a Fastly Compute@Edge service - - -w, --wasm-binary=WASM-BINARY Path to a pre-compiled Wasm binary - - compute publish [] - Build and deploy a Compute@Edge package to a Fastly service - - --comment=COMMENT Human-readable comment - --domain=DOMAIN The name of the domain associated to the - package - --include-source Include source code in built package - --language=LANGUAGE Language type - --name=NAME Package name - -p, --package=PACKAGE Path to a package tar.gz - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --skip-verification Skip verification steps and force build - --timeout=TIMEOUT Timeout, in seconds, for the build compilation - step - - compute serve [] - Build and run a Compute@Edge package locally - - --addr="127.0.0.1:7676" The IPv4 address and port to listen on - --env=ENV The environment configuration to use (e.g. stage) - --file="bin/main.wasm" The Wasm file to run - --include-source Include source code in built package - --language=LANGUAGE Language type - --name=NAME Package name - --skip-build Skip the build step - --skip-verification Skip verification steps and force build - --timeout=TIMEOUT Timeout, in seconds, for the build compilation step - --watch Watch for file changes, then rebuild project and - restart local server - - compute update --version=VERSION --package=PACKAGE [] - Update a package on a Fastly Compute@Edge service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -p, --package=PACKAGE Path to a package tar.gz - - compute validate --package=PACKAGE - Validate a Compute@Edge package - - -p, --package=PACKAGE Path to a package tar.gz - - config [] - Display the Fastly CLI configuration - - -l, --location Print the location of the CLI configuration file - - dictionary create --version=VERSION --name=NAME [] - Create a Fastly edge dictionary on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Name of Dictionary - --write-only=WRITE-ONLY Whether to mark this dictionary as write-only. - Can be true or false (defaults to false) - - dictionary delete --version=VERSION --name=NAME [] - Delete a Fastly edge dictionary from a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Name of Dictionary - - dictionary describe --version=VERSION --name=NAME [] - Show detailed information about a Fastly edge dictionary - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME Name of Dictionary - - dictionary list --version=VERSION [] - List all dictionaries on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - dictionary update --version=VERSION --name=NAME [] - Update name of dictionary on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Old name of Dictionary - --new-name=NEW-NAME New name of Dictionary - --write-only=WRITE-ONLY Whether to mark this dictionary as write-only. - Can be true or false (defaults to false) - - dictionary-item create --dictionary-id=DICTIONARY-ID --key=KEY --value=VALUE [] - Create a new item on a Fastly edge dictionary - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --dictionary-id=DICTIONARY-ID - Dictionary ID - --key=KEY Dictionary item key - --value=VALUE Dictionary item value - - dictionary-item delete --dictionary-id=DICTIONARY-ID --key=KEY [] - Delete an item from a Fastly edge dictionary - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --dictionary-id=DICTIONARY-ID - Dictionary ID - --key=KEY Dictionary item key - - dictionary-item describe --dictionary-id=DICTIONARY-ID --key=KEY [] - Show detailed information about a Fastly edge dictionary item - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --dictionary-id=DICTIONARY-ID - Dictionary ID - --key=KEY Dictionary item key - - dictionary-item list --dictionary-id=DICTIONARY-ID [] - List items in a Fastly edge dictionary - - --dictionary-id=DICTIONARY-ID - Dictionary ID - --direction=ascend Direction in which to sort results - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --sort="created" Field on which to sort - - dictionary-item update --dictionary-id=DICTIONARY-ID [] - Update or insert an item on a Fastly edge dictionary - - --dictionary-id=DICTIONARY-ID - Dictionary ID - --file=FILE Batch update json file - --key=KEY Dictionary item key - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --value=VALUE Dictionary item value - - domain create --name=NAME --version=VERSION [] - Create a domain on a Fastly service version - - -n, --name=NAME Domain name - --comment=COMMENT A descriptive note - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - - domain delete --name=NAME --version=VERSION [] - Delete a domain on a Fastly service version - - -n, --name=NAME Domain name - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - - domain describe --version=VERSION --name=NAME [] - Show detailed information about a domain on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME Name of domain - - domain list --version=VERSION [] - List domains on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - domain update --version=VERSION --name=NAME [] - Update a domain on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Domain name - --new-name=NEW-NAME New domain name - --comment=COMMENT A descriptive note - - domain validate --version=VERSION [] - Checks the status of a specific domain's DNS record for a Service Version - - --version=VERSION 'latest', 'active', or the number of a specific - version - -a, --all Checks the status of all domains' DNS records - for a Service Version - -n, --name=NAME The name of the domain associated with this - service - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - healthcheck create --version=VERSION --name=NAME [] - Create a healthcheck on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Healthcheck name - --comment=COMMENT A descriptive note - --method=METHOD Which HTTP method to use - --host=HOST Which host to check - --path=PATH The path to check - --http-version=HTTP-VERSION - Whether to use version 1.0 or 1.1 HTTP - --timeout=TIMEOUT Timeout in milliseconds - --check-interval=CHECK-INTERVAL - How often to run the healthcheck in - milliseconds - --expected-response=EXPECTED-RESPONSE - The status code expected from the host - --window=WINDOW The number of most recent healthcheck queries - to keep for this healthcheck - --threshold=THRESHOLD How many healthchecks must succeed to be - considered healthy - --initial=INITIAL When loading a config, the initial number of - probes to be seen as OK - - healthcheck delete --version=VERSION --name=NAME [] - Delete a healthcheck on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Healthcheck name - - healthcheck describe --version=VERSION --name=NAME [] - Show detailed information about a healthcheck on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME Name of healthcheck - - healthcheck list --version=VERSION [] - List healthchecks on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - healthcheck update --version=VERSION --name=NAME [] - Update a healthcheck on a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME Healthcheck name - --new-name=NEW-NAME Healthcheck name - --comment=COMMENT A descriptive note - --method=METHOD Which HTTP method to use - --host=HOST Which host to check - --path=PATH The path to check - --http-version=HTTP-VERSION - Whether to use version 1.0 or 1.1 HTTP - --timeout=TIMEOUT Timeout in milliseconds - --check-interval=CHECK-INTERVAL - How often to run the healthcheck in - milliseconds - --expected-response=EXPECTED-RESPONSE - The status code expected from the host - --window=WINDOW The number of most recent healthcheck queries - to keep for this healthcheck - --threshold=THRESHOLD How many healthchecks must succeed to be - considered healthy - --initial=INITIAL When loading a config, the initial number of - probes to be seen as OK - - ip-list - List Fastly's public IPs - - - log-tail [] - Tail Compute@Edge logs - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --from=FROM From time, in Unix seconds - --to=TO To time, in Unix seconds - --sort-buffer=1s Duration of sort buffer for received logs - --search-padding=2s Time beyond from/to to consider in searches - --stream=STREAM Output: stdout, stderr, both (default) - - logging azureblob create --name=NAME --version=VERSION --container=CONTAINER --account-name=ACCOUNT-NAME --sas-token=SAS-TOKEN [] - Create an Azure Blob Storage logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Azure Blob Storage logging - object. Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --container=CONTAINER The name of the Azure Blob Storage container in - which to store logs - --account-name=ACCOUNT-NAME - The unique Azure Blob Storage namespace in - which your data objects are stored - --sas-token=SAS-TOKEN The Azure shared access signature providing - write access to the blob service objects. - Be sure to update your token before it expires - or the logging functionality will not work - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --file-max-bytes=FILE-MAX-BYTES - The maximum size of a log file in bytes - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging azureblob delete --version=VERSION --name=NAME [] - Delete an Azure Blob Storage logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Azure Blob Storage logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging azureblob describe --version=VERSION --name=NAME [] - Show detailed information about an Azure Blob Storage logging endpoint on a - Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Azure Blob Storage logging - object - - logging azureblob list --version=VERSION [] - List Azure Blob Storage logging endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging azureblob update --version=VERSION --name=NAME [] - Update an Azure Blob Storage logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Azure Blob Storage logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Azure Blob Storage logging - object - --container=CONTAINER The name of the Azure Blob Storage container in - which to store logs - --account-name=ACCOUNT-NAME - The unique Azure Blob Storage namespace in - which your data objects are stored - --sas-token=SAS-TOKEN The Azure shared access signature providing - write access to the blob service objects. - Be sure to update your token before it expires - or the logging functionality will not work - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --file-max-bytes=FILE-MAX-BYTES - The maximum size of a log file in bytes - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging bigquery create --name=NAME --version=VERSION --project-id=PROJECT-ID --dataset=DATASET --table=TABLE --user=USER --secret-key=SECRET-KEY [] - Create a BigQuery logging endpoint on a Fastly service version - - -n, --name=NAME The name of the BigQuery logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --project-id=PROJECT-ID Your Google Cloud Platform project ID - --dataset=DATASET Your BigQuery dataset - --table=TABLE Your BigQuery table - --user=USER Your Google Cloud Platform service account - email address. The client_email field in your - service account authentication JSON. - --secret-key=SECRET-KEY Your Google Cloud Platform account secret key. - The private_key field in your service account - authentication JSON. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --template-suffix=TEMPLATE-SUFFIX - BigQuery table name suffix template - --format=FORMAT Apache style log formatting. Must produce JSON - that matches the schema of your BigQuery table - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - - logging bigquery delete --version=VERSION --name=NAME [] - Delete a BigQuery logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the BigQuery logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging bigquery describe --version=VERSION --name=NAME [] - Show detailed information about a BigQuery logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the BigQuery logging object - - logging bigquery list --version=VERSION [] - List BigQuery endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging bigquery update --version=VERSION --name=NAME [] - Update a BigQuery logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the BigQuery logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the BigQuery logging object - --project-id=PROJECT-ID Your Google Cloud Platform project ID - --dataset=DATASET Your BigQuery dataset - --table=TABLE Your BigQuery table - --user=USER Your Google Cloud Platform service account - email address. The client_email field in your - service account authentication JSON. - --secret-key=SECRET-KEY Your Google Cloud Platform account secret key. - The private_key field in your service account - authentication JSON. - --template-suffix=TEMPLATE-SUFFIX - BigQuery table name suffix template - --format=FORMAT Apache style log formatting. Must produce JSON - that matches the schema of your BigQuery table - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - - logging cloudfiles create --name=NAME --version=VERSION --user=USER --access-key=ACCESS-KEY --bucket=BUCKET [] - Create a Cloudfiles logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Cloudfiles logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --user=USER The username for your Cloudfile account - --access-key=ACCESS-KEY Your Cloudfile account access key - --bucket=BUCKET The name of your Cloudfiles container - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --path=PATH The path to upload logs to - --region=REGION The region to stream logs to. One of: - DFW-Dallas, ORD-Chicago, IAD-Northern Virginia, - LON-London, SYD-Sydney, HKG-Hong Kong - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging cloudfiles delete --version=VERSION --name=NAME [] - Delete a Cloudfiles logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Cloudfiles logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging cloudfiles describe --version=VERSION --name=NAME [] - Show detailed information about a Cloudfiles logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Cloudfiles logging object - - logging cloudfiles list --version=VERSION [] - List Cloudfiles endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging cloudfiles update --version=VERSION --name=NAME [] - Update a Cloudfiles logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Cloudfiles logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Cloudfiles logging object - --user=USER The username for your Cloudfile account - --access-key=ACCESS-KEY Your Cloudfile account access key - --bucket=BUCKET The name of your Cloudfiles container - --path=PATH The path to upload logs to - --region=REGION The region to stream logs to. One of: - DFW-Dallas, ORD-Chicago, IAD-Northern Virginia, - LON-London, SYD-Sydney, HKG-Hong Kong - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging datadog create --name=NAME --version=VERSION --auth-token=AUTH-TOKEN [] - Create a Datadog logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Datadog logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --auth-token=AUTH-TOKEN The API key from your Datadog account - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --region=REGION The region that log data will be sent to. - One of US, US3, US5, or EU. Defaults to US if - undefined - --format=FORMAT Apache style log formatting. For details on - the default value refer to the documentation - (https://developer.fastly.com/reference/api/logging/datadog/) - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging datadog delete --version=VERSION --name=NAME [] - Delete a Datadog logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Datadog logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging datadog describe --version=VERSION --name=NAME [] - Show detailed information about a Datadog logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Datadog logging object - - logging datadog list --version=VERSION [] - List Datadog endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging datadog update --version=VERSION --name=NAME [] - Update a Datadog logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Datadog logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Datadog logging object - --auth-token=AUTH-TOKEN The API key from your Datadog account - --region=REGION The region that log data will be sent to. - One of US, US3, US5, or EU. Defaults to US if - undefined - --format=FORMAT Apache style log formatting. For details on - the default value refer to the documentation - (https://developer.fastly.com/reference/api/logging/datadog/) - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging digitalocean create --name=NAME --version=VERSION --bucket=BUCKET --access-key=ACCESS-KEY --secret-key=SECRET-KEY [] - Create a DigitalOcean Spaces logging endpoint on a Fastly service version - - -n, --name=NAME The name of the DigitalOcean Spaces logging - object. Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --bucket=BUCKET The name of the DigitalOcean Space - --access-key=ACCESS-KEY Your DigitalOcean Spaces account access key - --secret-key=SECRET-KEY Your DigitalOcean Spaces account secret key - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --domain=DOMAIN The domain of the DigitalOcean Spaces endpoint - (default 'nyc3.digitaloceanspaces.com') - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging digitalocean delete --version=VERSION --name=NAME [] - Delete a DigitalOcean Spaces logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the DigitalOcean Spaces logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging digitalocean describe --version=VERSION --name=NAME [] - Show detailed information about a DigitalOcean Spaces logging endpoint on a - Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the DigitalOcean Spaces logging - object - - logging digitalocean list --version=VERSION [] - List DigitalOcean Spaces logging endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging digitalocean update --version=VERSION --name=NAME [] - Update a DigitalOcean Spaces logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the DigitalOcean Spaces logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the DigitalOcean Spaces logging - object - --bucket=BUCKET The name of the DigitalOcean Space - --domain=DOMAIN The domain of the DigitalOcean Spaces endpoint - (default 'nyc3.digitaloceanspaces.com') - --access-key=ACCESS-KEY Your DigitalOcean Spaces account access key - --secret-key=SECRET-KEY Your DigitalOcean Spaces account secret key - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging elasticsearch create --name=NAME --version=VERSION --index=INDEX --url=URL [] - Create an Elasticsearch logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Elasticsearch logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --index=INDEX The name of the Elasticsearch index to - send documents (logs) to. The index must - follow the Elasticsearch index format rules - (https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html). - We support strftime - (http://man7.org/linux/man-pages/man3/strftime.3.html) - interpolated variables inside braces prefixed - with a pound symbol. For example, #{%F} will - interpolate as YYYY-MM-DD with today's date - --url=URL The URL to stream logs to. Must use HTTPS. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --pipeline=PIPELINE The ID of the Elasticsearch ingest pipeline - to apply pre-process transformations - to before indexing. For example - my_pipeline_id. Learn more about creating - a pipeline in the Elasticsearch docs - (https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html) - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Elasticsearch can - ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --request-max-entries=REQUEST-MAX-ENTRIES - Maximum number of logs to append to a batch, - if non-zero. Defaults to 10k - --request-max-bytes=REQUEST-MAX-BYTES - Maximum size of log batch, if non-zero. - Defaults to 100MB - - logging elasticsearch delete --version=VERSION --name=NAME [] - Delete an Elasticsearch logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Elasticsearch logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging elasticsearch describe --version=VERSION --name=NAME [] - Show detailed information about an Elasticsearch logging endpoint on a - Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Elasticsearch logging object - - logging elasticsearch list --version=VERSION [] - List Elasticsearch endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging elasticsearch update --version=VERSION --name=NAME [] - Update an Elasticsearch logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Elasticsearch logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Elasticsearch logging object - --index=INDEX The name of the Elasticsearch index to - send documents (logs) to. The index must - follow the Elasticsearch index format rules - (https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html). - We support strftime - (http://man7.org/linux/man-pages/man3/strftime.3.html) - interpolated variables inside braces prefixed - with a pound symbol. For example, #{%F} will - interpolate as YYYY-MM-DD with today's date - --url=URL The URL to stream logs to. Must use HTTPS. - --pipeline=PIPELINE The ID of the Elasticsearch ingest pipeline - to apply pre-process transformations - to before indexing. For example - my_pipeline_id. Learn more about creating - a pipeline in the Elasticsearch docs - (https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html) - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Elasticsearch can - ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --request-max-entries=REQUEST-MAX-ENTRIES - Maximum number of logs to append to a batch, - if non-zero. Defaults to 10k - --request-max-bytes=REQUEST-MAX-BYTES - Maximum size of log batch, if non-zero. - Defaults to 100MB - - logging ftp create --name=NAME --version=VERSION --address=ADDRESS --user=USER --password=PASSWORD [] - Create an FTP logging endpoint on a Fastly service version - - -n, --name=NAME The name of the FTP logging object. Used as a - primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --address=ADDRESS An hostname or IPv4 address - --user=USER The username for the server (can be anonymous) - --password=PASSWORD The password for the server (for anonymous use - an email address) - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --port=PORT The port number - --path=PATH The path to upload log files to. If the path - ends in / then it is treated as a directory - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging ftp delete --version=VERSION --name=NAME [] - Delete an FTP logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the FTP logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging ftp describe --version=VERSION --name=NAME [] - Show detailed information about an FTP logging endpoint on a Fastly service - version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the FTP logging object - - logging ftp list --version=VERSION [] - List FTP endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging ftp update --version=VERSION --name=NAME [] - Update an FTP logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the FTP logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the FTP logging object - --address=ADDRESS An hostname or IPv4 address - --port=PORT The port number - --username=USERNAME The username for the server (can be anonymous) - --password=PASSWORD The password for the server (for anonymous use - an email address) - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --path=PATH The path to upload log files to. If the path - ends in / then it is treated as a directory - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging gcs create --name=NAME --version=VERSION --user=USER --bucket=BUCKET --secret-key=SECRET-KEY [] - Create a GCS logging endpoint on a Fastly service version - - -n, --name=NAME The name of the GCS logging object. Used as a - primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --user=USER Your GCS service account email address. - The client_email field in your service account - authentication JSON - --bucket=BUCKET The bucket of the GCS bucket - --secret-key=SECRET-KEY Your GCS account secret key. The private_key - field in your service account authentication - JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --path=PATH The path to upload logs to (default '/') - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging gcs delete --version=VERSION --name=NAME [] - Delete a GCS logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the GCS logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging gcs describe --version=VERSION --name=NAME [] - Show detailed information about a GCS logging endpoint on a Fastly service - version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the GCS logging object - - logging gcs list --version=VERSION [] - List GCS endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging gcs update --version=VERSION --name=NAME [] - Update a GCS logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the GCS logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the GCS logging object - --bucket=BUCKET The bucket of the GCS bucket - --user=USER Your GCS service account email address. - The client_email field in your service account - authentication JSON - --secret-key=SECRET-KEY Your GCS account secret key. The private_key - field in your service account authentication - JSON - --path=PATH The path to upload logs to (default '/') - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging googlepubsub create --name=NAME --version=VERSION --user=USER --secret-key=SECRET-KEY --topic=TOPIC --project-id=PROJECT-ID [] - Create a Google Cloud Pub/Sub logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Google Cloud Pub/Sub logging - object. Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --user=USER Your Google Cloud Platform service account - email address. The client_email field in your - service account authentication JSON - --secret-key=SECRET-KEY Your Google Cloud Platform account secret key. - The private_key field in your service account - authentication JSON - --topic=TOPIC The Google Cloud Pub/Sub topic to which logs - will be published - --project-id=PROJECT-ID The ID of your Google Cloud Platform project - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - - logging googlepubsub delete --version=VERSION --name=NAME [] - Delete a Google Cloud Pub/Sub logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Google Cloud Pub/Sub logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging googlepubsub describe --version=VERSION --name=NAME [] - Show detailed information about a Google Cloud Pub/Sub logging endpoint on a - Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Google Cloud Pub/Sub logging - object - - logging googlepubsub list --version=VERSION [] - List Google Cloud Pub/Sub endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging googlepubsub update --version=VERSION --name=NAME [] - Update a Google Cloud Pub/Sub logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Google Cloud Pub/Sub logging - object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Google Cloud Pub/Sub logging - object - --user=USER Your Google Cloud Platform service account - email address. The client_email field in your - service account authentication JSON - --secret-key=SECRET-KEY Your Google Cloud Platform account secret key. - The private_key field in your service account - authentication JSON - --topic=TOPIC The Google Cloud Pub/Sub topic to which logs - will be published - --project-id=PROJECT-ID The ID of your Google Cloud Platform project - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - - logging heroku create --name=NAME --version=VERSION --url=URL --auth-token=AUTH-TOKEN [] - Create a Heroku logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Heroku logging object. Used as - a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --url=URL The url to stream logs to - --auth-token=AUTH-TOKEN The token to use for authentication - (https://devcenter.heroku.com/articles/add-on-partner-log-integration) - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging heroku delete --version=VERSION --name=NAME [] - Delete a Heroku logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Heroku logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging heroku describe --version=VERSION --name=NAME [] - Show detailed information about a Heroku logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Heroku logging object - - logging heroku list --version=VERSION [] - List Heroku endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging heroku update --version=VERSION --name=NAME [] - Update a Heroku logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Heroku logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Heroku logging object - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --url=URL The url to stream logs to - --auth-token=AUTH-TOKEN The token to use for authentication - (https://devcenter.heroku.com/articles/add-on-partner-log-integration) - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging honeycomb create --name=NAME --version=VERSION --dataset=DATASET --auth-token=AUTH-TOKEN [] - Create a Honeycomb logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Honeycomb logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --dataset=DATASET The Honeycomb Dataset you want to log to - --auth-token=AUTH-TOKEN The Write Key from the Account page of your - Honeycomb account - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Honeycomb can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging honeycomb delete --version=VERSION --name=NAME [] - Delete a Honeycomb logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Honeycomb logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging honeycomb describe --version=VERSION --name=NAME [] - Show detailed information about a Honeycomb logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Honeycomb logging object - - logging honeycomb list --version=VERSION [] - List Honeycomb endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging honeycomb update --version=VERSION --name=NAME [] - Update a Honeycomb logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Honeycomb logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Honeycomb logging object - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Honeycomb can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --dataset=DATASET The Honeycomb Dataset you want to log to - --auth-token=AUTH-TOKEN The Write Key from the Account page of your - Honeycomb account - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging https create --name=NAME --version=VERSION --url=URL [] - Create an HTTPS logging endpoint on a Fastly service version - - -n, --name=NAME The name of the HTTPS logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --url=URL URL that log data will be sent to. Must use - the https protocol - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --content-type=CONTENT-TYPE - Content type of the header sent with the - request - --header-name=HEADER-NAME Name of the custom header sent with the - request - --header-value=HEADER-VALUE - Value of the custom header sent with the - request - --method=METHOD HTTP method used for request. Can be POST or - PUT. Defaults to POST if not specified - --json-format=JSON-FORMAT Enforces valid JSON formatting for log - entries. Can be disabled 0, array of json - (wraps JSON log batches in an array) 1, - or newline delimited json (places each JSON - log entry onto a new line in a batch) 2 - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that HTTPS can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --request-max-entries=REQUEST-MAX-ENTRIES - Maximum number of logs to append to a batch, - if non-zero. Defaults to 10k - --request-max-bytes=REQUEST-MAX-BYTES - Maximum size of log batch, if non-zero. - Defaults to 100MB - - logging https delete --version=VERSION --name=NAME [] - Delete an HTTPS logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the HTTPS logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging https describe --version=VERSION --name=NAME [] - Show detailed information about an HTTPS logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the HTTPS logging object - - logging https list --version=VERSION [] - List HTTPS endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging https update --version=VERSION --name=NAME [] - Update an HTTPS logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the HTTPS logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the HTTPS logging object - --url=URL URL that log data will be sent to. Must use - the https protocol - --content-type=CONTENT-TYPE - Content type of the header sent with the - request - --header-name=HEADER-NAME Name of the custom header sent with the - request - --header-value=HEADER-VALUE - Value of the custom header sent with the - request - --method=METHOD HTTP method used for request. Can be POST or - PUT. Defaults to POST if not specified - --json-format=JSON-FORMAT Enforces valid JSON formatting for log - entries. Can be disabled 0, array of json - (wraps JSON log batches in an array) 1, - or newline delimited json (places each JSON - log entry onto a new line in a batch) 2 - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that HTTPS can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --request-max-entries=REQUEST-MAX-ENTRIES - Maximum number of logs to append to a batch, - if non-zero. Defaults to 10k - --request-max-bytes=REQUEST-MAX-BYTES - Maximum size of log batch, if non-zero. - Defaults to 100MB - - logging kafka create --name=NAME --version=VERSION --topic=TOPIC --brokers=BROKERS [] - Create a Kafka logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Kafka logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --topic=TOPIC The Kafka topic to send logs to - --brokers=BROKERS A comma-separated list of IP addresses or - hostnames of Kafka brokers - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - One of: gzip, snappy, lz4 - --required-acks=REQUIRED-ACKS - The Number of acknowledgements a leader - must receive before a write is considered - successful. One of: 1 (default) One server - needs to respond. 0 No servers need to - respond. -1 Wait for all in-sync replicas to - respond - --use-tls Whether to use TLS for secure logging. - Can be either true or false - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Kafka can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --parse-log-keyvals Parse key-value pairs within the log format - --max-batch-size=MAX-BATCH-SIZE - The maximum size of the log batch in bytes - --use-sasl Enable SASL authentication. Requires - --auth-method, --username, and --password to - be specified - --auth-method=AUTH-METHOD SASL authentication method. Valid values are: - plain, scram-sha-256, scram-sha-512 - --username=USERNAME SASL authentication username. Required if - --auth-method is specified - --password=PASSWORD SASL authentication password. Required if - --auth-method is specified - - logging kafka delete --version=VERSION --name=NAME [] - Delete a Kafka logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Kafka logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging kafka describe --version=VERSION --name=NAME [] - Show detailed information about a Kafka logging endpoint on a Fastly service - version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Kafka logging object - - logging kafka list --version=VERSION [] - List Kafka endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging kafka update --version=VERSION --name=NAME [] - Update a Kafka logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Kafka logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Kafka logging object - --topic=TOPIC The Kafka topic to send logs to - --brokers=BROKERS A comma-separated list of IP addresses or - hostnames of Kafka brokers - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - One of: gzip, snappy, lz4 - --required-acks=REQUIRED-ACKS - The Number of acknowledgements a leader - must receive before a write is considered - successful. One of: 1 (default) One server - needs to respond. 0 No servers need to - respond. -1 Wait for all in-sync replicas to - respond - --use-tls Whether to use TLS for secure logging. - Can be either true or false - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --format=FORMAT Apache style log formatting. Your log must - produce valid JSON that Kafka can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --[no-]parse-log-keyvals Parse key-value pairs within the log format - --max-batch-size=MAX-BATCH-SIZE - The maximum size of the log batch in bytes - --use-sasl Enable SASL authentication. Requires - --auth-method, --username, and --password to - be specified - --auth-method=AUTH-METHOD SASL authentication method. Valid values are: - plain, scram-sha-256, scram-sha-512 - --username=USERNAME SASL authentication username. Required if - --auth-method is specified - --password=PASSWORD SASL authentication password. Required if - --auth-method is specified - - logging kinesis create --name=NAME --version=VERSION --stream-name=STREAM-NAME --region=REGION [] - Create an Amazon Kinesis logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Kinesis logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --stream-name=STREAM-NAME The Amazon Kinesis stream to send logs to - --region=REGION The AWS region where the Kinesis stream - exists - --access-key=ACCESS-KEY The access key associated with the target - Amazon Kinesis stream - --secret-key=SECRET-KEY The secret key associated with the target - Amazon Kinesis stream - --iam-role=IAM-ROLE The IAM role ARN for logging - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - - logging kinesis delete --version=VERSION --name=NAME [] - Delete a Kinesis logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Kinesis logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging kinesis describe --version=VERSION --name=NAME [] - Show detailed information about a Kinesis logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Kinesis logging object - - logging kinesis list --version=VERSION [] - List Kinesis endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging kinesis update --version=VERSION --name=NAME [] - Update a Kinesis logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Kinesis logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Kinesis logging object - --stream-name=STREAM-NAME Your Kinesis stream name - --access-key=ACCESS-KEY Your Kinesis account access key - --secret-key=SECRET-KEY Your Kinesis account secret key - --iam-role=IAM-ROLE The IAM role ARN for logging - --region=REGION The AWS region where the Kinesis stream - exists - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - - logging logentries create --name=NAME --version=VERSION [] - Create a Logentries logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Logentries logging object. - Used as a primary key for API access - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --port=PORT The port number - --use-tls Whether to use TLS for secure logging. Can be - either true or false - --auth-token=AUTH-TOKEN Use token based authentication - (https://logentries.com/doc/input-token/) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --region=REGION The region to which to stream logs - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - - logging logentries delete --version=VERSION --name=NAME [] - Delete a Logentries logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Logentries logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging logentries describe --version=VERSION --name=NAME [] - Show detailed information about a Logentries logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Logentries logging object - - logging logentries list --version=VERSION [] - List Logentries endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging logentries update --version=VERSION --name=NAME [] - Update a Logentries logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Logentries logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Logentries logging object - --port=PORT The port number - --use-tls Whether to use TLS for secure logging. Can be - either true or false - --auth-token=AUTH-TOKEN Use token based authentication - (https://logentries.com/doc/input-token/) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - --region=REGION The region to which to stream logs - - logging loggly create --name=NAME --version=VERSION --auth-token=AUTH-TOKEN [] - Create a Loggly logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Loggly logging object. Used as - a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --auth-token=AUTH-TOKEN The token to use for authentication - (https://www.loggly.com/docs/customer-token-authentication-token/) - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging loggly delete --version=VERSION --name=NAME [] - Delete a Loggly logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Loggly logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging loggly describe --version=VERSION --name=NAME [] - Show detailed information about a Loggly logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Loggly logging object - - logging loggly list --version=VERSION [] - List Loggly endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging loggly update --version=VERSION --name=NAME [] - Update a Loggly logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Loggly logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Loggly logging object - --auth-token=AUTH-TOKEN The token to use for authentication - (https://www.loggly.com/docs/customer-token-authentication-token/) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging logshuttle create --name=NAME --version=VERSION --url=URL --auth-token=AUTH-TOKEN [] - Create a Logshuttle logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Logshuttle logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --url=URL Your Log Shuttle endpoint url - --auth-token=AUTH-TOKEN The data authentication token associated with - this endpoint - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging logshuttle delete --version=VERSION --name=NAME [] - Delete a Logshuttle logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Logshuttle logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging logshuttle describe --version=VERSION --name=NAME [] - Show detailed information about a Logshuttle logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Logshuttle logging object - - logging logshuttle list --version=VERSION [] - List Logshuttle endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging logshuttle update --version=VERSION --name=NAME [] - Update a Logshuttle logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Logshuttle logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Logshuttle logging object - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --url=URL Your Log Shuttle endpoint url - --auth-token=AUTH-TOKEN The data authentication token associated with - this endpoint - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging newrelic create --key=KEY --name=NAME --version=VERSION [] - Create an New Relic logging endpoint attached to the specified service - version - - --key=KEY The Insert API key from the Account page of - your New Relic account - --name=NAME The name for the real-time logging - configuration - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --format=FORMAT A Fastly log format string. Must produce valid - JSON that New Relic Logs can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed - --region=REGION The region to which to stream logs - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging newrelic delete --name=NAME --version=VERSION [] - Delete the New Relic Logs logging object for a particular service and - version - - --name=NAME The name for the real-time logging - configuration to delete - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging newrelic describe --name=NAME --version=VERSION [] - Get the details of a New Relic Logs logging object for a particular service - and version - - --name=NAME The name for the real-time logging - configuration - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging newrelic list --version=VERSION [] - List all of the New Relic Logs logging objects for a particular service and - version - - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging newrelic update --name=NAME --version=VERSION [] - Update a New Relic Logs logging object for a particular service and version - - --name=NAME The name for the real-time logging - configuration to update - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --format=FORMAT A Fastly log format string. Must produce valid - JSON that New Relic Logs can ingest - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint - --key=KEY The Insert API key from the Account page of - your New Relic account - --new-name=NEW-NAME The name for the real-time logging - configuration - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed - --region=REGION The region to which to stream logs - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging openstack create --name=NAME --version=VERSION --bucket=BUCKET --access-key=ACCESS-KEY --user=USER --url=URL [] - Create an OpenStack logging endpoint on a Fastly service version - - -n, --name=NAME The name of the OpenStack logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --bucket=BUCKET The name of your OpenStack container - --access-key=ACCESS-KEY Your OpenStack account access key - --user=USER The username for your OpenStack account - --url=URL Your OpenStack auth url - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging openstack delete --version=VERSION --name=NAME [] - Delete an OpenStack logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the OpenStack logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging openstack describe --version=VERSION --name=NAME [] - Show detailed information about an OpenStack logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the OpenStack logging object - - logging openstack list --version=VERSION [] - List OpenStack logging endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging openstack update --version=VERSION --name=NAME [] - Update an OpenStack logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the OpenStack logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the OpenStack logging object - --bucket=BUCKET The name of the Openstack Space - --access-key=ACCESS-KEY Your OpenStack account access key - --user=USER The username for your OpenStack account. - --url=URL Your OpenStack auth url. - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging papertrail create --name=NAME --version=VERSION --address=ADDRESS [] - Create a Papertrail logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Papertrail logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --address=ADDRESS A hostname or IPv4 address - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --port=PORT The port number - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --format=FORMAT Apache style log formatting - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - - logging papertrail delete --version=VERSION --name=NAME [] - Delete a Papertrail logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Papertrail logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging papertrail describe --version=VERSION --name=NAME [] - Show detailed information about a Papertrail logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Papertrail logging object - - logging papertrail list --version=VERSION [] - List Papertrail endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging papertrail update --version=VERSION --name=NAME [] - Update a Papertrail logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Papertrail logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Papertrail logging object - --address=ADDRESS A hostname or IPv4 address - --port=PORT The port number - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --format=FORMAT Apache style log formatting - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - - logging s3 create --name=NAME --version=VERSION --bucket=BUCKET [] - Create an Amazon S3 logging endpoint on a Fastly service version - - -n, --name=NAME The name of the S3 logging object. Used as a - primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --bucket=BUCKET Your S3 bucket name - --access-key=ACCESS-KEY Your S3 account access key - --secret-key=SECRET-KEY Your S3 account secret key - --iam-role=IAM-ROLE The IAM role ARN for logging - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --domain=DOMAIN The domain of the S3 endpoint - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --redundancy=REDUNDANCY The S3 storage class. One of: standard, - intelligent_tiering, standard_ia, onezone_ia, - glacier, glacier_ir, deep_archive, or - reduced_redundancy - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --server-side-encryption=SERVER-SIDE-ENCRYPTION - Set to enable S3 Server Side Encryption. - Can be either AES256 or aws:kms - --server-side-encryption-kms-key-id=SERVER-SIDE-ENCRYPTION-KMS-KEY-ID - Server-side KMS Key ID. Must be set if - server-side-encryption is set to aws:kms - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging s3 delete --version=VERSION --name=NAME [] - Delete a S3 logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the S3 logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging s3 describe --version=VERSION --name=NAME [] - Show detailed information about a S3 logging endpoint on a Fastly service - version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the S3 logging object - - logging s3 list --version=VERSION [] - List S3 endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging s3 update --version=VERSION --name=NAME [] - Update a S3 logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the S3 logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the S3 logging object - --bucket=BUCKET Your S3 bucket name - --access-key=ACCESS-KEY Your S3 account access key - --secret-key=SECRET-KEY Your S3 account secret key - --iam-role=IAM-ROLE The IAM role ARN for logging - --domain=DOMAIN The domain of the S3 endpoint - --path=PATH The path to upload logs to - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --redundancy=REDUNDANCY The S3 storage class. One of: standard, - intelligent_tiering, standard_ia, onezone_ia, - glacier, glacier_ir, deep_archive, or - reduced_redundancy - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --server-side-encryption=SERVER-SIDE-ENCRYPTION - Set to enable S3 Server Side Encryption. - Can be either AES256 or aws:kms - --server-side-encryption-kms-key-id=SERVER-SIDE-ENCRYPTION-KMS-KEY-ID - Server-side KMS Key ID. Must be set if - server-side-encryption is set to aws:kms - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging scalyr create --name=NAME --version=VERSION --auth-token=AUTH-TOKEN [] - Create a Scalyr logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Scalyr logging object. Used as - a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --auth-token=AUTH-TOKEN The token to use for authentication - (https://www.scalyr.com/keys) - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --region=REGION The region that log data will be sent to. - One of US or EU. Defaults to US if undefined - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging scalyr delete --version=VERSION --name=NAME [] - Delete a Scalyr logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Scalyr logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging scalyr describe --version=VERSION --name=NAME [] - Show detailed information about a Scalyr logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Scalyr logging object - - logging scalyr list --version=VERSION [] - List Scalyr endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging scalyr update --version=VERSION --name=NAME [] - Update a Scalyr logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Scalyr logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Scalyr logging object - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --auth-token=AUTH-TOKEN The token to use for authentication - (https://www.scalyr.com/keys) - --region=REGION The region that log data will be sent to. - One of US or EU. Defaults to US if undefined - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - - logging sftp create --name=NAME --version=VERSION --address=ADDRESS --user=USER --ssh-known-hosts=SSH-KNOWN-HOSTS [] - Create an SFTP logging endpoint on a Fastly service version - - -n, --name=NAME The name of the SFTP logging object. Used as a - primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --address=ADDRESS The hostname or IPv4 address - --user=USER The username for the server - --ssh-known-hosts=SSH-KNOWN-HOSTS - A list of host keys for all hosts we can - connect to over SFTP - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --port=PORT The port number - --password=PASSWORD The password for the server. If both password - and secret_key are passed, secret_key will be - used in preference - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --secret-key=SECRET-KEY The SSH private key for the server. If both - password and secret_key are passed, secret_key - will be used in preference - --path=PATH The path to upload logs to. The directory must - exist on the SFTP server before logs can be - saved to it - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging sftp delete --version=VERSION --name=NAME [] - Delete an SFTP logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the SFTP logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging sftp describe --version=VERSION --name=NAME [] - Show detailed information about an SFTP logging endpoint on a Fastly service - version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the SFTP logging object - - logging sftp list --version=VERSION [] - List SFTP endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging sftp update --version=VERSION --name=NAME [] - Update an SFTP logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the SFTP logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the SFTP logging object - --address=ADDRESS The hostname or IPv4 address - --port=PORT The port number - --public-key=PUBLIC-KEY A PGP public key that Fastly will use to - encrypt your log files before writing them to - disk - --secret-key=SECRET-KEY The SSH private key for the server. If both - password and secret_key are passed, secret_key - will be used in preference - --ssh-known-hosts=SSH-KNOWN-HOSTS - A list of host keys for all hosts we can - connect to over SFTP - --user=USER The username for the server - --password=PASSWORD The password for the server. If both password - and secret_key are passed, secret_key will be - used in preference - --path=PATH The path to upload logs to. The directory must - exist on the SFTP server before logs can be - saved to it - --period=PERIOD How frequently log files are finalized so they - can be available for reading (in seconds, - default 3600) - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --gzip-level=GZIP-LEVEL What level of GZIP encoding to have when - dumping logs (default 0, no compression) - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --timestamp-format=TIMESTAMP-FORMAT - strftime specified timestamp formatting - (default "%Y-%m-%dT%H:%M:%S.000") - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug - --compression-codec=COMPRESSION-CODEC - The codec used for compression of your logs. - Valid values are zstd, snappy, and gzip. If - the specified codec is "gzip", gzip_level will - default to 3. To specify a different level, - leave compression_codec blank and explicitly - set the level using gzip_level. Specifying both - compression_codec and gzip_level in the same - API request will result in an error. - - logging splunk create --name=NAME --version=VERSION --url=URL [] - Create a Splunk logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Splunk logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --url=URL The URL to POST to - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - --auth-token=AUTH-TOKEN A Splunk token for use in posting logs over - HTTP to your collector - - logging splunk delete --version=VERSION --name=NAME [] - Delete a Splunk logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Splunk logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging splunk describe --version=VERSION --name=NAME [] - Show detailed information about a Splunk logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Splunk logging object - - logging splunk list --version=VERSION [] - List Splunk endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging splunk update --version=VERSION --name=NAME [] - Update a Splunk logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Splunk logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Splunk logging object - --url=URL The URL to POST to. - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - The hostname used to verify the server's - certificate. It can either be the Common Name - or a Subject Alternative Name (SAN) - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug. This field is not required and has - no default value - --auth-token=AUTH-TOKEN - - logging sumologic create --name=NAME --version=VERSION --url=URL [] - Create a Sumologic logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Sumologic logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --url=URL The URL to POST to - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - - logging sumologic delete --version=VERSION --name=NAME [] - Delete a Sumologic logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Sumologic logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging sumologic describe --version=VERSION --name=NAME [] - Show detailed information about a Sumologic logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Sumologic logging object - - logging sumologic list --version=VERSION [] - List Sumologic endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging sumologic update --version=VERSION --name=NAME [] - Update a Sumologic logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Sumologic logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Sumologic logging object - --url=URL The URL to POST to - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either - 2 (the default, version 2 log format) or - 1 (the version 1 log format). The logging - call gets placed by default in vcl_log if - format_version is set to 2 and in vcl_deliver - if format_version is set to 1 - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --placement=PLACEMENT Where in the generated VCL the logging call - should be placed, overriding any format_version - default. Can be none or waf_debug. This field - is not required and has no default value - - logging syslog create --name=NAME --version=VERSION --address=ADDRESS [] - Create a Syslog logging endpoint on a Fastly service version - - -n, --name=NAME The name of the Syslog logging object. - Used as a primary key for API access - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --address=ADDRESS A hostname or IPv4 address - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --port=PORT The port number - --use-tls Whether to use TLS for secure logging. - Can be either true or false - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - Used during the TLS handshake to validate the - certificate - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --auth-token=AUTH-TOKEN Whether to prepend each message with a - specific token - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - - logging syslog delete --version=VERSION --name=NAME [] - Delete a Syslog logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Syslog logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - logging syslog describe --version=VERSION --name=NAME [] - Show detailed information about a Syslog logging endpoint on a Fastly - service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - -n, --name=NAME The name of the Syslog logging object - - logging syslog list --version=VERSION [] - List Syslog endpoints on a Fastly service version - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - logging syslog update --version=VERSION --name=NAME [] - Update a Syslog logging endpoint on a Fastly service version - - --version=VERSION 'latest', 'active', or the number of a - specific version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -n, --name=NAME The name of the Syslog logging object - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --new-name=NEW-NAME New name of the Syslog logging object - --address=ADDRESS A hostname or IPv4 address - --port=PORT The port number - --use-tls Whether to use TLS for secure logging. - Can be either true or false - --tls-ca-cert=TLS-CA-CERT A secure certificate to authenticate the - server with. Must be in PEM format - --tls-hostname=TLS-HOSTNAME - Used during the TLS handshake to validate the - certificate - --tls-client-cert=TLS-CLIENT-CERT - The client certificate used to make - authenticated requests. Must be in PEM format - --tls-client-key=TLS-CLIENT-KEY - The client private key used to make - authenticated requests. Must be in PEM format - --auth-token=AUTH-TOKEN Whether to prepend each message with a - specific token - --format=FORMAT Apache style log formatting - --format-version=FORMAT-VERSION - The version of the custom logging format used - for the configured endpoint. Can be either 2 - (default) or 1 - --message-type=MESSAGE-TYPE - How the message should be formatted. One of: - classic (default), loggly, logplex or blank - --response-condition=RESPONSE-CONDITION - The name of an existing condition in the - configured endpoint, or leave blank to always - execute - --placement=PLACEMENT Where in the generated VCL the logging - call should be placed, overriding any - format_version default. Can be none or - waf_debug - - pops - List Fastly datacenters - - - profile create [] - Create user profile - - - profile delete - Delete user profile - - - profile list [] - List user profiles - - -j, --json Render output as JSON - - profile switch - Switch user profile - - - profile token [] - Print access token - - -n, --name=NAME Print access token for the named profile - - profile update [] - Update user profile - - - purge [] - Invalidate objects in the Fastly cache - - --all Purge everything from a service - --file=FILE Purge a service of a newline delimited list of - Surrogate Keys - --key=KEY Purge a service of objects tagged with a - Surrogate Key - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --soft A 'soft' purge marks affected objects as stale - rather than making them inaccessible - --url=URL Purge an individual URL - - service create --name=NAME [] - Create a Fastly service - - -n, --name=NAME Service name - --type=vcl Service type. Can be one of "wasm" or "vcl", defaults - to "vcl". - --comment=COMMENT Human-readable comment - - service delete [] - Delete a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - -f, --force Force deletion of an active service - - service describe [] - Show detailed information about a Fastly service - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - service list [] - List Fastly services - - --direction=ascend Direction in which to sort results - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort="created" Field on which to sort - - service search --name=NAME - Search for a Fastly service by name - - -n, --name=NAME Service name - - service update [] - Update a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - -n, --name=NAME Service name - --comment=COMMENT Human-readable comment - - service-version activate --version=VERSION [] - Activate a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - - service-version clone --version=VERSION [] - Clone a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - service-version deactivate --version=VERSION [] - Deactivate a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - service-version list [] - List Fastly service versions - - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - service-version lock --version=VERSION [] - Lock a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - - service-version update --version=VERSION [] - Update a Fastly service version - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --comment=COMMENT Human-readable comment - - stats historical [] - View historical stats for a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --from=FROM From time, accepted formats at - https://fastly.dev/reference/api/metrics-stats/historical-stats - --to=TO To time - --by=BY Aggregation period (minute/hour/day) - --region=REGION Filter by region ('stats regions' to list) - --format=FORMAT Output format (json) - - stats realtime [] - View realtime stats for a Fastly service - - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --format=FORMAT Output format (json) - - stats regions - List stats regions - - - tls-config describe --id=ID [] - Show a TLS configuration - - --id=ID Alphanumeric string identifying a TLS configuration - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - - tls-config list [] - List all TLS configurations - - --filter-bulk Optionally filter by the bulk attribute - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - - tls-config update --id=ID --name=NAME - Update a TLS configuration - - --id=ID Alphanumeric string identifying a TLS configuration - --name=NAME A custom name for your TLS configuration - - tls-custom activation enable --cert-id=CERT-ID --id=ID - Enable TLS for a particular TLS domain and certificate combination - - --cert-id=CERT-ID Alphanumeric string identifying a TLS certificate - --id=ID Alphanumeric string identifying a TLS activation - - tls-custom activation disable --id=ID - Disable TLS on the domain associated with this TLS activation - - --id=ID Alphanumeric string identifying a TLS activation - - tls-custom activation describe --id=ID [] - Show a TLS configuration - - --id=ID Alphanumeric string identifying a TLS activation - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - - tls-custom activation list [] - List all TLS activations - - --filter-cert=FILTER-CERT Limit the returned activations to a specific - certificate - --filter-config=FILTER-CONFIG - Limit the returned activations to a specific - TLS configuration - --filter-domain=FILTER-DOMAIN - Limit the returned rules to a specific domain - name - --include=INCLUDE Include related objects (comma-separated - values) - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - - tls-custom activation update --cert-id=CERT-ID --id=ID - Update the certificate used to terminate TLS traffic for the domain - associated with this TLS activation - - --cert-id=CERT-ID Alphanumeric string identifying a TLS certificate - --id=ID Alphanumeric string identifying a TLS activation - - tls-custom certificate create --cert-blob=CERT-BLOB [] - Create a TLS certificate - - --cert-blob=CERT-BLOB The PEM-formatted certificate blob - --id=ID Alphanumeric string identifying a TLS certificate - --name=NAME A customizable name for your certificate. Defaults - to the certificate's Common Name or first Subject - Alternative Names (SAN) entry - - tls-custom certificate delete --id=ID - Destroy a TLS certificate. TLS certificates already enabled for a domain - cannot be destroyed - - --id=ID Alphanumeric string identifying a TLS certificate - - tls-custom certificate describe --id=ID [] - Show a TLS certificate - - --id=ID Alphanumeric string identifying a TLS certificate - -j, --json Render output as JSON - - tls-custom certificate list [] - List all TLS certificates - - --filter-not-after=FILTER-NOT-AFTER - Limit the returned certificates to those that - expire prior to the specified date in UTC - --filter-domain=FILTER-DOMAIN - Limit the returned certificates to those that - include the specific domain - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort=SORT The order in which to list the results by creation - date - - tls-custom certificate update --cert-blob=CERT-BLOB --id=ID [] - Replace a TLS certificate with a newly reissued TLS certificate, or update a - TLS certificate's name - - --cert-blob=CERT-BLOB The PEM-formatted certificate blob - --id=ID Alphanumeric string identifying a TLS certificate - --name=NAME A customizable name for your certificate. Defaults - to the certificate's Common Name or first Subject - Alternative Names (SAN) entry - - tls-custom domain list [] - List all TLS domains - - --filter-cert=FILTER-CERT Limit the returned domains to those listed in - the given TLS certificate's SAN list - --filter-in-use Limit the returned domains to those currently - using Fastly to terminate TLS with SNI - --filter-subscription=FILTER-SUBSCRIPTION - Limit the returned domains to those for a - given TLS subscription - --include=INCLUDE Include related objects (comma-separated - values) - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort=SORT The order in which to list the results by - creation date - - tls-custom private-key create --key=KEY --name=NAME - Create a TLS certificate - - --key=KEY The contents of the private key. Must be a PEM-formatted key - --name=NAME A customizable name for your private key - - tls-custom private-key delete --id=ID - Destroy a TLS private key. Only private keys not already matched to any - certificates can be deleted - - --id=ID Alphanumeric string identifying a private Key - - tls-custom private-key describe --id=ID [] - Show a TLS private key - - --id=ID Alphanumeric string identifying a private Key - -j, --json Render output as JSON - - tls-custom private-key list [] - List all TLS private keys - - --filter-in-use=FILTER-IN-USE - Limit the returned keys to those without any - matching TLS certificates - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - - tls-platform upload --cert-blob=CERT-BLOB --intermediates-blob=INTERMEDIATES-BLOB [] - Upload a new certificate - - --cert-blob=CERT-BLOB The PEM-formatted certificate blob - --intermediates-blob=INTERMEDIATES-BLOB - The PEM-formatted chain of intermediate blobs - --allow-untrusted Allow certificates that chain to untrusted roots - --config=CONFIG ... Alphanumeric string identifying a TLS configuration - (set flag once per Configuration ID) - - tls-platform delete --id=ID - Destroy a certificate. This disables TLS for all domains listed as SAN - entries - - --id=ID Alphanumeric string identifying a TLS bulk certificate - - tls-platform describe --id=ID [] - Retrieve a single certificate - - --id=ID Alphanumeric string identifying a TLS bulk certificate - -j, --json Render output as JSON - - tls-platform list [] - List all certificates - - --filter-domain=FILTER-DOMAIN - Optionally filter by the bulk attribute - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort=SORT The order in which to list the results by creation - date - - tls-platform update --id=ID --cert-blob=CERT-BLOB --intermediates-blob=INTERMEDIATES-BLOB [] - Replace a certificate with a newly reissued certificate - - --id=ID Alphanumeric string identifying a TLS bulk - certificate - --cert-blob=CERT-BLOB The PEM-formatted certificate blob - --intermediates-blob=INTERMEDIATES-BLOB - The PEM-formatted chain of intermediate blobs - --allow-untrusted Allow certificates that chain to untrusted roots - - tls-subscription create --domain=DOMAIN [] - Create a new TLS subscription - - --domain=DOMAIN ... Domain(s) to add to the TLS certificates - generated for the subscription (set flag once per - domain) - --cert-auth=CERT-AUTH The entity that issues and certifies the TLS - certificates for your subscription. Valid values - are lets-encrypt or globalsign - --common-name=COMMON-NAME The domain name associated with the subscription. - Default to the first domain specified by --domain - --config=CONFIG Alphanumeric string identifying a TLS - configuration - - tls-subscription delete --id=ID [] - Destroy a TLS subscription. A subscription cannot be destroyed if there are - domains in the TLS enabled state - - --id=ID Alphanumeric string identifying a TLS subscription - --force A flag that allows you to edit and delete a subscription with - active domains - - tls-subscription describe --id=ID [] - Show a TLS subscription - - --id=ID Alphanumeric string identifying a TLS subscription - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - - tls-subscription list [] - List all TLS subscriptions - - --filter-active Limit the returned subscriptions to those that have - currently active orders - --filter-domain=FILTER-DOMAIN - Limit the returned subscriptions to those that - include the specific domain - --filter-state=FILTER-STATE - Limit the returned subscriptions by state - --include=INCLUDE Include related objects (comma-separated values) - -j, --json Render output as JSON - --page=PAGE Page number of data set to fetch - --per-page=PER-PAGE Number of records per page - --sort=SORT The order in which to list the results by creation - date - - tls-subscription update --id=ID [] - Change the TLS domains or common name associated with this subscription, - or update the TLS configuration for this set of domains - - --id=ID Alphanumeric string identifying a TLS - subscription - --common-name=COMMON-NAME The domain name associated with the subscription - --config=CONFIG Alphanumeric string identifying a TLS - configuration - --domain=DOMAIN ... Domain(s) to add to the TLS certificates - generated for the subscription (set flag once per - domain) - --force A flag that allows you to edit and delete a - subscription with active domains - - update - Update the CLI to the latest version - - - user create --login=LOGIN --name=NAME [] - Create a user of the Fastly API and web interface - - --login=LOGIN The login associated with the user (typically, an email - address) - --name=NAME The real life name of the user - --role=ROLE The permissions role assigned to the user. Can be user, - billing, engineer, or superuser - - user delete --id=ID - Delete a user of the Fastly API and web interface - - --id=ID Alphanumeric string identifying the user - - user describe [] - Get a specific user of the Fastly API and web interface - - --current Get the logged in user - --id=ID Alphanumeric string identifying the user - -j, --json Render output as JSON - - user list [] - List all users from a specified customer id - - --customer-id=CUSTOMER-ID Alphanumeric string identifying the customer - (falls back to FASTLY_CUSTOMER_ID) - -j, --json Render output as JSON - - user update [] - Update a user of the Fastly API and web interface - - --id=ID Alphanumeric string identifying the user - --login=LOGIN The login associated with the user (typically, an email - address) - --name=NAME The real life name of the user - --password-reset Requests a password reset for the specified user - --role=ROLE The permissions role assigned to the user. Can be user, - billing, engineer, or superuser - - vcl custom create --content=CONTENT --name=NAME --version=VERSION [] - Upload a VCL for a particular service and version - - --content=CONTENT VCL passed as file path or content, e.g. - $(< main.vcl) - --name=NAME The name of the VCL - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --main Whether the VCL is the 'main' entrypoint - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl custom delete --name=NAME --version=VERSION [] - Delete the uploaded VCL for a particular service and version - - --name=NAME The name of the VCL to delete - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl custom describe --name=NAME --version=VERSION [] - Get the uploaded VCL for a particular service and version - - --name=NAME The name of the VCL - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl custom list --version=VERSION [] - List the uploaded VCLs for a particular service and version - - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl custom update --name=NAME --version=VERSION [] - Update the uploaded VCL for a particular service and version - - --name=NAME The name of the VCL to update - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --new-name=NEW-NAME New name for the VCL - --content=CONTENT VCL passed as file path or content, e.g. - $(< main.vcl) - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl snippet create --content=CONTENT --name=NAME --version=VERSION --type=TYPE [] - Create a snippet for a particular service and version - - --content=CONTENT VCL snippet passed as file path or content, - e.g. $(< snippet.vcl) - --name=NAME The name of the VCL snippet - --version=VERSION 'latest', 'active', or the number of a specific - version - --type=TYPE The location in generated VCL where the snippet - should be placed - --autoclone If the selected service version is not - editable, clone it and use the clone. - --dynamic Whether the VCL snippet is dynamic or versioned - -p, --priority=PRIORITY Priority determines execution order. Lower - numbers execute first - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl snippet delete --name=NAME --version=VERSION [] - Delete a specific snippet for a particular service and version - - --name=NAME The name of the VCL snippet to delete - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl snippet describe --version=VERSION [] - Get the uploaded VCL snippet for a particular service and version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --dynamic Whether the VCL snippet is dynamic or versioned - -j, --json Render output as JSON - --name=NAME The name of the VCL snippet - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --snippet-id=SNIPPET-ID Alphanumeric string identifying a VCL Snippet - - vcl snippet list --version=VERSION [] - List the uploaded VCL snippets for a particular service and version - - --version=VERSION 'latest', 'active', or the number of a specific - version - -j, --json Render output as JSON - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - - vcl snippet update --version=VERSION [] - Update a VCL snippet for a particular service and version - - --version=VERSION 'latest', 'active', or the number of a specific - version - --autoclone If the selected service version is not - editable, clone it and use the clone. - --content=CONTENT VCL snippet passed as file path or content, - e.g. $(< snippet.vcl) - --dynamic Whether the VCL snippet is dynamic or versioned - --name=NAME The name of the VCL snippet to update - --new-name=NEW-NAME New name for the VCL snippet - -p, --priority=PRIORITY Priority determines execution order. Lower - numbers execute first - -s, --service-id=SERVICE-ID Service ID (falls back to FASTLY_SERVICE_ID, - then fastly.toml) - --service-name=SERVICE-NAME - The name of the service - --snippet-id=SNIPPET-ID Alphanumeric string identifying a VCL Snippet - --type=TYPE The location in generated VCL where the snippet - should be placed - - version - Display version information for the Fastly CLI - - - whoami - Get information about the currently authenticated account - - -SEE ALSO - https://developer.fastly.com/reference/cli/ - -For help on a specific command, try e.g. - - fastly help profile - fastly profile --help -`) + "\n\n" diff --git a/pkg/app/usage.go b/pkg/app/usage.go index 6ce0c2a02..586e7db1f 100644 --- a/pkg/app/usage.go +++ b/pkg/app/usage.go @@ -370,6 +370,10 @@ func processCommandInput( return command, cmdName, nil } +// metadata is combined into the usage output so the Developer Hub can display +// additional information about how to use the commands and what APIs they call. +// e.g. https://developer.fastly.com/reference/cli/vcl/snippet/create/ +// //go:embed metadata.json var metadata []byte diff --git a/pkg/commands/compute/build.go b/pkg/commands/compute/build.go index 857833c5a..53da8c1ce 100644 --- a/pkg/commands/compute/build.go +++ b/pkg/commands/compute/build.go @@ -16,33 +16,20 @@ import ( "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" - "github.com/kennygrant/sanitize" "github.com/mholt/archiver/v3" ) // IgnoreFilePath is the filepath name of the Fastly ignore file. const IgnoreFilePath = ".fastlyignore" -// CustomBuildScriptMessage is the message displayed to a user when there is a -// custom build script. -const CustomBuildScriptMessage = "This project has a custom build script defined in the fastly.toml manifest" - // CustomPostBuildScriptMessage is the message displayed to a user when there is a // custom post build script. const CustomPostBuildScriptMessage = "This project has a custom post build script defined in the fastly.toml manifest" -// Toolchain abstracts a Compute@Edge source language toolchain. -type Toolchain interface { - Initialize(out io.Writer) error - Verify(out io.Writer) error - Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error -} - // Flags represents the flags defined for the command. type Flags struct { IncludeSrc bool Lang string - PackageName string SkipVerification bool Timeout int } @@ -68,7 +55,6 @@ func NewBuildCommand(parent cmd.Registerer, globals *config.Data, data manifest. // `compute publish` and `compute serve`. c.CmdClause.Flag("include-source", "Include source code in built package").BoolVar(&c.Flags.IncludeSrc) c.CmdClause.Flag("language", "Language type").StringVar(&c.Flags.Lang) - c.CmdClause.Flag("name", "Package name").StringVar(&c.Flags.PackageName) c.CmdClause.Flag("skip-verification", "Skip verification steps and force build").BoolVar(&c.Flags.SkipVerification) c.CmdClause.Flag("timeout", "Timeout, in seconds, for the build compilation step").IntVar(&c.Flags.Timeout) @@ -112,34 +98,23 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { toolchain = strings.ToLower(strings.TrimSpace(toolchain)) - // Name from flag takes priority, otherwise infer from manifest - // error if neither are provided. Sanitize value to ensure it is a safe - // filepath, replacing spaces with hyphens etc. - var name string - - switch { - case c.Flags.PackageName != "": - name = c.Flags.PackageName - case c.Manifest.File.Name != "": - name = c.Manifest.File.Name - default: - return fmt.Errorf("name cannot be empty, please provide a name") - } - - name = sanitize.BaseName(name) + // ch is used to identify if the fastly.toml manifest has been patched with a + // language specific default build command (because one was missing). + ch := make(chan string) var language *Language switch toolchain { case "assemblyscript": language = NewLanguage(&LanguageOptions{ Name: "assemblyscript", - SourceDirectory: ASSourceDirectory, + SourceDirectory: AsSourceDirectory, IncludeFiles: []string{}, Toolchain: NewAssemblyScript( - name, - c.Manifest.File.Scripts, + &c.Manifest.File, c.Globals.ErrLog, c.Flags.Timeout, + progress, + ch, ), }) case "go": @@ -148,23 +123,25 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { SourceDirectory: GoSourceDirectory, IncludeFiles: []string{}, Toolchain: NewGo( - name, - c.Manifest.File.Scripts, + &c.Manifest.File, c.Globals.ErrLog, c.Flags.Timeout, c.Globals.File.Language.Go, + progress, + ch, ), }) case "javascript": language = NewLanguage(&LanguageOptions{ Name: "javascript", - SourceDirectory: JSSourceDirectory, + SourceDirectory: JsSourceDirectory, IncludeFiles: []string{}, Toolchain: NewJavaScript( - name, - c.Manifest.File.Scripts, + &c.Manifest.File, c.Globals.ErrLog, c.Flags.Timeout, + progress, + ch, ), }) case "rust": @@ -173,12 +150,12 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { SourceDirectory: RustSourceDirectory, IncludeFiles: []string{}, Toolchain: NewRust( - name, - c.Manifest.File.Scripts, + &c.Manifest.File, c.Globals.ErrLog, - c.Globals.HTTPClient, c.Flags.Timeout, c.Globals.File.Language.Rust, + progress, + ch, ), }) case "other": @@ -194,17 +171,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { return fmt.Errorf("unsupported language %s", toolchain) } - // NOTE: If there is a custom build script defined, then we set the toolchain - // to be "custom" as it means the CLI is no longer responsible for verifying - // the user's environment and isn't directly executing its own build process. - if c.Manifest.File.Scripts.Build != "" { - toolchain = "custom" - } - - // NOTE: When we find a custom build script, we don't verify the local - // environment (it's up to the user to ensure they have all the tools - // necessary to run their custom build script). - if c.Manifest.File.Scripts.Build == "" && !c.Flags.SkipVerification { + if !c.Flags.SkipVerification { progress.Step(fmt.Sprintf("Verifying local %s toolchain...", toolchain)) err = language.Verify(progress) @@ -220,25 +187,12 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { // print doesn't get hidden by the progress status. progress.Done() - if toolchain == "custom" { - if !c.Globals.Flag.AutoYes && !c.Globals.Flag.NonInteractive { - // NOTE: A third-party could share a project with a build command for a - // language that wouldn't normally require one (e.g. Rust), and do evil - // things. So we should notify the user and confirm they would like to - // continue with the build. - err := promptForBuildContinue(CustomBuildScriptMessage, c.Manifest.File.Scripts.Build, out, in, c.Globals.Verbose()) - if err != nil { - return err - } - } - } - if c.Globals.Verbose() { text.Break(out) } progress = text.ResetProgress(out, c.Globals.Verbose()) - progress.Step(fmt.Sprintf("Building package using %s toolchain...", toolchain)) + progress.Step("Running [scripts.build]...") postBuildCallback := func() error { if !c.Globals.Flag.AutoYes && !c.Globals.Flag.NonInteractive { @@ -264,7 +218,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { progress = text.ResetProgress(out, c.Globals.Verbose()) progress.Step("Creating package archive...") - dest := filepath.Join("pkg", fmt.Sprintf("%s.tar.gz", name)) + dest := filepath.Join("pkg", "package.tar.gz") files := []string{ manifest.Filename, @@ -309,7 +263,21 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { progress.Done() - text.Success(out, "Built package '%s' (%s)", name, dest) + // When patching fastly.toml with a default build command, in --verbose mode + // that information is already printed to the screen, but in standard output + // mode we need to ensure it's visible so users know the fastly.toml has been + // updated. + if !c.Globals.Verbose() { + select { + case msg := <-ch: + text.Info(out, msg) + default: + // no message, so moving on to prevent deadlock + } + close(ch) + } + + text.Success(out, "Built package (%s)", dest) return nil } diff --git a/pkg/commands/compute/build_test.go b/pkg/commands/compute/build_test.go index 48e5ca841..3468dcfe8 100644 --- a/pkg/commands/compute/build_test.go +++ b/pkg/commands/compute/build_test.go @@ -2,16 +2,17 @@ package compute_test import ( "bytes" + "fmt" "os" "path/filepath" "strings" "testing" - "github.com/fastly/cli/pkg/api" "github.com/fastly/cli/pkg/app" "github.com/fastly/cli/pkg/commands/compute" "github.com/fastly/cli/pkg/config" "github.com/fastly/cli/pkg/manifest" + fstruntime "github.com/fastly/cli/pkg/runtime" "github.com/fastly/cli/pkg/testutil" "github.com/fastly/cli/pkg/threadsafe" ) @@ -21,15 +22,11 @@ import ( // NOTE: // The defined tests rely on some key pieces of information: // -// 1. The `fastly` crate internally consumes a `fastly-sys` crate. -// 2. The `fastly-sys` create didn't exist until `fastly` 0.4.0. -// 3. Users of `fastly` should always have the latest `fastly-sys`. -// 4. Users of `fastly` shouldn't need to know about `fastly-sys`. -// 5. Each test has a 'default' Cargo.toml created for it. -// 6. Each test can override the default Cargo.toml by defining a `cargoManifest`. +// 1. Each test has a 'default' Cargo.toml created for it. +// 2. Each test can override the default Cargo.toml by defining a `cargoManifest`. // // You can locate the default Cargo.toml here: -// pkg/commands/compute/testdata/build/Cargo.toml +// ./testdata/build/rust/Cargo.toml func TestBuildRust(t *testing.T) { if os.Getenv("TEST_COMPUTE_BUILD_RUST") == "" && os.Getenv("TEST_COMPUTE_BUILD") == "" { t.Log("skipping test") @@ -44,8 +41,6 @@ func TestBuildRust(t *testing.T) { applicationConfig config.File fastlyManifest string cargoManifest string - cargoLock string - client api.HTTPClient wantError string wantRemediationError string wantOutputContains string @@ -53,7 +48,6 @@ func TestBuildRust(t *testing.T) { { name: "no fastly.toml manifest", args: args("compute build"), - client: versionClient{fastlyVersions: []string{"0.0.0"}}, wantError: "error reading package manifest", wantRemediationError: "Run `fastly compute init` to ensure a correctly configured manifest.", }, @@ -63,18 +57,8 @@ func TestBuildRust(t *testing.T) { fastlyManifest: ` manifest_version = 2 name = "test"`, - client: versionClient{fastlyVersions: []string{"0.0.0"}}, wantError: "language cannot be empty, please provide a language", }, - { - name: "empty name", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - language = "rust"`, - client: versionClient{fastlyVersions: []string{"0.0.0"}}, - wantError: "name cannot be empty, please provide a name", - }, { name: "unknown language", args: args("compute build"), @@ -82,11 +66,10 @@ func TestBuildRust(t *testing.T) { manifest_version = 2 name = "test" language = "foobar"`, - client: versionClient{fastlyVersions: []string{"0.0.0"}}, wantError: "unsupported language foobar", }, { - name: "error reading cargo metadata", + name: "missing fastly dependency", args: args("compute build"), fastlyManifest: ` manifest_version = 2 @@ -95,93 +78,15 @@ func TestBuildRust(t *testing.T) { cargoManifest: ` [package] name = "test"`, - client: versionClient{fastlyVersions: []string{"0.4.0"}}, - wantError: "reading cargo metadata", - applicationConfig: config.File{ - Language: config.Language{ - Rust: config.Rust{ - ToolchainVersion: "1.49.0", - ToolchainConstraint: ">= 1.54.0", - WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: "0.0.0", - RustupConstraint: ">= 1.23.0", - }, - }, - }, - }, - { - name: "fastly-sys crate not found", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - name = "test" - language = "rust"`, - cargoManifest: ` - [package] - name = "test" - version = "0.1.0" - - [dependencies] - fastly = "=0.3.2"`, - cargoLock: ` - [[package]] - name = "test" - version = "0.1.0" - - [[package]] - name = "fastly" - version = "0.3.2"`, - applicationConfig: config.File{ - Language: config.Language{ - Rust: config.Rust{ - ToolchainVersion: "1.49.0", - ToolchainConstraint: ">= 1.54.0", - WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: "0.0.0", - RustupConstraint: ">= 1.23.0", - }, - }, - }, - client: versionClient{ - fastlyVersions: []string{"0.4.0"}, - }, - wantError: "fastly-sys crate not found", // fastly 0.3.3 is where fastly-sys was introduced - wantRemediationError: "fastly = \"^0.4.0\"", - }, - { - name: "fastly-sys crate out-of-date", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - name = "test" - language = "rust"`, - cargoManifest: ` - [package] - name = "test" - version = "0.1.0" - - [dependencies] - fastly = "=0.4.0"`, - cargoLock: ` - [[package]] - name = "fastly-sys" - version = "0.3.7"`, + wantError: "failed to find SDK 'fastly' in the 'Cargo.toml' manifest", applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.4.0 <= 0.9.0", // the fastly-sys version in 0.6.0 is actually ^0.3.6 so a minimum of 0.4.0 causes the constraint to fail - RustupConstraint: ">= 1.23.0", }, }, }, - client: versionClient{ - fastlyVersions: []string{"0.6.0"}, - }, - wantError: "fastly crate not up-to-date", - wantRemediationError: "fastly = \"^0.6.0\"", }, { name: "rust toolchain does not match the constraint", @@ -197,104 +102,73 @@ func TestBuildRust(t *testing.T) { [dependencies] fastly = "=0.4.0"`, - cargoLock: ` - [[package]] - name = "fastly-sys" - version = "0.3.7"`, applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - // NOTE: A 'stable' release will fail the constraint, which is set - // to something lower than current stable. - ToolchainVersion: "1.0.0", + // NOTE: This test is purposely setting the constraint lower to what + // is reasonably going to be the current stable release installed to + // force the test failure scenario to be hit. ToolchainConstraint: ">= 1.0.0 < 1.40.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.4.0 <= 0.9.0", // the fastly-sys version in 0.6.0 is actually ^0.3.6 so a minimum of 0.4.0 causes the constraint to fail - RustupConstraint: ">= 1.23.0", }, }, }, - client: versionClient{ - fastlyVersions: []string{"0.6.0"}, - }, - wantError: "rustc constraint '>= 1.0.0 < 1.40.0' not met:", + wantError: "didn't meet the constraint >= 1.0.0 < 1.40.0", wantRemediationError: "Run `rustup update stable`, or ensure your `rust-toolchain` file specifies a version matching the constraint (e.g. `channel = \"stable\"`).", }, { name: "fastly crate prerelease", args: args("compute build"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "rust"`, + language = "rust" + + [scripts] + build = "%s"`, compute.RustDefaultBuildCommand), applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.3.0 <= 0.6.0", - RustupConstraint: ">= 1.23.0", }, }, }, cargoManifest: ` [package] - name = "test" + name = "compute-starter-kit-rust" version = "0.1.0" [dependencies] fastly = "0.6.0"`, - cargoLock: ` - [[package]] - name = "fastly-sys" - version = "0.3.7" - - [[package]] - name = "fastly" - version = "0.6.0"`, - client: versionClient{ - fastlyVersions: []string{"0.6.0"}, - }, - wantOutputContains: "Built package 'test'", + wantOutputContains: "Built package", }, { - name: "Rust success", + name: "successful build", args: args("compute build"), applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.3.0 <= 0.6.0", - RustupConstraint: ">= 1.23.0", }, }, }, - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "rust"`, + language = "rust" + + [scripts] + build = "%s"`, compute.RustDefaultBuildCommand), cargoManifest: ` [package] - name = "test" + name = "compute-starter-kit-rust" version = "0.1.0" [dependencies] fastly = "=0.6.0"`, - cargoLock: ` - [[package]] - name = "fastly" - version = "0.6.0" - - [[package]] - name = "fastly-sys" - version = "0.3.7"`, - client: versionClient{ - fastlyVersions: []string{"0.6.0"}, - }, - wantOutputContains: "Built package 'test'", + wantOutputContains: "Built package", }, } for testcaseIdx := range scenarios { @@ -319,7 +193,6 @@ func TestBuildRust(t *testing.T) { Write: []testutil.FileIO{ {Src: testcase.fastlyManifest, Dst: manifest.Filename}, {Src: testcase.cargoManifest, Dst: "Cargo.toml"}, - {Src: testcase.cargoLock, Dst: "Cargo.lock"}, }, }) defer os.RemoveAll(rootdir) @@ -335,7 +208,6 @@ func TestBuildRust(t *testing.T) { var stdout bytes.Buffer opts := testutil.NewRunOpts(testcase.args, &stdout) opts.ConfigFile = testcase.applicationConfig - opts.HTTPClient = testcase.client err = app.Run(opts) t.Log(stdout.String()) testutil.AssertErrorContains(t, err, testcase.wantError) @@ -358,6 +230,7 @@ func TestBuildAssemblyScript(t *testing.T) { name string args []string fastlyManifest string + skipWindows bool wantError string wantRemediationError string wantOutputContains string @@ -376,14 +249,6 @@ func TestBuildAssemblyScript(t *testing.T) { name = "test"`, wantError: "language cannot be empty, please provide a language", }, - { - name: "empty name", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - language = "assemblyscript"`, - wantError: "name cannot be empty, please provide a name", - }, { name: "unknown language", args: args("compute build"), @@ -394,16 +259,24 @@ func TestBuildAssemblyScript(t *testing.T) { wantError: "unsupported language foobar", }, { - name: "AssemblyScript success", + name: "successful build", args: args("compute build"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "assemblyscript"`, - wantOutputContains: "Built package 'test'", + language = "assemblyscript" + + [scripts] + build = "%s"`, compute.AsDefaultBuildCommand), + wantOutputContains: "Built package", + skipWindows: true, }, } { t.Run(testcase.name, func(t *testing.T) { + if fstruntime.Windows && testcase.skipWindows { + t.Skip() + } + // We're going to chdir to a build environment, // so save the PWD to return to, afterwards. pwd, err := os.Getwd() @@ -483,6 +356,7 @@ func TestBuildJavaScript(t *testing.T) { name string args []string fastlyManifest string + skipWindows bool sourceOverride string wantError string wantRemediationError string @@ -503,36 +377,39 @@ func TestBuildJavaScript(t *testing.T) { wantError: "language cannot be empty, please provide a language", }, { - name: "empty name", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - language = "javascript"`, - wantError: "name cannot be empty, please provide a name", - }, - { - name: "JavaScript compilation error", + name: "compilation error", args: args("compute build --verbose"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "javascript"`, + language = "javascript" + + [scripts] + build = "%s"`, compute.JsDefaultBuildCommand), sourceOverride: `D"F; 'GREGERgregeg ' ERG`, wantError: "error during execution process", }, { - name: "JavaScript success", + name: "successful build", args: args("compute build"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "javascript"`, - wantOutputContains: "Built package 'test'", + language = "javascript" + + [scripts] + build = "%s"`, compute.JsDefaultBuildCommand), + wantOutputContains: "Built package", + skipWindows: true, }, } { t.Run(testcase.name, func(t *testing.T) { + if fstruntime.Windows && testcase.skipWindows { + t.Skip() + } + if testcase.fastlyManifest != "" { if err := os.WriteFile(filepath.Join(rootdir, manifest.Filename), []byte(testcase.fastlyManifest), 0o777); err != nil { t.Fatal(err) @@ -629,34 +506,32 @@ func TestBuildGo(t *testing.T) { name = "test"`, wantError: "language cannot be empty, please provide a language", }, - { - name: "empty name", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - language = "go"`, - wantError: "name cannot be empty, please provide a name", - }, { name: "syntax error", args: args("compute build --verbose"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "go"`, + language = "go" + + [scripts] + build = "%s"`, compute.GoDefaultBuildCommand), sourceOverride: `D"F; 'GREGERgregeg ' ERG`, wantError: "error during execution process", }, { - name: "success", + name: "successful build", args: args("compute build"), - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" - language = "go"`, - wantOutputContains: "Built package 'test'", + language = "go" + + [scripts] + build = "%s"`, compute.GoDefaultBuildCommand), + wantOutputContains: "Built package", }, } { t.Run(testcase.name, func(t *testing.T) { @@ -707,7 +582,7 @@ func TestBuildGo(t *testing.T) { } } -func TestCustomBuild(t *testing.T) { +func TestOtherBuild(t *testing.T) { args := testutil.Args if os.Getenv("TEST_COMPUTE_BUILD") == "" { t.Log("skipping test") @@ -752,17 +627,6 @@ func TestCustomBuild(t *testing.T) { wantOutput []string wantRemediationError string }{ - { - name: "no custom build", - args: args("compute build --language other"), - fastlyManifest: ` - manifest_version = 2 - name = "test" - language = "other"`, - dontWantOutput: []string{compute.CustomBuildScriptMessage}, - wantError: "error reading custom build instructions from fastly.toml manifest", - wantRemediationError: "Add a [scripts.build] setting for your custom build process", - }, { name: "stop build process", args: args("compute build --language other"), @@ -771,16 +635,14 @@ func TestCustomBuild(t *testing.T) { name = "test" language = "other" [scripts] - build = "echo custom build"`, + build = "echo custom build" + post_build = "echo doing a post build"`, stdin: "N", wantOutput: []string{ - compute.CustomBuildScriptMessage, - "echo custom build", - "Are you sure you want to continue with the build step?", - "Stopping the build process", + "echo doing a post build", + "Are you sure you want to continue with the post build step?", + "Stopping the post build process", }, - wantError: "build process stopped by user", - wantRemediationError: "Remove or update the custom [scripts.build] in the fastly.toml manifest.", }, { name: "allow build process", @@ -790,14 +652,14 @@ func TestCustomBuild(t *testing.T) { name = "test" language = "other" [scripts] - build = "echo custom build"`, + build = "echo custom build" + post_build = "echo doing a post build"`, stdin: "Y", wantOutput: []string{ - compute.CustomBuildScriptMessage, - "echo custom build", - "Are you sure you want to continue with the build step?", - "Building package using custom toolchain", - "Built package 'test'", + "echo doing a post build", + "Are you sure you want to continue with the post build step?", + "Running [scripts.build]", + "Built package", }, }, { @@ -808,33 +670,15 @@ func TestCustomBuild(t *testing.T) { name = "test" language = "other" [scripts] - build = "echo custom build"`, - stdin: "Y", - wantOutput: []string{ - compute.CustomBuildScriptMessage, - "echo custom build", - "Are you sure you want to continue with the build step?", - "Building package using custom toolchain", - "Built package 'test'", - }, - }, - { - name: "display the custom build when using a non-other language", - args: args("compute build"), - fastlyManifest: ` - manifest_version = 2 - name = "test" - language = "rust" - [scripts] - build = "echo custom build"`, + build = "echo custom build" + post_build = "echo doing a post build"`, stdin: "Y", wantOutput: []string{ - compute.CustomBuildScriptMessage, - "echo custom build", - "Are you sure you want to continue with the build step?", - "Building package using custom toolchain", + "echo doing a post build", + "Are you sure you want to continue with the post build step?", + "Running [scripts.build]", + "Built package", }, - wantError: "error reading Cargo.toml manifest", // we expect this to error as we don't actually setup the relevant files for a rust build }, { name: "avoid prompt confirmation", @@ -846,11 +690,10 @@ func TestCustomBuild(t *testing.T) { [scripts] build = "echo custom build"`, wantOutput: []string{ - "Building package using custom toolchain", - "Built package 'test'", + "Running [scripts.build]", + "Built package", }, dontWantOutput: []string{ - compute.CustomBuildScriptMessage, "Are you sure you want to continue with the build step?", }, }, @@ -923,6 +766,7 @@ func TestCustomPostBuild(t *testing.T) { scenarios := []struct { applicationConfig config.File args []string + cargoManifest string dontWantOutput []string fastlyManifest string name string @@ -931,17 +775,6 @@ func TestCustomPostBuild(t *testing.T) { wantOutput []string wantRemediationError string }{ - { - name: "no custom post_build", - args: args("compute build --language other"), - fastlyManifest: ` - manifest_version = 2 - name = "test" - language = "other"`, - dontWantOutput: []string{compute.CustomPostBuildScriptMessage}, - wantError: "error reading custom build instructions from fastly.toml manifest", - wantRemediationError: "Add a [scripts.build] setting for your custom build process", - }, // NOTE: We need a fully functioning environment for the following tests, // otherwise the call to language.Verify() would fail before reaching // language.Build() and we need the build to complete because the @@ -952,20 +785,25 @@ func TestCustomPostBuild(t *testing.T) { applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.3.0 <= 0.6.0", - RustupConstraint: ">= 1.23.0", }, }, }, - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" language = "rust" [scripts] - post_build = "echo custom post_build"`, + build = "%s" + post_build = "echo custom post_build"`, compute.RustDefaultBuildCommand), + cargoManifest: ` + [package] + name = "compute-starter-kit-rust" + version = "0.1.0" + + [dependencies] + fastly = "=0.6.0"`, stdin: "N", wantOutput: []string{ compute.CustomPostBuildScriptMessage, @@ -980,27 +818,32 @@ func TestCustomPostBuild(t *testing.T) { applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.3.0 <= 0.6.0", - RustupConstraint: ">= 1.23.0", }, }, }, - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" language = "rust" [scripts] - post_build = "echo custom post_build"`, + build = "%s" + post_build = "echo custom post_build"`, compute.RustDefaultBuildCommand), + cargoManifest: ` + [package] + name = "compute-starter-kit-rust" + version = "0.1.0" + + [dependencies] + fastly = "=0.6.0"`, stdin: "Y", wantOutput: []string{ compute.CustomPostBuildScriptMessage, "echo custom post_build", "Are you sure you want to continue with the post build step?", - "Building package using rust toolchain", - "Built package 'test'", + "Running [scripts.build]", + "Built package", }, }, { @@ -1009,23 +852,28 @@ func TestCustomPostBuild(t *testing.T) { applicationConfig: config.File{ Language: config.Language{ Rust: config.Rust{ - ToolchainVersion: "1.49.0", ToolchainConstraint: ">= 1.54.0", WasmWasiTarget: "wasm32-wasi", - FastlySysConstraint: ">= 0.3.0 <= 0.6.0", - RustupConstraint: ">= 1.23.0", }, }, }, - fastlyManifest: ` + fastlyManifest: fmt.Sprintf(` manifest_version = 2 name = "test" language = "rust" [scripts] - post_build = "echo custom post_build"`, + build = "%s" + post_build = "echo custom post_build"`, compute.RustDefaultBuildCommand), + cargoManifest: ` + [package] + name = "compute-starter-kit-rust" + version = "0.1.0" + + [dependencies] + fastly = "=0.6.0"`, wantOutput: []string{ - "Building package using rust toolchain", - "Built package 'test'", + "Running [scripts.build]", + "Built package", }, dontWantOutput: []string{ compute.CustomPostBuildScriptMessage, @@ -1041,6 +889,11 @@ func TestCustomPostBuild(t *testing.T) { t.Fatal(err) } } + if testcase.cargoManifest != "" { + if err := os.WriteFile(filepath.Join(rootdir, "Cargo.toml"), []byte(testcase.cargoManifest), 0o777); err != nil { + t.Fatal(err) + } + } var stdout bytes.Buffer opts := testutil.NewRunOpts(testcase.args, &stdout) diff --git a/pkg/commands/compute/compute_mocks_test.go b/pkg/commands/compute/compute_mocks_test.go index 540ac2b35..1be75eb22 100644 --- a/pkg/commands/compute/compute_mocks_test.go +++ b/pkg/commands/compute/compute_mocks_test.go @@ -5,11 +5,6 @@ package compute_test // also a mocked HTTP client). import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "github.com/fastly/cli/pkg/testutil" "github.com/fastly/go-fastly/v6/fastly" ) @@ -85,32 +80,3 @@ func getServiceDetailsWasm(i *fastly.GetServiceInput) (*fastly.ServiceDetail, er Type: "wasm", }, nil } - -type versionClient struct { - fastlyVersions []string - fastlySysVersions []string -} - -func (v versionClient) Do(req *http.Request) (*http.Response, error) { - var vs []string - - if strings.Contains(req.URL.String(), "crates/fastly-sys/") { - vs = v.fastlySysVersions - } - if strings.Contains(req.URL.String(), "crates/fastly/") { - vs = v.fastlyVersions - } - - rec := httptest.NewRecorder() - - var versions []string - for _, vv := range vs { - versions = append(versions, fmt.Sprintf(`{"num":"%s"}`, vv)) - } - - _, err := fmt.Fprintf(rec, `{"versions":[%s]}`, strings.Join(versions, ",")) - if err != nil { - return nil, err - } - return rec.Result(), nil -} diff --git a/pkg/commands/compute/compute_test.go b/pkg/commands/compute/compute_test.go index 8bcc1f95b..5dcf282cb 100644 --- a/pkg/commands/compute/compute_test.go +++ b/pkg/commands/compute/compute_test.go @@ -1,21 +1,14 @@ package compute_test import ( - "fmt" - "net/http" - "net/http/httptest" "os" "path/filepath" "reflect" - "strings" "testing" - "github.com/Masterminds/semver/v3" - "github.com/fastly/cli/pkg/api" "github.com/fastly/cli/pkg/commands/compute" "github.com/fastly/cli/pkg/commands/update" "github.com/fastly/cli/pkg/config" - fsterr "github.com/fastly/cli/pkg/errors" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/testutil" "github.com/fastly/kingpin" @@ -353,160 +346,3 @@ func TestGetNonIgnoredFiles(t *testing.T) { }) } } - -func TestGetLatestCrateVersion(t *testing.T) { - for _, testcase := range []struct { - name string - inputClient api.HTTPClient - wantVersion *semver.Version - wantError string - }{ - { - name: "http error", - inputClient: &errorClient{testutil.Err}, - wantError: testutil.Err.Error(), - }, - { - name: "no valid versions", - inputClient: &httpClient{[]string{}}, - wantError: "no valid crate versions found", - }, - { - name: "unsorted", - inputClient: &httpClient{[]string{"0.5.23", "0.1.0", "1.2.3", "0.7.3"}}, - wantVersion: semver.MustParse("1.2.3"), - }, - { - name: "reverse chronological", - inputClient: &httpClient{[]string{"1.2.3", "0.8.3", "0.3.2"}}, - wantVersion: semver.MustParse("1.2.3"), - }, - { - name: "contains pre-release", - inputClient: &httpClient{[]string{"0.2.3", "0.8.3", "0.3.2", "0.9.0-beta.2"}}, - wantVersion: semver.MustParse("0.8.3"), - }, - } { - t.Run(testcase.name, func(t *testing.T) { - errlog := fsterr.MockLog{} - v, err := compute.GetLatestCrateVersion(testcase.inputClient, "fastly", errlog) - testutil.AssertErrorContains(t, err, testcase.wantError) - if err == nil && !v.Equal(testcase.wantVersion) { - t.Errorf("wanted version %s, got %s", testcase.wantVersion, v) - } - }) - } -} - -func TestGetCrateVersionFromMetadata(t *testing.T) { - for _, testcase := range []struct { - name string - inputLock compute.CargoMetadata - inputCrate string - wantVersion *semver.Version - wantError string - }{ - { - name: "crate not found", - inputLock: compute.CargoMetadata{}, - inputCrate: "fastly", - wantError: "fastly crate not found", - }, - { - name: "parsing error", - inputLock: compute.CargoMetadata{ - Package: []compute.CargoMetadataPackage{ - { - Name: "foo", - Version: "1.2.3", - }, - { - Name: "fastly", - Version: "dgsfdfg", - }, - }, - }, - inputCrate: "fastly", - wantError: "error parsing cargo metadata", - }, - { - name: "success", - inputLock: compute.CargoMetadata{ - Package: []compute.CargoMetadataPackage{ - { - Name: "foo", - Version: "1.2.3", - }, - { - Name: "fastly-sys", - Version: "0.3.0", - }, - { - Name: "fastly", - Version: "3.0.0", - }, - }, - }, - inputCrate: "fastly", - wantVersion: semver.MustParse("3.0.0"), - }, - { - name: "success nested", - inputLock: compute.CargoMetadata{ - Package: []compute.CargoMetadataPackage{ - { - Name: "foo", - Version: "1.2.3", - }, - { - Name: "fastly", - Version: "3.0.0", - Dependencies: []compute.CargoMetadataPackage{ - { - Name: "fastly-sys", - Version: "0.3.0", - }, - }, - }, - }, - }, - inputCrate: "fastly-sys", - wantVersion: semver.MustParse("0.3.0"), - }, - } { - t.Run(testcase.name, func(t *testing.T) { - v, err := compute.GetCrateVersionFromMetadata(testcase.inputLock, testcase.inputCrate) - testutil.AssertErrorContains(t, err, testcase.wantError) - if err == nil && !v.Equal(testcase.wantVersion) { - t.Errorf("wanted version %s, got %s", testcase.wantVersion, v) - } - }) - } -} - -type errorClient struct { - err error -} - -func (c errorClient) Do(*http.Request) (*http.Response, error) { - return nil, c.err -} - -type httpClient struct { - versions []string -} - -func (v httpClient) Do(*http.Request) (*http.Response, error) { - rec := httptest.NewRecorder() - - var versions []string - for _, vv := range v.versions { - versions = append(versions, fmt.Sprintf(`{"num":"%s"}`, vv)) - } - - _, err := fmt.Fprintf(rec, `{"versions":[%s]}`, strings.Join(versions, ",")) - if err != nil { - return nil, err - } - return rec.Result(), nil -} diff --git a/pkg/commands/compute/deploy.go b/pkg/commands/compute/deploy.go index 12719e799..a356a97c5 100644 --- a/pkg/commands/compute/deploy.go +++ b/pkg/commands/compute/deploy.go @@ -23,7 +23,6 @@ import ( "github.com/fastly/cli/pkg/text" "github.com/fastly/cli/pkg/undo" "github.com/fastly/go-fastly/v6/fastly" - "github.com/kennygrant/sanitize" "github.com/mholt/archiver/v3" ) @@ -79,7 +78,6 @@ func NewDeployCommand(parent cmd.Registerer, globals *config.Data, data manifest }) 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("name", "Package name").StringVar(&c.Manifest.Flag.Name) c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').StringVar(&c.Package) return &c } @@ -103,7 +101,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { // VALIDATE PACKAGE... - pkgName, pkgPath, hashSum, err := validatePackage(c.Manifest, c.Package, errLog, out) + pkgPath, hashSum, err := validatePackage(c.Manifest, c.Package, errLog, out) if err != nil { return err } @@ -122,7 +120,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { if source == manifest.SourceUndefined { newService = true - serviceID, serviceVersion, err = manageNoServiceIDFlow(c.Globals.Flag, in, out, verbose, apiClient, pkgName, c.Package, errLog, &c.Manifest.File, activateTrial) + serviceID, serviceVersion, err = manageNoServiceIDFlow(c.Globals.Flag, in, out, verbose, apiClient, c.Package, errLog, &c.Manifest.File, activateTrial) if err != nil { return err } @@ -241,7 +239,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { // provider type they should be. The reason we don't implement logic for // creating logging objects is because the API input fields vary // significantly between providers. - loggers.Configure() + _ = loggers.Configure() } } @@ -378,47 +376,52 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) { // // NOTE: It also validates if the package size exceeds limit: // https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits -func validatePackage(data manifest.Data, packageFlag string, errLog fsterr.LogInterface, out io.Writer) (pkgName, pkgPath, hashSum string, err error) { +func validatePackage( + data manifest.Data, + packageFlag string, + errLog fsterr.LogInterface, + out io.Writer, +) (pkgPath, hashSum string, err error) { err = data.File.ReadError() if err != nil { if packageFlag == "" { if errors.Is(err, os.ErrNotExist) { err = fsterr.ErrReadingManifest } - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } // NOTE: Before returning the manifest read error, we'll attempt to read // the manifest from within the given package archive. err := readManifestFromPackageArchive(&data, packageFlag, out) if err != nil { - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } } - pkgName, source := data.Name() - pkgPath, err = packagePath(packageFlag, pkgName, source) + pkgPath, err = packagePath(packageFlag) if err != nil { errLog.AddWithContext(err, map[string]any{ "Package path": packageFlag, - "Package name": pkgName, - "Source": source, }) - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } + pkgSize, err := packageSize(pkgPath) if err != nil { errLog.AddWithContext(err, map[string]any{ "Package path": pkgPath, }) - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } + if pkgSize > PackageSizeLimit { - return pkgName, pkgPath, hashSum, fsterr.RemediationError{ + return pkgPath, hashSum, fsterr.RemediationError{ Inner: fmt.Errorf("package size is too large (%d bytes)", pkgSize), Remediation: fsterr.PackageSizeRemediation, } } + contents := map[string]*bytes.Buffer{ "fastly.toml": {}, "main.wasm": {}, @@ -436,13 +439,15 @@ func validatePackage(data manifest.Data, packageFlag string, errLog fsterr.LogIn "Package path": pkgPath, "Package size": pkgSize, }) - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } + hashSum, err = getHashSum(contents) if err != nil { - return pkgName, pkgPath, hashSum, err + return pkgPath, hashSum, err } - return pkgName, pkgPath, hashSum, nil + + return pkgPath, hashSum, nil } // readManifestFromPackageArchive extracts the manifest file from the given @@ -515,16 +520,11 @@ func locateManifest(path string) (string, error) { // packagePath generates a path that points to a package tar inside the pkg // directory if the `path` flag was not set by the user. -func packagePath(path string, name string, source manifest.Source) (string, error) { +func packagePath(path string) (string, error) { if path == "" { - if source == manifest.SourceUndefined { - return "", fsterr.ErrReadingManifest - } - - path = filepath.Join("pkg", fmt.Sprintf("%s.tar.gz", sanitize.BaseName(name))) + path = filepath.Join("pkg", "package.tar.gz") return path, nil } - return path, nil } @@ -573,7 +573,7 @@ func manageNoServiceIDFlow( out io.Writer, verbose bool, apiClient api.Interface, - pkgName, packageFlag string, + packageFlag string, errLog fsterr.LogInterface, manifestFile *manifest.File, activateTrial activator, @@ -596,16 +596,28 @@ func manageNoServiceIDFlow( text.Break(out) } + defaultServiceName := manifestFile.Name + var serviceName string + + if !globalFlags.AcceptDefaults && !globalFlags.NonInteractive { + serviceName, err = text.Input(out, text.BoldYellow(fmt.Sprintf("Service name: [%s] ", defaultServiceName)), in) + if err != nil || serviceName == "" { + serviceName = defaultServiceName + } + } else { + serviceName = defaultServiceName + } + progress := text.NewProgress(out, verbose) // There is no service and so we'll do a one time creation of the service // // NOTE: we're shadowing the `serviceVersion` and `serviceID` variables. - serviceID, serviceVersion, err = createService(pkgName, apiClient, activateTrial, progress, errLog) + serviceID, serviceVersion, err = createService(serviceName, apiClient, activateTrial, progress, errLog) if err != nil { progress.Fail() errLog.AddWithContext(err, map[string]any{ - "Package name": pkgName, + "Service name": serviceName, }) return serviceID, serviceVersion, err } diff --git a/pkg/commands/compute/deploy_test.go b/pkg/commands/compute/deploy_test.go index 18eec7427..8e589fed8 100644 --- a/pkg/commands/compute/deploy_test.go +++ b/pkg/commands/compute/deploy_test.go @@ -780,7 +780,8 @@ func TestDeploy(t *testing.T) { UpdatePackageFn: updatePackageOk, }, stdin: []string{ - "Y", // when prompted to create a new service + "Y", // when prompted to create a new service + "foobar", // when prompted for service name "fastly.com", "443", "my_backend_name", @@ -810,7 +811,8 @@ func TestDeploy(t *testing.T) { UpdatePackageFn: updatePackageOk, }, stdin: []string{ - "Y", // when prompted to create a new service + "Y", // when prompted to create a new service + "foobar", // when prompted for service name "fastly.com", "443", "", // this is so we generate a backend name using a built-in formula @@ -844,8 +846,9 @@ func TestDeploy(t *testing.T) { UpdatePackageFn: updatePackageOk, }, stdin: []string{ - "Y", // when prompted to create a new service - "", // this stops prompting for backends + "Y", // when prompted to create a new service + "foobar", // when prompted for service name + "", // this stops prompting for backends }, wantOutput: []string{ "Backend (hostname or IP address, or leave blank to stop adding backends):", diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index 975ef4c1d..4d7eadcea 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -55,8 +55,6 @@ func NewInitCommand(parent cmd.Registerer, globals *config.Data, data manifest.D c.Globals = globals c.manifest = data c.CmdClause = parent.Command("init", "Initialize a new Compute@Edge package locally") - c.CmdClause.Flag("name", "Name of package, falls back to --directory").Short('n').StringVar(&c.manifest.File.Name) - c.CmdClause.Flag("description", "Description of the package").StringVar(&c.manifest.File.Description) c.CmdClause.Flag("directory", "Destination to write the new package, defaulting to the current directory").Short('p').StringVar(&c.dir) c.CmdClause.Flag("author", "Author(s) of the package").Short('a').StringsVar(&c.manifest.File.Authors) c.CmdClause.Flag("language", "Language of the package").Short('l').HintOptions(Languages...).EnumVar(&c.language, Languages...) @@ -86,7 +84,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { text.Break(out) - cont, err := verifyDirectory(c.dir, c.skipVerification, out, in) + cont, err := verifyDirectory(c.Globals.Flag, c.dir, c.skipVerification, out, in) if err != nil { c.Globals.ErrLog.Add(err) return err @@ -138,7 +136,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { email = p.Email } - name, desc, authors, err := promptOrReturn(c.manifest, c.dir, email, in, out) + name, desc, authors, err := promptOrReturn(c.Globals.Flag, c.manifest, c.dir, email, in, out) if err != nil { c.Globals.ErrLog.AddWithContext(err, map[string]any{ "Description": desc, @@ -147,8 +145,8 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { return err } - languages := NewLanguages(c.Globals.File.StarterKits, c.Globals, name, mf.Scripts) - language, err := selectLanguage(c.from, c.language, languages, mf, in, out) + languages := NewLanguages(c.Globals.File.StarterKits, c.Globals, mf, out) + language, err := selectLanguage(c.Globals.Flag, c.from, c.language, languages, mf, in, out) if err != nil { c.Globals.ErrLog.AddWithContext(err, map[string]any{ "Language": c.language, @@ -159,7 +157,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { var from, branch, tag string if noProjectFiles(c.from, language, mf) { - from, branch, tag, err = promptForStarterKit(language.StarterKits, in, out) + from, branch, tag, err = promptForStarterKit(c.Globals.Flag, language.StarterKits, in, out) if err != nil { c.Globals.ErrLog.AddWithContext(err, map[string]any{ "From": c.from, @@ -213,7 +211,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { // verifyDirectory indicates if the user wants to continue with the execution // flow when presented with a prompt that suggests the current directory isn't // empty. -func verifyDirectory(dir string, skipVerification bool, out io.Writer, in io.Reader) (bool, error) { +func verifyDirectory(flags config.Flag, dir string, skipVerification bool, out io.Writer, in io.Reader) (bool, error) { if skipVerification { return true, nil } @@ -231,9 +229,16 @@ func verifyDirectory(dir string, skipVerification bool, out io.Writer, in io.Rea return false, err } - if len(files) > 0 { + if len(files) > 0 && !flags.AutoYes && !flags.NonInteractive { label := fmt.Sprintf("The current directory isn't empty. Are you sure you want to initialize a Compute@Edge project in %s? [y/N] ", dir) - return text.AskYesNo(out, label, in) + result, err := text.AskYesNo(out, label, in) + if err != nil { + return false, err + } + if result { + text.Break(out) + } + return result, nil } return true, nil @@ -305,21 +310,21 @@ func verifyDestination(path string, progress text.Progress) (dst string, err err // promptOrReturn will prompt the user for information missing from the // fastly.toml manifest file, otherwise if it already exists then the value is // returned as is. -func promptOrReturn(m manifest.Data, path, email string, in io.Reader, out io.Writer) (name, description string, authors []string, err error) { +func promptOrReturn(flags config.Flag, m manifest.Data, path, email string, in io.Reader, out io.Writer) (name, description string, authors []string, err error) { name, _ = m.Name() - name, err = packageName(name, path, in, out) + name, err = packageName(flags, name, path, in, out) if err != nil { return name, description, authors, err } description, _ = m.Description() - description, err = packageDescription(description, in, out) + description, err = packageDescription(flags, description, in, out) if err != nil { return name, description, authors, err } authors, _ = m.Authors() - authors, err = packageAuthors(authors, email, in, out) + authors, err = packageAuthors(flags, authors, email, in, out) if err != nil { return name, description, authors, err } @@ -332,9 +337,13 @@ func promptOrReturn(m manifest.Data, path, email string, in io.Reader, out io.Wr // // It will use a default of the current directory path if no value provided by // the user via the prompt. -func packageName(name string, dirPath string, in io.Reader, out io.Writer) (string, error) { +func packageName(flags config.Flag, name string, dirPath string, in io.Reader, out io.Writer) (string, error) { defaultName := filepath.Base(dirPath) + if name == "" && (flags.AcceptDefaults || flags.NonInteractive) { + return defaultName, nil + } + if name == "" { var err error @@ -353,7 +362,11 @@ func packageName(name string, dirPath string, in io.Reader, out io.Writer) (stri // packageDescription prompts the user for a package description unless already // defined either via the corresponding CLI flag or the manifest file. -func packageDescription(desc string, in io.Reader, out io.Writer) (string, error) { +func packageDescription(flags config.Flag, desc string, in io.Reader, out io.Writer) (string, error) { + if desc == "" && (flags.AcceptDefaults || flags.NonInteractive) { + return desc, nil + } + if desc == "" { var err error @@ -371,7 +384,11 @@ func packageDescription(desc string, in io.Reader, out io.Writer) (string, error // // It will use a default of the user's email found within the manifest, if set // there, otherwise the value will be an empty slice. -func packageAuthors(authors []string, manifestEmail string, in io.Reader, out io.Writer) ([]string, error) { +func packageAuthors(flags config.Flag, authors []string, manifestEmail string, in io.Reader, out io.Writer) ([]string, error) { + defaultValue := []string{manifestEmail} + if len(authors) == 0 && (flags.AcceptDefaults || flags.NonInteractive) { + return defaultValue, nil + } if len(authors) == 0 { label := "Author: " @@ -387,7 +404,7 @@ func packageAuthors(authors []string, manifestEmail string, in io.Reader, out io if author != "" { authors = []string{author} } else { - authors = []string{manifestEmail} + authors = defaultValue } } @@ -396,13 +413,13 @@ func packageAuthors(authors []string, manifestEmail string, in io.Reader, out io // selectLanguage decides whether to prompt the user for a language if none // defined or try and match the --language flag against available languages. -func selectLanguage(from string, langFlag string, ls []*Language, mf manifest.File, in io.Reader, out io.Writer) (*Language, error) { +func selectLanguage(flags config.Flag, from string, langFlag string, ls []*Language, mf manifest.File, in io.Reader, out io.Writer) (*Language, error) { if from != "" && langFlag == "" || mf.Exists() { return nil, nil } if langFlag == "" { - return promptForLanguage(ls, in, out) + return promptForLanguage(flags, ls, in, out) } for _, language := range ls { @@ -416,17 +433,23 @@ func selectLanguage(from string, langFlag string, ls []*Language, mf manifest.Fi // promptForLanguage prompts the user for a package language unless already // defined either via the corresponding CLI flag or the manifest file. -func promptForLanguage(languages []*Language, in io.Reader, out io.Writer) (*Language, error) { - var language *Language +func promptForLanguage(flags config.Flag, languages []*Language, in io.Reader, out io.Writer) (*Language, error) { + var ( + language *Language + option string + err error + ) - text.Output(out, "%s", text.Bold("Language:")) - for i, lang := range languages { - text.Output(out, "[%d] %s", i+1, lang.DisplayName) - } + if !flags.AcceptDefaults && !flags.NonInteractive { + text.Output(out, "%s", text.Bold("Language:")) + for i, lang := range languages { + text.Output(out, "[%d] %s", i+1, lang.DisplayName) + } - option, err := text.Input(out, "Choose option: [1] ", in, validateLanguageOption(languages)) - if err != nil { - return nil, fmt.Errorf("reading input %w", err) + option, err = text.Input(out, "Choose option: [1] ", in, validateLanguageOption(languages)) + if err != nil { + return nil, fmt.Errorf("reading input %w", err) + } } if option == "" { @@ -435,7 +458,7 @@ func promptForLanguage(languages []*Language, in io.Reader, out io.Writer) (*Lan i, err := strconv.Atoi(option) if err != nil { - return nil, fmt.Errorf("selecting language") + return nil, fmt.Errorf("failed to identify chosen language") } language = languages[i-1] @@ -472,16 +495,21 @@ func noProjectFiles(from string, language *Language, mf manifest.File) bool { // promptForStarterKit prompts the user for a package starter kit. // // It returns the path to the starter kit, and the corresponding branch/tag, -func promptForStarterKit(kits []config.StarterKit, in io.Reader, out io.Writer) (from string, branch string, tag string, err error) { - text.Output(out, "%s", text.Bold("Starter kit:")) - for i, kit := range kits { - fmt.Fprintf(out, "[%d] %s\n", i+1, text.Bold(kit.Name)) - text.Indent(out, 4, "%s\n%s", kit.Description, kit.Path) - } - option, err := text.Input(out, "Choose option or paste git URL: [1] ", in, validateTemplateOptionOrURL(kits)) - if err != nil { - return "", "", "", fmt.Errorf("error reading input: %w", err) +func promptForStarterKit(flags config.Flag, kits []config.StarterKit, in io.Reader, out io.Writer) (from string, branch string, tag string, err error) { + var option string + + if !flags.AcceptDefaults && !flags.NonInteractive { + text.Output(out, "%s", text.Bold("Starter kit:")) + for i, kit := range kits { + fmt.Fprintf(out, "[%d] %s\n", i+1, text.Bold(kit.Name)) + text.Indent(out, 4, "%s\n%s", kit.Description, kit.Path) + } + option, err = text.Input(out, "Choose option or paste git URL: [1] ", in, validateTemplateOptionOrURL(kits)) + if err != nil { + return "", "", "", fmt.Errorf("error reading input: %w", err) + } } + if option == "" { option = "1" } @@ -802,10 +830,12 @@ func updateManifest( fmt.Fprintf(progress, "Setting package name in manifest to %q...\n", name) m.Name = name + // NOTE: We allow an empty description to be set. if desc != "" { - fmt.Fprintf(progress, "Setting description in manifest to %s...\n", desc) - m.Description = desc + desc = " to " + desc } + fmt.Fprintf(progress, "Setting description in manifest%s...\n", desc) + m.Description = desc if len(authors) > 0 { fmt.Fprintf(progress, "Setting authors in manifest to %s...\n", strings.Join(authors, ", ")) @@ -852,7 +882,7 @@ func initializeLanguage(progress text.Progress, language *Language, languages [] } } - if language.Name != "other" && build == "" { + if language.Name != "other" { if err := language.Initialize(progress); err != nil { return nil, err } diff --git a/pkg/commands/compute/init_test.go b/pkg/commands/compute/init_test.go index 3656526b1..ed97ab08f 100644 --- a/pkg/commands/compute/init_test.go +++ b/pkg/commands/compute/init_test.go @@ -62,23 +62,24 @@ func TestInit(t *testing.T) { wantError: "failed to get package: 404 Not Found", }, { - name: "with name", - args: args("compute init --name test"), + name: "name prompt", + args: args("compute init"), configFile: config.File{ StarterKits: config.StarterKitLanguages{ Rust: skRust, }, }, + stdin: "foobar", // expect the first prompt to be for the package name. wantOutput: []string{ "Initializing...", "Fetching package template...", "Updating package manifest...", }, - manifestIncludes: `name = "test"`, + manifestIncludes: `name = "foobar"`, }, { - name: "with description", - args: args("compute init --description test"), + name: "description prompt empty", + args: args("compute init"), configFile: config.File{ StarterKits: config.StarterKitLanguages{ Rust: skRust, @@ -89,7 +90,7 @@ func TestInit(t *testing.T) { "Fetching package template...", "Updating package manifest...", }, - manifestIncludes: `description = "test"`, + manifestIncludes: `description = ""`, // expect this to be empty }, { name: "with author", diff --git a/pkg/commands/compute/language.go b/pkg/commands/compute/language.go index 503867845..db218b04b 100644 --- a/pkg/commands/compute/language.go +++ b/pkg/commands/compute/language.go @@ -2,6 +2,7 @@ package compute import ( "fmt" + "io" "runtime" "sort" "strings" @@ -15,19 +16,25 @@ import ( // NOTE: The 'timeout' value zero is passed into each New call as it's // only useful during the `compute build` phase and is expected to be // provided by the user via a flag on the build command. -func NewLanguages(kits config.StarterKitLanguages, d *config.Data, pkgName string, scripts manifest.Scripts) []*Language { +func NewLanguages(kits config.StarterKitLanguages, d *config.Data, fastlyManifest manifest.File, out io.Writer) []*Language { + // WARNING: Do not reorder these options as they affect the rendered output. + // They are placed in order of language maturity/importance. + // + // A change to this order will also break the tests, as the logic defaults to + // the first language in the list if nothing entered at the relevant language + // prompt. return []*Language{ NewLanguage(&LanguageOptions{ Name: "rust", DisplayName: "Rust (limited availability)", StarterKits: kits.Rust, Toolchain: NewRust( - pkgName, - scripts, + &fastlyManifest, d.ErrLog, - d.HTTPClient, 0, d.File.Language.Rust, + out, + nil, ), }), NewLanguage(&LanguageOptions{ @@ -35,10 +42,11 @@ func NewLanguages(kits config.StarterKitLanguages, d *config.Data, pkgName strin DisplayName: "JavaScript (limited availability)", StarterKits: kits.JavaScript, Toolchain: NewJavaScript( - pkgName, - scripts, + &fastlyManifest, d.ErrLog, 0, + out, + nil, ), }), NewLanguage(&LanguageOptions{ @@ -46,11 +54,12 @@ func NewLanguages(kits config.StarterKitLanguages, d *config.Data, pkgName strin DisplayName: "Go (beta)", StarterKits: kits.Go, Toolchain: NewGo( - pkgName, - scripts, + &fastlyManifest, d.ErrLog, 0, d.File.Language.Go, + out, + nil, ), }), NewLanguage(&LanguageOptions{ @@ -58,10 +67,11 @@ func NewLanguages(kits config.StarterKitLanguages, d *config.Data, pkgName strin DisplayName: "AssemblyScript (beta)", StarterKits: kits.AssemblyScript, Toolchain: NewAssemblyScript( - pkgName, - scripts, + &fastlyManifest, d.ErrLog, 0, + out, + nil, ), }), NewLanguage(&LanguageOptions{ diff --git a/pkg/commands/compute/language_assemblyscript.go b/pkg/commands/compute/language_assemblyscript.go index d5b67f0fe..f9fa07ebe 100644 --- a/pkg/commands/compute/language_assemblyscript.go +++ b/pkg/commands/compute/language_assemblyscript.go @@ -3,19 +3,84 @@ package compute import ( "fmt" "io" - "os" "path/filepath" - "time" fsterr "github.com/fastly/cli/pkg/errors" - fstexec "github.com/fastly/cli/pkg/exec" - "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" ) -// ASSourceDirectory represents the source code directory. -const ASSourceDirectory = "assembly" +// AsCompilation is a language specific compilation target that converts the +// language code into a Wasm binary. +const AsCompilation = "asc" + +// AsCompilationURL is the official assemblyscript package URL. +const AsCompilationURL = "https://www.npmjs.com/package/assemblyscript" + +// AsDefaultBuildCommand is a build command compiled into the CLI binary so it +// can be used as a fallback for customer's who have an existing C@E project and +// are simply upgrading their CLI version and might not be familiar with the +// changes in the 4.0.0 release with regards to how build logic has moved to the +// fastly.toml manifest. +const AsDefaultBuildCommand = "$(npm bin)/asc assembly/index.ts --outFile bin/main.wasm --optimize --noAssert" + +// AsSDK is the required Compute@Edge SDK. +// https://www.npmjs.com/package/@fastly/as-compute +const AsSDK = "@fastly/as-compute" + +// AsSourceDirectory represents the source code directory. +const AsSourceDirectory = "assembly" + +// NewAssemblyScript constructs a new AssemblyScript toolchain. +func NewAssemblyScript( + fastlyManifest *manifest.File, + errlog fsterr.LogInterface, + timeout int, + out io.Writer, + ch chan string, +) *AssemblyScript { + return &AssemblyScript{ + JavaScript: JavaScript{ + errlog: errlog, + timeout: timeout, + validator: ToolchainValidator{ + Compilation: AsCompilation, + CompilationDirectPath: func() (string, error) { + p, err := getJsToolchainBinPath(JsToolchain) + if err != nil { + errlog.Add(err) + remediation := "npm install --global npm@latest" + return "", fsterr.RemediationError{ + Inner: fmt.Errorf("could not determine %s bin path", JsToolchain), + Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), + } + } + + return filepath.Join(p, AsCompilation), nil + }, + CompilationCommandRemediation: fmt.Sprintf(JsCompilationCommandRemediation, AsSDK), + CompilationSkipVersion: true, + CompilationURL: AsCompilationURL, + DefaultBuildCommand: AsDefaultBuildCommand, + ErrLog: errlog, + FastlyManifestFile: fastlyManifest, + Installer: JsInstaller, + Manifest: JsManifest, + ManifestRemediation: JsManifestRemediation, + Output: out, + PatchedManifestNotifier: ch, + SDK: AsSDK, + SDKCustomValidator: validateJsSDK, + Toolchain: JsToolchain, + ToolchainLanguage: "AssemblyScript", + ToolchainSkipVersion: true, + ToolchainURL: JsToolchainURL, + }, + }, + errlog: errlog, + postBuild: fastlyManifest.Scripts.PostBuild, + } +} // AssemblyScript implements a Toolchain for the AssemblyScript language. // @@ -28,101 +93,25 @@ const ASSourceDirectory = "assembly" type AssemblyScript struct { JavaScript - build string - errlog fsterr.LogInterface + // errlog is an abstraction for recording errors to disk. + errlog fsterr.LogInterface + // postBuild is a custom script executed after the build but before the Wasm + // binary is added to the .tar.gz archive. postBuild string } -// NewAssemblyScript constructs a new AssemblyScript toolchain. -func NewAssemblyScript(pkgName string, scripts manifest.Scripts, errlog fsterr.LogInterface, timeout int) *AssemblyScript { - return &AssemblyScript{ - JavaScript: JavaScript{ - build: scripts.Build, - errlog: errlog, - packageDependency: "assemblyscript", - packageExecutable: "asc", - pkgName: pkgName, - timeout: timeout, - toolchain: JsToolchain, - }, - build: scripts.Build, - errlog: errlog, - postBuild: scripts.PostBuild, - } -} - -// Build implements the Toolchain interface and attempts to compile the package -// AssemblyScript source to a Wasm binary. +// Build compiles the user's source code into a Wasm binary. func (a AssemblyScript) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { - // Check if bin directory exists and create if not. - pwd, err := os.Getwd() - if err != nil { - a.errlog.Add(err) - return fmt.Errorf("getting current working directory: %w", err) - } - binDir := filepath.Join(pwd, "bin") - if err := filesystem.MakeDirectoryIfNotExists(binDir); err != nil { - a.errlog.Add(err) - return fmt.Errorf("making bin directory: %w", err) - } - - toolchaindir, err := getJsToolchainBinPath(a.toolchain) - if err != nil { - a.errlog.Add(err) - return fmt.Errorf("getting npm path: %w", err) - } - - cmd := filepath.Join(toolchaindir, "asc") - args := []string{ - fmt.Sprintf("%s/index.ts", ASSourceDirectory), - "--outFile", - filepath.Join(binDir, "main.wasm"), - "--optimize", - "--noAssert", - } - - if a.build != "" { - cmd, args = a.Shell.Build(a.build) - } - - err = a.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - - // NOTE: We set the progress indicator to Done() so that any output we now - // print via the post_build callback doesn't get hidden by the progress status. - // The progress is 'reset' inside the main build controller `build.go`. - progress.Done() - - if a.postBuild != "" { - if err = callback(); err == nil { - cmd, args := a.Shell.Build(a.postBuild) - err := a.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - } - } - - return nil -} - -func (a AssemblyScript) execCommand(cmd string, args []string, out, progress io.Writer, verbose bool) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Progress: progress, - Verbose: verbose, - } - if a.timeout > 0 { - s.Timeout = time.Duration(a.timeout) * time.Second - } - if err := s.Exec(); err != nil { - a.errlog.Add(err) - return err - } - return nil + // NOTE: We deliberately reference the validator pointer to the fastly.toml + // This is because the manifest.File might be updated when migrating a + // pre-existing project to use the CLI v4.0.0 (as prior to this version the + // manifest would not require [script.build] to be defined). + // As of v4.0.0 if no value is set, then we provide a default. + return build(buildOpts{ + buildScript: a.validator.FastlyManifestFile.Scripts.Build, + buildFn: a.Shell.Build, + errlog: a.errlog, + postBuild: a.postBuild, + timeout: a.timeout, + }, out, progress, verbose, nil, callback) } diff --git a/pkg/commands/compute/language_go.go b/pkg/commands/compute/language_go.go index bc24b43e0..6f2c2720b 100644 --- a/pkg/commands/compute/language_go.go +++ b/pkg/commands/compute/language_go.go @@ -1,44 +1,104 @@ package compute import ( - "bufio" - "bytes" - "errors" - "fmt" "io" - "os" - "os/exec" - "path/filepath" - "strings" - "time" + "regexp" - "github.com/Masterminds/semver/v3" "github.com/fastly/cli/pkg/config" fsterr "github.com/fastly/cli/pkg/errors" - fstexec "github.com/fastly/cli/pkg/exec" - "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" ) -// GoSourceDirectory represents the source code directory (i.e. root directory). +// GoCompilation is a language specific compilation target that converts the +// language code into a Wasm binary. +const GoCompilation = "tinygo" + +// GoCompilationURL is the official TinyGo website URL. +const GoCompilationURL = "https://tinygo.org" + +// GoCompilationTargetCommand is the shell command for returning the tinygo +// version. +const GoCompilationTargetCommand = "tinygo version" + +// GoConstraints is the set of supported toolchain and compilation versions. +// +// NOTE: Two keys are supported: "toolchain" and "compilation", with the latter +// being optional as not all language compilation steps are separate tools from +// the toolchain itself. +var GoConstraints = make(map[string]string) + +// GoDefaultBuildCommand is a build command compiled into the CLI binary so it +// can be used as a fallback for customer's who have an existing C@E project and +// are simply upgrading their CLI version and might not be familiar with the +// changes in the 4.0.0 release with regards to how build logic has moved to the +// fastly.toml manifest. +const GoDefaultBuildCommand = "tinygo build -target=wasi -wasm-abi=generic -gc=conservative -o bin/main.wasm ./" + +// GoInstaller is the command used to install the dependencies defined within +// the Go language manifest. +const GoInstaller = "go mod download" + +// GoManifest is the manifest file for defining project configuration. +const GoManifest = "go.mod" + +// GoManifestRemediation is a error remediation message for a missing manifest. +const GoManifestRemediation = "go mod init" + +// GoSDK is the required Compute@Edge SDK. +// https://pkg.go.dev/github.com/fastly/compute-sdk-go +const GoSDK = "fastly/compute-sdk-go" + +// GoSourceDirectory represents the source code directory. │ │ const GoSourceDirectory = "." -// GoManifestName represents the language file for configuring dependencies. -const GoManifestName = "go.mod" +// GoToolchain is the executable responsible for managing dependencies. +const GoToolchain = "go" + +// GoToolchainURL is the official Go website URL. +const GoToolchainURL = "https://go.dev/" + +// GoToolchainVersionCommand is the shell command for returning the go version. +const GoToolchainVersionCommand = "go version" // NewGo constructs a new Go toolchain. -func NewGo(pkgName string, scripts manifest.Scripts, errlog fsterr.LogInterface, timeout int, cfg config.Go) *Go { +func NewGo( + fastlyManifest *manifest.File, + errlog fsterr.LogInterface, + timeout int, + cfg config.Go, + out io.Writer, + ch chan string, +) *Go { + GoConstraints["toolchain"] = cfg.ToolchainConstraint + GoConstraints["compilation"] = cfg.TinyGoConstraint + return &Go{ Shell: Shell{}, - build: scripts.Build, - compiler: "tinygo", - config: cfg, errlog: errlog, - pkgName: pkgName, - postBuild: scripts.PostBuild, + postBuild: fastlyManifest.Scripts.PostBuild, timeout: timeout, - toolchain: "go", + validator: ToolchainValidator{ + Compilation: GoCompilation, + CompilationTargetCommand: GoCompilationTargetCommand, + CompilationTargetPattern: regexp.MustCompile(`tinygo version (?P\d[^\s]+)`), + CompilationURL: GoCompilationURL, + Constraints: GoConstraints, + DefaultBuildCommand: GoDefaultBuildCommand, + ErrLog: errlog, + FastlyManifestFile: fastlyManifest, + Installer: GoInstaller, + Manifest: GoManifest, + ManifestRemediation: GoManifestRemediation, + Output: out, + PatchedManifestNotifier: ch, + SDK: GoSDK, + Toolchain: GoToolchain, + ToolchainLanguage: "Go", + ToolchainVersionCommand: GoToolchainVersionCommand, + ToolchainVersionPattern: regexp.MustCompile(`go version go(?P\d[^\s]+)`), + ToolchainURL: GoToolchainURL, + }, } } @@ -51,320 +111,39 @@ func NewGo(pkgName string, scripts manifest.Scripts, errlog fsterr.LogInterface, type Go struct { Shell - // build is a custom build script defined in fastly.toml using [scripts.build]. - build string - // compiler is a WASM/WASI capable compiler (i.e. not the standard go compiler) - compiler string - // config is Go configuration such as toolchain constraints. - config config.Go // errlog is an abstraction for recording errors to disk. errlog fsterr.LogInterface - // pkgName is the name of the package (also used as the module name). - pkgName string - // postBuild is a custom script executed after the build but before the WASM + // postBuild is a custom script executed after the build but before the Wasm // binary is added to the .tar.gz archive. postBuild string // timeout is the build execution threshold. timeout int - // toolchain is the go executable. - toolchain string -} - -// Initialize implements the Toolchain interface and initializes a newly cloned -// package by installing required dependencies. -func (g Go) Initialize(out io.Writer) error { - // Remediation used in variation sections. - goURL := "https://go.dev/" - remediation := fmt.Sprintf("To fix this error, install %s by visiting:\n\n\t$ %s\n\nThen execute:\n\n\t$ fastly compute init", g.toolchain, text.Bold(goURL)) - - var ( - bin string - err error - ) - - // 1. Check go command is on $PATH. - { - fmt.Fprintf(out, "Checking if %s is installed...\n", g.toolchain) - - bin, err = exec.LookPath(g.toolchain) - if err != nil { - g.errlog.Add(err) - - return fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` not found in $PATH", g.toolchain), - Remediation: remediation, - } - } - - fmt.Fprintf(out, "Found %s at %s\n", g.toolchain, bin) - } - - // 2. Check go version is correct. - { - // gosec flagged this: - // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments - // Disabling as we trust the source of the variable. - /* #nosec */ - cmd := exec.Command(bin, "version") // e.g. go version go1.18 darwin/amd64 - stdoutStderr, err := cmd.CombinedOutput() - output := string(stdoutStderr) - if err != nil { - if len(stdoutStderr) > 0 { - err = fmt.Errorf("%w: %s", err, strings.TrimSpace(output)) - } - g.errlog.Add(err) - return err - } - - segs := strings.Split(output, " ") - if len(segs) < 3 { - return errors.New("unexpected go version output") - } - version := segs[2][2:] - - v, err := semver.NewVersion(version) - if err != nil { - return fmt.Errorf("error parsing version output %s into a semver: %w", version, err) - } - - c, err := semver.NewConstraint(g.config.ToolchainConstraint) - if err != nil { - return fmt.Errorf("error parsing toolchain constraint %s into a semver: %w", g.config.ToolchainConstraint, err) - } - - if !c.Check(v) { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("version %s didn't meet the constraint %s", version, g.config.ToolchainConstraint), - Remediation: remediation, - } - g.errlog.Add(err) - return err - } - } - - // 3. Set package name. - { - m, err := filepath.Abs(GoManifestName) - if err != nil { - g.errlog.Add(err) - return fmt.Errorf("getting %s path: %w", JSManifestName, err) - } - - if !filesystem.FileExists(m) { - msg := fmt.Sprintf(fsterr.FormatTemplate, text.Bold("go mod init")) - remediation := fmt.Sprintf("%s\n\nThen execute:\n\n\t$ fastly compute init", msg) - err := fsterr.RemediationError{ - Inner: fmt.Errorf("%s not found", JSManifestName), - Remediation: remediation, - } - g.errlog.Add(err) - return err - } - - if err := g.setPackageName(GoManifestName); err != nil { - g.errlog.Add(err) - return fmt.Errorf("error updating %s manifest: %w", GoManifestName, err) - } - - fmt.Fprintf(out, "Found %s at %s\n", GoManifestName, m) - } - - // 4. Download dependencies. - { - fmt.Fprintf(out, "Installing package dependencies...\n") - cmd := fstexec.Streaming{ - Command: "go", - Args: []string{"mod", "download"}, - Env: os.Environ(), - Output: out, - } - return cmd.Exec() - } -} - -// Verify implements the Toolchain interface and verifies whether the Go -// language toolchain is correctly configured on the host. -func (g *Go) Verify(out io.Writer) error { - // Remediation used in variation sections. - tinygoURL := "https://tinygo.org" - remediation := fmt.Sprintf("To fix this error, install %s by visiting:\n\n\t$ %s", g.compiler, text.Bold(tinygoURL)) - - var ( - bin string - err error - ) - - // 1. Check tinygo command is on $PATH. - { - fmt.Fprintf(out, "Checking if %s is installed...\n", g.compiler) - - bin, err = exec.LookPath(g.compiler) - if err != nil { - g.errlog.Add(err) - - return fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` not found in $PATH", g.compiler), - Remediation: remediation, - } - } - - fmt.Fprintf(out, "Found %s at %s\n", g.compiler, bin) - } - - // 2. Check tinygo version is correct. - { - // gosec flagged this: - // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments - // Disabling as we trust the source of the variable. - /* #nosec */ - cmd := exec.Command(bin, "version") // e.g. tinygo version 0.24.0 darwin/amd64 (using go version go1.18 and LLVM version 14.0.0) - stdoutStderr, err := cmd.CombinedOutput() - output := string(stdoutStderr) - if err != nil { - if len(stdoutStderr) > 0 { - err = fmt.Errorf("%w: %s", err, strings.TrimSpace(output)) - } - g.errlog.Add(err) - return err - } - - segs := strings.Split(output, " ") - if len(segs) < 3 { - return errors.New("unexpected tinygo version output") - } - version := segs[2] - - v, err := semver.NewVersion(version) - if err != nil { - return fmt.Errorf("error parsing version output %s into a semver: %w", version, err) - } - - c, err := semver.NewConstraint(g.config.TinyGoConstraint) - if err != nil { - return fmt.Errorf("error parsing toolchain constraint %s into a semver: %w", g.config.TinyGoConstraint, err) - } - - if !c.Check(v) { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("version %s didn't meet the constraint %s", version, g.config.TinyGoConstraint), - Remediation: remediation, - } - g.errlog.Add(err) - return err - } - } - return nil + // validator is an abstraction to validate required resources are installed. + validator ToolchainValidator } -// Build implements the Toolchain interface and attempts to compile the package -// Go source to a Wasm binary. -func (g *Go) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { - cmd := g.compiler - args := []string{ - "build", - "-target=wasi", - "-wasm-abi=generic", - "-gc=conservative", - "-o=bin/main.wasm", - fmt.Sprintf("./%s", GoSourceDirectory), - } - - // A bin directory is required. - dir, err := os.Getwd() - if err != nil { - g.errlog.Add(err) - return fmt.Errorf("getting current working directory: %w", err) - } - binDir := filepath.Join(dir, "bin") - if err := filesystem.MakeDirectoryIfNotExists(binDir); err != nil { - g.errlog.Add(err) - return fmt.Errorf("creating bin directory: %w", err) - } - - if g.build != "" { - cmd, args = g.Shell.Build(g.build) - } - - err = g.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - - // NOTE: We set the progress indicator to Done() so that any output we now - // print via the post_build callback doesn't get hidden by the progress status. - // The progress is 'reset' inside the main build controller `build.go`. - progress.Done() - - if g.postBuild != "" { - if err = callback(); err == nil { - cmd, args := g.Shell.Build(g.postBuild) - err := g.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - } - } - +// Initialize handles any non-build related set-up. +func (g Go) Initialize(_ io.Writer) error { return nil } -func (g Go) execCommand(cmd string, args []string, out, progress io.Writer, verbose bool) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Progress: progress, - Verbose: verbose, - } - if g.timeout > 0 { - s.Timeout = time.Duration(g.timeout) * time.Second - } - if err := s.Exec(); err != nil { - g.errlog.Add(err) - return err - } - return nil +// Verify ensures the user's environment has all the required resources/tools. +func (g *Go) Verify(_ io.Writer) error { + return g.validator.Validate() } -// setPackageName into go.mod manifest. -// -// NOTE: The implementation scans the go.mod line-by-line looking for the -// module directive (typically the first line, but not guaranteed) and replaces -// the module path with the user's configured package name. -func (g Go) setPackageName(path string) (err error) { - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable - // - // Disabling as we require a user to configure their own environment. - /* #nosec */ - f, err := os.Open(path) - if err != nil { - return err - } - fi, err := f.Stat() - if err != nil { - return err - } - - var b bytes.Buffer - - s := bufio.NewScanner(f) - for s.Scan() { - line := s.Text() - if strings.HasPrefix(line, "module ") { - line = fmt.Sprintf("module %s", g.pkgName) - } - b.WriteString(line + "\n") - } - if err := s.Err(); err != nil { - return err - } - - err = os.WriteFile(path, b.Bytes(), fi.Mode()) - if err != nil { - return err - } - - return nil +// Build compiles the user's source code into a Wasm binary. +func (g *Go) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { + // NOTE: We deliberately reference the validator pointer to the fastly.toml + // This is because the manifest.File might be updated when migrating a + // pre-existing project to use the CLI v4.0.0 (as prior to this version the + // manifest would not require [script.build] to be defined). + // As of v4.0.0 if no value is set, then we provide a default. + return build(buildOpts{ + buildScript: g.validator.FastlyManifestFile.Scripts.Build, + buildFn: g.Shell.Build, + errlog: g.errlog, + postBuild: g.postBuild, + timeout: g.timeout, + }, out, progress, verbose, nil, callback) } diff --git a/pkg/commands/compute/language_javascript.go b/pkg/commands/compute/language_javascript.go index 772a9759e..049715f66 100644 --- a/pkg/commands/compute/language_javascript.go +++ b/pkg/commands/compute/language_javascript.go @@ -4,334 +4,166 @@ import ( "encoding/json" "fmt" "io" - "os" - "os/exec" "path/filepath" - "strings" - "time" fsterr "github.com/fastly/cli/pkg/errors" - fstexec "github.com/fastly/cli/pkg/exec" - "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" ) -// JSSourceDirectory represents the source code directory. -const JSSourceDirectory = "src" +// JsCompilation is a language specific compilation target that converts the +// language code into a Wasm binary. +const JsCompilation = "js-compute-runtime" -// JSManifestName represents the language file for configuring dependencies. -const JSManifestName = "package.json" +// JsCompilationCommandRemediation is the command to execute to fix the missing +// compilation target. +const JsCompilationCommandRemediation = "npm install --save-dev %s" -// JsToolchain represents the default JS toolchain. -const JsToolchain = "npm" +// JsCompilationURL is the official Fastly C@E JS runtime package URL. +const JsCompilationURL = "https://www.npmjs.com/package/@fastly/js-compute" -// SetPackageName into package.json manifest. -// -// NOTE: We can't presume to know the structure of the package.json manifest, -// and so we use the json package to unmarshal the entire file into a generic -// map data structure before updating the name field and marshalling it back to -// json afterwards. -func SetPackageName(name, path string) (err error) { - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable - // - // Disabling as we require a user to configure their own environment. - /* #nosec */ - data, err := os.ReadFile(path) - if err != nil { - return err - } +// JsDefaultBuildCommand is a build command compiled into the CLI binary so it +// can be used as a fallback for customer's who have an existing C@E project and +// are simply upgrading their CLI version and might not be familiar with the +// changes in the 4.0.0 release with regards to how build logic has moved to the +// fastly.toml manifest. +const JsDefaultBuildCommand = "$(npm bin)/webpack && $(npm bin)/js-compute-runtime ./bin/index.js ./bin/main.wasm" - var i any - if err = json.Unmarshal(data, &i); err != nil { - return err - } +// JsInstaller is the command used to install the dependencies defined within +// the Js language manifest. +const JsInstaller = "npm install" - m, ok := i.(map[string]any) - if !ok { - return err - } - if _, ok := m["name"]; ok { - m["name"] = name - } +// JsManifest is the manifest file for defining project configuration. +const JsManifest = "package.json" - data, err = json.MarshalIndent(m, "", " ") - if err != nil { - return err - } +// JsManifestRemediation is a error remediation message for a missing manifest. +const JsManifestRemediation = "npm init" - if err := os.WriteFile(path, data, 0o600); err != nil { - return fmt.Errorf("error updating %s manifest file: %w", JSManifestName, err) - } - return nil -} +// JsSDK is the required Compute@Edge SDK. +// https://www.npmjs.com/package/@fastly/js-compute +const JsSDK = "@fastly/js-compute" -// JavaScript implements a Toolchain for the JavaScript language. -type JavaScript struct { - Shell +// JsSourceDirectory represents the source code directory. │ │ +const JsSourceDirectory = "src" - build string - errlog fsterr.LogInterface - packageDependency string - packageExecutable string - pkgName string - postBuild string - timeout int - toolchain string - validateScriptBuild bool -} +// JsToolchain is the executable responsible for managing dependencies. +const JsToolchain = "npm" + +// JsToolchainURL is the official JS website URL. +const JsToolchainURL = "https://nodejs.org/" // NewJavaScript constructs a new JavaScript toolchain. -func NewJavaScript(pkgName string, scripts manifest.Scripts, errlog fsterr.LogInterface, timeout int) *JavaScript { +func NewJavaScript( + fastlyManifest *manifest.File, + errlog fsterr.LogInterface, + timeout int, + out io.Writer, + ch chan string, +) *JavaScript { return &JavaScript{ - Shell: Shell{}, - build: scripts.Build, - errlog: errlog, - packageDependency: "@fastly/js-compute", - packageExecutable: "js-compute-runtime", - pkgName: pkgName, - postBuild: scripts.PostBuild, - timeout: timeout, - toolchain: JsToolchain, - validateScriptBuild: true, + Shell: Shell{}, + errlog: errlog, + postBuild: fastlyManifest.Scripts.PostBuild, + timeout: timeout, + validator: ToolchainValidator{ + Compilation: JsCompilation, + CompilationDirectPath: func() (string, error) { + p, err := getJsToolchainBinPath(JsToolchain) + if err != nil { + errlog.Add(err) + remediation := "npm install --global npm@latest" + return "", fsterr.RemediationError{ + Inner: fmt.Errorf("could not determine %s bin path", JsToolchain), + Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), + } + } + + return filepath.Join(p, JsCompilation), nil + }, + CompilationCommandRemediation: fmt.Sprintf(JsCompilationCommandRemediation, JsSDK), + CompilationSkipVersion: true, + CompilationURL: JsCompilationURL, + DefaultBuildCommand: JsDefaultBuildCommand, + ErrLog: errlog, + FastlyManifestFile: fastlyManifest, + Installer: JsInstaller, + Manifest: JsManifest, + ManifestRemediation: JsManifestRemediation, + Output: out, + PatchedManifestNotifier: ch, + SDK: JsSDK, + SDKCustomValidator: validateJsSDK, + Toolchain: JsToolchain, + ToolchainLanguage: "JavaScript", + ToolchainSkipVersion: true, + ToolchainURL: JsToolchainURL, + }, } } -// Initialize implements the Toolchain interface and initializes a newly cloned -// package by installing required dependencies. -func (j JavaScript) Initialize(out io.Writer) error { - // 1) Check a.toolchain is on $PATH - // - // npm, a Node/JavaScript toolchain installer/manager, is needed to install - // the package dependencies on initialization. We only check whether the - // binary exists on the users $PATH and error with installation help text. - fmt.Fprintf(out, "Checking if %s is installed...\n", j.toolchain) - - p, err := exec.LookPath(j.toolchain) - if err != nil { - j.errlog.Add(err) - nodejsURL := "https://nodejs.org/" - remediation := fmt.Sprintf("To fix this error, install Node.js and %s by visiting:\n\n\t$ %s\n\nThen execute:\n\n\t$ fastly compute init", j.toolchain, text.Bold(nodejsURL)) - - return fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` not found in $PATH", j.toolchain), - Remediation: remediation, - } - } - - fmt.Fprintf(out, "Found %s at %s\n", j.toolchain, p) - - // 2) Check package.json file exists in $PWD - // - // A valid npm package manifest file is needed for the install command to - // work. Therefore, we first assert whether one exists in the current $PWD. - m, err := filepath.Abs(JSManifestName) - if err != nil { - j.errlog.Add(err) - return fmt.Errorf("getting %s path: %w", JSManifestName, err) - } - - if !filesystem.FileExists(m) { - msg := fmt.Sprintf(fsterr.FormatTemplate, text.Bold("npm init")) - remediation := fmt.Sprintf("%s\n\nThen execute\n\n\t$ fastly compute init", msg) - err := fsterr.RemediationError{ - Inner: fmt.Errorf("%s not found", JSManifestName), - Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), - } - j.errlog.Add(err) - return err - } - - if err := SetPackageName(j.pkgName, m); err != nil { - j.errlog.Add(err) - return fmt.Errorf("error updating %s manifest: %w", JSManifestName, err) - } - - fmt.Fprintf(out, "Found %s at %s\n", JSManifestName, m) - fmt.Fprintf(out, "Installing package dependencies...\n") +// JavaScript implements a Toolchain for the JavaScript language. +type JavaScript struct { + Shell - cmd := fstexec.Streaming{ - Command: j.toolchain, - Args: []string{"install"}, - Env: []string{}, - Output: out, - } - return cmd.Exec() + // errlog is an abstraction for recording errors to disk. + errlog fsterr.LogInterface + // postBuild is a custom script executed after the build but before the Wasm + // binary is added to the .tar.gz archive. + postBuild string + // timeout is the build execution threshold. + timeout int + // validator is an abstraction to validate required resources are installed. + validator ToolchainValidator } -// Verify implements the Toolchain interface and verifies whether the -// JavaScript language toolchain is correctly configured on the host. -func (j JavaScript) Verify(out io.Writer) error { - // 1) Check a.toolchain is on $PATH - // - // npm, a popular Node/JavaScript toolchain installer/manager, which is needed - // to assert that the correct versions of the js-compute-runtime compiler and - // @fastly/js-compute package are installed. We only check whether the binary - // exists on the users $PATH and error with installation help text. - fmt.Fprintf(out, "Checking if %s is installed...\n", j.toolchain) - - p, err := exec.LookPath(j.toolchain) - if err != nil { - j.errlog.Add(err) - nodejsURL := "https://nodejs.org/" - remediation := fmt.Sprintf("To fix this error, install Node.js and %s by visiting:\n\n\t$ %s", j.toolchain, text.Bold(nodejsURL)) - - return fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` not found in $PATH", j.toolchain), - Remediation: remediation, - } - } - - fmt.Fprintf(out, "Found %s at %s\n", j.toolchain, p) - - // 2) Check package.json file exists in $PWD - // - // A valid package is needed for compilation and to assert whether the - // required dependencies are installed locally. Therefore, we first assert - // whether one exists in the current $PWD. - pkg, err := filepath.Abs(JSManifestName) - if err != nil { - j.errlog.Add(err) - return fmt.Errorf("getting %s path: %w", JSManifestName, err) - } - - if !filesystem.FileExists(pkg) { - remediation := "npm init" - err := fsterr.RemediationError{ - Inner: fmt.Errorf("%s not found", JSManifestName), - Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), - } - j.errlog.Add(err) - return err - } - - fmt.Fprintf(out, "Found %s at %s\n", JSManifestName, pkg) - - // 3) Check if `js-compute-runtime` is installed. - // - // js-compute-runtime is the JavaScript compiler. We first check if the - // required dependency exists in the package.json and then whether the - // js-compute-runtime binary exists in the toolchain bin directory. - fmt.Fprintf(out, "Checking if %s is installed...\n", j.packageDependency) - if !checkJsPackageDependencyExists(j.toolchain, j.packageDependency) { - remediation := fmt.Sprintf("npm install --save-dev %s", j.packageDependency) - err := fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` not installed", j.packageDependency), - Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), - } - j.errlog.Add(err) - return err - } - - p, err = getJsToolchainBinPath(j.toolchain) - if err != nil { - j.errlog.Add(err) - remediation := "npm install --global npm@latest" - return fsterr.RemediationError{ - Inner: fmt.Errorf("could not determine %s bin path", j.toolchain), - Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), - } - } - - path, err := exec.LookPath(filepath.Join(p, j.packageExecutable)) - if err != nil { - j.errlog.Add(err) - return fmt.Errorf("getting %s path: %w", j.packageExecutable, err) - } - if !filesystem.FileExists(path) { - remediation := fmt.Sprintf("npm install --save-dev %s", j.packageDependency) - err := fsterr.RemediationError{ - Inner: fmt.Errorf("`%s` binary not found in %s", j.packageExecutable, p), - Remediation: fmt.Sprintf(fsterr.FormatTemplate, text.Bold(remediation)), - } - j.errlog.Add(err) - return err - } - - fmt.Fprintf(out, "Found %s at %s\n", j.packageExecutable, path) - - if j.validateScriptBuild { - remediation := "npm run" - pkgErr := fmt.Sprintf("%s requires a `script` field with a `build` step defined that calls the `%s` binary", JSManifestName, j.packageExecutable) - remediation = fmt.Sprintf("Check your %s has a `script` field with a `build` step defined:\n\n\t$ %s", JSManifestName, text.Bold(remediation)) +// Initialize handles any non-build related set-up. +func (j JavaScript) Initialize(out io.Writer) error { + return nil +} - // gosec flagged this: - // G204 (CWE-78): Subprocess launched with variable - // Disabling as the variables come from trusted sources: - // The CLI parser enforces supported values via EnumVar. - /* #nosec */ - cmd := exec.Command(j.toolchain, "run") - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { - j.errlog.Add(err) - return fsterr.RemediationError{ - Inner: fmt.Errorf("%s: %w", pkgErr, err), - Remediation: remediation, - } - } +// Verify ensures the user's environment has all the required resources/tools. +func (j JavaScript) Verify(_ io.Writer) error { + return j.validator.Validate() +} - if !strings.Contains(string(stdoutStderr), " build\n") { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("%s:\n\n%s", pkgErr, stdoutStderr), - Remediation: remediation, - } - j.errlog.Add(err) - return err - } - } +// Build compiles the user's source code into a Wasm binary. +func (j JavaScript) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { + // NOTE: We deliberately reference the validator pointer to the fastly.toml + // This is because the manifest.File might be updated when migrating a + // pre-existing project to use the CLI v4.0.0 (as prior to this version the + // manifest would not require [script.build] to be defined). + // As of v4.0.0 if no value is set, then we provide a default. + return build(buildOpts{ + buildScript: j.validator.FastlyManifestFile.Scripts.Build, + buildFn: j.Shell.Build, + errlog: j.errlog, + postBuild: j.postBuild, + timeout: j.timeout, + }, out, progress, verbose, nil, callback) +} - return nil +// JsPackage represents a package.json manifest. +type JsPackage struct { + Dependencies map[string]string } -// Build implements the Toolchain interface and attempts to compile the package -// JavaScript source to a Wasm binary. -func (j JavaScript) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { - cmd := j.toolchain - args := []string{"run", "build"} +// validateJsSDK marshals the JS manifest into JSON to check if the dependency +// has been defined in the package.json manifest. +func validateJsSDK(name string, bs []byte) error { + e := fmt.Errorf(SDKErrMessageFormat, name, JsManifest) - if j.build != "" { - cmd, args = j.Shell.Build(j.build) - } + var p JsPackage - err := j.execCommand(cmd, args, out, progress, verbose) + err := json.Unmarshal(bs, &p) if err != nil { - return err + return e } - // NOTE: We set the progress indicator to Done() so that any output we now - // print via the post_build callback doesn't get hidden by the progress status. - // The progress is 'reset' inside the main build controller `build.go`. - progress.Done() - - if j.postBuild != "" { - if err = callback(); err == nil { - cmd, args := j.Shell.Build(j.postBuild) - err := j.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } + for k := range p.Dependencies { + if k == name { + return nil } } - return nil -} - -func (j JavaScript) execCommand(cmd string, args []string, out, progress io.Writer, verbose bool) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Progress: progress, - Verbose: verbose, - } - if j.timeout > 0 { - s.Timeout = time.Duration(j.timeout) * time.Second - } - if err := s.Exec(); err != nil { - j.errlog.Add(err) - return err - } - return nil + return e } diff --git a/pkg/commands/compute/language_other.go b/pkg/commands/compute/language_other.go index 1ad0da719..9b520566e 100644 --- a/pkg/commands/compute/language_other.go +++ b/pkg/commands/compute/language_other.go @@ -1,31 +1,18 @@ package compute import ( - "fmt" "io" - "os" - "time" fsterr "github.com/fastly/cli/pkg/errors" - fstexec "github.com/fastly/cli/pkg/exec" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" ) -// Other implements a Toolchain for languages without official support. -type Other struct { - Shell - - build string - errlog fsterr.LogInterface - postBuild string - timeout int -} - // NewOther constructs a new unsupported language instance. func NewOther(scripts manifest.Scripts, errlog fsterr.LogInterface, timeout int) *Other { return &Other{ - Shell: Shell{}, + Shell: Shell{}, + build: scripts.Build, errlog: errlog, postBuild: scripts.PostBuild, @@ -33,6 +20,21 @@ func NewOther(scripts manifest.Scripts, errlog fsterr.LogInterface, timeout int) } } +// Other implements a Toolchain for languages without official support. +type Other struct { + Shell + + // build is a shell command defined in fastly.toml using [scripts.build]. + build string + // errlog is an abstraction for recording errors to disk. + errlog fsterr.LogInterface + // postBuild is a custom script executed after the build but before the Wasm + // binary is added to the .tar.gz archive. + postBuild string + // timeout is the build execution threshold. + timeout int +} + // Initialize is a no-op. func (o Other) Initialize(_ io.Writer) error { return nil @@ -46,54 +48,11 @@ func (o Other) Verify(_ io.Writer) error { // Build implements the Toolchain interface and attempts to compile the package // source to a Wasm binary. func (o Other) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { - if o.build == "" { - err := fmt.Errorf("error reading custom build instructions from fastly.toml manifest") - o.errlog.Add(err) - return fsterr.RemediationError{ - Inner: err, - Remediation: fsterr.ComputeBuildRemediation, - } - } - cmd, args := o.Shell.Build(o.build) - - err := o.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - - // NOTE: We set the progress indicator to Done() so that any output we now - // print via the post_build callback doesn't get hidden by the progress status. - // The progress is 'reset' inside the main build controller `build.go`. - progress.Done() - - if o.postBuild != "" { - if err = callback(); err == nil { - cmd, args := o.Shell.Build(o.postBuild) - err := o.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - } - } - - return nil -} - -func (o Other) execCommand(cmd string, args []string, out, progress io.Writer, verbose bool) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Progress: progress, - Verbose: verbose, - } - if o.timeout > 0 { - s.Timeout = time.Duration(o.timeout) * time.Second - } - if err := s.Exec(); err != nil { - o.errlog.Add(err) - return err - } - return nil + return build(buildOpts{ + buildScript: o.build, + buildFn: o.Shell.Build, + errlog: o.errlog, + postBuild: o.postBuild, + timeout: o.timeout, + }, out, progress, verbose, nil, callback) } diff --git a/pkg/commands/compute/language_rust.go b/pkg/commands/compute/language_rust.go index 3dc169c40..69baa193f 100644 --- a/pkg/commands/compute/language_rust.go +++ b/pkg/commands/compute/language_rust.go @@ -4,370 +4,209 @@ import ( "bufio" "bytes" "encoding/json" - "errors" "fmt" "io" - "io/fs" - "net/http" "os" "os/exec" "path/filepath" - "sort" + "regexp" "strings" - "time" - "github.com/Masterminds/semver/v3" - "github.com/fastly/cli/pkg/api" "github.com/fastly/cli/pkg/config" fsterr "github.com/fastly/cli/pkg/errors" - fstexec "github.com/fastly/cli/pkg/exec" "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/text" toml "github.com/pelletier/go-toml" ) -// RustSourceDirectory represents the source code directory. -const RustSourceDirectory = "src" +// RustCompilation is a language specific compilation target that converts the +// language code into a Wasm binary. +const RustCompilation = "wasm32-wasi" -// RustManifestName represents the language file for configuring dependencies. -const RustManifestName = "Cargo.toml" +// RustCompilationURL is the specification URL for the wasm32-wasi target. +const RustCompilationURL = "https://doc.rust-lang.org/stable/nightly-rustc/rustc_target/spec/wasm32_wasi/index.html" -// CargoPackage models the package configuration properties of a Rust Cargo -// package which we are interested in and is embedded within CargoManifest and -// CargoLock. -type CargoPackage struct { - Name string `toml:"name" json:"name"` - Version string `toml:"version" json:"version"` -} +// RustCompilationCommandRemediation is the command to execute to fix the +// missing compilation target. +const RustCompilationCommandRemediation = "rustup target add %s --toolchain $ACTIVE_TOOLCHAIN" -// CargoManifest models the package configuration properties of a Rust Cargo -// manifest which we are interested in and are read from the Cargo.toml manifest -// file within the $PWD of the package. -type CargoManifest struct { - Package CargoPackage `toml:"package"` -} +// RustCompilationTargetCommand is the shell command for returning the list of +// installed compilation targets. +const RustCompilationTargetCommand = "rustup target list --installed --toolchain %s" -// Read the contents of the Cargo.toml manifest from filename. -func (m *CargoManifest) Read(path string) error { - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable. - // Disabling as we need to load the Cargo.toml from the user's file system. - // This file is decoded into a predefined struct, any unrecognised fields are dropped. - /* #nosec */ - data, err := os.ReadFile(path) - if err != nil { - return err - } - err = toml.Unmarshal(data, m) - return err -} +// RustConstraints is the set of supported toolchain and compilation versions. +// +// NOTE: Two keys are supported: "toolchain" and "compilation", with the latter +// being optional as not all language compilation steps are separate tools from +// the toolchain itself. +var RustConstraints = make(map[string]string) -// SetPackageName into Cargo.toml manifest. -func (m *CargoManifest) SetPackageName(name, path string) error { - if err := m.Read("Cargo.toml"); err != nil { - return fmt.Errorf("error reading Cargo.toml manifest: %w", err) - } - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable. - // Disabling as we need to load the Cargo.toml from the user's file system. - // This file is read, and the content is written back with the package - // name updated. - /* #nosec */ - data, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("error reading Cargo.toml file: %w", err) - } - data = bytes.ReplaceAll(data, []byte(m.Package.Name), []byte(name)) - if err := os.WriteFile(path, data, 0o600); err != nil { - return fmt.Errorf("error updating Cargo manifest file: %w", err) - } - return nil -} +// RustDefaultBuildCommand is a build command compiled into the CLI binary so it +// can be used as a fallback for customer's who have an existing C@E project and +// are simply upgrading their CLI version and might not be familiar with the +// changes in the 4.0.0 release with regards to how build logic has moved to the +// fastly.toml manifest. +const RustDefaultBuildCommand = "cargo build --bin compute-starter-kit-rust --release --target wasm32-wasi --color always" -// CargoMetadataPackage models the package structure returned when executing -// the command `cargo metadata`. -type CargoMetadataPackage struct { - Name string `toml:"name" json:"name"` - Version string `toml:"version" json:"version"` - Dependencies []CargoMetadataPackage `toml:"dependencies" json:"dependencies"` -} +// RustManifest is the manifest file for defining project configuration. +const RustManifest = "Cargo.toml" -// CargoMetadata models information about the workspace members and resolved -// dependencies of the current package via `cargo metadata` command output. -type CargoMetadata struct { - Package []CargoMetadataPackage `json:"packages"` - TargetDirectory string `json:"target_directory"` -} +// RustManifestRemediation is a error remediation message for a missing manifest. +const RustManifestRemediation = "cargo new $NAME --bin" -// Read the contents of the Cargo.lock file from filename. -func (m *CargoMetadata) Read(errlog fsterr.LogInterface) error { - cmd := exec.Command("cargo", "metadata", "--quiet", "--format-version", "1") - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { - if len(stdoutStderr) > 0 { - err = fmt.Errorf("%s", strings.TrimSpace(string(stdoutStderr))) - } - errlog.Add(err) - return err - } - r := bytes.NewReader(stdoutStderr) - if err := json.NewDecoder(r).Decode(&m); err != nil { - errlog.Add(err) - return err - } - return nil -} +// RustSDK is the required Compute@Edge SDK. +// https://crates.io/crates/fastly +const RustSDK = "fastly" -// Rust implements a Toolchain for the Rust language. -type Rust struct { - Shell +// RustSourceDirectory represents the source code directory. │ │ +const RustSourceDirectory = "src" - build string - client api.HTTPClient - config config.Rust - errlog fsterr.LogInterface - pkgName string - postBuild string - timeout int -} +// RustToolchain is the executable responsible for managing dependencies. +const RustToolchain = "cargo" + +// RustToolchainCommandRemediation is the command to execute to fix the +// toolchain. +const RustToolchainCommandRemediation = "Run `rustup update stable`, or ensure your `rust-toolchain` file specifies a version matching the constraint (e.g. `channel = \"stable\"`)." + +// RustToolchainURL is the official Rust website URL. +const RustToolchainURL = "https://doc.rust-lang.org/stable/cargo/" + +// RustToolchainVersionCommand is the shell command for returning the Rust +// version. +const RustToolchainVersionCommand = "cargo version" // NewRust constructs a new Rust toolchain. -func NewRust(pkgName string, scripts manifest.Scripts, errlog fsterr.LogInterface, client api.HTTPClient, timeout int, cfg config.Rust) *Rust { +func NewRust( + fastlyManifest *manifest.File, + errlog fsterr.LogInterface, + timeout int, + cfg config.Rust, + out io.Writer, + ch chan string, +) *Rust { + RustConstraints["toolchain"] = cfg.ToolchainConstraint + return &Rust{ Shell: Shell{}, - build: scripts.Build, - client: client, config: cfg, errlog: errlog, - pkgName: pkgName, - postBuild: scripts.PostBuild, + postBuild: fastlyManifest.Scripts.PostBuild, timeout: timeout, + validator: ToolchainValidator{ + Compilation: RustCompilation, + CompilationIntegrated: true, + CompilationCommandRemediation: fmt.Sprintf(RustCompilationCommandRemediation, cfg.WasmWasiTarget), + CompilationTargetCommand: fmt.Sprintf(RustCompilationTargetCommand, rustupToolchain()), + CompilationTargetPattern: regexp.MustCompile(fmt.Sprintf(`(?P)%s`, RustCompilation)), + CompilationURL: RustCompilationURL, + Constraints: RustConstraints, + DefaultBuildCommand: RustDefaultBuildCommand, + ErrLog: errlog, + FastlyManifestFile: fastlyManifest, + Manifest: RustManifest, + ManifestRemediation: RustManifestRemediation, + Output: out, + PatchedManifestNotifier: ch, + SDK: RustSDK, + SDKCustomValidator: validateRustSDK, + Toolchain: RustToolchain, + ToolchainCommandRemediation: RustToolchainCommandRemediation, + ToolchainLanguage: "Rust", + ToolchainVersionCommand: RustToolchainVersionCommand, + ToolchainVersionPattern: regexp.MustCompile(`cargo (?P\d[^\s]+)`), + ToolchainURL: RustToolchainURL, + }, } } -// Verify implements the Toolchain interface and verifies whether the Rust -// language toolchain is correctly configured on the host. -// -// NOTE: -// Steps for validation are: -// -// 1. Validate `rustc` installed. -// 2. Validate `rustc --version` meets the constraint. -// 3. Validate `wasm32-wasi` target is added to the relevant toolchain. -// 4. Validate `cargo` is installed. -// 5. Validate `fastly-sys` crate version. -// 6. Validate `fastly` crate version (optional upgrade suggestion). -func (r *Rust) Verify(out io.Writer) (err error) { - fmt.Fprintf(out, "Checking if `rustc` is installed...\n") - - err = validateCompilerExists(r.errlog) - if err != nil { - return err - } - - fmt.Fprintf(out, "Checking the `rustc` version...\n") - - err = validateCompilerVersion(r.config.ToolchainConstraint, r.errlog) - if err != nil { - return err - } - - fmt.Fprintf(out, "Checking the `wasm32-wasi` target is installed...\n") - - err = validateWasmTarget(r.config.WasmWasiTarget, r.errlog) - if err != nil { - return err - } - - fmt.Fprintf(out, "Checking if `cargo` is installed...\n") - - err = validateCargoExists(r.errlog) - if err != nil { - return err - } - - // Validate the fastly and fastly-sys crates... - - latestFastlyCrate, err := GetLatestCrateVersion(r.client, "fastly", r.errlog) - if err != nil { - return fmt.Errorf("error fetching latest `fastly` crate version: %w", err) - } - - var metadata CargoMetadata - if err := metadata.Read(r.errlog); err != nil { - return fmt.Errorf("error reading cargo metadata: %w", err) - } - - err = validateFastlySysCrate(metadata, r.config.FastlySysConstraint, latestFastlyCrate.String(), r.errlog) - if err != nil { - return err - } - - err = validateFastlyCrate(metadata, latestFastlyCrate, out, r.errlog) - if err != nil { - return err - } +// Rust implements a Toolchain for the Rust language. +type Rust struct { + Shell - return nil + // config is the Rust specific application configuration. + config config.Rust + // errlog is an abstraction for recording errors to disk. + errlog fsterr.LogInterface + // postBuild is a custom script executed after the build but before the Wasm + // binary is added to the .tar.gz archive. + postBuild string + // timeout is the build execution threshold. + timeout int + // validator is an abstraction to validate required resources are installed. + validator ToolchainValidator } -// validateCompilerExists checks if `rustc` is installed. -func validateCompilerExists(errlog fsterr.LogInterface) error { - _, err := exec.LookPath("rustc") - if err != nil { - errlog.Add(err) - return fsterr.RemediationError{ - Inner: err, - Remediation: "Ensure the `rustc` compiler is installed:\n\n\thttps://www.rust-lang.org/tools/install", - } - } +// Initialize implements the Toolchain interface and initializes a newly cloned +// package. It is a noop for Rust as the Cargo toolchain handles these steps. +func (r Rust) Initialize(_ io.Writer) error { return nil } -// validateCompilerVersion checks the `rustc` version meets our constraint. -func validateCompilerVersion(constraint string, errlog fsterr.LogInterface) error { - version, err := rustcVersion(errlog) - if err != nil { - return err - } - - rustcVersion, err := semver.NewVersion(version) - if err != nil { - errlog.Add(err) - return fmt.Errorf("error parsing `%s` output %q into a semver: %w", "rustc --version", version, err) - } - - rustcConstraint, err := semver.NewConstraint(constraint) - if err != nil { - errlog.Add(err) - return fmt.Errorf("error parsing rustup constraint: %w", err) - } - - if !rustcConstraint.Check(rustcVersion) { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("rustc constraint '%s' not met: %s", constraint, version), - Remediation: "Run `rustup update stable`, or ensure your `rust-toolchain` file specifies a version matching the constraint (e.g. `channel = \"stable\"`).", - } - errlog.Add(err) - return err - } - - return nil +// Verify ensures the user's environment has all the required resources/tools. +func (r *Rust) Verify(_ io.Writer) error { + return r.validator.Validate() } -// rustcVersion returns the active rustc compiler version. -func rustcVersion(errlog fsterr.LogInterface) (string, error) { - cmd := []string{"rustc", "--version"} - c := exec.Command(cmd[0], cmd[1:]...) // #nosec G204 - stdoutStderr, err := c.CombinedOutput() - if err != nil { - errlog.Add(err) - return "", fmt.Errorf("error executing `%s`: %w", strings.Join(cmd, " "), err) - } - - // Note: when `rustc` is managed by `rustup`, and the toolchain that - // `rustup` considers active is not installed, then the first use of `rustc` - // (or any Rust distribution executable) will install it. This produces some - // `rustup` output before the real `rustc --version` command fires. - scanner := bufio.NewScanner(bytes.NewReader(stdoutStderr)) - line := "" - for scanner.Scan() { - line = scanner.Text() - } - err = scanner.Err() - if line == "" || err != nil { - err = fmt.Errorf("error reading `%s` output: %w", strings.Join(cmd, " "), err) - errlog.Add(err) - return "", err - } - line = strings.TrimSpace(line) - - // Example outputs: - // rustc 1.54.0 (a178d0322 2021-07-26) - // rustc 1.56.0-nightly (2d2bc94c8 2021-08-15) - parts := strings.Split(line, " ") - if len(parts) < 2 { - err := fmt.Errorf("error reading `%s` output", strings.Join(cmd, " ")) - errlog.Add(err) - return "", err - } - - version := strings.Split(parts[1], "-") - if len(version) > 1 { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("non-stable releases are not supported: %s", parts[1]), - Remediation: "Run `rustup update stable`, or ensure your `rust-toolchain` file specifies a version matching the constraint (e.g. `channel = \"stable\"`). Alternatively utilise the CLI's `--force` flag.", - } - errlog.Add(err) - return "", err - } - - return version[0], nil +// Build compiles the user's source code into a Wasm binary. +func (r *Rust) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { + // NOTE: We deliberately reference the validator pointer to the fastly.toml + // This is because the manifest.File might be updated when migrating a + // pre-existing project to use the CLI v4.0.0 (as prior to this version the + // manifest would not require [script.build] to be defined). + // As of v4.0.0 if no value is set, then we provide a default. + return build(buildOpts{ + buildScript: r.validator.FastlyManifestFile.Scripts.Build, + buildFn: r.Shell.Build, + errlog: r.errlog, + postBuild: r.postBuild, + timeout: r.timeout, + }, out, progress, verbose, r.ProcessLocation, callback) } -// validateWasmTarget checks the `wasm32-wasi` target is installed. -// -// If the user has `rustup` installed then we use it to identify if the target -// is installed, otherwise we fallback to a low-level check of the target -// directory using `rustc --print sysroot`. -func validateWasmTarget(target string, errlog fsterr.LogInterface) error { - _, err := exec.LookPath("rustup") - if err != nil { - errlog.Add(err) - return rustcSysroot(target, errlog) - } - - toolchain, err := rustupToolchain(errlog) +// ProcessLocation ensures the generated Rust Wasm binary is moved to the +// required location for packaging. +func (r *Rust) ProcessLocation() error { + dir, err := os.Getwd() if err != nil { - return err + r.errlog.Add(err) + return fmt.Errorf("getting current working directory: %w", err) } - - cmd := []string{"rustup", "target", "list", "--installed", "--toolchain", toolchain} - c := exec.Command(cmd[0], cmd[1:]...) // #nosec G204 - stdoutStderr, err := c.CombinedOutput() - if err != nil { - errlog.Add(err) - return fmt.Errorf("error executing `%s`: %w", strings.Join(cmd, " "), err) + var metadata CargoMetadata + if err := metadata.Read(r.errlog); err != nil { + r.errlog.Add(err) + return fmt.Errorf("error reading cargo metadata: %w", err) } - - scanner := bufio.NewScanner(strings.NewReader(string(stdoutStderr))) - scanner.Split(bufio.ScanWords) - found := false - for scanner.Scan() { - if scanner.Text() == target { - found = true - break - } + var m CargoManifest + if err := m.Read("Cargo.toml"); err != nil { + return fmt.Errorf("error reading Cargo.toml manifest: %w", err) } + src := filepath.Join(metadata.TargetDirectory, r.config.WasmWasiTarget, "release", fmt.Sprintf("%s.wasm", m.Package.Name)) + dst := filepath.Join(dir, "bin", "main.wasm") - if !found { - err := fsterr.RemediationError{ - Inner: fmt.Errorf("rust target %s not found", target), - Remediation: fmt.Sprintf("Run the following command:\n\n\t$ %s\n", text.Bold(fmt.Sprintf("rustup target add %s --toolchain %s", target, toolchain))), - } - errlog.Add(err) - return err + err = filesystem.CopyFile(src, dst) + if err != nil { + r.errlog.Add(err) + return fmt.Errorf("copying wasm binary: %w", err) } - return nil } -// rustupToolchain returns the active rustup toolchain. -func rustupToolchain(errlog fsterr.LogInterface) (string, error) { +// rustupToolchain returns the active rustup toolchain and falls back to stable +// if there was an error. +func rustupToolchain() string { + stable := "stable" cmd := []string{"rustup", "show", "active-toolchain"} c := exec.Command(cmd[0], cmd[1:]...) // #nosec G204 stdoutStderr, err := c.CombinedOutput() if err != nil { - errlog.Add(err) - return "", fmt.Errorf("error executing `%s`: %w", strings.Join(cmd, " "), err) + return stable } reader := bufio.NewReader(bytes.NewReader(stdoutStderr)) line, err := reader.ReadString('\n') if err != nil { - errlog.Add(err) - return "", fmt.Errorf("error reading `%s` output: %w", strings.Join(cmd, " "), err) + return stable } // Example outputs: @@ -375,339 +214,91 @@ func rustupToolchain(errlog fsterr.LogInterface) (string, error) { // 1.54.0-x86_64-apple-darwin (directory override for '/Users/integralist/Code/fastly/cli') parts := strings.Split(line, "-") if len(parts) < 2 { - err := fmt.Errorf("error reading `%s` output", strings.Join(cmd, " ")) - errlog.Add(err) - return "", err + return "stable" } - return parts[0], nil + return parts[0] } -// rustcSysroot validates if the wasm32-wasi target is installed by using the -// low-level rustc compiler `--print sysroot` flag. -// -// This is called only when the user doesn't have `rustup` installed. -func rustcSysroot(target string, errlog fsterr.LogInterface) error { - cmd := []string{"rustc", "--print", "sysroot"} - c := exec.Command(cmd[0], cmd[1:]...) // #nosec G204 - stdoutStderr, err := c.CombinedOutput() - if err != nil { - errlog.Add(err) - return fmt.Errorf("error executing `%s`: %w", strings.Join(cmd, " "), err) - } - - sysroot := strings.TrimSpace(string(stdoutStderr)) - path := filepath.Join(sysroot, "lib", "rustlib", "wasm32-wasi") - if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - errlog.Add(err) - return fsterr.RemediationError{ - Inner: fmt.Errorf("the Rust target directory `%s` doesn't exist", path), - Remediation: fmt.Sprintf("Ensure the following target is installed:\n\n\t%s", target), - } - } - - return nil +// CargoPackage models the package configuration properties of a Rust Cargo +// package which we are interested in and is embedded within CargoManifest and +// CargoLock. +type CargoPackage struct { + Name string `toml:"name" json:"name"` + Version string `toml:"version" json:"version"` } -// validateCargoExists checks `cargo` is installed. -func validateCargoExists(errlog fsterr.LogInterface) error { - _, err := exec.LookPath("cargo") - if err != nil { - errlog.Add(err) - return fsterr.RemediationError{ - Inner: err, - Remediation: "Ensure the `cargo` package manager is installed:\n\n\thttps://doc.rust-lang.org/cargo/getting-started/installation.html", - } - } - return nil +// CargoManifest models the package configuration properties of a Rust Cargo +// manifest which we are interested in and are read from the Cargo.toml manifest +// file within the $PWD of the package. +type CargoManifest struct { + Package CargoPackage `toml:"package"` } -// validateFastlySysCrate checks the `fastly-sys` crate version meets our constraint. -// -// The following logic is an requirement that we don't want a customer to -// have to think about and so we don't indicate to the user that we're -// validating the fastly-sys crate specifically (i.e. we make the messaging -// generic towards the fastly crate). -func validateFastlySysCrate(metadata CargoMetadata, constraint string, latestFastlyCrateVersion string, errlog fsterr.LogInterface) error { - fastlySysConstraint, err := semver.NewConstraint(constraint) - if err != nil { - errlog.Add(err) - return fmt.Errorf("error parsing crate constraint: %w", err) - } - - fastlySysVersion, err := GetCrateVersionFromMetadata(metadata, "fastly-sys") +// Read the contents of the Cargo.toml manifest from filename. +func (m *CargoManifest) Read(path string) error { + // gosec flagged this: + // G304 (CWE-22): Potential file inclusion via variable. + // Disabling as we need to load the Cargo.toml from the user's file system. + // This file is decoded into a predefined struct, any unrecognised fields are dropped. + /* #nosec */ + data, err := os.ReadFile(path) if err != nil { - errlog.Add(err) - return newCargoUpdateRemediationErr(err, latestFastlyCrateVersion) - } - - if ok := fastlySysConstraint.Check(fastlySysVersion); !ok { - err := fmt.Errorf("fastly crate not up-to-date") - errlog.Add(err) - return newCargoUpdateRemediationErr(err, latestFastlyCrateVersion) + return err } - - return nil + err = toml.Unmarshal(data, m) + return err } -// validateFastlyCrate checks the `fastly` crate version meets our constraint. -// -// The folllowing logic is an optional upgrade suggestion and so we don't -// display any up front message to say we're checking the fastly crate. -func validateFastlyCrate(metadata CargoMetadata, v *semver.Version, out io.Writer, errlog fsterr.LogInterface) error { - fastlyVersion, err := GetCrateVersionFromMetadata(metadata, "fastly") - if err != nil { - errlog.Add(err) - return newCargoUpdateRemediationErr(err, v.String()) - } - - // If the fastly crate version is a prerelease, exit early. We assume that the - // user knows what they are doing and avoids any confusing messaging to - // "upgrade" to an older version. - if fastlyVersion.Prerelease() != "" { - return nil - } - - // If the fastly crate version is lower than the latest, suggest user should - // update, but don't error. - if fastlyVersion.LessThan(v) { - text.Break(out) - text.Info(out, fmt.Sprintf( - "an optional upgrade for the fastly crate is available, edit %s with:\n\n\t %s\n\nAnd then run the following command:\n\n\t$ %s\n", - text.Bold(RustManifestName), - text.Bold(fmt.Sprintf(`fastly = "^%s"`, v.String())), - text.Bold("cargo update -p fastly"), - )) - text.Break(out) - } - - return nil +// CargoMetadataPackage models the package structure returned when executing +// the command `cargo metadata`. +type CargoMetadataPackage struct { + Name string `toml:"name" json:"name"` + Version string `toml:"version" json:"version"` + Dependencies []CargoMetadataPackage `toml:"dependencies" json:"dependencies"` } -// Initialize implements the Toolchain interface and initializes a newly cloned -// package. It is a noop for Rust as the Cargo toolchain handles these steps. -func (r Rust) Initialize(_ io.Writer) error { - var m CargoManifest - if err := m.SetPackageName(r.pkgName, RustManifestName); err != nil { - r.errlog.Add(err) - return fmt.Errorf("error updating %s manifest: %w", RustManifestName, err) - } - return nil +// CargoMetadata models information about the workspace members and resolved +// dependencies of the current package via `cargo metadata` command output. +type CargoMetadata struct { + Package []CargoMetadataPackage `json:"packages"` + TargetDirectory string `json:"target_directory"` } -// Build implements the Toolchain interface and attempts to compile the package -// Rust source to a Wasm binary. -// -// NOTE: The callback function is called before executing any potential custom -// post_build script defined, allowing the controlling build logic to display a -// message to the user informing them a post_build is going to execute. -func (r *Rust) Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error { - // Get binary name from Cargo.toml. - var m CargoManifest - if err := m.Read(RustManifestName); err != nil { - r.errlog.Add(err) - return fmt.Errorf("error reading %s manifest: %w", RustManifestName, err) - } - binName := m.Package.Name - - cmd := "cargo" - args := []string{ - "build", - "--bin", - binName, - "--release", - "--target", - r.config.WasmWasiTarget, - "--color", - "always", - } - if verbose { - args = append(args, "--verbose") - } - - if r.build != "" { - cmd, args = r.Shell.Build(r.build) - } - - // Execute the `cargo build` commands with the Wasm WASI target, release - // flags and env vars. - err := r.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } - - // Get working directory. - dir, err := os.Getwd() - if err != nil { - r.errlog.Add(err) - return fmt.Errorf("getting current working directory: %w", err) - } - var metadata CargoMetadata - if err := metadata.Read(r.errlog); err != nil { - r.errlog.Add(err) - return fmt.Errorf("error reading cargo metadata: %w", err) - } - src := filepath.Join(metadata.TargetDirectory, r.config.WasmWasiTarget, "release", fmt.Sprintf("%s.wasm", binName)) - dst := filepath.Join(dir, "bin", "main.wasm") - - // Check if bin directory exists and create if not. - binDir := filepath.Join(dir, "bin") - if err := filesystem.MakeDirectoryIfNotExists(binDir); err != nil { - r.errlog.Add(err) - return fmt.Errorf("creating bin directory: %w", err) - } - - err = filesystem.CopyFile(src, dst) +// Read the contents of the Cargo.lock file from filename. +func (m *CargoMetadata) Read(errlog fsterr.LogInterface) error { + cmd := exec.Command("cargo", "metadata", "--quiet", "--format-version", "1") + stdoutStderr, err := cmd.CombinedOutput() if err != nil { - r.errlog.Add(err) - return fmt.Errorf("copying wasm binary: %w", err) - } - - // NOTE: We set the progress indicator to Done() so that any output we now - // print via the post_build callback doesn't get hidden by the progress status. - // The progress is 'reset' inside the main build controller `build.go`. - progress.Done() - - if r.postBuild != "" { - if err = callback(); err == nil { - cmd, args := r.Shell.Build(r.postBuild) - err := r.execCommand(cmd, args, out, progress, verbose) - if err != nil { - return err - } + if len(stdoutStderr) > 0 { + err = fmt.Errorf("%s", strings.TrimSpace(string(stdoutStderr))) } + errlog.Add(err) + return err } - - return nil -} - -// TODO: Consider generics to avoid re-implementing this same logic. -func (r Rust) execCommand(cmd string, args []string, out, progress io.Writer, verbose bool) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Progress: progress, - Verbose: verbose, - } - if r.timeout > 0 { - s.Timeout = time.Duration(r.timeout) * time.Second - } - if err := s.Exec(); err != nil { - r.errlog.Add(err) + r := bytes.NewReader(stdoutStderr) + if err := json.NewDecoder(r).Decode(&m); err != nil { + errlog.Add(err) return err } return nil } -// CargoCrateVersion models a Cargo crate version returned by the crates.io API. -type CargoCrateVersion struct { - Version string `json:"num"` -} - -// CargoCrateVersions models a Cargo crate version returned by the crates.io API. -type CargoCrateVersions struct { - Versions []CargoCrateVersion `json:"versions"` -} - -// GetLatestCrateVersion fetches all versions of a given Rust crate from the -// crates.io HTTP API and returns the latest valid semver version. -func GetLatestCrateVersion(client api.HTTPClient, name string, errlog fsterr.LogInterface) (*semver.Version, error) { - url := fmt.Sprintf("https://crates.io/api/v1/crates/%s/versions", name) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - errlog.Add(err) - return nil, err - } - - resp, err := client.Do(req) - if err != nil { - errlog.Add(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - errlog.Add(err) - return nil, fmt.Errorf("error fetching latest crate version: %s", resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - errlog.Add(err) - return nil, err - } +// validateRustSDK marshals the Rust manifest into toml to check if the +// dependency has been defined in the Cargo.toml manifest. +func validateRustSDK(name string, bs []byte) error { + e := fmt.Errorf(SDKErrMessageFormat, name, RustManifest) - crate := CargoCrateVersions{} - err = json.Unmarshal(body, &crate) + tree, err := toml.LoadBytes(bs) if err != nil { - errlog.Add(err) - return nil, err + return e } - var versions []*semver.Version - for _, v := range crate.Versions { - // Parse version string and only append if not a prerelease. - if version, err := semver.NewVersion(v.Version); err == nil && version.Prerelease() == "" { - versions = append(versions, version) + if v, ok := tree.GetArray("dependencies").(*toml.Tree); ok { + if dependency, ok := v.Get("fastly").(string); ok && dependency != "" { + return nil } } - if len(versions) < 1 { - return nil, fmt.Errorf("no valid crate versions found") - } - - sort.Sort(semver.Collection(versions)) - - latest := versions[len(versions)-1] - - return latest, nil -} - -// GetCrateVersionFromMetadata searches for a crate inside a CargoMetadata tree -// and returns the crates version as a semver.Version. -func GetCrateVersionFromMetadata(metadata CargoMetadata, crate string) (*semver.Version, error) { - // Search for crate in metadata tree. - var c CargoMetadataPackage - for _, p := range metadata.Package { - if p.Name == crate { - c = p - break - } - for _, pp := range p.Dependencies { - if pp.Name == crate { - c = pp - break - } - } - } - - if c.Name == "" { - return nil, fmt.Errorf("%s crate not found", crate) - } - - // Parse lockfile version to semver.Version. - version, err := semver.NewVersion(c.Version) - if err != nil { - return nil, fmt.Errorf("error parsing cargo metadata: %w", err) - } - - return version, nil -} - -// newCargoUpdateRemediationErr constructs a new RemediationError which wraps a -// cargo error and suggests to update the fastly crate to a specified version as -// its remediation message. -func newCargoUpdateRemediationErr(err error, version string) fsterr.RemediationError { - return fsterr.RemediationError{ - Inner: err, - Remediation: fmt.Sprintf( - "To fix this error, edit %s with:\n\n\t %s\n\nAnd then run the following command:\n\n\t$ %s\n", - text.Bold(RustManifestName), - text.Bold(fmt.Sprintf(`fastly = "^%s"`, version)), - text.Bold("cargo update -p fastly"), - ), - } + return e } diff --git a/pkg/commands/compute/language_toolchain.go b/pkg/commands/compute/language_toolchain.go index 5bbfcd42f..89acc6b9b 100644 --- a/pkg/commands/compute/language_toolchain.go +++ b/pkg/commands/compute/language_toolchain.go @@ -1,10 +1,580 @@ package compute import ( + "bytes" + "fmt" + "io" + "os" "os/exec" + "path/filepath" + "regexp" "strings" + "time" + + "github.com/Masterminds/semver/v3" + fsterr "github.com/fastly/cli/pkg/errors" + fstexec "github.com/fastly/cli/pkg/exec" + "github.com/fastly/cli/pkg/filesystem" + "github.com/fastly/cli/pkg/manifest" + "github.com/fastly/cli/pkg/text" + toml "github.com/pelletier/go-toml" ) +// SDKErrMessageFormat is a format string that can be used by the +// ToolchainValidator and any other language files that need to implement custom +// validation. +const SDKErrMessageFormat = "failed to find SDK '%s' in the '%s' manifest" + +// Toolchain abstracts a Compute@Edge source language toolchain. +type Toolchain interface { + // Initialize handles any non-build related set-up. + Initialize(out io.Writer) error + + // Verify ensures the user's environment has all the required resources/tools. + Verify(out io.Writer) error + + // Build compiles the user's source code into a Wasm binary. + Build(out io.Writer, progress text.Progress, verbose bool, callback func() error) error +} + +// ToolchainValidator represents required tools and files that need to exist. +type ToolchainValidator struct { + // Compilation is a language specific compilation target that converts the + // language code into a Wasm binary (e.g. wasm32-wasi, tinygo). + Compilation string + + // CompilationDirectPath is a language specific function type that returns the + // direct path to a binary to be looked up rather than the $PATH environment + // variable. This is typically used for looking up installed NPM binaries that + // don't exist in the $PATH but the `npm` binary can internally resolve them. + CompilationDirectPath func() (string, error) + + // CompilationIntegrated is a language specific indicator that the + // compilation target is integrated with the toolchain and not an external + // tool (e.g. Rust's wasm32-wasi target). + CompilationIntegrated bool + + // CompilationCommandRemediation is a language specific shell command a user + // can execute to fix the missing compilation target. + CompilationCommandRemediation string + + // CompilationSkipVersion is a language specific indicator that the + // compilation target does not need to have its version checked against a + // constraint defined in the CLI's application configuration. + CompilationSkipVersion bool + + // CompilationTargetCommand is a language specific shell command that returns + // the Compilation target. + CompilationTargetCommand string + + // CompilationTargetPattern is a language specific regular expression that + // validates the compilation target is installed. + CompilationTargetPattern *regexp.Regexp + + // CompilationURL is a language specific homepage for the compilation target. + CompilationURL string + + // Constraints is a language specific set of supported toolchain and + // compilation versions. Two keys are supported: "toolchain" and + // "compilation", with the latter being optional as not all language + // compilation steps are separate tools from the toolchain itself. + Constraints map[string]string + + // DefaultBuildCommand is a build command compiled into the CLI binary so it + // can be used as a fallback for customer's who have an existing C@E project and + // are simply upgrading their CLI version and might not be familiar with the + // changes in the 4.0.0 release with regards to how build logic has moved to the + // fastly.toml manifest. + DefaultBuildCommand string + + // ErrLog is used to log any errors to the user's local error log file, which + // is also persisted to Sentry for Fastly error tracking/managment. + ErrLog fsterr.LogInterface + + // FastlyManifestFile is a reference to the in-memory manifest.File data + // structure. The reference is needed so the ToolchainValidator can update the + // manifest file. + FastlyManifestFile *manifest.File + + // Installer is a language specific command to install the dependencies + // defined within the language manifest (e.g. go mod download, npm install). + Installer string + + // Manifest is a language specific manifest file for defining project + // configuration (e.g. package.json, Cargo.toml, go.mod). + Manifest string + + // ManifestRemediation is a language specific error remediation. + ManifestRemediation string + + // PatchedManifestNotifier allows the caller to be notified of when the + // fastly.toml manifest has been patched with a default build command. + // + // WARNING: This is an unbuffered channel and should only receive one message. + // We should only be sending a message to it once from buildScript(). + PatchedManifestNotifier chan<- string + + // Output is the output buffer to write messages to (typically io.Stdout) + Output io.Writer + + // SDK is a language specific Compute@Edge compatible SDK (e.g. + // @fastly/js-compute, compute-sdk-go). + SDK string + + // SDKCustomValidator allows a supported language to define their own method + // for how to validate if their manifest contains the required SDK dependency. + SDKCustomValidator func(name string, bs []byte) error + + // Toolchain is a language specific executable responsible for managing + // dependencies (e.g. npm, cargo, go). + Toolchain string + + // ToolchainCommandRemediation is a language specific shell command a user + // can execute to fix the missing compilation target. + ToolchainCommandRemediation string + + // ToolchainLanguage is the language of the Compute@Edge project. + // This is used for displaying debug information. + ToolchainLanguage string + + // ToolchainSkipVersion is a language specific indicator that the + // toolchain does not need to have its version checked against a constraint + // defined in the CLI's application configuration. + ToolchainSkipVersion bool + + // ToolchainURL is a language specific homepage for the toolchain. + ToolchainURL string + + // ToolchainVersionCommand is a language specific shell command that returns + // the Toolchain version. + ToolchainVersionCommand string + + // ToolchainVersionPattern is a language specific regular expression that matches the + // version of the language Toolchain version command. + ToolchainVersionPattern *regexp.Regexp +} + +// Validate ensures the user's local environment has all required resources. +func (tv ToolchainValidator) Validate() error { + if err := tv.toolchain(); err != nil { + return err + } + if err := tv.manifestFile(); err != nil { + return err + } + if err := tv.sdk(); err != nil { + return err + } + if err := tv.installDependencies(); err != nil { + return err + } + if err := tv.compilation(); err != nil { + return err + } + if err := tv.buildScript(); err != nil { + return err + } + return tv.binDir() +} + +// toolchain validates the toolchain is installed. +func (tv ToolchainValidator) toolchain() error { + fmt.Fprintf(tv.Output, "\nChecking if '%s' is installed...\n", tv.Toolchain) + + bin, err := exec.LookPath(tv.Toolchain) + if err != nil { + tv.ErrLog.Add(err) + + return fsterr.RemediationError{ + Inner: fmt.Errorf("'%s' not found in $PATH", tv.Toolchain), + Remediation: tv.visitURLRemediation(tv.Toolchain, tv.ToolchainURL), + } + } + + fmt.Fprintf(tv.Output, "Found '%s' at %s\n", tv.Toolchain, bin) + + if tv.ToolchainSkipVersion { + return nil + } + return tv.toolchainVersion() +} + +// toolchainVersion validates the toolchain/compilation meets the defined +// constraints. +func (tv ToolchainValidator) toolchainVersion() error { + args := strings.Split(tv.ToolchainVersionCommand, " ") + + // gosec flagged this: + // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments + // Disabling as we trust the source of the variable. + /* #nosec */ + cmd := exec.Command(args[0], args[1:]...) + stdoutStderr, err := cmd.CombinedOutput() + output := string(stdoutStderr) + if err != nil { + if len(stdoutStderr) > 0 { + err = fmt.Errorf("%w: %s", err, strings.TrimSpace(output)) + } + tv.ErrLog.Add(err) + return fmt.Errorf("failed to execute command '%s': %w", tv.ToolchainVersionCommand, err) + } + + match := tv.ToolchainVersionPattern.FindStringSubmatch(output) + if len(match) < 2 { // We expect a pattern with one capture group. + err := fmt.Errorf("failed to parse the toolchain version: '%s'", tv.ToolchainVersionPattern) + tv.ErrLog.Add(err) + return err + } + version := match[1] + + v, err := semver.NewVersion(version) + if err != nil { + err = fmt.Errorf("error parsing version output %s into a semver: %w", version, err) + tv.ErrLog.Add(err) + return err + } + + constraint, ok := tv.Constraints["toolchain"] + if !ok { + err := fmt.Errorf("failed to lookup the toolchain constraint: '%s'", tv.Constraints) + tv.ErrLog.Add(err) + return err + } + + c, err := semver.NewConstraint(constraint) + if err != nil { + err = fmt.Errorf("error parsing toolchain constraint %s into a semver: %w", constraint, err) + tv.ErrLog.Add(err) + return err + } + + if !c.Check(v) { + err := fsterr.RemediationError{ + Inner: fmt.Errorf("toolchain version %s didn't meet the constraint %s", version, constraint), + Remediation: tv.commandRemediation(tv.Toolchain, tv.ToolchainURL, tv.ToolchainCommandRemediation), + } + tv.ErrLog.Add(err) + return err + } + + return nil +} + +// manifestFile validates the language manifestFile can be found. +func (tv ToolchainValidator) manifestFile() error { + fmt.Fprintf(tv.Output, "\nChecking if manifest '%s' exists...\n", tv.Manifest) + + m, err := filepath.Abs(tv.Manifest) + if err != nil { + tv.ErrLog.Add(err) + return fmt.Errorf("failed to construct path to '%s': %w", tv.Manifest, err) + } + + if !filesystem.FileExists(m) { + msg := fmt.Sprintf(fsterr.FormatTemplate, text.Bold(tv.ManifestRemediation)) + remediation := fmt.Sprintf("%s\n\nThen execute:\n\n\t$ fastly compute build", msg) + err := fsterr.RemediationError{ + Inner: fmt.Errorf("%s not found", tv.Manifest), + Remediation: remediation, + } + tv.ErrLog.Add(err) + return err + } + + fmt.Fprintf(tv.Output, "Found '%s' at %s\n", tv.Manifest, m) + + return nil +} + +// sdk validates the relevant Compute@Edge SDK is defined in the language +// specific manifest. +func (tv ToolchainValidator) sdk() error { + fmt.Fprintf(tv.Output, "\nChecking if '%s' is defined in '%s'...\n", tv.SDK, tv.Manifest) + + m, err := filepath.Abs(tv.Manifest) + if err != nil { + tv.ErrLog.Add(err) + return fmt.Errorf("failed to construct path to '%s': %w", tv.Manifest, err) + } + + // gosec flagged this: + // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments + // Disabling as we trust the source of the variable. + /* #nosec */ + bs, err := os.ReadFile(m) + if err != nil { + err = fmt.Errorf("failed to read file '%s': %w", m, err) + tv.ErrLog.Add(err) + return err + } + + success := fmt.Sprintf("Found '%s' in '%s'\n", tv.SDK, tv.Manifest) + + if tv.SDKCustomValidator != nil { + if err := tv.SDKCustomValidator(tv.SDK, bs); err != nil { + return err + } + fmt.Fprint(tv.Output, success) + return nil + } + + if !bytes.Contains(bs, []byte(tv.SDK)) { + err = fmt.Errorf(SDKErrMessageFormat, tv.SDK, m) + tv.ErrLog.Add(err) + return err + } + + fmt.Fprint(tv.Output, success) + return nil +} + +// installDependencies will download the language dependencies if a command is +// provided (e.g. `npm install`, `go mod download` etc). +func (tv ToolchainValidator) installDependencies() error { + if tv.Installer != "" { + fmt.Fprintf(tv.Output, "\nInstalling package dependencies...\n") + installer := strings.Split(tv.Installer, " ") + cmd := fstexec.Streaming{ + Command: installer[0], + Args: installer[1:], + Env: os.Environ(), + Output: tv.Output, + } + return cmd.Exec() + } + + return nil +} + +// compilation validates the compilation target/tool is installed. +// +// NOTE: Some languages use an external command for compilation, while other +// languages might have the Wasm compilation target integrated to their +// toolchain. If an external command, we lookup the command on the $PATH and +// check its version meets any defined constraints. +func (tv ToolchainValidator) compilation() error { + fmt.Fprintf(tv.Output, "\nChecking if '%s' is installed...\n", tv.Compilation) + + // Lookup the compilation target as an executable binary. + if !tv.CompilationIntegrated { + bin := tv.Compilation + + if tv.CompilationDirectPath != nil { + var err error + bin, err = tv.CompilationDirectPath() + if err != nil { + return err + } + } + + bin, err := exec.LookPath(bin) + if err != nil { + tv.ErrLog.Add(err) + + remediation := tv.visitURLRemediation(tv.Compilation, tv.CompilationURL) + + if tv.CompilationCommandRemediation != "" { + remediation = tv.commandRemediation(tv.Compilation, tv.CompilationURL, tv.CompilationCommandRemediation) + } + + return fsterr.RemediationError{ + Inner: fmt.Errorf("'%s' not found", tv.Compilation), + Remediation: remediation, + } + } + + fmt.Fprintf(tv.Output, "Found '%s' at %s\n", tv.Compilation, bin) + } + + // Some languages (JavaScript/AssemblyScript) don't need their compilation + // tool version checked so we allow the check to be skipped. + if tv.CompilationSkipVersion { + return nil + } + + args := strings.Split(tv.CompilationTargetCommand, " ") + + // gosec flagged this: + // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments + // Disabling as we trust the source of the variable. + /* #nosec */ + cmd := exec.Command(args[0], args[1:]...) + stdoutStderr, err := cmd.CombinedOutput() + output := string(stdoutStderr) + if err != nil { + if len(stdoutStderr) > 0 { + err = fmt.Errorf("%w: %s", err, strings.TrimSpace(output)) + } + tv.ErrLog.Add(err) + return fmt.Errorf("failed to execute command '%s': %w", tv.CompilationTargetCommand, err) + } + + match := tv.CompilationTargetPattern.FindStringSubmatch(output) + if len(match) < 2 { // We expect a pattern with one capture group. + err := fmt.Errorf("failed to find '%s' with the pattern '%s'", tv.Compilation, tv.CompilationTargetPattern) + tv.ErrLog.Add(err) + return err + } + + if tv.CompilationIntegrated { + fmt.Fprintf(tv.Output, "Found '%s'\n", tv.Compilation) + } + + // If dealing with an executable binary, check the version constraints. + if !tv.CompilationIntegrated { + version := match[1] + return tv.compilationVersion(version) + } + + return nil +} + +// compilationVersion validates the compilation target version constraints are +// met. +func (tv ToolchainValidator) compilationVersion(version string) error { + fmt.Fprintf(tv.Output, "\nChecking version constraints for '%s'...\n", tv.Compilation) + + v, err := semver.NewVersion(version) + if err != nil { + err = fmt.Errorf("error parsing version output %s into a semver: %w", version, err) + tv.ErrLog.Add(err) + return err + } + + constraint, ok := tv.Constraints["compilation"] + if !ok { + err := fmt.Errorf("failed to lookup the compilation constraint: '%s'", tv.Constraints) + tv.ErrLog.Add(err) + return err + } + + c, err := semver.NewConstraint(constraint) + if err != nil { + err = fmt.Errorf("error parsing compilation constraint %s into a semver: %w", constraint, err) + tv.ErrLog.Add(err) + return err + } + + if !c.Check(v) { + err := fsterr.RemediationError{ + Inner: fmt.Errorf("version %s didn't meet the constraint %s", version, constraint), + Remediation: tv.visitURLRemediation(tv.Compilation, tv.CompilationURL), + } + tv.ErrLog.Add(err) + return err + } + + fmt.Fprintf(tv.Output, "Version constraints for '%s' are met\n", tv.Compilation) + + return nil +} + +// buildScript validates the Fastly manifest contains a scripts.build value. +func (tv ToolchainValidator) buildScript() error { + fmt.Fprintf(tv.Output, "\nChecking manifest '%s' contains a build script...\n", manifest.Filename) + + m, err := filepath.Abs(manifest.Filename) + if err != nil { + tv.ErrLog.Add(err) + return fmt.Errorf("failed to construct path to '%s': %w", manifest.Filename, err) + } + + // gosec flagged this: + // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments + // Disabling as we trust the source of the variable. + /* #nosec */ + bs, err := os.ReadFile(m) + if err != nil { + err = fmt.Errorf("failed to read file '%s': %w", m, err) + tv.ErrLog.Add(err) + return err + } + + tree, err := toml.LoadBytes(bs) + if err != nil { + err = fmt.Errorf("failed to parse toml file '%s': %w", m, err) + tv.ErrLog.Add(err) + return err + } + + if v, ok := tree.GetArray("scripts").(*toml.Tree); ok { + if script, ok := v.Get("build").(string); ok && script != "" { + fmt.Fprintf(tv.Output, "Found [scripts.build] '%s'\n", script) + return nil + } + } + + err = fmt.Errorf("failed to find a [scripts] section with a 'build' property in the '%s' manifest", m) + tv.ErrLog.Add(err) + + // We failed to find a [scripts.build] which is required, so we'll set a + // default value for the user. + tree.Set("scripts.build", tv.DefaultBuildCommand) + + data, err := tree.Marshal() + if err != nil { + return fmt.Errorf("error updating fastly.toml with a default [scripts.build]: %w", err) + } + + err = tv.FastlyManifestFile.Load(data) + if err != nil { + return fsterr.RemediationError{ + Inner: err, + Remediation: fmt.Sprintf(fsterr.ComputeBuildRemediation, tv.DefaultBuildCommand), + } + } + + fmt.Fprintf(tv.Output, "No build command found. Patching fastly.toml with the default build command for %s: %s\n", tv.ToolchainLanguage, tv.DefaultBuildCommand) + + if tv.PatchedManifestNotifier != nil { + go func() { + tv.PatchedManifestNotifier <- fmt.Sprintf("No build command was found in fastly.toml. A default build command for %s has been added to fastly.toml", tv.ToolchainLanguage) + }() + } + return nil +} + +// binDir validates a bin directory is available. +func (tv ToolchainValidator) binDir() error { + dir, err := os.Getwd() + if err != nil { + err = fmt.Errorf("failed to identify the current working directory: %w", err) + tv.ErrLog.Add(err) + return err + } + + binDir := filepath.Join(dir, "bin") + if err := filesystem.MakeDirectoryIfNotExists(binDir); err != nil { + err = fmt.Errorf("failed to create 'bin' directory: %w", err) + tv.ErrLog.Add(err) + return err + } + + return nil +} + +// visitURLRemediation returns a remediation error that suggests visiting the +// official resource URL. +func (tv ToolchainValidator) visitURLRemediation(resource, resourceURL string) string { + return fmt.Sprintf(`To fix this error, install '%s' by visiting: + + %s + + Then execute: + + $ fastly compute build`, resource, text.Bold(resourceURL)) +} + +// commandRemediation returns a remediation error that suggests executing a +// command to resolve the missing resource. +func (tv ToolchainValidator) commandRemediation(resource, resourceURL, resourceCommand string) string { + return fmt.Sprintf(`To fix this error, install '%s' by executing: + $ %s + + Visit %s for more information.`, resource, resourceCommand, text.Bold(resourceURL)) +} + +// getJsToolchainBinPath returns the path to where NPM installs binaries. func getJsToolchainBinPath(bin string) (string, error) { // gosec flagged this: // G204 (CWE-78): Subprocess launched with variable @@ -18,12 +588,75 @@ func getJsToolchainBinPath(bin string) (string, error) { return strings.TrimSpace(string(path)), nil } -func checkJsPackageDependencyExists(bin, name string) bool { - // gosec flagged this: - // G204 (CWE-78): Subprocess launched with variable - // Disabling as the variables come from trusted sources: - // The CLI parser enforces supported values via EnumVar. - /* #nosec */ - err := exec.Command(bin, "list", "--json", "--depth", "0", name).Run() - return err == nil +// execCommand opens a sub shell to execute the language build script. +func execCommand(cmd string, args []string, out, progress io.Writer, verbose bool, timeout int, errlog fsterr.LogInterface) error { + s := fstexec.Streaming{ + Command: cmd, + Args: args, + Env: os.Environ(), + Output: out, + Progress: progress, + Verbose: verbose, + } + if timeout > 0 { + s.Timeout = time.Duration(timeout) * time.Second + } + if err := s.Exec(); err != nil { + errlog.Add(err) + return err + } + return nil +} + +// buildOpts enables reducing the number of arguments passed to `build()`. +// +// NOTE: We're unable to make the build function generic. +// The generics support in Go1.18 doesn't include accessing struct fields. +type buildOpts struct { + buildScript string + buildFn func(string) (string, []string) + errlog fsterr.LogInterface + postBuild string + timeout int +} + +// build compiles the user's source code into a Wasm binary. +func build( + l buildOpts, + out io.Writer, + progress text.Progress, + verbose bool, + optionalLocationProcess func() error, + postBuildCallback func() error, +) error { + cmd, args := l.buildFn(l.buildScript) + + err := execCommand(cmd, args, out, progress, verbose, l.timeout, l.errlog) + if err != nil { + return err + } + + if optionalLocationProcess != nil { + err := optionalLocationProcess() + if err != nil { + return err + } + } + + // NOTE: We set the progress indicator to Done() so that any output we now + // print via the post_build callback doesn't get hidden by the progress status. + // The progress is 'reset' inside the main build controller `build.go`. + progress.Done() + + if l.postBuild != "" { + if err = postBuildCallback(); err == nil { + cmd, args := l.buildFn(l.postBuild) + err := execCommand(cmd, args, out, progress, verbose, l.timeout, l.errlog) + if err != nil { + return err + } + } + } + + return nil } diff --git a/pkg/commands/compute/publish.go b/pkg/commands/compute/publish.go index 2f51a5c93..817def00c 100644 --- a/pkg/commands/compute/publish.go +++ b/pkg/commands/compute/publish.go @@ -19,7 +19,6 @@ type PublishCommand struct { // Build fields includeSrc cmd.OptionalBool lang cmd.OptionalString - name cmd.OptionalString skipVerification cmd.OptionalBool timeout cmd.OptionalInt @@ -44,7 +43,6 @@ func NewPublishCommand(parent cmd.Registerer, globals *config.Data, build *Build c.CmdClause.Flag("domain", "The name of the domain associated to the package").Action(c.domain.Set).StringVar(&c.domain.Value) c.CmdClause.Flag("include-source", "Include source code in built package").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value) c.CmdClause.Flag("language", "Language type").Action(c.lang.Set).StringVar(&c.lang.Value) - c.CmdClause.Flag("name", "Package name").Action(c.name.Set).StringVar(&c.name.Value) c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').Action(c.pkg.Set).StringVar(&c.pkg.Value) c.RegisterFlag(cmd.StringFlagOpts{ Name: cmd.FlagServiceIDName, @@ -85,9 +83,6 @@ func (c *PublishCommand) Exec(in io.Reader, out io.Writer) (err error) { if c.lang.WasSet { c.build.Flags.Lang = c.lang.Value } - if c.name.WasSet { - c.build.Flags.PackageName = c.name.Value - } if c.skipVerification.WasSet { c.build.Flags.SkipVerification = c.skipVerification.Value } @@ -105,9 +100,6 @@ func (c *PublishCommand) Exec(in io.Reader, out io.Writer) (err error) { text.Break(out) // Reset the fields on the DeployCommand based on PublishCommand values. - if c.name.WasSet { - c.manifest.Flag.Name = c.name.Value - } if c.pkg.WasSet { c.deploy.Package = c.pkg.Value } diff --git a/pkg/commands/compute/serve.go b/pkg/commands/compute/serve.go index 51b8b5de9..a5d6e2bc5 100644 --- a/pkg/commands/compute/serve.go +++ b/pkg/commands/compute/serve.go @@ -41,7 +41,6 @@ type ServeCommand struct { // Build fields includeSrc cmd.OptionalBool lang cmd.OptionalString - name cmd.OptionalString skipVerification cmd.OptionalBool timeout cmd.OptionalInt @@ -71,7 +70,6 @@ func NewServeCommand(parent cmd.Registerer, globals *config.Data, build *BuildCo c.CmdClause.Flag("file", "The Wasm file to run").Default("bin/main.wasm").StringVar(&c.file) c.CmdClause.Flag("include-source", "Include source code in built package").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value) c.CmdClause.Flag("language", "Language type").Action(c.lang.Set).StringVar(&c.lang.Value) - c.CmdClause.Flag("name", "Package name").Action(c.name.Set).StringVar(&c.name.Value) c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.skipBuild) c.CmdClause.Flag("skip-verification", "Skip verification steps and force build").Action(c.skipVerification.Set).BoolVar(&c.skipVerification.Value) c.CmdClause.Flag("timeout", "Timeout, in seconds, for the build compilation step").Action(c.timeout.Set).IntVar(&c.timeout.Value) @@ -135,9 +133,6 @@ func (c *ServeCommand) Build(in io.Reader, out io.Writer) error { if c.lang.WasSet { c.build.Flags.Lang = c.lang.Value } - if c.name.WasSet { - c.build.Flags.PackageName = c.name.Value - } if c.skipVerification.WasSet { c.build.Flags.SkipVerification = c.skipVerification.Value } diff --git a/pkg/commands/compute/testdata/build/go/go.mod b/pkg/commands/compute/testdata/build/go/go.mod index e1e2e02a2..6d8bbcf9f 100644 --- a/pkg/commands/compute/testdata/build/go/go.mod +++ b/pkg/commands/compute/testdata/build/go/go.mod @@ -1,3 +1,5 @@ module cli-go-sdk go 1.18 + +require github.com/fastly/compute-sdk-go v0.1.1 diff --git a/pkg/commands/compute/testdata/build/rust/Cargo.toml b/pkg/commands/compute/testdata/build/rust/Cargo.toml index e4f4cf92b..17000eaff 100644 --- a/pkg/commands/compute/testdata/build/rust/Cargo.toml +++ b/pkg/commands/compute/testdata/build/rust/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "edge-compute-default-rust-template" +name = "compute-starter-kit-rust" version = "0.1.0" authors = ["phamann "] edition = "2018" diff --git a/pkg/config/config.go b/pkg/config/config.go index 060f8c53b..a79e1ac69 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -222,26 +222,12 @@ type Go struct { // Rust represents Rust C@E language specific configuration. type Rust struct { - // ToolchainVersion is the `rustup` toolchain string for the compiler that we - // support - // - // Deprecated: Use ToolchainConstraint instead - ToolchainVersion string `toml:"toolchain_version"` - // ToolchainConstraint is the `rustup` toolchain constraint for the compiler // that we support (a range is expected, e.g. >= 1.49.0 < 2.0.0). ToolchainConstraint string `toml:"toolchain_constraint"` // WasmWasiTarget is the Rust compilation target for Wasi capable Wasm. WasmWasiTarget string `toml:"wasm_wasi_target"` - - // FastlySysConstraint is a free-form semver constraint for the internal Rust - // ABI version that should be supported. - FastlySysConstraint string `toml:"fastly_sys_constraint"` - - // RustupConstraint is a free-form semver constraint for the rustup version - // that should be installed. - RustupConstraint string `toml:"rustup_constraint"` } // Profiles represents multiple profile accounts. diff --git a/pkg/config/testdata/config-incompatible-config-version.toml b/pkg/config/testdata/config-incompatible-config-version.toml index 6ea3b0420..7b0b57274 100644 --- a/pkg/config/testdata/config-incompatible-config-version.toml +++ b/pkg/config/testdata/config-incompatible-config-version.toml @@ -11,11 +11,8 @@ version = "0.0.1" [language] [language.rust] - toolchain_version = "1.49.0" - # we're also missing the 'toolchain_constraint' property + # we're missing the 'toolchain_constraint' property wasm_wasi_target = "wasm32-wasi" - fastly_sys_constraint = ">= 0.3.3 < 0.5.0" - rustup_constraint = ">= 1.23.0" [starter-kits] [[starter-kits.assemblyscript]] diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index b6c0c3e3b..e94529f9b 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -11,11 +11,8 @@ version = "0.0.1" [language] [language.rust] - toolchain_version = "1.49.0" toolchain_constraint = ">= 1.49.0 < 2.0.0" wasm_wasi_target = "wasm32-wasi" - fastly_sys_constraint = ">= 0.3.3 < 0.5.0" - rustup_constraint = ">= 1.23.0" [starter-kits] [[starter-kits.assemblyscript]] diff --git a/pkg/config/testdata/static/config.toml b/pkg/config/testdata/static/config.toml index 09857ad49..d45fb9fcd 100644 --- a/pkg/config/testdata/static/config.toml +++ b/pkg/config/testdata/static/config.toml @@ -9,11 +9,8 @@ ttl = "5m" [language] [language.rust] - toolchain_version = "1.49.0" toolchain_constraint = ">= 1.49.0 < 2.0.0" wasm_wasi_target = "wasm32-wasi" - fastly_sys_constraint = ">= 0.3.3 < 0.5.0" - rustup_constraint = ">= 1.23.0" [starter-kits] [[starter-kits.assemblyscript]] diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index e32c8369e..ae67f1a17 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -104,7 +104,7 @@ var ErrInvalidArchive = RemediationError{ // with the custom build defined in the fastly.toml manifest file. var ErrBuildStopped = RemediationError{ Inner: fmt.Errorf("build process stopped by user"), - Remediation: "Remove or update the custom [scripts.build] in the fastly.toml manifest.", + Remediation: "Check the [scripts.build] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.", } // ErrInvalidVerboseJSONCombo means the user provided both a --verbose and diff --git a/pkg/errors/remediation_error.go b/pkg/errors/remediation_error.go index 2cbbf493a..7cb1d50c7 100644 --- a/pkg/errors/remediation_error.go +++ b/pkg/errors/remediation_error.go @@ -144,7 +144,7 @@ var ComputeServeRemediation = strings.Join([]string{ // ComputeBuildRemediation suggests configuring a `[scripts.build]` setting in // the fastly.toml manifest. var ComputeBuildRemediation = strings.Join([]string{ - "Add a [scripts.build] setting for your custom build process.", + "Add a [scripts] section with `build = \"%s\"`.", "See more at https://developer.fastly.com/reference/fastly-toml/", }, " ") diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index 09ec7a097..3cd56d374 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -21,12 +21,6 @@ type Source uint8 const ( // Filename is the name of the package manifest file. // It is expected to be a project specific configuration file. - // - // TODO: The filename needs to be referenced outside of the compute package - // so consider moving this constant to a different location in the top-level - // pkg directory instead or even moving the whole manifest package up to the - // top-level pkg directory as finding a suitable package for just the - // manifest filename could be tricky. Filename = "fastly.toml" // ManifestLatestVersion represents the latest known manifest schema version @@ -73,10 +67,6 @@ type Data struct { // Name yields a Name. func (d *Data) Name() (string, Source) { - if d.Flag.Name != "" { - return d.Flag.Name, SourceFlag - } - if d.File.Name != "" { return d.File.Name, SourceFile } @@ -103,10 +93,6 @@ func (d *Data) ServiceID() (string, Source) { // Description yields a Description. func (d *Data) Description() (string, Source) { - if d.Flag.Description != "" { - return d.Flag.Description, SourceFlag - } - if d.File.Description != "" { return d.File.Description, SourceFile } @@ -217,7 +203,7 @@ type File struct { readError error } -// Scripts represents custom operations. +// Scripts represents build configuration. type Scripts struct { Build string `toml:"build,omitempty"` PostBuild string `toml:"post_build,omitempty"` @@ -305,83 +291,6 @@ func (f *File) SetOutput(output io.Writer) { f.output = output } -// Read loads the manifest file content from disk. -func (f *File) Read(path string) (err error) { - defer func() { - if err != nil { - f.readError = err - } - }() - - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable. - // Disabling as we need to load the fastly.toml from the user's file system. - // This file is decoded into a predefined struct, any unrecognised fields are dropped. - /* #nosec */ - data, err := os.ReadFile(path) - if err != nil { - f.errLog.Add(err) - return err - } - - // NOTE: temporary fix needed because of a bug that appeared in v0.25.0 where - // the manifest_version was stored in fastly.toml as a 'section', e.g. - // `[manifest_version]`. - // - // This subsequently would cause errors when trying to unmarshal the data, so - // we need to identify if it exists in the file (as a section) and remove it. - // - // We do this before trying to unmarshal the toml data into a go data - // structure otherwise we'll see errors from the toml library. - manifestSection, err := containsManifestSection(data) - if err != nil { - f.errLog.Add(err) - return fmt.Errorf("failed to parse the fastly.toml manifest: %w", err) - } - - if manifestSection { - buf, err := stripManifestSection(bytes.NewReader(data), path) - if err != nil { - f.errLog.Add(err) - return fsterr.ErrInvalidManifestVersion - } - data = buf.Bytes() - } - - // The AutoMigrateVersion() method will either return the []byte unmodified or - // it will have updated the manifest_version field to reflect the latest - // version supported by the Fastly CLI. - data, err = f.AutoMigrateVersion(data, path) - if err != nil { - f.errLog.Add(err) - return err - } - - err = toml.Unmarshal(data, f) - if err != nil { - f.errLog.Add(err) - return fsterr.ErrParsingManifest - } - - f.exists = true - - if f.ManifestVersion == 0 { - f.ManifestVersion = ManifestLatestVersion - - text.Warning(f.output, fmt.Sprintf("The fastly.toml was missing a `manifest_version` field. A default schema version of `%d` will be used.", ManifestLatestVersion)) - text.Break(f.output) - text.Output(f.output, fmt.Sprintf("Refer to the fastly.toml package manifest format: %s", SpecURL)) - text.Break(f.output) - err = f.Write(path) - if err != nil { - f.errLog.Add(err) - return fmt.Errorf("unable to save fastly.toml manifest change: %w", err) - } - } - - return nil -} - // AutoMigrateVersion updates the manifest_version value to // ManifestLatestVersion if the current version is less than the latest // supported and only if there is no [setup] configuration defined. @@ -461,7 +370,7 @@ func (f *File) AutoMigrateVersion(data []byte, path string) ([]byte, error) { // This only happens once. All future file reads result in one Unmarshal. err = toml.Unmarshal(data, f) if err != nil { - return data, fmt.Errorf("error unmarshalling fastly.toml: %w", err) + return data, fmt.Errorf("error unmarshaling fastly.toml: %w", err) } if err = f.Write(path); err != nil { @@ -474,6 +383,125 @@ func (f *File) AutoMigrateVersion(data []byte, path string) ([]byte, error) { return data, fsterr.ErrIncompatibleManifestVersion } +// Load parses the input data into the File struct and persists it to disk. +// +// NOTE: This is used by the `compute build` command logic. +// Which has to modify the toml tree for supporting a v4.0.0 migration path. +// e.g. if user manifest is missing [scripts.build] then add a default value. +func (f *File) Load(data []byte) error { + err := toml.Unmarshal(data, f) + if err != nil { + return fmt.Errorf("error unmarshaling fastly.toml: %w", err) + } + return f.Write(Filename) +} + +// Read loads the manifest file content from disk. +func (f *File) Read(path string) (err error) { + defer func() { + if err != nil { + f.readError = err + } + }() + + // gosec flagged this: + // G304 (CWE-22): Potential file inclusion via variable. + // Disabling as we need to load the fastly.toml from the user's file system. + // This file is decoded into a predefined struct, any unrecognised fields are dropped. + /* #nosec */ + data, err := os.ReadFile(path) + if err != nil { + f.errLog.Add(err) + return err + } + + // NOTE: temporary fix needed because of a bug that appeared in v0.25.0 where + // the manifest_version was stored in fastly.toml as a 'section', e.g. + // `[manifest_version]`. + // + // This subsequently would cause errors when trying to unmarshal the data, so + // we need to identify if it exists in the file (as a section) and remove it. + // + // We do this before trying to unmarshal the toml data into a go data + // structure otherwise we'll see errors from the toml library. + manifestSection, err := containsManifestSection(data) + if err != nil { + f.errLog.Add(err) + return fmt.Errorf("failed to parse the fastly.toml manifest: %w", err) + } + + if manifestSection { + buf, err := stripManifestSection(bytes.NewReader(data), path) + if err != nil { + f.errLog.Add(err) + return fsterr.ErrInvalidManifestVersion + } + data = buf.Bytes() + } + + // The AutoMigrateVersion() method will either return the []byte unmodified or + // it will have updated the manifest_version field to reflect the latest + // version supported by the Fastly CLI. + data, err = f.AutoMigrateVersion(data, path) + if err != nil { + f.errLog.Add(err) + return err + } + + err = toml.Unmarshal(data, f) + if err != nil { + f.errLog.Add(err) + return fsterr.ErrParsingManifest + } + + if f.ManifestVersion == 0 { + f.ManifestVersion = ManifestLatestVersion + + text.Warning(f.output, fmt.Sprintf("The fastly.toml was missing a `manifest_version` field. A default schema version of `%d` will be used.", ManifestLatestVersion)) + text.Break(f.output) + text.Output(f.output, fmt.Sprintf("Refer to the fastly.toml package manifest format: %s", SpecURL)) + text.Break(f.output) + err = f.Write(path) + if err != nil { + f.errLog.Add(err) + return fmt.Errorf("unable to save fastly.toml manifest change: %w", err) + } + } + + f.exists = true + + return nil +} + +// Write persists the manifest content to disk. +func (f *File) Write(path string) error { + // gosec flagged this: + // G304 (CWE-22): Potential file inclusion via variable + // + // Disabling as in most cases this is provided by a static constant embedded + // from the 'manifest' package, and in other cases we want the user to be + // able to provide a custom path to their fastly.toml manifest. + /* #nosec */ + fp, err := os.Create(path) + if err != nil { + return err + } + + if err := appendSpecRef(fp); err != nil { + return err + } + + if err := toml.NewEncoder(fp).Encode(f); err != nil { + return err + } + + if err := fp.Sync(); err != nil { + return err + } + + return fp.Close() +} + // containsManifestSection loads the slice of bytes into a toml tree structure // before checking if the manifest_version is defined as a toml section block. func containsManifestSection(data []byte) (bool, error) { @@ -527,35 +555,6 @@ func stripManifestSection(r io.Reader, path string) (*bytes.Buffer, error) { return buf, nil } -// Write persists the manifest content to disk. -func (f *File) Write(path string) error { - // gosec flagged this: - // G304 (CWE-22): Potential file inclusion via variable - // - // Disabling as in most cases this is provided by a static constant embedded - // from the 'manifest' package, and in other cases we want the user to be - // able to provide a custom path to their fastly.toml manifest. - /* #nosec */ - fp, err := os.Create(path) - if err != nil { - return err - } - - if err := appendSpecRef(fp); err != nil { - return err - } - - if err := toml.NewEncoder(fp).Encode(f); err != nil { - return err - } - - if err := fp.Sync(); err != nil { - return err - } - - return fp.Close() -} - // appendSpecRef appends the fastly.toml specification URL to the manifest. func appendSpecRef(w io.Writer) error { s := fmt.Sprintf("# %s\n# %s\n\n", SpecIntro, SpecURL)