package main import ( "context" "errors" "fmt" "io" "math/rand" "net/url" "os" "strings" "sync/atomic" "time" "github.com/google/uuid" "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" ) func main() { var root *serpent.Cmd root = &serpent.Cmd{ Use: "cliui", Short: "Used for visually testing UI components for the CLI.", HelpHandler: func(inv *serpent.Invocation) error { _, _ = fmt.Fprintln(inv.Stdout, "This command is used for visually testing UI components for the CLI.") _, _ = fmt.Fprintln(inv.Stdout, "It is not intended to be used by end users.") _, _ = fmt.Fprintln(inv.Stdout, "Subcommands: ") for _, child := range root.Children { _, _ = fmt.Fprintf(inv.Stdout, "- %s\n", child.Use) } return nil }, } root.Children = append(root.Children, &serpent.Cmd{ Use: "prompt", Handler: func(inv *serpent.Invocation) error { _, err := cliui.Prompt(inv, cliui.PromptOptions{ Text: "What is our " + cliui.Field("company name") + "?", Default: "acme-corp", Validate: func(s string) error { if !strings.EqualFold(s, "coder") { return xerrors.New("Err... nope!") } return nil }, }) if errors.Is(err, cliui.Canceled) { return nil } if err != nil { return err } _, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: "Do you want to accept?", Default: cliui.ConfirmYes, IsConfirm: true, }) if errors.Is(err, cliui.Canceled) { return nil } if err != nil { return err } _, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: "Enter password", Secret: true, }) return err }, }) root.Children = append(root.Children, &serpent.Cmd{ Use: "select", Handler: func(inv *serpent.Invocation) error { value, err := cliui.Select(inv, cliui.SelectOptions{ Options: []string{"Tomato", "Banana", "Onion", "Grape", "Lemon"}, Size: 3, }) _, _ = fmt.Printf("Selected: %q\n", value) return err }, }) root.Children = append(root.Children, &serpent.Cmd{ Use: "job", Handler: func(inv *serpent.Invocation) error { job := codersdk.ProvisionerJob{ Status: codersdk.ProvisionerJobPending, CreatedAt: dbtime.Now(), } go func() { time.Sleep(time.Second) if job.Status != codersdk.ProvisionerJobPending { return } started := dbtime.Now() job.StartedAt = &started job.Status = codersdk.ProvisionerJobRunning time.Sleep(3 * time.Second) if job.Status != codersdk.ProvisionerJobRunning { return } completed := dbtime.Now() job.CompletedAt = &completed job.Status = codersdk.ProvisionerJobSucceeded }() err := cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{ Fetch: func() (codersdk.ProvisionerJob, error) { return job, nil }, Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) { logs := make(chan codersdk.ProvisionerJobLog) go func() { defer close(logs) ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() count := 0 for { select { case <-inv.Context().Done(): return case <-ticker.C: if job.Status == codersdk.ProvisionerJobSucceeded || job.Status == codersdk.ProvisionerJobCanceled { return } log := codersdk.ProvisionerJobLog{ CreatedAt: time.Now(), Output: fmt.Sprintf("Some log %d", count), Level: codersdk.LogLevelInfo, } switch { case count == 10: log.Stage = "Setting Up" case count == 20: log.Stage = "Executing Hook" case count == 30: log.Stage = "Parsing Variables" case count == 40: log.Stage = "Provisioning" case count == 50: log.Stage = "Cleaning Up" } if count%5 == 0 { log.Level = codersdk.LogLevelWarn } count++ if log.Output == "" && log.Stage == "" { continue } logs <- log } } }() return logs, io.NopCloser(strings.NewReader("")), nil }, Cancel: func() error { job.Status = codersdk.ProvisionerJobCanceling time.Sleep(time.Second) job.Status = codersdk.ProvisionerJobCanceled completed := dbtime.Now() job.CompletedAt = &completed return nil }, }) return err }, }) root.Children = append(root.Children, &serpent.Cmd{ Use: "agent", Handler: func(inv *serpent.Invocation) error { var agent codersdk.WorkspaceAgent var logs []codersdk.WorkspaceAgentLog fetchSteps := []func(){ func() { createdAt := time.Now().Add(-time.Minute) agent = codersdk.WorkspaceAgent{ CreatedAt: createdAt, Status: codersdk.WorkspaceAgentConnecting, LifecycleState: codersdk.WorkspaceAgentLifecycleCreated, } }, func() { time.Sleep(time.Second) agent.Status = codersdk.WorkspaceAgentTimeout }, func() { agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting startingAt := time.Now() agent.StartedAt = &startingAt for i := 0; i < 10; i++ { level := codersdk.LogLevelInfo if rand.Float64() > 0.75 { //nolint:gosec level = codersdk.LogLevelError } logs = append(logs, codersdk.WorkspaceAgentLog{ CreatedAt: time.Now().Add(-time.Duration(10-i) * 144 * time.Millisecond), Output: fmt.Sprintf("Some log %d", i), Level: level, }) } }, func() { time.Sleep(time.Second) firstConnectedAt := time.Now() agent.FirstConnectedAt = &firstConnectedAt lastConnectedAt := firstConnectedAt.Add(0) agent.LastConnectedAt = &lastConnectedAt agent.Status = codersdk.WorkspaceAgentConnected }, func() {}, func() { time.Sleep(5 * time.Second) agent.Status = codersdk.WorkspaceAgentConnected lastConnectedAt := time.Now() agent.LastConnectedAt = &lastConnectedAt }, } err := cliui.Agent(inv.Context(), inv.Stdout, uuid.Nil, cliui.AgentOptions{ FetchInterval: 100 * time.Millisecond, Wait: true, Fetch: func(_ context.Context, _ uuid.UUID) (codersdk.WorkspaceAgent, error) { if len(fetchSteps) == 0 { return agent, nil } step := fetchSteps[0] fetchSteps = fetchSteps[1:] step() return agent, nil }, FetchLogs: func(_ context.Context, _ uuid.UUID, _ int64, follow bool) (<-chan []codersdk.WorkspaceAgentLog, io.Closer, error) { logsC := make(chan []codersdk.WorkspaceAgentLog, len(logs)) if follow { go func() { defer close(logsC) for _, log := range logs { logsC <- []codersdk.WorkspaceAgentLog{log} time.Sleep(144 * time.Millisecond) } agent.LifecycleState = codersdk.WorkspaceAgentLifecycleReady readyAt := dbtime.Now() agent.ReadyAt = &readyAt }() } else { logsC <- logs close(logsC) } return logsC, closeFunc(func() error { return nil }), nil }, }) if err != nil { return err } return nil }, }) root.Children = append(root.Children, &serpent.Cmd{ Use: "resources", Handler: func(inv *serpent.Invocation) error { disconnected := dbtime.Now().Add(-4 * time.Second) return cliui.WorkspaceResources(inv.Stdout, []codersdk.WorkspaceResource{{ Transition: codersdk.WorkspaceTransitionStart, Type: "google_compute_disk", Name: "root", }, { Transition: codersdk.WorkspaceTransitionStop, Type: "google_compute_disk", Name: "root", }, { Transition: codersdk.WorkspaceTransitionStart, Type: "google_compute_instance", Name: "dev", Agents: []codersdk.WorkspaceAgent{{ CreatedAt: dbtime.Now().Add(-10 * time.Second), Status: codersdk.WorkspaceAgentConnecting, LifecycleState: codersdk.WorkspaceAgentLifecycleCreated, Name: "dev", OperatingSystem: "linux", Architecture: "amd64", }}, }, { Transition: codersdk.WorkspaceTransitionStart, Type: "kubernetes_pod", Name: "dev", Agents: []codersdk.WorkspaceAgent{{ Status: codersdk.WorkspaceAgentConnected, LifecycleState: codersdk.WorkspaceAgentLifecycleReady, Name: "go", Architecture: "amd64", OperatingSystem: "linux", }, { DisconnectedAt: &disconnected, Status: codersdk.WorkspaceAgentDisconnected, LifecycleState: codersdk.WorkspaceAgentLifecycleReady, Name: "postgres", Architecture: "amd64", OperatingSystem: "linux", }}, }}, cliui.WorkspaceResourcesOptions{ WorkspaceName: "dev", HideAgentState: false, HideAccess: false, }) }, }) root.Children = append(root.Children, &serpent.Cmd{ Use: "git-auth", Handler: func(inv *serpent.Invocation) error { var count atomic.Int32 var githubAuthed atomic.Bool var gitlabAuthed atomic.Bool go func() { // Sleep to display the loading indicator. time.Sleep(time.Second) // Swap to true to display success and move onto GitLab. githubAuthed.Store(true) // Show the loading indicator again... time.Sleep(time.Second * 2) // Complete the auth! gitlabAuthed.Store(true) }() return cliui.ExternalAuth(inv.Context(), inv.Stdout, cliui.ExternalAuthOptions{ Fetch: func(ctx context.Context) ([]codersdk.TemplateVersionExternalAuth, error) { count.Add(1) return []codersdk.TemplateVersionExternalAuth{{ ID: "github", Type: codersdk.EnhancedExternalAuthProviderGitHub.String(), Authenticated: githubAuthed.Load(), AuthenticateURL: "https://example.com/gitauth/github?redirect=" + url.QueryEscape("/gitauth?notify"), }, { ID: "gitlab", Type: codersdk.EnhancedExternalAuthProviderGitLab.String(), Authenticated: gitlabAuthed.Load(), AuthenticateURL: "https://example.com/gitauth/gitlab?redirect=" + url.QueryEscape("/gitauth?notify"), }}, nil }, }) }, }) err := root.Invoke(os.Args[1:]...).WithOS().Run() if err != nil { _, _ = fmt.Println(err.Error()) os.Exit(1) } } type closeFunc func() error func (f closeFunc) Close() error { return f() }