Skip to content

Commit

Permalink
refactor: move main logic to custom init
Browse files Browse the repository at this point in the history
  • Loading branch information
Integralist committed Nov 13, 2023
1 parent d0552f7 commit 45a5e55
Show file tree
Hide file tree
Showing 80 changed files with 2,190 additions and 1,261 deletions.
174 changes: 9 additions & 165 deletions cmd/fastly/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,198 +2,42 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"

"github.com/fastly/go-fastly/v8/fastly"
"github.com/fatih/color"
"github.com/skratchdot/open-golang/open"

"github.com/fastly/cli/pkg/api"
"github.com/fastly/cli/pkg/app"
"github.com/fastly/cli/pkg/auth"
"github.com/fastly/cli/pkg/commands/compute"
"github.com/fastly/cli/pkg/config"
"github.com/fastly/cli/pkg/env"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/github"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/sync"
"github.com/fastly/cli/pkg/text"
)

func main() {
// Parse the arguments provided by the user via the command-line interface.
args := os.Args[1:]

// Define a HTTP client that will be used for making arbitrary HTTP requests.
httpClient := &http.Client{Timeout: time.Minute * 2}

// Define the standard input/output streams.
var (
in io.Reader = os.Stdin
out io.Writer = sync.NewWriter(color.Output)
)

// Read relevant configuration options from the user's environment.
var e config.Environment
e.Read(env.Parse(os.Environ()))

// Identify verbose flag early (before Kingpin parser has executed) so we can
// print additional output related to the CLI configuration.
var verboseOutput bool
for _, seg := range args {
if seg == "-v" || seg == "--verbose" {
verboseOutput = true
}
}

// Identify auto-yes/non-interactive flag early (before Kingpin parser has
// executed) so we can handle the interactive prompts appropriately with
// regards to processing the CLI configuration.
var autoYes, nonInteractive bool
for _, seg := range args {
if seg == "-y" || seg == "--auto-yes" {
autoYes = true
}
if seg == "-i" || seg == "--non-interactive" {
nonInteractive = true
if err := app.Run(os.Args, os.Stdin); err != nil {
if skipExit := processErr(err, os.Args, os.Stdout); skipExit {
return
}
}

// Extract a subset of configuration options from the local app directory.
var cfg config.File
cfg.SetAutoYes(autoYes)
cfg.SetNonInteractive(nonInteractive)
// The CLI relies on a valid configuration, otherwise we can't continue.
err := cfg.Read(config.FilePath, in, out, fsterr.Log, verboseOutput)
if err != nil {
fsterr.Deduce(err).Print(color.Error)
// WARNING: os.Exit will exit, and any `defer` calls will not be run.
os.Exit(1)
}

// Extract user's project configuration from the fastly.toml manifest.
var md manifest.Data
md.File.Args = args
md.File.SetErrLog(fsterr.Log)
md.File.SetOutput(out)

// NOTE: We skip handling the error because not all commands relate to Compute.
_ = md.File.Read(manifest.Filename)

// Configure authentication inputs.
// We do this here so that we can mock the values in our test suite.
req, err := http.NewRequest(http.MethodGet, auth.OIDCMetadata, nil)
if err != nil {
err = fmt.Errorf("failed to construct request object for OpenID Connect .well-known metadata: %w", err)
handleErr(err, out)
os.Exit(1)
}
resp, err := httpClient.Do(req)
if err != nil {
err = fmt.Errorf("failed to request OpenID Connect .well-known metadata: %w", err)
handleErr(err, out)
os.Exit(1)
}
openIDConfig, err := io.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("failed to read OpenID Connect .well-known metadata: %w", err)
handleErr(err, out)
os.Exit(1)
}
_ = resp.Body.Close()
var wellknown auth.WellKnownEndpoints
err = json.Unmarshal(openIDConfig, &wellknown)
if err != nil {
err = fmt.Errorf("failed to unmarshal OpenID Connect .well-known metadata: %w", err)
handleErr(err, out)
os.Exit(1)
}
result := make(chan auth.AuthorizationResult)
router := http.NewServeMux()
authServer := &auth.Server{
DebugMode: e.DebugMode,
HTTPClient: httpClient,
Result: result,
Router: router,
WellKnownEndpoints: wellknown,
}
router.HandleFunc("/callback", authServer.HandleCallback())

// The `main` function is a shim for calling `app.Run()`.
err = app.Run(app.RunOpts{
APIClient: func(token, endpoint string, debugMode bool) (api.Interface, error) {
client, err := fastly.NewClientForEndpoint(token, endpoint)
if debugMode {
client.DebugMode = true
}
return client, err
},
Args: args,
AuthServer: authServer,
ConfigFile: cfg,
ConfigPath: config.FilePath,
Env: e,
ErrLog: fsterr.Log,
ExecuteWasmTools: compute.ExecuteWasmTools,
HTTPClient: httpClient,
Manifest: &md,
Opener: open.Run,
Stdin: in,
Stdout: out,
Versioners: app.Versioners{
CLI: github.New(github.Opts{
HTTPClient: httpClient,
Org: "fastly",
Repo: "cli",
Binary: "fastly",
}),
Viceroy: github.New(github.Opts{
HTTPClient: httpClient,
Org: "fastly",
Repo: "viceroy",
Binary: "viceroy",
Version: md.File.LocalServer.ViceroyVersion,
}),
WasmTools: github.New(github.Opts{
HTTPClient: httpClient,
Org: "bytecodealliance",
Repo: "wasm-tools",
Binary: "wasm-tools",
External: true,
Nested: true,
}),
},
})
}

// processErr persists the error log to disk and deduces the error type.
func processErr(err error, args []string, out io.Writer) (skipExit bool) {
// NOTE: We persist any error log entries to disk before attempting to handle
// a possible error response from app.Run as there could be errors recorded
// during the execution flow but were otherwise handled without bubbling an
// error back the call stack, and so if the user still experiences something
// unexpected we will have a record of any errors that happened along the way.
logErr := fsterr.Log.Persist(fsterr.LogPath, args)
logErr := fsterr.Log.Persist(fsterr.LogPath, args[1:])
if logErr != nil {
fsterr.Deduce(logErr).Print(color.Error)
}
if err != nil {
handleErr(err, out)
os.Exit(1)
}
}

func handleErr(err error, out io.Writer) {
text.Break(out)
fsterr.Deduce(err).Print(color.Error)
exitError := fsterr.SkipExitError{}
if errors.As(err, &exitError) {
if exitError.Skip {
return // skip returning an error for 'help' output
}
return exitError.Skip
}
return false
}
20 changes: 12 additions & 8 deletions pkg/app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"github.com/fastly/kingpin"

"github.com/fastly/cli/pkg/auth"
"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/commands/acl"
"github.com/fastly/cli/pkg/commands/aclentry"
Expand Down Expand Up @@ -85,7 +86,10 @@ func defineCommands(
app *kingpin.Application,
g *global.Data,
m manifest.Data,
opts RunOpts,
opener func(string) error,
authServer auth.Starter,
versioners Versioners,
factory APIClientFactory,
) []cmd.Command {
shellcompleteCmdRoot := shellcomplete.NewRootCommand(app, g)

Expand All @@ -95,7 +99,7 @@ func defineCommands(
// messes up the order of the commands in the `--help` output. So to make the
// placement of the `sso` subcommand not look too odd we place it at the
// beginning of the list of commands.
ssoCmdRoot := sso.NewRootCommand(app, g, opts.Opener, opts.AuthServer)
ssoCmdRoot := sso.NewRootCommand(app, g, opener, authServer)

aclCmdRoot := acl.NewRootCommand(app, g)
aclCreate := acl.NewCreateCommand(aclCmdRoot.CmdClause, g, m)
Expand All @@ -121,15 +125,15 @@ func defineCommands(
backendList := backend.NewListCommand(backendCmdRoot.CmdClause, g, m)
backendUpdate := backend.NewUpdateCommand(backendCmdRoot.CmdClause, g, m)
computeCmdRoot := compute.NewRootCommand(app, g)
computeBuild := compute.NewBuildCommand(computeCmdRoot.CmdClause, g, opts.Versioners.WasmTools)
computeBuild := compute.NewBuildCommand(computeCmdRoot.CmdClause, g, versioners.WasmTools)
computeDeploy := compute.NewDeployCommand(computeCmdRoot.CmdClause, g)
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)
computeServe := compute.NewServeCommand(computeCmdRoot.CmdClause, g, computeBuild, versioners.Viceroy)
computeUpdate := compute.NewUpdateCommand(computeCmdRoot.CmdClause, g, m)
computeValidate := compute.NewValidateCommand(computeCmdRoot.CmdClause, g)
configCmdRoot := config.NewRootCommand(app, g)
Expand Down Expand Up @@ -343,12 +347,12 @@ func defineCommands(
popCmdRoot := pop.NewRootCommand(app, g)
productsCmdRoot := products.NewRootCommand(app, g, m)
profileCmdRoot := profile.NewRootCommand(app, g)
profileCreate := profile.NewCreateCommand(profileCmdRoot.CmdClause, profile.APIClientFactory(opts.APIClient), g, ssoCmdRoot)
profileCreate := profile.NewCreateCommand(profileCmdRoot.CmdClause, profile.APIClientFactory(factory), g, ssoCmdRoot)
profileDelete := profile.NewDeleteCommand(profileCmdRoot.CmdClause, g)
profileList := profile.NewListCommand(profileCmdRoot.CmdClause, g)
profileSwitch := profile.NewSwitchCommand(profileCmdRoot.CmdClause, g)
profileToken := profile.NewTokenCommand(profileCmdRoot.CmdClause, g)
profileUpdate := profile.NewUpdateCommand(profileCmdRoot.CmdClause, profile.APIClientFactory(opts.APIClient), g, ssoCmdRoot)
profileUpdate := profile.NewUpdateCommand(profileCmdRoot.CmdClause, profile.APIClientFactory(factory), g, ssoCmdRoot)
purgeCmdRoot := purge.NewRootCommand(app, g, m)
rateLimitCmdRoot := ratelimit.NewRootCommand(app, g)
rateLimitCreate := ratelimit.NewCreateCommand(rateLimitCmdRoot.CmdClause, g, m)
Expand Down Expand Up @@ -432,7 +436,7 @@ func defineCommands(
tlsSubscriptionDescribe := tlssubscription.NewDescribeCommand(tlsSubscriptionCmdRoot.CmdClause, g, m)
tlsSubscriptionList := tlssubscription.NewListCommand(tlsSubscriptionCmdRoot.CmdClause, g, m)
tlsSubscriptionUpdate := tlssubscription.NewUpdateCommand(tlsSubscriptionCmdRoot.CmdClause, g, m)
updateRoot := update.NewRootCommand(app, opts.ConfigPath, opts.Versioners.CLI, g)
updateRoot := update.NewRootCommand(app, g.ConfigPath, versioners.CLI, g)
userCmdRoot := user.NewRootCommand(app, g)
userCreate := user.NewCreateCommand(userCmdRoot.CmdClause, g, m)
userDelete := user.NewDeleteCommand(userCmdRoot.CmdClause, g, m)
Expand All @@ -458,7 +462,7 @@ func defineCommands(
vclSnippetDescribe := snippet.NewDescribeCommand(vclSnippetCmdRoot.CmdClause, g, m)
vclSnippetList := snippet.NewListCommand(vclSnippetCmdRoot.CmdClause, g, m)
vclSnippetUpdate := snippet.NewUpdateCommand(vclSnippetCmdRoot.CmdClause, g, m)
versionCmdRoot := version.NewRootCommand(app, opts.Versioners.Viceroy)
versionCmdRoot := version.NewRootCommand(app, versioners.Viceroy)
whoamiCmdRoot := whoami.NewRootCommand(app, g)

return []cmd.Command{
Expand Down
Loading

0 comments on commit 45a5e55

Please sign in to comment.