Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compute): add metadata subcommand #1013

Merged
merged 19 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
492021f
feat(telemetry): add telemetry command
Integralist Sep 18, 2023
a3868f1
refactor(telemetry): enable by default
Integralist Sep 20, 2023
38d64bc
doc(DEVELOP): clarify the app config flows
Integralist Sep 21, 2023
ac7293e
doc(config): annotate File fields
Integralist Sep 21, 2023
e241978
feat: display telemetry to user on first run after install or update
Integralist Sep 21, 2023
3c632e1
fix(ci): add /dev/null equivalent for Windows
Integralist Sep 21, 2023
c0586e0
refactor: rename telemetry wasm-metadata
Integralist Sep 26, 2023
8d6673f
refactor: move `telemetry` command to `compute metadata`
Integralist Sep 26, 2023
58c96f4
refactor(compute/metadata): display updated config settings
Integralist Oct 2, 2023
849e7db
fix(metadata): machine info must be opt-in
Integralist Oct 5, 2023
cc4d645
fix(compute/metadata): reference correct error type
Integralist Oct 10, 2023
7551b2d
remove(telemetry): command moved to compute metadata.
Integralist Oct 25, 2023
9f5b4dd
fix(compute/build): remove unused arg
Integralist Oct 31, 2023
265cc0f
fix(app): update commandCollectsData() list
Integralist Oct 31, 2023
fc49e5b
feat: only record metadata for items marked as enabled
Integralist Oct 31, 2023
4afd2ee
fix(app): hide metadata message unless the user has opted into data c…
Integralist Oct 31, 2023
3705675
refactor: messaging to include additional links
Integralist Oct 31, 2023
00feb73
refactor(compute/metadata): make a hidden command for now
Integralist Oct 31, 2023
e35ebba
refactor: move sdk data into package_info
Integralist Oct 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .fastly/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ config_version = 4
[fastly]
api_endpoint = "https://api.fastly.com"

[wasm-metadata]
build_info = "enable"
machine_info = "disable" # users have to opt-in for this (everything else they'll have to opt-out)
package_info = "enable"

[language]
[language.go]
tinygo_constraint = ">= 0.28.1-0" # NOTE -0 indicates to the CLI's semver package that we accept pre-releases (TinyGo users commonly use pre-releases).
Expand Down
8 changes: 7 additions & 1 deletion DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,20 @@ CLI_CATEGORY=logging CLI_CATEGORY_COMMAND=logging CLI_PACKAGE=foobar CLI_COMMAND

### `.fastly/config.toml`

The CLI dynamically generates the `./pkg/config/config.toml` within the CI release process so it can be embedded into the CLI binary.
The CLI dynamically generates the `./pkg/config/config.toml` within the CI release process so it can be embedded into the CLI binary.

The file is added to `.gitignore` to avoid it being added to the git repository.

When compiling the CLI for a new release, it will execute [`./scripts/config.sh`](./scripts/config.sh). The script uses [`./.fastly/config.toml`](./.fastly/config.toml) as a template file to then dynamically inject a list of starter kits (pulling their data from their public repositories).

The resulting configuration is then saved to disk at `./pkg/config/config.toml` and embedded into the CLI when compiled.

When a user installs the CLI for the first time, they'll have no existing config and so the embedded config will be used. In the future, when the user updates their CLI, the existing config they have will be used.

If the config has changed in any way, then you (the CLI developer) should ensure the `config_version` number is bumped before publishing a new CLI release. This is because when the user updates to that new CLI version and the invoke the CLI, the CLI will identify a mismatch between the user's local config version and the embedded config version. This will cause the embedded config to be merged with the local config and consequently the user's config will be updated to include the new fields.

> **NOTE:** The CLI does provide a `fastly config --reset` option that resets the config to a version compatible with the user's current CLI version. This is fallback for users who run into issues for whatever reason.

### Running Compute commands locally

If you need to test the Fastly CLI locally while developing a Compute feature, then use the `--dir` flag (exposed on `compute build`, `compute deploy`, `compute serve` and `compute publish`) to ensure the CLI doesn't attempt to treat the repository directory as your project directory.
Expand Down
2 changes: 2 additions & 0 deletions pkg/app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func defineCommands(
computeHashFiles := compute.NewHashFilesCommand(computeCmdRoot.CmdClause, g, computeBuild)
computeHashsum := compute.NewHashsumCommand(computeCmdRoot.CmdClause, g, computeBuild)
computeInit := compute.NewInitCommand(computeCmdRoot.CmdClause, g, m)
computeMetadata := compute.NewMetadataCommand(computeCmdRoot.CmdClause, g)
computePack := compute.NewPackCommand(computeCmdRoot.CmdClause, g, m)
computePublish := compute.NewPublishCommand(computeCmdRoot.CmdClause, g, computeBuild, computeDeploy)
computeServe := compute.NewServeCommand(computeCmdRoot.CmdClause, g, computeBuild, opts.Versioners.Viceroy)
Expand Down Expand Up @@ -481,6 +482,7 @@ func defineCommands(
computeHashFiles,
computeHashsum,
computeInit,
computeMetadata,
computePack,
computePublish,
computeServe,
Expand Down
31 changes: 27 additions & 4 deletions pkg/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"strings"
"time"

"github.com/fastly/go-fastly/v8/fastly"
"github.com/fastly/kingpin"
Expand Down Expand Up @@ -96,15 +97,15 @@ func Run(opts RunOpts) error {
app.Flag("verbose", "Verbose logging").Short('v').BoolVar(&g.Flags.Verbose)

commands := defineCommands(app, &g, *opts.Manifest, opts)
command, name, err := processCommandInput(opts, app, &g, commands)
command, commandName, err := processCommandInput(opts, app, &g, commands)
if err != nil {
return err
}
// We short-circuit the execution for specific cases:
//
// - cmd.ArgsIsHelpJSON() == true
// - shell autocompletion flag provided
switch name {
switch commandName {
case "help--format=json":
fallthrough
case "help--formatjson":
Expand All @@ -113,6 +114,18 @@ func Run(opts RunOpts) error {
return nil
}

if !g.Config.CLI.MetadataNoticeDisplayed && commandCollectsData(commandName) {
// FIXME: We need the actual URL to point users to.
text.Important(g.Output, "The Fastly CLI is configured to collect data related to Wasm builds (e.g. compilation times, resource usage, and other non-identifying data). To learn more about what data is being collected, why, and how to disable it: https://www.fastly.com/")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaskiratr we'll need an actual URL here (or should this be https://bit.ly/wasm-metadata which points to our community forum thread on the topic)?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll update the CLI reference page with the information about what data is being collected, why, and how to disable it

text.Break(g.Output)
g.Config.CLI.MetadataNoticeDisplayed = true
err := g.Config.Write(g.ConfigPath)
if err != nil {
return fmt.Errorf("failed to persist change to metadata notice: %w", err)
}
time.Sleep(5 * time.Second) // this message is only displayed once so give the user a chance to see it before it possibly scrolls off screen
}

if g.Flags.Quiet {
opts.Manifest.File.SetQuiet(true)
}
Expand All @@ -136,7 +149,7 @@ func Run(opts RunOpts) error {
// If we are using the token from config file, check the file's permissions
// to assert if they are not too open or have been altered outside of the
// application and warn if so.
segs := strings.Split(name, " ")
segs := strings.Split(commandName, " ")
if source == lookup.SourceFile && (len(segs) > 0 && segs[0] != "profile") {
if fi, err := os.Stat(config.FilePath); err == nil {
if mode := fi.Mode().Perm(); mode > config.FilePermissions {
Expand Down Expand Up @@ -177,7 +190,7 @@ func Run(opts RunOpts) error {
return fmt.Errorf("error constructing Fastly realtime stats client: %w", err)
}

if opts.Versioners.CLI != nil && name != "update" && !version.IsPreRelease(revision.AppVersion) {
if opts.Versioners.CLI != nil && commandName != "update" && !version.IsPreRelease(revision.AppVersion) {
f := update.CheckAsync(
revision.AppVersion,
opts.Versioners.CLI,
Expand Down Expand Up @@ -246,3 +259,13 @@ func determineProfile(manifestValue, flagValue string, profiles config.Profiles)
name, _ := profile.Default(profiles)
return name
}

// commandCollectsData determines if the command to be executed is one that
// collects data related to a Wasm binary.
func commandCollectsData(command string) bool {
switch command {
case "compute build", "compute hashsum", "compute hash-files", "compute publish", "compute serve":
return true
}
return false
}
2 changes: 1 addition & 1 deletion pkg/app/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ whoami

go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
_, _ = io.Copy(&buf, r)
outC <- buf.String()
}()

Expand Down
4 changes: 2 additions & 2 deletions pkg/commands/compute/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) {
// Also make sure hidden flags (across all composite commands) aren't hidden.
metadataDisable, _ := strconv.ParseBool(c.Globals.Env.WasmMetadataDisable)
if c.MetadataEnable && !metadataDisable {
if err := c.AnnotateWasmBinaryLong(wasmtools, metadataArgs, language, out); err != nil {
if err := c.AnnotateWasmBinaryLong(wasmtools, metadataArgs, language); err != nil {
return err
}
} else {
Expand Down Expand Up @@ -327,7 +327,7 @@ func (c *BuildCommand) AnnotateWasmBinaryShort(wasmtools string, args []string)
}

// AnnotateWasmBinaryLong annotates the Wasm binary will all available data.
func (c *BuildCommand) AnnotateWasmBinaryLong(wasmtools string, args []string, language *Language, out io.Writer) error {
func (c *BuildCommand) AnnotateWasmBinaryLong(wasmtools string, args []string, language *Language) error {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)

Expand Down
99 changes: 99 additions & 0 deletions pkg/commands/compute/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package compute

import (
"fmt"
"io"

"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/config"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/global"
"github.com/fastly/cli/pkg/text"
)

// MetadataCommand controls what metadata is collected for a Wasm binary.
type MetadataCommand struct {
cmd.Base

disable bool
disableBuild bool
disableMachine bool
disablePackage bool
enable bool
enableBuild bool
enableMachine bool
enablePackage bool
}

// NewMetadataCommand returns a new command registered in the parent.
func NewMetadataCommand(parent cmd.Registerer, g *global.Data) *MetadataCommand {
var c MetadataCommand
c.Globals = g
c.CmdClause = parent.Command("metadata", "Control what metadata is collected")
c.CmdClause.Flag("disable", "Disable all metadata").BoolVar(&c.disable)
c.CmdClause.Flag("disable-build", "Disable metadata for information regarding the time taken for builds and compilation processes").BoolVar(&c.disableBuild)
c.CmdClause.Flag("disable-machine", "Disable metadata for general, non-identifying system specifications (CPU, RAM, operating system)").BoolVar(&c.disableMachine)
c.CmdClause.Flag("disable-package", "Disable metadata for packages and libraries utilized in your source code").BoolVar(&c.disablePackage)
c.CmdClause.Flag("enable", "Enable all metadata").BoolVar(&c.enable)
c.CmdClause.Flag("enable-build", "Enable metadata for information regarding the time taken for builds and compilation processes").BoolVar(&c.enableBuild)
c.CmdClause.Flag("enable-machine", "Enable metadata for general, non-identifying system specifications (CPU, RAM, operating system)").BoolVar(&c.enableMachine)
c.CmdClause.Flag("enable-package", "Enable metadata for packages and libraries utilized in your source code").BoolVar(&c.enablePackage)
return &c
}

// Exec implements the command interface.
func (c *MetadataCommand) Exec(_ io.Reader, out io.Writer) error {
if c.disable && c.enable {
return fsterr.ErrInvalidEnableDisableFlagCombo
}
if c.disable {
c.Globals.Config.WasmMetadata = toggleAll("disable")
}
if c.enable {
c.Globals.Config.WasmMetadata = toggleAll("enable")
}
if c.disable && (c.enableBuild || c.enableMachine || c.enablePackage) {
text.Info(out, "We will disable all metadata except for the specified `--enable-*` flags")
text.Break(out)
}
if c.enable && (c.disableBuild || c.disableMachine || c.disablePackage) {
text.Info(out, "We will enable all metadata except for the specified `--disable-*` flags")
text.Break(out)
}
if c.enableBuild {
c.Globals.Config.WasmMetadata.BuildInfo = "enable"
}
if c.enableMachine {
c.Globals.Config.WasmMetadata.MachineInfo = "enable"
}
if c.enablePackage {
c.Globals.Config.WasmMetadata.PackageInfo = "enable"
}
if c.disableBuild {
c.Globals.Config.WasmMetadata.BuildInfo = "disable"
}
if c.disableMachine {
c.Globals.Config.WasmMetadata.MachineInfo = "disable"
}
if c.disablePackage {
c.Globals.Config.WasmMetadata.PackageInfo = "disable"
}
err := c.Globals.Config.Write(c.Globals.ConfigPath)
if err != nil {
return fmt.Errorf("failed to persist metadata choices to disk: %w", err)
}
text.Success(out, "configuration updated (see: `fastly config`)")
Integralist marked this conversation as resolved.
Show resolved Hide resolved
text.Break(out)
text.Output(out, "Build Information: %s", c.Globals.Config.WasmMetadata.BuildInfo)
text.Output(out, "Machine Information: %s", c.Globals.Config.WasmMetadata.MachineInfo)
text.Output(out, "Package Information: %s", c.Globals.Config.WasmMetadata.PackageInfo)
return nil
}

func toggleAll(state string) config.WasmMetadata {
var t config.WasmMetadata
t.BuildInfo = state
t.MachineInfo = state
t.PackageInfo = state
return t
}
Loading
Loading