Skip to content

Commit 138af77

Browse files
committed
polish
1 parent d8ea803 commit 138af77

File tree

20 files changed

+674
-778
lines changed

20 files changed

+674
-778
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
- name: Regen code, confirm unchanged
6161
if: ${{ matrix.checkGenCommands }}
6262
run: |
63-
go run ./cmd/gen-commands -input internal/temporalcli/commands.yaml -input cliext/option-sets.yaml -pkg temporalcli -context "*CommandContext" > internal/temporalcli/commands.gen.go
63+
go run ./cmd/gen-commands -input internal/temporalcli/commands.yaml -pkg temporalcli -context "*CommandContext" > internal/temporalcli/commands.gen.go
6464
go run ./cmd/gen-commands -input cliext/option-sets.yaml -pkg cliext > cliext/flags.gen.go
6565
git diff --exit-code
6666

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ This will expect every non-parent command to have a `run` method, so for new com
3131
Once a command is updated, the CI will automatically generate new docs
3232
and create a PR in the Documentation repo with the corresponding updates. To generate these docs locally, run:
3333

34-
go run ./cmd/gen-docs -input internal/temporalcli/commands.yaml -output dist/docs
34+
go run ./cmd/gen-docs -input internal/temporalcli/commands.yaml -input cliext/option-sets.yaml -output dist/docs
3535

3636
This will auto-generate a new set of docs to `dist/docs/`. If a new root command is added, a new file will be automatically generated, like `temporal activity` and `activity.mdx`.
3737

Makefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@ all: gen build
44

55
gen: internal/temporalcli/commands.gen.go cliext/flags.gen.go
66

7-
# Generate CLI commands code from commands.yaml, using option sets from cliext
8-
internal/temporalcli/commands.gen.go: internal/temporalcli/commands.yaml cliext/option-sets.yaml
7+
internal/temporalcli/commands.gen.go: internal/temporalcli/commands.yaml
98
go run ./cmd/gen-commands \
109
-input internal/temporalcli/commands.yaml \
11-
-input cliext/option-sets.yaml \
1210
-pkg temporalcli \
1311
-context "*CommandContext" > $@
1412

15-
# Generate cliext flags code from option-sets.yaml (no commands = cliext mode)
1613
cliext/flags.gen.go: cliext/option-sets.yaml
1714
go run ./cmd/gen-commands \
1815
-input cliext/option-sets.yaml \

cliext/client.go

Lines changed: 58 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ package cliext
22

33
import (
44
"context"
5-
"crypto/tls"
6-
"crypto/x509"
75
"fmt"
86
"log/slog"
97
"net/http"
10-
"os"
118
"strings"
129

1310
"go.temporal.io/sdk/client"
@@ -17,7 +14,7 @@ import (
1714
"google.golang.org/grpc"
1815
)
1916

20-
// ClientOptionsBuilder contains options for building Temporal client options.
17+
// ClientOptionsBuilder contains options for building SDK client.Options
2118
type ClientOptionsBuilder struct {
2219
// CommonOptions contains common CLI options including profile config.
2320
CommonOptions CommonOptions
@@ -29,14 +26,14 @@ type ClientOptionsBuilder struct {
2926
// Logger is the slog logger to use for the client. If set, it will be
3027
// wrapped with the SDK's structured logger adapter.
3128
Logger *slog.Logger
29+
// oauthConfig is initialized during Build() if OAuth is configured.
30+
oauthConfig *OAuthConfig
3231
}
3332

34-
// BuildClientOptions creates SDK client options from a ClientOptionsBuilder.
35-
// If OAuth is configured and no APIKey is set, OAuth will be used to obtain an access token.
36-
// Returns the client options and the resolved namespace (which may differ from input if loaded from profile).
37-
func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.Options, string, error) {
38-
cfg := opts.ClientOptions
39-
common := opts.CommonOptions
33+
// Build creates SDK client.Options
34+
func (b *ClientOptionsBuilder) Build(ctx context.Context) (client.Options, error) {
35+
cfg := b.ClientOptions
36+
common := b.CommonOptions
4037

4138
// Load a client config profile if configured
4239
var profile envconfig.ClientConfigProfile
@@ -47,22 +44,22 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
4744
ConfigFileProfile: common.Profile,
4845
DisableFile: common.DisableConfigFile,
4946
DisableEnv: common.DisableConfigEnv,
50-
EnvLookup: opts.EnvLookup,
47+
EnvLookup: b.EnvLookup,
5148
})
5249
if err != nil {
53-
return client.Options{}, "", fmt.Errorf("failed loading client config: %w", err)
50+
return client.Options{}, fmt.Errorf("failed loading client config: %w", err)
5451
}
5552
}
5653

5754
// To support legacy TLS environment variables, if they are present, we will
5855
// have them force-override anything loaded from existing file or env
59-
if !common.DisableConfigEnv && opts.EnvLookup != nil {
60-
oldEnvTLSCert, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT")
61-
oldEnvTLSCertData, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT_DATA")
62-
oldEnvTLSKey, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY")
63-
oldEnvTLSKeyData, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY_DATA")
64-
oldEnvTLSCA, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_CA")
65-
oldEnvTLSCAData, _ := opts.EnvLookup.LookupEnv("TEMPORAL_TLS_CA_DATA")
56+
if !common.DisableConfigEnv && b.EnvLookup != nil {
57+
oldEnvTLSCert, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT")
58+
oldEnvTLSCertData, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT_DATA")
59+
oldEnvTLSKey, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY")
60+
oldEnvTLSKeyData, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY_DATA")
61+
oldEnvTLSCA, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_CA")
62+
oldEnvTLSCAData, _ := b.EnvLookup.LookupEnv("TEMPORAL_TLS_CA_DATA")
6663
if oldEnvTLSCert != "" || oldEnvTLSCertData != "" ||
6764
oldEnvTLSKey != "" || oldEnvTLSKeyData != "" ||
6865
oldEnvTLSCA != "" || oldEnvTLSCAData != "" {
@@ -96,13 +93,10 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
9693
if cfg.FlagSet != nil && cfg.FlagSet.Changed("address") {
9794
profile.Address = cfg.Address
9895
}
99-
resolvedNamespace := profile.Namespace
10096
if cfg.FlagSet != nil && cfg.FlagSet.Changed("namespace") {
10197
profile.Namespace = cfg.Namespace
102-
resolvedNamespace = cfg.Namespace
10398
} else if profile.Namespace == "" {
10499
profile.Namespace = cfg.Namespace
105-
resolvedNamespace = cfg.Namespace
106100
}
107101

108102
// Set API key on profile if provided (OAuth credentials are set later on clientOpts)
@@ -114,7 +108,7 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
114108
if len(cfg.GrpcMeta) > 0 {
115109
grpcMetaFromArg, err := parseKeyValuePairs(cfg.GrpcMeta)
116110
if err != nil {
117-
return client.Options{}, "", fmt.Errorf("invalid gRPC meta: %w", err)
111+
return client.Options{}, fmt.Errorf("invalid gRPC meta: %w", err)
118112
}
119113
if len(profile.GRPCMeta) == 0 {
120114
profile.GRPCMeta = make(map[string]string, len(cfg.GrpcMeta))
@@ -125,6 +119,8 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
125119
}
126120

127121
// If any of these TLS values are present, set TLS if not set, and set values.
122+
// NOTE: This means that tls=false does not explicitly disable TLS when set
123+
// via envconfig.
128124
if cfg.Tls ||
129125
cfg.TlsCertPath != "" || cfg.TlsKeyPath != "" || cfg.TlsCaPath != "" ||
130126
cfg.TlsCertData != "" || cfg.TlsKeyData != "" || cfg.TlsCaData != "" {
@@ -178,7 +174,12 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
178174
// Convert profile to client options.
179175
clientOpts, err := profile.ToClientOptions(envconfig.ToClientOptionsRequest{})
180176
if err != nil {
181-
return client.Options{}, "", fmt.Errorf("failed to build client options: %w", err)
177+
return client.Options{}, fmt.Errorf("failed to build client options: %w", err)
178+
}
179+
180+
// Set client authority if provided.
181+
if cfg.ClientAuthority != "" {
182+
clientOpts.ConnectionOptions.Authority = cfg.ClientAuthority
182183
}
183184

184185
// Set identity if provided.
@@ -187,43 +188,48 @@ func BuildClientOptions(ctx context.Context, opts ClientOptionsBuilder) (client.
187188
}
188189

189190
// Set logger if provided.
190-
if opts.Logger != nil {
191-
clientOpts.Logger = log.NewStructuredLogger(opts.Logger)
191+
if b.Logger != nil {
192+
clientOpts.Logger = log.NewStructuredLogger(b.Logger)
192193
}
193194

194-
// Set OAuth credentials if configured and no API key is set.
195-
// OAuth config is loaded on-demand from the config file.
195+
// Attempt to configure OAuth config if no API key is set.
196196
if cfg.ApiKey == "" {
197-
clientOpts.Credentials = client.NewAPIKeyDynamicCredentials(
198-
NewOAuthDynamicTokenProvider(opts))
199-
}
200-
201-
// Set client authority if provided.
202-
if cfg.ClientAuthority != "" {
203-
clientOpts.ConnectionOptions.Authority = cfg.ClientAuthority
204-
}
205-
206-
// Set connect timeout for GetSystemInfo if provided.
207-
if common.ClientConnectTimeout != 0 {
208-
clientOpts.ConnectionOptions.GetSystemInfoTimeout = common.ClientConnectTimeout.Duration()
197+
result, err := LoadClientOAuth(LoadClientOAuthOptions{
198+
ConfigFilePath: common.ConfigFile,
199+
ProfileName: common.Profile,
200+
EnvLookup: b.EnvLookup,
201+
})
202+
if err != nil {
203+
return client.Options{}, fmt.Errorf("failed to load OAuth config: %w", err)
204+
}
205+
// Only set credentials if OAuth is configured with an access token
206+
if result.OAuth != nil && result.OAuth.AccessToken != "" {
207+
b.oauthConfig = result.OAuth
208+
clientOpts.Credentials = client.NewAPIKeyDynamicCredentials(b.getOAuthToken)
209+
}
209210
}
210211

211-
// Add codec interceptor if codec endpoint is configured.
212+
// Remote codec
212213
if profile.Codec != nil && profile.Codec.Endpoint != "" {
213214
codecHeaders, err := parseKeyValuePairs(cfg.CodecHeader)
214215
if err != nil {
215-
return client.Options{}, "", fmt.Errorf("invalid codec headers: %w", err)
216+
return client.Options{}, fmt.Errorf("invalid codec headers: %w", err)
216217
}
217218
interceptor, err := newPayloadCodecInterceptor(
218219
profile.Namespace, profile.Codec.Endpoint, profile.Codec.Auth, codecHeaders)
219220
if err != nil {
220-
return client.Options{}, "", fmt.Errorf("failed creating payload codec interceptor: %w", err)
221+
return client.Options{}, fmt.Errorf("failed creating payload codec interceptor: %w", err)
221222
}
222223
clientOpts.ConnectionOptions.DialOptions = append(
223224
clientOpts.ConnectionOptions.DialOptions, grpc.WithChainUnaryInterceptor(interceptor))
224225
}
225226

226-
return clientOpts, resolvedNamespace, nil
227+
// Set connect timeout for GetSystemInfo if provided.
228+
if common.ClientConnectTimeout != 0 {
229+
clientOpts.ConnectionOptions.GetSystemInfoTimeout = common.ClientConnectTimeout.Duration()
230+
}
231+
232+
return clientOpts, nil
227233
}
228234

229235
// parseKeyValuePairs parses a slice of "KEY=VALUE" strings into a map.
@@ -270,52 +276,13 @@ func newPayloadCodecInterceptor(
270276
)
271277
}
272278

273-
// BuildTLSConfig creates a TLS configuration from the ClientOptions TLS settings.
274-
// This is useful when you need the TLS config separately from the full client options.
275-
func BuildTLSConfig(cfg ClientOptions) (*tls.Config, error) {
276-
if !cfg.Tls && cfg.TlsCertPath == "" && cfg.TlsKeyPath == "" && cfg.TlsCaPath == "" &&
277-
cfg.TlsCertData == "" && cfg.TlsKeyData == "" && cfg.TlsCaData == "" {
278-
return nil, nil
279-
}
280-
281-
tlsConfig := &tls.Config{
282-
ServerName: cfg.TlsServerName,
283-
InsecureSkipVerify: cfg.TlsDisableHostVerification,
284-
}
285-
286-
// Load client certificate.
287-
if cfg.TlsCertPath != "" && cfg.TlsKeyPath != "" {
288-
cert, err := tls.LoadX509KeyPair(cfg.TlsCertPath, cfg.TlsKeyPath)
289-
if err != nil {
290-
return nil, fmt.Errorf("failed to load client certificate: %w", err)
291-
}
292-
tlsConfig.Certificates = []tls.Certificate{cert}
293-
} else if cfg.TlsCertData != "" && cfg.TlsKeyData != "" {
294-
cert, err := tls.X509KeyPair([]byte(cfg.TlsCertData), []byte(cfg.TlsKeyData))
295-
if err != nil {
296-
return nil, fmt.Errorf("failed to parse client certificate: %w", err)
297-
}
298-
tlsConfig.Certificates = []tls.Certificate{cert}
299-
}
300-
301-
// Load CA certificate.
302-
if cfg.TlsCaPath != "" || cfg.TlsCaData != "" {
303-
pool := x509.NewCertPool()
304-
var caData []byte
305-
if cfg.TlsCaPath != "" {
306-
var err error
307-
caData, err = os.ReadFile(cfg.TlsCaPath)
308-
if err != nil {
309-
return nil, fmt.Errorf("failed to read CA certificate: %w", err)
310-
}
311-
} else {
312-
caData = []byte(cfg.TlsCaData)
313-
}
314-
if !pool.AppendCertsFromPEM(caData) {
315-
return nil, fmt.Errorf("failed to parse CA certificate")
316-
}
317-
tlsConfig.RootCAs = pool
279+
// getOAuthToken returns a valid OAuth access token from the builder's configuration.
280+
// It uses oauth2.TokenSource to automatically refresh the token when needed.
281+
func (b *ClientOptionsBuilder) getOAuthToken(ctx context.Context) (string, error) {
282+
tokenSource := b.oauthConfig.newTokenSource(ctx)
283+
token, err := tokenSource.Token()
284+
if err != nil {
285+
return "", err
318286
}
319-
320-
return tlsConfig, nil
287+
return token.AccessToken, nil
321288
}

cliext/client.oauth.go

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)