Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions internal/ci/ci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Package ci provides helpers for detecting CI/CD execution environments.
package ci

import "os"

// IsCI determines if the current execution context is within a known CI/CD system.
// This is based on https://github.com/watson/ci-info/blob/HEAD/index.js.
func IsCI() bool {
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
}

// IsGitHubActions determines if the current execution context is within GitHub Actions.
// GitHub Actions sets the GITHUB_ACTIONS environment variable to "true" for all steps.
// See https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables.
func IsGitHubActions() bool {
return os.Getenv("GITHUB_ACTIONS") == "true"
}
56 changes: 56 additions & 0 deletions internal/ci/ci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package ci

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestIsCI(t *testing.T) {
tests := []struct {
name string
env map[string]string
want bool
}{
{name: "no CI env vars", env: map[string]string{}, want: false},
{name: "CI set", env: map[string]string{"CI": "true"}, want: true},
{name: "BUILD_NUMBER set", env: map[string]string{"BUILD_NUMBER": "42"}, want: true},
{name: "RUN_ID set", env: map[string]string{"RUN_ID": "abc"}, want: true},
{name: "CI empty string", env: map[string]string{"CI": ""}, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("CI", "")
t.Setenv("BUILD_NUMBER", "")
t.Setenv("RUN_ID", "")
for k, v := range tt.env {
t.Setenv(k, v)
}
assert.Equal(t, tt.want, IsCI())
})
}
}

func TestIsGitHubActions(t *testing.T) {
tests := []struct {
name string
value string
set bool
want bool
}{
{name: "unset", set: false, want: false},
{name: "true", value: "true", set: true, want: true},
{name: "false", value: "false", set: true, want: false},
{name: "empty", value: "", set: true, want: false},
{name: "other value", value: "yes", set: true, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("GITHUB_ACTIONS", "")
if tt.set {
t.Setenv("GITHUB_ACTIONS", tt.value)
}
assert.Equal(t, tt.want, IsGitHubActions())
})
}
}
9 changes: 6 additions & 3 deletions internal/ghcmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/agents"
"github.com/cli/cli/v2/internal/build"
"github.com/cli/cli/v2/internal/ci"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/config/migration"
"github.com/cli/cli/v2/internal/gh"
Expand Down Expand Up @@ -69,9 +70,11 @@ func Main() exitCode {
ghExecutablePath := executablePath("gh")

additionalCommonDimensions := ghtelemetry.Dimensions{
"version": strings.TrimPrefix(buildVersion, "v"),
"is_tty": strconv.FormatBool(ioStreams.IsStdoutTTY()),
"agent": string(agents.Detect()),
"version": strings.TrimPrefix(buildVersion, "v"),
"is_tty": strconv.FormatBool(ioStreams.IsStdoutTTY()),
"agent": string(agents.Detect()),
"ci": strconv.FormatBool(ci.IsCI()),
"github_actions": strconv.FormatBool(ci.IsGitHubActions()),
}

var telemetryService ghtelemetry.Service
Expand Down
13 changes: 3 additions & 10 deletions internal/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"time"

"github.com/cli/cli/v2/internal/ci"
"github.com/cli/cli/v2/pkg/extensions"
"github.com/hashicorp/go-version"
"github.com/mattn/go-isatty"
Expand Down Expand Up @@ -42,7 +43,7 @@ func ShouldCheckForExtensionUpdate() bool {
if os.Getenv("CODESPACES") != "" {
return false
}
return !IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
return !ci.IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
}

// CheckForExtensionUpdate checks whether an update exists for a specific extension based on extension type and recency of last check within past 24 hours.
Expand Down Expand Up @@ -83,7 +84,7 @@ func ShouldCheckForUpdate() bool {
if os.Getenv("CODESPACES") != "" {
return false
}
return !IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
return !ci.IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
}

// CheckForUpdate checks whether an update exists for the GitHub CLI based on recency of last check within past 24 hours.
Expand Down Expand Up @@ -182,11 +183,3 @@ func versionGreaterThan(v, w string) bool {
func IsTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}

// IsCI determines if the current execution context is within a known CI/CD system.
// This is based on https://github.com/watson/ci-info/blob/HEAD/index.js.
func IsCI() bool {
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
}
4 changes: 2 additions & 2 deletions pkg/cmd/copilot/copilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import (
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/ci"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/internal/safepaths"
"github.com/cli/cli/v2/internal/update"
ghzip "github.com/cli/cli/v2/internal/zip"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
Expand Down Expand Up @@ -150,7 +150,7 @@ func runCopilot(opts *CopilotOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "%s Copilot CLI was not installed", opts.IO.ColorScheme().WarningIcon())
return cmdutil.SilentError
}
} else if !update.IsCI() {
} else if !ci.IsCI() {
fmt.Fprintf(opts.IO.ErrOut, "%s Copilot CLI not installed", opts.IO.ColorScheme().WarningIcon())
return cmdutil.SilentError
}
Expand Down
Loading