Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add secret to sdk #3091

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open

feat: Add secret to sdk #3091

wants to merge 19 commits into from

Conversation

sfc-gh-fbudzynski
Copy link
Collaborator

@sfc-gh-fbudzynski sfc-gh-fbudzynski commented Sep 19, 2024

Changes

  • Changed name of Secret struct in common_types.go due to naming clash
  • Added Secret Object to SDK
  • Unit and integration tests

References

Copy link

Integration tests failure for 01a70062a3b2aed8351229d43d94e520ec3c1e05

Copy link

Integration tests failure for 929c810a42e5eade5724b107bb10432ed35e376b

Copy link

Integration tests failure for 8c5dcfb3ad8ad5d7394d32c8c8f6896697c5e498

return c.context.client.Secrets
}

func (c *SecretClient) CreateSecreteWithBasicFlow(t *testing.T, id sdk.SchemaObjectIdentifier, username, password string) (*sdk.Secret, func()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing error: "Secrete". Usually in testing client you don't have to emphasize that it's for Secret when it's already on SecretClient, the function may be renamed to just CreateWithBasicFlow. Same for CleanupSecretFunc where it can be just CleanupFunc or better DropFunc.

pkg/sdk/secrets_def.go Show resolved Hide resolved
IfNotExists().
Name().
PredefinedQueryStructField("Type", "string", g.StaticOptions().SQL("TYPE = OAUTH2")).
Identifier("SecurityIntegration", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions().Required().Equals().SQL("API_AUTHENTICATION").Required()).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this field to ApiIntegration (same for other types of Create). Security integration is a very general term and can mean many types of objects ApiIntegration would be a specific security integration that can be used with this secret. Plus, you can remove one of those .Require(), calling it once is enough.

OptionalComment().
WithValidation(g.ValidIdentifier, "name").
WithValidation(g.ConflictingFields, "OrReplace", "IfNotExists"),
secretsSecurityIntegrationScopeDef,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also have it in the operation above. Wouldn't it produce the same struct (SecurityIntegrationScope) twice in the generated output? As far as I remember it should work like that, but maybe in the meantime we added some kind of filtering to prevent it from happening. Oh ok, there's filtering, even I did it 😅

Copy link
Collaborator Author

@sfc-gh-fbudzynski sfc-gh-fbudzynski Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I remove the duplicate regardless of filtering or leave it as is?

Name().
WithValidation(g.ValidIdentifier, "name"),
).ShowOperation(
"https://docs.snowflake.com/en/sql-reference/sql/show-secret",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be show-secrets

})

// regarding the https://docs.snowflake.com/en/sql-reference/sql/create-secret secret should inherit security_integration scopes, but it does not do so
t.Run("Create: SecretWithOAuthClientCredentialsFlow - No Scopes Specified", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test 👍


integrationId := testClientHelper().Ids.RandomAccountObjectIdentifier()

refreshTokenExpiryTime := time.Now().Add(Day).Format(time.DateOnly)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try other formats? How Snowflake behaves when we give it a different one? You can check manually.

require.NotContains(t, returnedSecrets, *secret2)
})

t.Run("Show: SecretWithGenericString with Like", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't go that deep to test SHOW LIKE and SHOW IN for every secret type, but if we already have it lets leave it.

})

t.Run("ShowByID", func(t *testing.T) {
_, id := createSecretWithGenericString(t, "foo", nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To really test ShowById the best way would be to create a new schema on top of test database (you can do it with testClientHelper().Schema.CreateSchemaInDatabase(t, testClientHelper().Ids.DatabaseId())) and then create a secret with the same name as the first one in this newly created schema, and then see what ShowById returns. Take a look at this test "show by id - same name in different schemas", as a good example.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test seems similar to what you have at the bottom of this file, but not quite.

return secret
}

t.Run("Show with In - same id in different schemas", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a lot of SHOW tests, isn't this case covered in one of them? You can also test it in the main function, no need to have it in TestInt_SecretsShowWithIn.

Copy link
Collaborator

@sfc-gh-jmichalak sfc-gh-jmichalak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a good first PR 👍

return secret, c.CleanupSecretFunc(t, id)
}

func (c *SecretClient) CleanupSecretFunc(t *testing.T, id sdk.SchemaObjectIdentifier) func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: In general, we name these functions like DropFunc. Also, Secret should be not present in function names.

return func() {
_, err := c.client().ShowByID(ctx, id)
if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) {
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool that you thought about checking an object here, but when we use IF EXISTS, this is not needed.

Field("Owner", "string").
Field("Comment", "string").
Field("SecretType", "string").
Field("OauthScopes", "sql.NullString").
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we try to parse fields containing lists/objects in convert, so that they are available as slices in the object struct. So, this should be "[]string". The same applies to OauthScopes in SecretDetails.

Field("OwnerRoleType", "string")

var secretDetailsDbRow = g.DbStruct("secretDetailsDBRow").
Field("created_on", "string").
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no Time()?

SQL("SECRET").
IfNotExists().
Name().
PredefinedQueryStructField("Type", "string", g.StaticOptions().SQL("TYPE = PASSWORD")).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: rather than having this field exported, you could use a name like secretType. In other places as well.

assert.NotEmpty(t, s.DatabaseName)
assert.NotEmpty(t, s.SchemaName)
assert.NotEmpty(t, s.OwnerRoleType)
assert.NotEmpty(t, s.Owner)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we use generated asserts - check views integration tests.

SchemaName string
DatabaseName string
Owner string
Comment sql.NullString
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sql.* types should be present only in dbRows - we want to separate things returned from Snowflake from things returned by the SDK. You can use *string here.

Name: id.Name(),
SecretType: "OAUTH2",
Comment: "a",
OauthRefreshTokenExpiryTime: stringDateToSnowflakeTimeFormat(time.DateOnly, refreshTokenExpiryTime),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sfc-gh-jcieslak Do we have a helper function for comparing time? If not, this could be moved to helpers somewhere.

),
)
err := client.Secrets.Alter(ctx, setRequest)
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're missing assert here.

require.Contains(t, returnedSecrets, *secretGenericString)
})

t.Run("Show: SecretWithOAuthClientCredentialsFlow with Like", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cool you've provided all of these cases, but I think that one case for each filtering option is enough (with an arbitrary type).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants