Skip to content

Commit

Permalink
Merge branch '3.x' into sup-157-bk-pipeline-create
Browse files Browse the repository at this point in the history
  • Loading branch information
lizrabuya authored Jun 11, 2024
2 parents 605e1da + 28f9b8d commit 3774338
Show file tree
Hide file tree
Showing 23 changed files with 218 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .buildkite/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This uses the upstream image without any changes intentionally.
# This allows dependabot to keep this version up-to-date for us automatically, since it does not work with
# docker-compose directly
FROM goreleaser/goreleaser-pro:v1.26.2-pro@sha256:e3c5989f459122528766ec55ebcd77d8abd970500fe219936278aac2da077d71
FROM goreleaser/goreleaser-pro:v2.0.0-pro@sha256:3dcb9b7ebf61b7de4cff9f444695e3323c7dc56850ffb30dd89ebbd630dbad3e
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
project_name: bk
version: 2

release:
name_template: Buildkite CLI {{.Version}}
Expand Down
3 changes: 2 additions & 1 deletion fixtures/config/user.basic.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
openai_token: jumanji
selected_org: buildkite-org
organizations:
buildkite-test:
api_token: test-token-abcd
api_token: test-token-abcd
2 changes: 1 addition & 1 deletion generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func main() {
os.Exit(1)
}

if err = os.WriteFile(schemaFile, []byte(schema), 0644); err != nil {
if err = os.WriteFile(schemaFile, []byte(schema), 0o644); err != nil {
fmt.Println(err)
os.Exit(1)
}
Expand Down
6 changes: 4 additions & 2 deletions internal/agent/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"github.com/pkg/browser"
)

var agentListStyle = lipgloss.NewStyle().Padding(1, 2)
var viewPortStyle = agentListStyle.Copy()
var (
agentListStyle = lipgloss.NewStyle().Padding(1, 2)
viewPortStyle = agentListStyle.Copy()
)

type AgentListModel struct {
agentList list.Model
Expand Down
1 change: 0 additions & 1 deletion internal/build/resolver/current_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ func TestResolveBuildFromCurrentBranch(t *testing.T) {
}
r := resolver.ResolveBuildFromCurrentBranch(testRepository(), pipelineResolver, &f)
b, err := r(context.Background())

if err != nil {
t.Fatal(err)
}
Expand Down
1 change: 0 additions & 1 deletion internal/build/resolver/user_builds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ func TestResolveBuildForCurrentUser(t *testing.T) {

r := resolver.ResolveBuildForCurrentUser("main", pipelineResolver, f)
build, err := r(context.Background())

if err != nil {
t.Fatal(err)
}
Expand Down
1 change: 0 additions & 1 deletion internal/build/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ func getBuildStateColor(state string) lipgloss.Color {
}

func renderBuildState(state string, blocked bool) string {

var stateIcon string
stateColor := getBuildStateColor(state)

Expand Down
13 changes: 12 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ func (conf *Config) SelectOrganization(org string) error {
return conf.localConfig.WriteConfig()
}

// SetOpenAIToken sets the user's OpenAI token in the user config file
func (conf *Config) SetOpenAIToken(token string) error {
conf.userConfig.Set("openai_token", token)
return conf.userConfig.WriteConfig()
}

// GetOpenAIToken reads the user's OpenAI token from the user config file
func (conf *Config) GetOpenAIToken() string {
return conf.userConfig.GetString("openai_token")
}

// APIToken gets the API token configured for the currently selected organization
func (conf *Config) APIToken() string {
slug := conf.OrganizationSlug()
Expand Down Expand Up @@ -202,7 +213,7 @@ func createIfNotExistsConfigDir() (string, error) {

configDir := filepath.Join(homeDir, ".config")
if _, err := os.Stat(configDir); errors.Is(err, os.ErrNotExist) {
err := os.Mkdir(configDir, 0755)
err := os.Mkdir(configDir, 0o755)
if err != nil {
return "", err
}
Expand Down
87 changes: 87 additions & 0 deletions internal/job/view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package job

import (
"errors"
"fmt"
"log"
"time"

"github.com/buildkite/go-buildkite/v3/buildkite"
"github.com/charmbracelet/lipgloss"
)

func JobSummary(job *buildkite.Job) string {
jobName := getJobName(*job)
jobTotalTime, err := calculateTotalTime(job.StartedAt, job.FinishedAt)
if err != nil {
log.Fatal("Unable to calculate total job time", err)
}

jobInfo := fmt.Sprintf("%s %s (%s)", renderJobState(*job.State), jobName, lipgloss.NewStyle().Foreground(lipgloss.Color("#5A5A5A")).Render(jobTotalTime.String()))

summary := lipgloss.JoinVertical(lipgloss.Top,
lipgloss.NewStyle().Align(lipgloss.Left).Padding(0, 1).Render(),
lipgloss.NewStyle().Bold(true).Padding(0, 1).Render(jobInfo),
)
return summary
}

func getJobName(job buildkite.Job) string {
var jobName string

switch {
case job.Name != nil:
jobName = *job.Name
case job.Label != nil:
jobName = *job.Label
default:
jobName = *job.Command
}

return jobName
}

func renderJobState(state string) string {
var stateIcon string
stateColor := getJobStateColor(state)

switch state {
case "passed":
stateIcon = "✔"
case "running":
stateIcon = "▶"
case "failed", "failing":
stateIcon = "✖"
case "canceled":
stateIcon = "🚫"
case "canceling":
stateIcon = "🚫(cancelling...)"
default:
stateIcon = "❔"
}

return lipgloss.NewStyle().Foreground(stateColor).Render(stateIcon)
}

func getJobStateColor(state string) lipgloss.Color {
var stateColor lipgloss.Color
switch state {
case "passed":
stateColor = lipgloss.Color("#9dcc3a") // green
case "running":
stateColor = lipgloss.Color("#FF6E00")
case "failed", "failing":
stateColor = lipgloss.Color("#ff0000")
default:
stateColor = lipgloss.Color("#5A5A5A") // grey
}
return stateColor
}

func calculateTotalTime(startedAt, finishedAt *buildkite.Timestamp) (time.Duration, error) {
if startedAt == nil || finishedAt == nil {
return 0, errors.New("both startedAt and finishedAt must be non-nil")
}

return finishedAt.Time.Sub(startedAt.Time), nil
}
1 change: 0 additions & 1 deletion internal/pipeline/resolver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,4 @@ func TestResolvePipelineFromConfig(t *testing.T) {
t.Errorf("pipeline name should resolve temporarily to pipeline1")
}
})

}
1 change: 0 additions & 1 deletion internal/pipeline/resolver/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func TestResolvePipelinesFromPath(t *testing.T) {
if len(pipelines) != 0 {
t.Errorf("Expected 0 pipeline, got %d", len(pipelines))
}

})

t.Run("one pipeline", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/agent/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func RunStop(cmd *cobra.Command, args []string, opts *AgentStopOptions) error {
// use a wait group to ensure we exit the program after all agents have finished
var wg sync.WaitGroup
// this semaphore is used to limit how many concurrent API requests can be sent
var sem = semaphore.NewWeighted(opts.limit)
sem := semaphore.NewWeighted(opts.limit)

var agents []agent.StoppableAgent
// this command accepts either input from stdin or positional arguments (not both) in that order
Expand Down
1 change: 0 additions & 1 deletion pkg/cmd/agent/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ func NewCmdAgentView(f *factory.Factory) *cobra.Command {

l := io.NewPendingCommand(func() tea.Msg {
agentData, _, err := f.RestAPIClient.Agents.Get(org, id)

if err != nil {
return err
}
Expand Down
41 changes: 41 additions & 0 deletions pkg/cmd/ai/add/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package add

import (
"github.com/AlecAivazis/survey/v2"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/spf13/cobra"
)

func NewCmdAIAdd(f *factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Args: cobra.NoArgs,
Short: "Add an OpenAI token.",
Long: "Add an OpenAI token for use with CLI AI tooling",
RunE: func(cmd *cobra.Command, args []string) error {
return ConfigureRun(f)
},
}

return cmd
}

func ConfigureRun(f *factory.Factory) error {
qs := []*survey.Question{
{
Name: "token",
Prompt: &survey.Password{Message: "Paste your OpenAI token:"},
Validate: survey.Required,
},
}
answers := struct{ Token string }{}

err := survey.Ask(qs, &answers)
if err != nil {
return err
}

err = f.Config.SetOpenAIToken(answers.Token)

return err
}
23 changes: 23 additions & 0 deletions pkg/cmd/ai/ai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ai

import (
"github.com/MakeNowJust/heredoc"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/spf13/cobra"
)

func NewCmdAI(f *factory.Factory) *cobra.Command {
cmd := cobra.Command{
Use: "ai <command>",
Short: "Manage AI integration.",
Long: "Work with Buildkite AI.",
Example: heredoc.Doc(`
# To configure your AI token
$ bk ai configure
`),
}

cmd.AddCommand(NewCmdAIConfigure(f))

return &cmd
}
34 changes: 34 additions & 0 deletions pkg/cmd/ai/configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ai

import (
"errors"

addCmd "github.com/buildkite/cli/v3/pkg/cmd/ai/add"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/spf13/cobra"
)

func NewCmdAIConfigure(f *factory.Factory) *cobra.Command {
var force bool

cmd := &cobra.Command{
Use: "configure",
Aliases: []string{"config"},
Args: cobra.NoArgs,
Short: "Configure OpenAI token.",
RunE: func(cmd *cobra.Command, args []string) error {
// if the token already exists and --force is not used
if !force && f.Config.GetOpenAIToken() != "" {
return errors.New("OpenAI token already configured. You must use --force.")
}

return addCmd.ConfigureRun(f)
},
}

cmd.Flags().BoolVar(&force, "force", false, "Force setting a new token")

cmd.AddCommand(addCmd.NewCmdAIAdd(f))

return cmd
}
1 change: 0 additions & 1 deletion pkg/cmd/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ func NewCmdBuild(f *factory.Factory) *cobra.Command {
}

func openBuildInBrowser(openInWeb bool, webUrl string) error {

if openInWeb {
fmt.Printf("Opening %s in your browser\n", webUrl)
err := browser.OpenURL(webUrl)
Expand Down
1 change: 0 additions & 1 deletion pkg/cmd/build/cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func cancelBuild(org string, pipeline string, buildId string, web bool, f *facto
}

return io.PendingOutput(renderResult(fmt.Sprintf("Cancelling build: %s\n", *build.WebURL)))

}, fmt.Sprintf("Cancelling build #%s from pipeline %s", buildId, pipeline))

p := tea.NewProgram(l)
Expand Down
2 changes: 0 additions & 2 deletions pkg/cmd/build/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func NewCmdBuildNew(f *factory.Factory) *cobra.Command {

func newBuild(org string, pipeline string, f *factory.Factory, message string, commit string, branch string, web bool) error {
l := io.NewPendingCommand(func() tea.Msg {

if len(branch) == 0 {
p, _, err := f.RestAPIClient.Pipelines.Get(org, pipeline)
if err != nil {
Expand All @@ -85,7 +84,6 @@ func newBuild(org string, pipeline string, f *factory.Factory, message string, c

return io.PendingOutput(lipgloss.JoinVertical(lipgloss.Top,
lipgloss.NewStyle().Padding(1, 1).Render(fmt.Sprintf("Build created: %s\n", *build.WebURL))))

}, fmt.Sprintf("Starting new build for %s", pipeline))
p := tea.NewProgram(l)
_, err := p.Run()
Expand Down
1 change: 0 additions & 1 deletion pkg/cmd/build/rebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func rebuild(org string, pipeline string, buildId string, web bool, f *factory.F
}

return io.PendingOutput(renderResult(fmt.Sprintf("Build created: %s\n", *build.WebURL)))

}, fmt.Sprintf("Rerunning build #%s for pipeline %s", buildId, pipeline))

p := tea.NewProgram(l)
Expand Down
11 changes: 9 additions & 2 deletions pkg/cmd/build/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/buildkite/cli/v3/internal/build"
buildResolver "github.com/buildkite/cli/v3/internal/build/resolver"
"github.com/buildkite/cli/v3/internal/io"
"github.com/buildkite/cli/v3/internal/job"
pipelineResolver "github.com/buildkite/cli/v3/internal/pipeline/resolver"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/go-buildkite/v3/buildkite"
Expand All @@ -35,8 +36,8 @@ func NewCmdBuildView(f *factory.Factory) *cobra.Command {
If the pipeline argument is omitted, it will be resolved using the current directory.
`),
RunE: func(cmd *cobra.Command, args []string) error {
var buildArtifacts = make([]buildkite.Artifact, 0)
var buildAnnotations = make([]buildkite.Annotation, 0)
buildArtifacts := make([]buildkite.Artifact, 0)
buildAnnotations := make([]buildkite.Annotation, 0)

pipelineRes := pipelineResolver.NewAggregateResolver(
pipelineResolver.ResolveFromPositionalArgument(args, 1, f.Config),
Expand Down Expand Up @@ -86,6 +87,12 @@ func NewCmdBuildView(f *factory.Factory) *cobra.Command {

// Obtain build summary and return
summary := build.BuildSummary(b)
if len(b.Jobs) > 0 {
summary += lipgloss.NewStyle().Bold(true).Padding(0, 1).Render("\nJobs")
for _, j := range b.Jobs {
summary += job.JobSummary(j)
}
}
if len(buildArtifacts) > 0 {
summary += lipgloss.NewStyle().Bold(true).Padding(0, 1).Render("\nArtifacts")
for _, a := range buildArtifacts {
Expand Down
Loading

0 comments on commit 3774338

Please sign in to comment.