Skip to content
Draft
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
56 changes: 42 additions & 14 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ executors:
xcode: 15.1.0
resource_class: macos.m1.medium.gen1
environment:
CGO_ENABLED: 0
TERM: xterm-256color

commands:
Expand Down Expand Up @@ -64,6 +63,27 @@ commands:
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt -q update -y
sudo apt -q install -y --no-install-recommends goreleaser=<< parameters.version >>
install-goreleaser-mac:
parameters:
version:
type: string
default: "1.19.1"
steps:
- run:
name: Install GoReleaser
command: |
ARCH=$(uname -m)
curl -sL "https://github.com/goreleaser/goreleaser/releases/download/v<< parameters.version >>/goreleaser_Darwin_${ARCH}.tar.gz" \
| tar -xz -C /tmp goreleaser
sudo mv /tmp/goreleaser /usr/local/bin/goreleaser
install-go-mac:
steps:
- run:
name: Install Go
command: |
curl -OL https://go.dev/dl/go1.23.4.darwin-arm64.pkg
sudo installer -pkg ./go1.23.4.darwin-arm64.pkg -target /
echo 'export PATH="/usr/local/go/bin:$PATH"' >> ~/.bash_profile
gomod:
steps:
- restore_cache:
Expand Down Expand Up @@ -102,10 +122,7 @@ jobs:
executor: mac
steps:
- checkout
- run: |
curl -OL https://go.dev/dl/go1.23.4.darwin-arm64.pkg
sudo installer -pkg ./go1.23.4.darwin-arm64.pkg -target /
echo 'export PATH="/usr/local/go/bin:$PATH"' >> ~/.bash_profile
- install-go-mac
- gomod
- run: make test
build:
Expand Down Expand Up @@ -184,7 +201,7 @@ jobs:
- run: golangci-lint run

deploy-test:
executor: go
executor: mac
steps:
- run:
name: Skip this job if this is a forked pull request
Expand All @@ -194,7 +211,8 @@ jobs:
circleci step halt
fi
- checkout
- install-goreleaser
- install-go-mac
- install-goreleaser-mac
- gomod
- run:
name: Release
Expand All @@ -203,17 +221,14 @@ jobs:
git config --global user.name $GH_NAME
git tag -a "v0.1.$CIRCLE_BUILD_NUM" -m "Release v0.1.$CIRCLE_BUILD_NUM"
goreleaser --skip-publish
- setup_remote_docker:
docker_layer_caching: true
- build-docker-image
- build-alpine-image
- deploy-save-workspace-and-artifacts

deploy:
executor: go
executor: mac
steps:
- checkout
- install-goreleaser
- install-go-mac
- install-goreleaser-mac
- run:
name: Tag Repo
command: |
Expand All @@ -225,6 +240,12 @@ jobs:
- run:
name: Release
command: goreleaser
- deploy-save-workspace-and-artifacts

deploy-docker:
executor: go
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: true
- run:
Expand All @@ -244,7 +265,6 @@ jobs:
docker push circleci/circleci-cli:0.1.$CIRCLE_BUILD_NUM-alpine
docker tag circleci/circleci-cli:0.1.$CIRCLE_BUILD_NUM-alpine circleci/circleci-cli:alpine
docker push circleci/circleci-cli:alpine
- deploy-save-workspace-and-artifacts

snap:
docker:
Expand Down Expand Up @@ -364,3 +384,11 @@ workflows:
only: main
context:
- devex-release
- deploy-docker:
requires:
- deploy
filters:
branches:
only: main
context:
- devex-release
19 changes: 16 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ archives:
- md_docs/LICENSE

builds:
# macOS: CGO_ENABLED=1 required for go-keyring (Apple Security framework)
- binary: circleci
env:
- CGO_ENABLED=0
- CGO_ENABLED=1
- GO111MODULE=on
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm64
Expand All @@ -28,3 +27,17 @@ builds:
# These are the defaults specified by goreleaser:
# https://github.com/goreleaser/goreleaser/blob/682c811106f56ffe06c4212de546aec62161fb9d/internal/builders/golang/build.go#L46
- -s -w -X github.com/CircleCI-Public/circleci-cli/version.Version={{.Version}} -X github.com/CircleCI-Public/circleci-cli/version.Commit={{.ShortCommit}} -X github.com/CircleCI-Public/circleci-cli/version.packageManager=release

# Windows and Linux: fully static, no CGO needed
- binary: circleci
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- windows
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w -X github.com/CircleCI-Public/circleci-cli/version.Version={{.Version}} -X github.com/CircleCI-Public/circleci-cli/version.Commit={{.ShortCommit}} -X github.com/CircleCI-Public/circleci-cli/version.packageManager=release
3 changes: 2 additions & 1 deletion cmd/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var _ = Describe("Check", func() {
)

BeforeEach(func() {
checkCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli")
checkCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli", "-tags=keychain_mock")
Expect(err).ShouldNot(HaveOccurred())

tempSettings = clitest.WithTempSettings()
Expand All @@ -52,6 +52,7 @@ var _ = Describe("Check", func() {

BeforeEach(func() {
checkCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli",
"-tags=keychain_mock",
"-ldflags",
"-X github.com/CircleCI-Public/circleci-cli/cmd.AutoUpdate=false -X github.com/CircleCI-Public/circleci-cli/version.packageManager=release",
)
Expand Down
2 changes: 1 addition & 1 deletion cmd/cmd_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var _ = BeforeSuite(func() {
SetDefaultEventuallyTimeout(time.Second * 30)

var err error
pathCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli")
pathCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli", "-tags=keychain_mock")
Ω(err).ShouldNot(HaveOccurred())
})

Expand Down
3 changes: 2 additions & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var _ = Describe("Root", func() {
tempSettings = clitest.WithTempSettings()

noUpdateCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli",
"-tags=keychain_mock",
"-ldflags",
"-X github.com/CircleCI-Public/circleci-cli/version.packageManager=homebrew",
)
Expand Down Expand Up @@ -75,7 +76,7 @@ var _ = Describe("Root", func() {
)

BeforeEach(func() {
updateCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli")
updateCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli", "-tags=keychain_mock")
Expect(err).ShouldNot(HaveOccurred())

command = exec.Command(updateCLI, "help",
Expand Down
4 changes: 2 additions & 2 deletions cmd/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var _ = Describe("Update", func() {

Describe("telemetry", func() {
It("should send telemetry event when calling parent command", func() {
updateCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli")
updateCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli", "-tags=keychain_mock")
Expect(err).ShouldNot(HaveOccurred())

command = exec.Command(updateCLI,
Expand Down Expand Up @@ -170,7 +170,7 @@ var _ = Describe("Update", func() {

Describe("update", func() {
BeforeEach(func() {
updateCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli")
updateCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli", "-tags=keychain_mock")
Expect(err).ShouldNot(HaveOccurred())

command = exec.Command(updateCLI,
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
)

require (
al.essio.dev/pkg/shellescape v1.5.1 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
Expand All @@ -60,6 +61,7 @@ require (
github.com/cloudflare/circl v1.6.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
Expand All @@ -69,6 +71,7 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
Expand Down Expand Up @@ -110,6 +113,7 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/zalando/go-keyring v0.2.6 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
Expand Down Expand Up @@ -72,6 +74,8 @@ github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKA
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -129,6 +133,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
Expand Down Expand Up @@ -330,6 +336,8 @@ github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBe
github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
Expand Down
17 changes: 17 additions & 0 deletions settings/keychain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package settings

import "github.com/zalando/go-keyring"

const KeychainService = "com.circleci.cli"

func GetTokenFromKeychain(host string) (string, error) {
return keyring.Get(KeychainService, host)
}

func SetTokenInKeychain(host, token string) error {
return keyring.Set(KeychainService, host, token)
}

func DeleteTokenFromKeychain(host string) error {
return keyring.Delete(KeychainService, host)
}
12 changes: 12 additions & 0 deletions settings/keychain_mock_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build keychain_mock

package settings

import "github.com/zalando/go-keyring"

// init calls MockInit when the binary is compiled with -tags=keychain_mock
// (i.e. in integration test builds) so keychain calls use an in-memory store
// instead of the OS keychain, preventing hangs on CI runners.
func init() {
keyring.MockInit()
}
20 changes: 20 additions & 0 deletions settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

"github.com/zalando/go-keyring"
yaml "gopkg.in/yaml.v3"

"github.com/CircleCI-Public/circleci-cli/api/header"
Expand Down Expand Up @@ -162,6 +163,25 @@ func (cfg *Config) LoadFromDisk() error {
return nil
}

if cfg.Host != "" {
keychainToken, keychainErr := GetTokenFromKeychain(cfg.Host)
if keychainErr == nil && keychainToken != "" {
cfg.Token = keychainToken
} else if errors.Is(keychainErr, keyring.ErrNotFound) && cfg.Token != "" {
// Migrate: YAML has token but keychain doesn't — move it silently
if setErr := SetTokenInKeychain(cfg.Host, cfg.Token); setErr == nil {
savedToken := cfg.Token
cfg.Token = ""
enc, merr := yaml.Marshal(cfg)
cfg.Token = savedToken
if merr == nil {
_ = os.WriteFile(cfg.FileUsed, enc, 0600)
}
}
}
// If keychain is unavailable (other error), YAML token is used as-is (silent fallback)
}

return cfg.WithHTTPClient()
}

Expand Down
5 changes: 5 additions & 0 deletions settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import (
"strings"
"testing"

"github.com/zalando/go-keyring"
"github.com/CircleCI-Public/circleci-cli/settings"
"gotest.tools/v3/assert"
)

func init() {
keyring.MockInit()
}

func TestWithHTTPClient(t *testing.T) {
table := []struct {
label string
Expand Down