Skip to content

Commit 27795e7

Browse files
authored
Merge pull request #32 from thalassa-cloud/tcloud-support-env-variables
feat: Improved support for access token authentication and support env variables
2 parents 0bfea53 + dcfa88c commit 27795e7

7 files changed

Lines changed: 109 additions & 42 deletions

File tree

cmd/context/create.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"net/url"
8+
"strings"
89

910
"github.com/spf13/cobra"
1011

@@ -39,11 +40,19 @@ var createCmd = &cobra.Command{
3940

4041
oidcClientID := contextstate.ClientIdOrFlag()
4142
oidcClientSecret := contextstate.ClientSecretOrFlag()
42-
if token == "" && oidcClientID == "" && oidcClientSecret == "" {
43+
accessToken := contextstate.AccessToken()
44+
if token == "" && oidcClientID == "" && oidcClientSecret == "" && accessToken == "" {
4345
return errors.New("no token or oidc client id and secret set")
4446
}
4547

46-
if oidcClientID != "" && oidcClientSecret != "" {
48+
if accessToken != "" {
49+
if strings.HasPrefix(accessToken, "tc_pat_") {
50+
return errors.New("access token is a personal access token, use 'tcloud context login --token <token>' to login with a personal access token")
51+
}
52+
if err := contextstate.LoginWithAccessToken(ctx, accessToken, apiURL); err != nil {
53+
return fmt.Errorf("failed to login with access token: %w", err)
54+
}
55+
} else if oidcClientID != "" && oidcClientSecret != "" {
4756
if err := contextstate.LoginWithAPIEndpointOidc(ctx, oidcClientID, oidcClientSecret, apiURL); err != nil {
4857
return fmt.Errorf("failed to login with oidc: %w", err)
4958
}

cmd/context/login.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ package context
33
import (
44
"errors"
55
"fmt"
6+
"strings"
67

78
"github.com/spf13/cobra"
89

910
"github.com/thalassa-cloud/cli/internal/config/contextstate"
1011
"github.com/thalassa-cloud/client-go/pkg/client"
11-
"github.com/thalassa-cloud/client-go/thalassa"
1212
)
1313

1414
// createCmd represents the create command
1515
var loginCmd = &cobra.Command{
1616
Use: "login",
1717
Short: "Login to Thalassa Cloud",
18-
Long: "Login to Thalassa Cloud using a personal access token, using the current context. Overrides the current context if --name is set.",
18+
Long: "Login to Thalassa Cloud using a personal access token, access token, or OIDC client id and secret, using the current context. Overrides the current context if --name is set.",
1919
Args: cobra.NoArgs,
2020
RunE: func(cmd *cobra.Command, args []string) error {
2121
token := contextstate.PersonalAccessToken()
22+
accessToken := contextstate.AccessToken()
2223
apiURL := contextstate.Server()
2324

2425
if apiURL == "" {
@@ -33,28 +34,28 @@ var loginCmd = &cobra.Command{
3334
opts := []client.Option{}
3435
if oidcClientID != "" && oidcClientSecret != "" {
3536
opts = append(opts, client.WithAuthOIDC(oidcClientID, oidcClientSecret, tokenURL))
36-
} else {
37+
} else if accessToken != "" {
38+
opts = append(opts, client.WithToken(accessToken))
39+
} else if token != "" {
3740
opts = append(opts, client.WithAuthPersonalToken(token))
3841
}
3942
if len(opts) == 0 {
4043
return errors.New("no authentication method provided")
4144
}
4245
opts = append(opts, client.WithBaseURL(apiURL))
43-
opts = append(opts, client.WithOrganisation(contextstate.Organisation()))
44-
client, err := thalassa.NewClient(opts...)
45-
46-
// Test the token and api endpoint
47-
if err != nil {
48-
return err
49-
}
50-
_, err = client.Me().ListMyOrganisations(cmd.Context())
51-
if err != nil {
52-
return fmt.Errorf("failed to test token and api endpoint: %w", err)
46+
if contextstate.Organisation() != "" {
47+
opts = append(opts, client.WithOrganisation(contextstate.Organisation()))
5348
}
5449

5550
if oidcClientID != "" && oidcClientSecret != "" {
5651
return contextstate.LoginWithAPIEndpointOidc(cmd.Context(), oidcClientID, oidcClientSecret, apiURL)
5752
}
53+
if accessToken != "" {
54+
if strings.HasPrefix(accessToken, "tc_pat_") {
55+
return errors.New("access token is a personal access token, use 'tcloud context login --token <token>' to login with a personal access token")
56+
}
57+
return contextstate.LoginWithAccessToken(cmd.Context(), accessToken, apiURL)
58+
}
5859
return contextstate.LoginWithAPIEndpoint(cmd.Context(), token, apiURL)
5960
},
6061
}

cmd/context/organisation.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package context
22

33
import (
4-
"errors"
54
"fmt"
65
"os"
76

@@ -48,9 +47,8 @@ func getSelectedOrganisation(args []string) (string, error) {
4847
return fzf.InteractiveChoice(command)
4948
} else if len(args) == 1 {
5049
return args[0], nil
51-
} else {
52-
return "", errors.New("invalid organisation")
5350
}
51+
return "", nil
5452
}
5553

5654
func init() {

internal/config/contextstate/login.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func LoginWithAPIEndpointOidc(ctx context.Context, clientID, clientSecret, apiEn
3232
context.Users.User.ClientID = clientID
3333
context.Users.User.ClientSecret = clientSecret
3434
context.Users.User.Token = ""
35+
context.Users.User.AccessToken = ""
3536

3637
context.Servers.API.Server = u.String()
3738
if err := CombineConfigContext(context); err != nil {
@@ -40,6 +41,28 @@ func LoginWithAPIEndpointOidc(ctx context.Context, clientID, clientSecret, apiEn
4041
return Save()
4142
}
4243

44+
func LoginWithAccessToken(ctx context.Context, accessToken, apiEndpoint string) error {
45+
context, err := GetContextConfiguration()
46+
if err != nil {
47+
return err
48+
}
49+
// validate api endpoint
50+
u, err := url.Parse(apiEndpoint)
51+
if err != nil {
52+
return fmt.Errorf("invalid api endpoint: %w", err)
53+
}
54+
55+
context.Users.User.AccessToken = accessToken
56+
context.Users.User.Token = ""
57+
context.Users.User.ClientID = ""
58+
context.Users.User.ClientSecret = ""
59+
context.Servers.API.Server = u.String()
60+
if err := CombineConfigContext(context); err != nil {
61+
return err
62+
}
63+
return Save()
64+
}
65+
4366
func LoginWithAPIEndpoint(ctx context.Context, token, apiEndpoint string) error {
4467
context, err := GetContextConfiguration()
4568
if err != nil {
@@ -54,6 +77,7 @@ func LoginWithAPIEndpoint(ctx context.Context, token, apiEndpoint string) error
5477
context.Users.User.Token = token
5578
context.Users.User.ClientID = ""
5679
context.Users.User.ClientSecret = ""
80+
context.Users.User.AccessToken = ""
5781

5882
context.Servers.API.Server = u.String()
5983
if err := CombineConfigContext(context); err != nil {
@@ -76,6 +100,9 @@ func Logout() error {
76100
return err
77101
}
78102
context.Users.User.Token = ""
103+
context.Users.User.AccessToken = ""
104+
context.Users.User.ClientID = ""
105+
context.Users.User.ClientSecret = ""
79106
if err := CombineConfigContext(context); err != nil {
80107
return err
81108
}

internal/config/contextstate/manager.go

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ import (
1111

1212
const (
1313
DefaultConfigFilename = ".tcloud"
14+
DefaultAPIURL = "https://api.thalassa.cloud"
15+
)
16+
17+
const (
18+
ThalassaConfigEnvVar = "THALASSA_CONFIG"
19+
ThalassaCConfigEnvVar = "THALASSACONFIG"
20+
21+
ThalassaAccessTokenEnvVar = "THALASSA_ACCESS_TOKEN"
22+
ThalassaPersonalAccessTokenEnvVar = "THALASSA_PERSONAL_ACCESS_TOKEN"
23+
ThalassaOIDCClientIDEnvVar = "THALASSA_OIDC_CLIENT_ID"
24+
ThalassaOIDCClientSecretEnvVar = "THALASSA_OIDC_CLIENT_SECRET"
25+
ThalassaOrganisationIDEnvVar = "THALASSA_ORGANISATION_ID"
26+
27+
ThalassaAPIEndpointEnvVar = "THALASSA_API_ENDPOINT"
1428
)
1529

1630
var (
@@ -77,10 +91,10 @@ func Init() {
7791
}
7892

7993
func getConfigFilename() string {
80-
if configFilename := os.Getenv("THALASSA_CONFIG"); configFilename != "" {
94+
if configFilename := os.Getenv(ThalassaConfigEnvVar); configFilename != "" {
8195
return configFilename
8296
}
83-
if configFilename := os.Getenv("THALASSACONFIG"); configFilename != "" {
97+
if configFilename := os.Getenv(ThalassaCConfigEnvVar); configFilename != "" {
8498
return configFilename
8599
}
86100
home, err := homedir.Dir()
@@ -119,35 +133,40 @@ func Organisation() string {
119133
if OrganisationFlag != "" {
120134
return OrganisationFlag
121135
}
136+
if organisation := os.Getenv(ThalassaOrganisationIDEnvVar); organisation != "" {
137+
return organisation
138+
}
122139

123140
currentcontext, err := globalConfigManager.Get()
124141
if err != nil {
125-
fmt.Fprintln(os.Stderr, err)
126-
os.Exit(1)
142+
return ""
127143
}
128-
organisation := currentcontext.Organisation
129-
return organisation
144+
return currentcontext.Organisation
130145
}
131146

132147
func Server() string {
133148
if EndpointFlag != "" {
134149
return EndpointFlag
135150
}
151+
if server := os.Getenv(ThalassaAPIEndpointEnvVar); server != "" {
152+
return server
153+
}
136154

137155
currentcontext, err := globalConfigManager.Get()
138156
if err != nil {
139-
fmt.Fprintln(os.Stderr, err)
140-
os.Exit(1)
157+
return DefaultAPIURL
141158
}
142-
143-
return currentcontext.Servers.API.Server
159+
if currentcontext.Servers.API.Server != "" {
160+
return currentcontext.Servers.API.Server
161+
}
162+
return DefaultAPIURL
144163
}
145164

146165
func AccessToken() string {
147166
if AccessTokenFlag != "" {
148167
return AccessTokenFlag
149168
}
150-
if accessToken := os.Getenv("THALASSA_ACCESS_TOKEN"); accessToken != "" {
169+
if accessToken := os.Getenv(ThalassaAccessTokenEnvVar); accessToken != "" {
151170
return accessToken
152171
}
153172
return ""
@@ -157,13 +176,12 @@ func PersonalAccessToken() string {
157176
if PersonalAccessTokenFlag != "" {
158177
return PersonalAccessTokenFlag
159178
}
160-
if personalAccessToken := os.Getenv("THALASSA_PERSONAL_ACCESS_TOKEN"); personalAccessToken != "" {
179+
if personalAccessToken := os.Getenv(ThalassaPersonalAccessTokenEnvVar); personalAccessToken != "" {
161180
return personalAccessToken
162181
}
163182
currentcontext, err := globalConfigManager.Get()
164183
if err != nil {
165-
fmt.Fprintln(os.Stderr, err)
166-
os.Exit(1)
184+
return ""
167185
}
168186

169187
return currentcontext.Users.User.Token
@@ -173,14 +191,16 @@ func ClientIdOrFlag() string {
173191
if OidcClientIDFlag != "" {
174192
return OidcClientIDFlag
175193
}
194+
if clientID := os.Getenv(ThalassaOIDCClientIDEnvVar); clientID != "" {
195+
return clientID
196+
}
176197
return ClientId()
177198
}
178199

179200
func ClientId() string {
180201
currentcontext, err := globalConfigManager.Get()
181202
if err != nil {
182-
fmt.Fprintln(os.Stderr, err)
183-
os.Exit(1)
203+
return ""
184204
}
185205
return currentcontext.Users.User.ClientID
186206
}
@@ -189,14 +209,16 @@ func ClientSecretOrFlag() string {
189209
if OidcClientSecretFlag != "" {
190210
return OidcClientSecretFlag
191211
}
212+
if clientSecret := os.Getenv(ThalassaOIDCClientSecretEnvVar); clientSecret != "" {
213+
return clientSecret
214+
}
192215
return ClientSecret()
193216
}
194217

195218
func ClientSecret() string {
196219
currentcontext, err := globalConfigManager.Get()
197220
if err != nil {
198-
fmt.Fprintln(os.Stderr, err)
199-
os.Exit(1)
221+
return ""
200222
}
201223
return currentcontext.Users.User.ClientSecret
202224
}

internal/config/contextstate/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Users struct {
4848

4949
type User struct {
5050
Token string `yaml:"token,omitempty"`
51+
AccessToken string `yaml:"accessToken,omitempty"`
5152
ClientID string `yaml:"clientID,omitempty"`
5253
ClientSecret string `yaml:"clientSecret,omitempty"`
5354
}

internal/thalassaclient/client.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,26 @@ import (
1111
)
1212

1313
func GetThalassaClient() (thalassa.Client, error) {
14+
var org string
1415
context, err := contextstate.GetContext()
1516
if err != nil {
16-
return nil, err
17+
if !errors.Is(err, contextstate.ErrContextNotFound) {
18+
return nil, fmt.Errorf("failed to get context: %w", err)
19+
}
20+
}
21+
endpoint := "https://api.thalassa.cloud"
22+
if context.Servers.API.Server != "" {
23+
endpoint = context.Servers.API.Server
1724
}
1825

1926
opts := []client.Option{
20-
client.WithBaseURL(context.Servers.API.Server),
21-
client.WithOrganisation(context.Organisation),
27+
client.WithBaseURL(endpoint),
2228
client.WithUserAgent(version.UserAgent()),
2329
}
30+
org = context.Organisation
31+
if org != "" {
32+
opts = append(opts, client.WithOrganisation(org))
33+
}
2434

2535
if contextstate.Debug() {
2636
fmt.Println("Debug mode enabled")
@@ -31,12 +41,11 @@ func GetThalassaClient() (thalassa.Client, error) {
3141
token := contextstate.PersonalAccessToken()
3242
clientID := contextstate.ClientIdOrFlag()
3343
clientSecret := contextstate.ClientSecretOrFlag()
34-
3544
accessToken := contextstate.AccessToken()
3645
if accessToken != "" {
3746
opts = append(opts, client.WithToken(accessToken))
3847
} else if clientID != "" && clientSecret != "" {
39-
opts = append(opts, client.WithAuthOIDC(clientID, clientSecret, fmt.Sprintf("%s/oidc/token", context.Servers.API.Server)))
48+
opts = append(opts, client.WithAuthOIDC(clientID, clientSecret, fmt.Sprintf("%s/oidc/token", endpoint)))
4049
} else if token != "" {
4150
opts = append(opts, client.WithAuthPersonalToken(token))
4251
} else {
@@ -45,7 +54,7 @@ func GetThalassaClient() (thalassa.Client, error) {
4554

4655
client, err := thalassa.NewClient(opts...)
4756
if err != nil {
48-
return nil, err
57+
return nil, fmt.Errorf("failed to create client: %w", err)
4958
}
5059
return client, nil
5160
}

0 commit comments

Comments
 (0)