diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8201c207..ca8e2386 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,16 +18,16 @@ jobs: env: GO111MODULE: on steps: - - name: Set up Go 1.21 + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + + - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version-file: "go.mod" cache: false id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - - name: Build run: make diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 19f71a02..a29764cc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + # NOTE: this is a workaround for codeql go toolchain setup + # ref: https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 74e580d1..2189a5b8 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,11 +22,11 @@ jobs: name: lint runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version-file: "go.mod" cache: false - - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0f0b68d..31e84d53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,16 +9,16 @@ jobs: env: GO111MODULE: on steps: - - name: Set up Go 1.21 + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + + - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version-file: "go.mod" cache: false id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - # Read changelog and read versions etc. - name: Check version is mentioned in Changelog.md id: changelog_reader diff --git a/Makefile b/Makefile index 6b545d9b..e5bff5ff 100644 --- a/Makefile +++ b/Makefile @@ -7,22 +7,9 @@ ifeq ($(OS),windows) BIN = bin/$(OS)_$(ARCH)$(if $(GOARM),v$(GOARM),)/$(TARGET).exe endif -GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -GIT_HASH := $(shell git rev-parse --verify HEAD) -GIT_TAG := $(shell git describe --tags --exact-match --abbrev=0 2>/dev/null || echo "") -BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") -PLATFORM := $(OS)/$(ARCH)$(if $(GOARM),v$(GOARM),) - -ifdef GIT_TAG - VERSION := $(GIT_TAG)/$(GIT_HASH) -else - VERSION := $(GIT_BRANCH)/$(GIT_HASH) -endif +GIT_TAG := $(shell git describe --tags --exact-match --abbrev=0 2>/dev/null || echo "") -LDFLAGS := -X main.version=$(VERSION) \ - -X main.goVersion=$(shell go version | cut -d " " -f 3) \ - -X main.buildTime=$(BUILD_TIME) \ - -X 'main.platform=$(PLATFORM)' +LDFLAGS := -X main.gitTag=$(GIT_TAG) all: $(TARGET) diff --git a/go.mod b/go.mod index 414a8ec8..031ecc70 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Azure/kubelogin -go 1.18 +go 1.21.3 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 diff --git a/main.go b/main.go index 2905369e..51b1b8bc 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ func main() { pflag.CommandLine.AddGoFlag(flag.CommandLine.Lookup("v")) pflag.CommandLine.AddGoFlag(flag.CommandLine.Lookup("logtostderr")) _ = pflag.CommandLine.Set("logtostderr", "true") - root := cmd.NewRootCmd(v.String()) + root := cmd.NewRootCmd(loadVersion().String()) if err := root.Execute(); err != nil { os.Exit(1) } diff --git a/pkg/token/options_ctor.go b/pkg/internal/token/options_ctor.go similarity index 100% rename from pkg/token/options_ctor.go rename to pkg/internal/token/options_ctor.go diff --git a/pkg/token/options_ctor_test.go b/pkg/internal/token/options_ctor_test.go similarity index 100% rename from pkg/token/options_ctor_test.go rename to pkg/internal/token/options_ctor_test.go diff --git a/pkg/token/types.go b/pkg/internal/token/types.go similarity index 100% rename from pkg/token/types.go rename to pkg/internal/token/types.go diff --git a/pkg/token/options.go b/pkg/token/options.go deleted file mode 100644 index 8b729947..00000000 --- a/pkg/token/options.go +++ /dev/null @@ -1,47 +0,0 @@ -package token - -import "github.com/Azure/kubelogin/pkg/internal/token" - -// list of supported login methods for library consumers - -const ( - ServicePrincipalLogin = token.ServicePrincipalLogin - ROPCLogin = token.ROPCLogin - MSILogin = token.MSILogin - WorkloadIdentityLogin = token.WorkloadIdentityLogin -) - -// Options defines the options for getting token. -// This struct is a subset of internal/token.Options where its values are copied -// to internal type. See internal/token/options.go for details -type Options struct { - LoginMethod string - - // shared login settings - - Environment string - TenantID string - ServerID string - ClientID string - - // for ServicePrincipalLogin & ROPCLogin - - ClientSecret string - ClientCert string - ClientCertPassword string - IsPoPTokenEnabled bool - PoPTokenClaims string - - // for ROPCLogin - Username string - Password string - - // for MSILogin - - IdentityResourceID string - - // for WorkloadIdentityLogin - - AuthorityHost string - FederatedTokenFile string -} diff --git a/pkg/token/provider.go b/pkg/token/provider.go deleted file mode 100644 index 479d9ce1..00000000 --- a/pkg/token/provider.go +++ /dev/null @@ -1,41 +0,0 @@ -package token - -import ( - "context" - - "github.com/Azure/kubelogin/pkg/internal/token" -) - -type tokenProviderShim struct { - impl token.TokenProvider -} - -var _ TokenProvider = (*tokenProviderShim)(nil) - -func (tp *tokenProviderShim) GetAccessToken(ctx context.Context) (AccessToken, error) { - t, err := tp.impl.Token(ctx) - if err != nil { - return AccessToken{}, err - } - - rv := AccessToken{ - Token: t.AccessToken, - ExpiresOn: t.Expires(), - } - - return rv, nil -} - -// GetTokenProvider returns a token provider based on the given options. -func GetTokenProvider(options *Options) (TokenProvider, error) { - impl, err := token.NewTokenProvider(options.toInternalOptions()) - if err != nil { - return nil, err - } - - rv := &tokenProviderShim{ - impl: impl, - } - - return rv, nil -} diff --git a/pkg/token/provider_test.go b/pkg/token/provider_test.go deleted file mode 100644 index 4bc41e00..00000000 --- a/pkg/token/provider_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package token - -import ( - "context" - "encoding/json" - "testing" - - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/kubelogin/pkg/internal/token/mock_token" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" -) - -func TestGetTokenProvider(t *testing.T) { - t.Run("invalid login method", func(t *testing.T) { - opts := &Options{ - LoginMethod: "invalid-login-method", - } - tp, err := GetTokenProvider(opts) - assert.Error(t, err) - assert.Nil(t, tp) - }) - - t.Run("basic", func(t *testing.T) { - opts := &Options{ - LoginMethod: MSILogin, - ClientID: "client-id", - IdentityResourceID: "identity-resource-id", - ServerID: "server-id", - } - tp, err := GetTokenProvider(opts) - assert.NoError(t, err) - assert.NotNil(t, tp) - }) -} - -func TestTokenProviderShim_GetAccessToken(t *testing.T) { - t.Run("failure case", func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - mockTokenProvider := mock_token.NewMockTokenProvider(mockCtrl) - mockTokenProvider.EXPECT().Token(gomock.Any()).Return(adal.Token{}, assert.AnError) - - tp := &tokenProviderShim{ - impl: mockTokenProvider, - } - - token, err := tp.GetAccessToken(context.Background()) - assert.Equal(t, AccessToken{}, token) - assert.Equal(t, assert.AnError, err) - }) - - t.Run("success case", func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - adalToken := adal.Token{ - AccessToken: "access-token", - ExpiresOn: json.Number("1700000000"), - } - mockTokenProvider := mock_token.NewMockTokenProvider(mockCtrl) - mockTokenProvider.EXPECT().Token(gomock.Any()).Return(adalToken, nil) - - tp := &tokenProviderShim{ - impl: mockTokenProvider, - } - - token, err := tp.GetAccessToken(context.Background()) - assert.NoError(t, err) - assert.Equal(t, adalToken.AccessToken, token.Token) - assert.Equal(t, adalToken.Expires(), token.ExpiresOn) - }) -} diff --git a/version.go b/version.go index 8378df28..29cf2637 100644 --- a/version.go +++ b/version.go @@ -1,6 +1,15 @@ package main -import "fmt" +import ( + "fmt" + "runtime" + "runtime/debug" +) + +// gitTag provides the git tag used to build this binary. +// This is set via ldflags at build time, which normally set by the release pipeline. +// For go install binary, this value stays empty. +var gitTag string type Version struct { Version string @@ -9,23 +18,77 @@ type Version struct { Platform string } -var ( - v Version - version string - goVersion string - buildTime string - platform string -) +func loadVersion() Version { + rv := Version{ + Version: "unknown", + GoVersion: "unknown", + BuildTime: "unknown", + Platform: runtime.GOOS + "/" + runtime.GOARCH, + } + if gitTag != "" { + rv.Version = gitTag + } + + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return rv + } + + rv.GoVersion = buildInfo.GoVersion + + var ( + modified bool + revision string + buildTime string + ) + for _, s := range buildInfo.Settings { + if s.Value == "" { + continue + } -func init() { - v = Version{ - Version: version, - GoVersion: goVersion, - BuildTime: buildTime, - Platform: platform, + switch s.Key { + case "vcs.revision": + revision = s.Value + case "vcs.modified": + modified = s.Value == "true" + case "vcs.time": + buildTime = s.Value + } } + + // in Go install mode, this is a known issue that vcs information will not be available. + // ref: https://github.com/golang/go/issues/51279 + // Fallback to use module version and stop here as vcs information is incomplete. + if revision == "" { + if buildInfo.Main.Version != "" { + // fallback to use module version (legacy usage) + rv.Version = buildInfo.Main.Version + } + + return rv + } + + if modified { + revision += "-dirty" + } + if gitTag != "" { + revision = gitTag + "/" + revision + } + rv.Version = revision + + if buildTime != "" { + rv.BuildTime = buildTime + } + + return rv } func (ver Version) String() string { - return fmt.Sprintf("\ngit hash: %s\nGo version: %s\nBuild time: %s\nPlatform: %s", v.Version, v.GoVersion, v.BuildTime, v.Platform) + return fmt.Sprintf( + "\ngit hash: %s\nGo version: %s\nBuild time: %s\nPlatform: %s", + ver.Version, + ver.GoVersion, + ver.BuildTime, + ver.Platform, + ) } diff --git a/version_test.go b/version_test.go new file mode 100644 index 00000000..913799a8 --- /dev/null +++ b/version_test.go @@ -0,0 +1,11 @@ +package main + +import "testing" + +func Test_loadVersion(t *testing.T) { + version := loadVersion() + versionString := version.String() + if versionString == "" { + t.Errorf("versionString is empty") + } +} \ No newline at end of file