-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f09f87
commit 59d20f4
Showing
14 changed files
with
481 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package structs | ||
|
||
// RunState is the state of the run. | ||
type RunState string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package structs | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// RunStateTransition represents a single run state transition. | ||
type RunStateTransition struct { | ||
HasLogs bool `graphql:"hasLogs"` | ||
Note *string `graphql:"note"` | ||
State RunState `graphql:"state"` | ||
Terminal bool `graphql:"terminal"` | ||
Timestamp int `graphql:"timestamp"` | ||
Username *string `graphql:"username"` | ||
} | ||
|
||
func (r *RunStateTransition) About() string { | ||
parts := []string{ | ||
string(r.State), | ||
time.Unix(int64(r.Timestamp), 0).Format(time.UnixDate), | ||
} | ||
|
||
if username := r.Username; username != nil { | ||
parts = append(parts, *username) | ||
} | ||
|
||
if note := r.Note; note != nil { | ||
parts = append(parts, *note) | ||
} | ||
|
||
return strings.Join(parts, "\t") | ||
} | ||
|
||
func (r *RunStateTransition) Error() error { | ||
if r.State == RunState("FINISHED") { | ||
return nil | ||
} | ||
|
||
return fmt.Errorf("finished with %s state", r.State) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package structs | ||
|
||
// RunType is the type of the run. | ||
type RunType string | ||
|
||
func NewRunType(in string) *RunType { | ||
out := RunType(in) | ||
return &out | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package actions | ||
|
||
import "github.com/urfave/cli/v2" | ||
|
||
// Multi combines multiple CLI actions. | ||
func Multi(steps ...cli.BeforeFunc) cli.BeforeFunc { | ||
return func(ctx *cli.Context) error { | ||
for _, step := range steps { | ||
if err := step(ctx); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package authenticated | ||
|
||
import ( | ||
"github.com/urfave/cli/v2" | ||
|
||
"github.com/spacelift-io/spacelift-cli/client" | ||
"github.com/spacelift-io/spacelift-cli/client/session" | ||
) | ||
|
||
// Client is the authenticated client that can be used by all CLI commands. | ||
var Client client.Client | ||
|
||
// Ensure is a way of ensuring that the Client exists, and it meant to be used | ||
// as a Before action for commands that need it. | ||
func Ensure(*cli.Context) error { | ||
ctx, httpClient := session.Defaults() | ||
|
||
session, err := session.New(ctx, httpClient) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
Client = client.New(httpClient, session) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package stack | ||
|
||
import ( | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
var stackID string | ||
|
||
func beforeEach(cliCtx *cli.Context) error { | ||
stackID = cliCtx.String(flagStackID.Name) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package stack | ||
|
||
import "github.com/urfave/cli/v2" | ||
|
||
var flagStackID = &cli.StringFlag{ | ||
Name: "id", | ||
Usage: "User-facing ID (slug) of the stack", | ||
Required: true, | ||
} | ||
|
||
var flagCommitSHA = &cli.StringFlag{ | ||
Name: "sha", | ||
Usage: "Commit SHA for the newly created run", | ||
} | ||
|
||
var flagRun = &cli.StringFlag{ | ||
Name: "run", | ||
Usage: "ID of the run", | ||
Required: true, | ||
} | ||
|
||
var flagNoInit = &cli.BoolFlag{ | ||
Name: "noinit", | ||
Usage: "Indicate whether to skip initialization for a task", | ||
Value: false, | ||
} | ||
|
||
var flagTail = &cli.BoolFlag{ | ||
Name: "tail", | ||
Usage: "Indicate whether to tail the run", | ||
Value: false, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package stack | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/shurcooL/graphql" | ||
|
||
"github.com/spacelift-io/spacelift-cli/client/structs" | ||
"github.com/spacelift-io/spacelift-cli/cmd/internal/authenticated" | ||
) | ||
|
||
func runLogs(ctx context.Context, stack, run string) (err error) { | ||
lines := make(chan string) | ||
|
||
go func() { | ||
err = runStates(ctx, stack, run, lines) | ||
}() | ||
|
||
for line := range lines { | ||
fmt.Print(line) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func runStates(ctx context.Context, stack, run string, sink chan<- string) error { | ||
defer func() { close(sink) }() | ||
|
||
var query struct { | ||
Stack *struct { | ||
Run *struct { | ||
History []structs.RunStateTransition `graphql:"history"` | ||
} `graphql:"run(id: $run)"` | ||
} `graphql:"stack(id: $stack)"` | ||
} | ||
|
||
variables := map[string]interface{}{ | ||
"stack": graphql.ID(stack), | ||
"run": graphql.ID(run), | ||
} | ||
|
||
reportedStates := make(map[structs.RunState]struct{}) | ||
|
||
for { | ||
if err := authenticated.Client.Query(ctx, &query, variables); err != nil { | ||
return err | ||
} | ||
|
||
if query.Stack == nil || query.Stack.Run == nil { | ||
return errors.New("not found") | ||
} | ||
|
||
history := query.Stack.Run.History | ||
|
||
for index := range history { | ||
// Unlike the GUI, we go earliest first. | ||
transition := history[len(history)-index-1] | ||
|
||
if _, ok := reportedStates[transition.State]; ok { | ||
continue | ||
} | ||
reportedStates[transition.State] = struct{}{} | ||
|
||
fmt.Println("") | ||
fmt.Println("-----------------") | ||
fmt.Println(transition.About()) | ||
fmt.Println("-----------------") | ||
fmt.Println("") | ||
|
||
if transition.HasLogs { | ||
if err := runStateLogs(ctx, stack, run, transition.State, sink); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if transition.Terminal { | ||
return transition.Error() | ||
} | ||
} | ||
|
||
// TODO: Increase the timeout every time there's nothing new. | ||
time.Sleep(5 * time.Second) | ||
} | ||
} | ||
|
||
func runStateLogs(ctx context.Context, stack, run string, state structs.RunState, sink chan<- string) error { | ||
var query struct { | ||
Stack *struct { | ||
Run *struct { | ||
Logs *struct { | ||
Exists bool `graphql:"exists"` | ||
Finished bool `graphql:"finished"` | ||
HasMore bool `graphql:"hasMore"` | ||
Messages []struct { | ||
Body string `graphql:"message"` | ||
} `graphql:"messages"` | ||
NextToken *graphql.String `graphql:"nextToken"` | ||
} `graphql:"logs(state: $state, token: $token)"` | ||
} `graphql:"run(id: $run)"` | ||
} `graphql:"stack(id: $stack)"` | ||
} | ||
|
||
var token *graphql.String | ||
|
||
variables := map[string]interface{}{ | ||
"stack": graphql.ID(stack), | ||
"run": graphql.ID(run), | ||
"state": state, | ||
"token": token, | ||
} | ||
|
||
var backOff time.Duration | ||
|
||
for { | ||
if err := authenticated.Client.Query(ctx, &query, variables); err != nil { | ||
return err | ||
} | ||
|
||
if query.Stack == nil || query.Stack.Run == nil || query.Stack.Run.Logs == nil { | ||
return errors.New("not found") | ||
} | ||
|
||
logs := query.Stack.Run.Logs | ||
variables["token"] = logs.NextToken | ||
|
||
for _, message := range logs.Messages { | ||
sink <- message.Body | ||
} | ||
|
||
if logs.Finished { | ||
break | ||
} | ||
|
||
if logs.HasMore { | ||
backOff = 0 | ||
} else { | ||
backOff++ | ||
} | ||
|
||
time.Sleep(backOff * time.Second) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.