laskoviymishka commented on code in PR #1574:
URL: https://github.com/apache/iceberg-go/pull/1574#discussion_r3690095025


##########
catalog/glue/glue.go:
##########
@@ -123,7 +123,11 @@ func toAwsConfig(ctx context.Context, p 
iceberg.Properties) (aws.Config, error)
        }
 
        key, secret, token := p[AccessKeyID], p[SecretAccessKey], 
p[SessionToken]
-       if key != "" || secret != "" || token != "" {
+       hasKey, hasSecret := key != "", secret != ""
+       if hasKey != hasSecret || (token != "" && !hasKey) {
+               return aws.Config{}, errors.New("glue.access-key-id and 
glue.secret-access-key must be configured together")

Review Comment:
   minor: this same message fires for the token-only case, where it reads a 
little oddly, the user set `session-token` and gets told about 
`access-key-id`/`secret-access-key`. A distinct message for 
token-without-a-keypair would point at the actual offending field.



##########
io/gocloud/s3.go:
##########
@@ -68,9 +68,13 @@ func ParseAWSConfig(ctx context.Context, props 
map[string]string) (*aws.Config,
 
        accessKey, secretAccessKey := props[io.S3AccessKeyID], 
props[io.S3SecretAccessKey]
        token := props[io.S3SessionToken]
-       if accessKey != "" || secretAccessKey != "" || token != "" {
+       hasAccessKey, hasSecretAccessKey := accessKey != "", secretAccessKey != 
""
+       if hasAccessKey != hasSecretAccessKey || (token != "" && !hasAccessKey) 
{
+               return nil, errors.New("s3.access-key-id and 
s3.secret-access-key must be configured together")

Review Comment:
   I think this only guards one of the two paths into the S3 config, and the 
other one behaves differently for the same input.
   
   `resolveS3AWSConfig` calls `ParseAWSConfig` only when there's no ambient 
context config. When a context config is present it takes the override block 
lower down (around 198-205), which installs static creds only when both key and 
secret are set and silently drops a lone `s3.session-token`. So identical 
partial props now hard-error on the no-context path but pass silently on the 
context path, the outcome depends on whether a context config happens to exist, 
which is invisible to the caller. The `// A partial set ... falls through` 
comment right above that block now also contradicts what `ParseAWSConfig` does.
   
   I'd either apply the same check at the override block and make that the 
single enforcement point where creds actually get installed, or keep the 
asymmetry deliberately and update that comment to explain it. wdyt?



##########
catalog/glue/glue_test.go:
##########
@@ -50,6 +50,33 @@ import (
 
 var errGluePurgeRemove = errors.New("glue purge remove failed")
 
+func TestLoadAWSConfigRejectsIncompleteStaticCredentials(t *testing.T) {
+       t.Parallel()
+
+       tests := []iceberg.Properties{
+               {AccessKeyID: "access"},
+               {SecretAccessKey: "secret"},
+               {SessionToken: "token"},
+               {AccessKeyID: "access", SessionToken: "token"},
+               {SecretAccessKey: "secret", SessionToken: "token"},
+       }
+
+       for _, props := range tests {

Review Comment:
   the loop asserts five cases without a `t.Run`, so a failure points at this 
one line with no way to tell which combination broke. Wrapping each case in a 
named `t.Run` (with `t.Parallel()` moved inside) would localize it, the other 
table tests in this file already do that. Same applies to 
`TestParseAWSConfigRejectsIncompleteStaticCredentials` in the s3 test.



##########
catalog/glue/glue.go:
##########
@@ -123,7 +123,11 @@ func toAwsConfig(ctx context.Context, p 
iceberg.Properties) (aws.Config, error)
        }
 
        key, secret, token := p[AccessKeyID], p[SecretAccessKey], 
p[SessionToken]
-       if key != "" || secret != "" || token != "" {
+       hasKey, hasSecret := key != "", secret != ""
+       if hasKey != hasSecret || (token != "" && !hasKey) {

Review Comment:
   this exact condition shows up verbatim in `ParseAWSConfig` too, just with 
`hasAccessKey`/`hasSecretAccessKey` instead of `hasKey`/`hasSecret`. Two call 
sites isn't much, but the expression is dense enough that the copies could 
drift, and neither has a comment explaining why a token requires both keys.
   
   Could we pull it into a small `validateStaticAWSCredentials(key, secret, 
token) error` shared by both builders? That unifies the naming and gives us one 
place for the explanatory comment. Thoughts?



##########
io/gocloud/s3_test.go:
##########
@@ -114,6 +114,33 @@ func TestResolveS3AWSConfigCredentialPrecedence(t 
*testing.T) {
        })
 }
 
+func TestParseAWSConfigRejectsIncompleteStaticCredentials(t *testing.T) {
+       t.Parallel()
+
+       tests := []map[string]string{
+               {io.S3AccessKeyID: "access"},
+               {io.S3SecretAccessKey: "secret"},
+               {io.S3SessionToken: "token"},
+               {io.S3AccessKeyID: "access", io.S3SessionToken: "token"},
+               {io.S3SecretAccessKey: "secret", io.S3SessionToken: "token"},
+       }
+
+       for _, props := range tests {
+               _, err := ParseAWSConfig(context.Background(), props)
+               require.ErrorContains(t, err, "s3.access-key-id and 
s3.secret-access-key must be configured together")

Review Comment:
   these assert the error by literal string against an inline `errors.New`, so 
renaming a property-key constant would invalidate the assertion while still 
compiling, and callers can't distinguish this error programmatically. The 
package leans on `errors.Is` sentinels elsewhere (`ErrNoSuchTable` and 
friends), a package-level `ErrIncompleteStaticCredentials` used here and 
asserted with `errors.Is` would be sturdier. Not blocking.



##########
io/gocloud/s3_test.go:
##########
@@ -114,6 +114,33 @@ func TestResolveS3AWSConfigCredentialPrecedence(t 
*testing.T) {
        })
 }
 
+func TestParseAWSConfigRejectsIncompleteStaticCredentials(t *testing.T) {
+       t.Parallel()
+
+       tests := []map[string]string{

Review Comment:
   the only happy path covered is all-three-fields. The two most common 
production cases aren't asserted: empty props falling through to the default 
chain (IAM role / env creds), and key+secret with no token. Without the 
empty-props case, an over-eager future edit to the condition (say `==` instead 
of `!=`) would reject every default-chain user and this suite wouldn't catch 
it. Worth adding both, with the empty case asserting no error and that 
`cfg.Credentials` isn't a static provider.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to