Skip to content

Commit

Permalink
Allow SPACELIFT_API_TOKEN environment variable to be overridden
Browse files Browse the repository at this point in the history
Updating the environment variable code to check for empty values. This allows `SPACELIFT_API_TOKEN` to be set to an empty string, giving the possibility to specify `SPACELIFT_API_KEY_ID` and `SPACELIFT_API_KEY_SECRET` inside a Spacelift run to use different credentials than the run credentials.
  • Loading branch information
Jeff Wenzbauer authored Jul 10, 2023
1 parent a3c1ca5 commit 7fee831
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 8 deletions.
22 changes: 14 additions & 8 deletions client/session/from_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,46 @@ const (
EnvSpaceliftAPIGitHubToken = "SPACELIFT_API_GITHUB_TOKEN" // #nosec G101
)

var (
errEnvSpaceliftAPIKeyID = fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeyID)
errEnvSpaceliftAPIKeySecret = fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeySecret)
errEnvSpaceliftAPIKeyEndpoint = fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeyEndpoint)
)

// FromEnvironment creates a Spacelift session from the environment.
func FromEnvironment(ctx context.Context, client *http.Client) func(func(string) (string, bool)) (Session, error) {
return func(lookup func(string) (string, bool)) (Session, error) {
if lookup == nil {
lookup = os.LookupEnv
}

if token, ok := lookup(EnvSpaceliftAPIToken); ok {
if token, ok := lookup(EnvSpaceliftAPIToken); ok && token != "" {
return FromAPIToken(ctx, client)(token)
}

endpoint, ok := lookup(EnvSpaceliftAPIKeyEndpoint)
if !ok {
if !ok || endpoint == "" {
// Keep backwards compatibility with older version of spacectl.
endpoint, ok = lookup(EnvSpaceliftAPIEndpoint)
if !ok {
return nil, fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeyEndpoint)
return nil, errEnvSpaceliftAPIKeyEndpoint
}
fmt.Printf("Environment variable %q is deprecated, please use %q\n", EnvSpaceliftAPIEndpoint, EnvSpaceliftAPIKeyEndpoint)
}
endpoint = strings.TrimSuffix(endpoint, "/")

if gitHubToken, ok := lookup(EnvSpaceliftAPIGitHubToken); ok {
if gitHubToken, ok := lookup(EnvSpaceliftAPIGitHubToken); ok && gitHubToken != "" {
return FromGitHubToken(ctx, client)(endpoint, gitHubToken)
}

keyID, ok := lookup(EnvSpaceliftAPIKeyID)
if !ok {
return nil, fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeyID)
if !ok || keyID == "" {
return nil, errEnvSpaceliftAPIKeyID
}

keySecret, ok := lookup(EnvSpaceliftAPIKeySecret)
if !ok {
return nil, fmt.Errorf("%s missing from the environment", EnvSpaceliftAPIKeySecret)
if !ok || keySecret == "" {
return nil, errEnvSpaceliftAPIKeySecret
}

return FromAPIKey(ctx, client)(endpoint, keyID, keySecret)
Expand Down
101 changes: 101 additions & 0 deletions client/session/from_environment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package session

import (
"context"
"github.com/franela/goblin"
"net/http"
"net/http/httptest"
"testing"
)

// lookupWithEnv creates a custom lookup func which will return desired test values
func lookupWithEnv(envSpaceliftAPIKeyEndpoint, envSpaceliftAPIToken, envSpaceliftAPIKeyID, envSpaceliftAPIKeySecret string) func(e string) (string, bool) {
return func(e string) (string, bool) {
s := ""
b := false

switch e {
case EnvSpaceliftAPIToken:
s = envSpaceliftAPIToken
b = true
case EnvSpaceliftAPIKeyEndpoint:
s = envSpaceliftAPIKeyEndpoint
b = true
case EnvSpaceliftAPIKeyID:
s = envSpaceliftAPIKeyID
b = true
case EnvSpaceliftAPIKeySecret:
s = envSpaceliftAPIKeySecret
b = true
}

return s, b
}
}

func TestFromEnvironment(t *testing.T) {
g := goblin.Goblin(t)

g.Describe("FromEnvironment", func() {
g.Describe("EnvSpaceliftAPIKeyEndpoint is not set", func() {
l := lookupWithEnv("", "", "abc123", "SuperSecret")
g.It("expect an error to find api endpoint in environment", func() {
_, err := FromEnvironment(context.TODO(), nil)(l)
g.Assert(err).Equal(errEnvSpaceliftAPIKeyEndpoint)
})
})

g.Describe("EnvSpaceliftAPIToken is set, EnvSpaceliftAPIKeyID is set, EnvSpaceliftAPIKeySecret is set", func() {
g.It("expect EnvSpaceliftAPIToken to be used and EnvSpaceliftAPIKeyID and EnvSpaceliftAPIKeySecret to be ignored", func() {
// Create a mock http server to handle JWT exchange
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{"data":{"apiKeyUser":{"jwt":"SuperSecretJWT","validUntil":123}}}`))
}))
// Close the server when test finishes
defer server.Close()
// API Token JWT generated at https://jwt.io/#debugger-io with contents:
// Header: {"alg": "HS256","typ": "JWT"}
// Payload: {"aud": "spacectl","exp": 1516239022}
l := lookupWithEnv(server.URL, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJzcGFjZWN0bCIsImV4cCI6MTUxNjIzOTAyMn0.fsKd_N2TKXpx83JSPPw47zYzQ8sbSzGVPZcyGpwp05U", "abc123", "SuperSecret")
s, err := FromEnvironment(context.TODO(), server.Client())(l)
g.Assert(err).IsNil("expected no error when creating session")
g.Assert(s.Type()).Equal(CredentialsTypeAPIToken)
})
})

g.Describe("EnvSpaceliftAPIToken is an empty string", func() {
g.Describe("EnvSpaceliftAPIKeyID is set, EnvSpaceliftAPIKeySecret is set", func() {
g.It("expect a CredentialsTypeAPIKey session to be created", func() {
// Create a mock http server to handle JWT exchange
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{"data":{"apiKeyUser":{"jwt":"SuperSecretJWT","validUntil":123}}}`))
}))
// Close the server when test finishes
defer server.Close()

l := lookupWithEnv(server.URL, "", "abc123", "SuperSecret")

s, err := FromEnvironment(context.TODO(), server.Client())(l)
g.Assert(err).IsNil("expected no error when creating session")
g.Assert(s.Type()).Equal(CredentialsTypeAPIKey)
})
})

g.Describe("EnvSpaceliftAPIKeyID is an empty string, EnvSpaceliftAPIKeySecret is set", func() {
g.It("expect an error to find api key id in environment", func() {
l := lookupWithEnv("https://spacectl.app.spacelift.io", "", "", "SuperSecret")
_, err := FromEnvironment(context.TODO(), nil)(l)
g.Assert(err).Equal(errEnvSpaceliftAPIKeyID)
})
})

g.Describe("EnvSpaceliftAPIKeyID is set, EnvSpaceliftAPIKeySecret is an empty string", func() {
g.It("expect an error to find api key secret in environment", func() {
l := lookupWithEnv("https://spacectl.app.spacelift.io", "", "abc123", "")
_, err := FromEnvironment(context.TODO(), nil)(l)
g.Assert(err).Equal(errEnvSpaceliftAPIKeySecret)
})
})
})
})
}

0 comments on commit 7fee831

Please sign in to comment.