Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into fix-3328
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jmichalak committed Jan 9, 2025
2 parents f1447b1 + 1323cda commit 8e8caac
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 15 deletions.
16 changes: 15 additions & 1 deletion docs/resources/external_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ resource "snowflake_external_table" "external_table" {
type = "text"
}
}
# with a location pointing to an existing stage
# name is hardcoded, please see resource documentation for other options
resource "snowflake_external_table" "external_table_with_location" {
database = "db"
schema = "schema"
name = "external_table_with_location"
location = "@MYDB.MYSCHEMA.MYSTAGE"
column {
name = "id"
type = "int"
}
}
```

-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources).
Expand All @@ -44,7 +58,7 @@ resource "snowflake_external_table" "external_table" {
- `column` (Block List, Min: 1) Definitions of a column to create in the external table. Minimum one required. (see [below for nested schema](#nestedblock--column))
- `database` (String) The database in which to create the external table.
- `file_format` (String) Specifies the file format for the external table.
- `location` (String) Specifies a location for the external table.
- `location` (String) Specifies a location for the external table, using its FQDN. You can hardcode it (`"@MYDB.MYSCHEMA.MYSTAGE"`), or populate dynamically (`"@${snowflake_stage.mystage.fully_qualified_name}"`)
- `name` (String) Specifies the identifier for the external table; must be unique for the database and schema in which the externalTable is created.
- `schema` (String) The schema in which to create the external table.

Expand Down
13 changes: 12 additions & 1 deletion docs/resources/stage.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ resource "snowflake_stage" "example_stage" {
schema = "EXAMPLE_SCHEMA"
credentials = "AWS_KEY_ID='${var.example_aws_key_id}' AWS_SECRET_KEY='${var.example_aws_secret_key}'"
}
# with an existing hardcoded file format
# please see other examples in the resource documentation
resource "snowflake_stage" "example_stage_with_file_format" {
name = "EXAMPLE_STAGE"
url = "s3://com.example.bucket/prefix"
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
credentials = "AWS_KEY_ID='${var.example_aws_key_id}' AWS_SECRET_KEY='${var.example_aws_secret_key}'"
file_format = "FORMAT_NAME = DB.SCHEMA.FORMATNAME"
}
```

-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources).
Expand All @@ -43,7 +54,7 @@ resource "snowflake_stage" "example_stage" {
- `credentials` (String, Sensitive) Specifies the credentials for the stage.
- `directory` (String) Specifies the directory settings for the stage.
- `encryption` (String) Specifies the encryption settings for the stage.
- `file_format` (String) Specifies the file format for the stage. Specifying the default Snowflake value (e.g. TYPE = CSV) will currently result in a permadiff (check [#2679](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2679)). For now, omit the default values; it will be fixed in the upcoming provider versions.
- `file_format` (String) Specifies the file format for the stage. Specifying the default Snowflake value (e.g. TYPE = CSV) will currently result in a permadiff (check [#2679](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2679)). For now, omit the default values; it will be fixed in the upcoming provider versions. Examples of usage: <b>1. with hardcoding value:</b> `file_format="FORMAT_NAME = DB.SCHEMA.FORMATNAME"` <b>2. from dynamic value:</b> `file_format = "FORMAT_NAME = ${snowflake_file_format.myfileformat.fully_qualified_name}"` <b>3. from expression:</b> `file_format = format("FORMAT_NAME =%s.%s.MYFILEFORMAT", var.db_name, each.value.schema_name)`. Reference: [#265](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/265)
- `snowflake_iam_user` (String) An AWS IAM user created for your Snowflake account. This user is the same for every external S3 stage created in your account.
- `storage_integration` (String) Specifies the name of the storage integration used to delegate authentication responsibility for external cloud storage to a Snowflake identity and access management (IAM) entity.
- `tag` (Block List, Deprecated) Definitions of a tag to associate with the resource. (see [below for nested schema](#nestedblock--tag))
Expand Down
14 changes: 14 additions & 0 deletions examples/resources/snowflake_external_table/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ resource "snowflake_external_table" "external_table" {
type = "text"
}
}

# with a location pointing to an existing stage
# name is hardcoded, please see resource documentation for other options
resource "snowflake_external_table" "external_table_with_location" {
database = "db"
schema = "schema"
name = "external_table_with_location"
location = "@MYDB.MYSCHEMA.MYSTAGE"

column {
name = "id"
type = "int"
}
}
11 changes: 11 additions & 0 deletions examples/resources/snowflake_stage/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ resource "snowflake_stage" "example_stage" {
schema = "EXAMPLE_SCHEMA"
credentials = "AWS_KEY_ID='${var.example_aws_key_id}' AWS_SECRET_KEY='${var.example_aws_secret_key}'"
}

# with an existing hardcoded file format
# please see other examples in the resource documentation
resource "snowflake_stage" "example_stage_with_file_format" {
name = "EXAMPLE_STAGE"
url = "s3://com.example.bucket/prefix"
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
credentials = "AWS_KEY_ID='${var.example_aws_key_id}' AWS_SECRET_KEY='${var.example_aws_secret_key}'"
file_format = "FORMAT_NAME = DB.SCHEMA.FORMATNAME"
}
2 changes: 1 addition & 1 deletion pkg/resources/external_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ var externalTableSchema = map[string]*schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Specifies a location for the external table.",
Description: "Specifies a location for the external table, using its FQDN. You can hardcode it (`\"@MYDB.MYSCHEMA.MYSTAGE\"`), or populate dynamically (`\"@${snowflake_stage.mystage.fully_qualified_name}\"`)",
},
"file_format": {
Type: schema.TypeString,
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var stageSchema = map[string]*schema.Schema{
"file_format": {
Type: schema.TypeString,
Optional: true,
Description: "Specifies the file format for the stage. Specifying the default Snowflake value (e.g. TYPE = CSV) will currently result in a permadiff (check [#2679](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2679)). For now, omit the default values; it will be fixed in the upcoming provider versions.",
Description: "Specifies the file format for the stage. Specifying the default Snowflake value (e.g. TYPE = CSV) will currently result in a permadiff (check [#2679](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2679)). For now, omit the default values; it will be fixed in the upcoming provider versions. Examples of usage: <b>1. with hardcoding value:</b> `file_format=\"FORMAT_NAME = DB.SCHEMA.FORMATNAME\"` <b>2. from dynamic value:</b> `file_format = \"FORMAT_NAME = ${snowflake_file_format.myfileformat.fully_qualified_name}\"` <b>3. from expression:</b> `file_format = format(\"FORMAT_NAME =%s.%s.MYFILEFORMAT\", var.db_name, each.value.schema_name)`. Reference: [#265](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/265)",
DiffSuppressFunc: suppressQuoting,
},
"copy_options": {
Expand Down
27 changes: 26 additions & 1 deletion pkg/sdk/poc/generator/interface.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
package generator

import "fmt"

type objectIdentifierKind string

const (
AccountObjectIdentifier objectIdentifierKind = "AccountObjectIdentifier"
DatabaseObjectIdentifier objectIdentifierKind = "DatabaseObjectIdentifier"
SchemaObjectIdentifier objectIdentifierKind = "SchemaObjectIdentifier"
SchemaObjectIdentifierWithArguments objectIdentifierKind = "SchemaObjectIdentifierWithArguments"
)

func toObjectIdentifierKind(s string) (objectIdentifierKind, error) {
switch s {
case "AccountObjectIdentifier":
return AccountObjectIdentifier, nil
case "DatabaseObjectIdentifier":
return DatabaseObjectIdentifier, nil
case "SchemaObjectIdentifier":
return SchemaObjectIdentifier, nil
case "SchemaObjectIdentifierWithArguments":
return SchemaObjectIdentifierWithArguments, nil
default:
return "", fmt.Errorf("invalid string identifier type: %s", s)
}
}

// Interface groups operations for particular object or objects family (e.g. DATABASE ROLE)
type Interface struct {
// Name is the interface's name, e.g. "DatabaseRoles"
Expand Down Expand Up @@ -28,6 +54,5 @@ func (i *Interface) NameLowerCased() string {

// ObjectIdentifierKind returns the level of the object identifier (e.g. for DatabaseObjectIdentifier, it returns the prefix "Database")
func (i *Interface) ObjectIdentifierPrefix() idPrefix {
// return strings.Replace(i.IdentifierKind, "ObjectIdentifier", "", 1)
return identifierStringToPrefix(i.IdentifierKind)
}
11 changes: 8 additions & 3 deletions pkg/sdk/poc/generator/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func (s *Operation) withHelperStructs(helperStructs ...*Field) *Operation {
return s
}

func (s *Operation) withObjectInterface(objectInterface *Interface) *Operation {
s.ObjectInterface = objectInterface
return s
}

func addShowMapping(op *Operation, from, to *Field) {
op.ShowMapping = newMapping("convert", from, to)
}
Expand Down Expand Up @@ -170,9 +175,9 @@ func (i *Interface) ShowByIdOperationWithNoFiltering() *Interface {

// ShowByIdOperationWithFiltering adds a ShowByID operation to the interface with filtering. Should be used for objects that implement filtering options e.g. Like or In.
func (i *Interface) ShowByIdOperationWithFiltering(filter ShowByIDFilteringKind, filtering ...ShowByIDFilteringKind) *Interface {
op := newNoSqlOperation(string(OperationKindShowByID))
op.ObjectInterface = i
op.withFiltering(append([]ShowByIDFilteringKind{filter}, filtering...)...)
op := newNoSqlOperation(string(OperationKindShowByID)).
withObjectInterface(i).
withFiltering(append(filtering, filter)...)
i.Operations = append(i.Operations, op)
return i
}
Expand Down
60 changes: 60 additions & 0 deletions pkg/sdk/poc/generator/show_object_methods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package generator

import (
"log"
"slices"
)

type ShowObjectIdMethod struct {
StructName string
IdentifierKind objectIdentifierKind
Args []string
}

func newShowObjectIDMethod(structName string, idType objectIdentifierKind) *ShowObjectIdMethod {
return &ShowObjectIdMethod{
StructName: structName,
IdentifierKind: idType,
Args: idTypeParts[idType],
}
}

var idTypeParts map[objectIdentifierKind][]string = map[objectIdentifierKind][]string{
AccountObjectIdentifier: {"Name"},
DatabaseObjectIdentifier: {"DatabaseName", "Name"},
SchemaObjectIdentifier: {"DatabaseName", "SchemaName", "Name"},
}

func checkRequiredFieldsForIdMethod(structName string, helperStructs []*Field, idKind objectIdentifierKind) bool {
if requiredFields, ok := idTypeParts[idKind]; ok {
for _, field := range helperStructs {
if field.Name == structName {
return containsFieldNames(field.Fields, requiredFields...)
}
}
}
log.Printf("[WARN] no required fields mapping defined for identifier %s", idKind)
return false
}

func containsFieldNames(fields []*Field, names ...string) bool {
fieldNames := []string{}
for _, field := range fields {
fieldNames = append(fieldNames, field.Name)
}

for _, name := range names {
if !slices.Contains(fieldNames, name) {
return false
}
}
return true
}

type ShowObjectTypeMethod struct {
StructName string
}

func newShowObjectTypeMethod(structName string) *ShowObjectTypeMethod {
return &ShowObjectTypeMethod{StructName: structName}
}
50 changes: 50 additions & 0 deletions pkg/sdk/poc/generator/show_object_methods_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package generator

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestIdentifierStringToObjectIdentifier(t *testing.T) {
tests := []struct {
input string
expected objectIdentifierKind
}{
{"AccountObjectIdentifier", AccountObjectIdentifier},
{"DatabaseObjectIdentifier", DatabaseObjectIdentifier},
{"SchemaObjectIdentifier", SchemaObjectIdentifier},
{"SchemaObjectIdentifierWithArguments", SchemaObjectIdentifierWithArguments},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result, err := toObjectIdentifierKind(test.input)
require.NoError(t, err)
require.Equal(t, test.expected, result)
})
}
}

func TestIdentifierStringToObjectIdentifier_Invalid(t *testing.T) {
tests := []struct {
input string
err string
}{
{"accountobjectidentifier", "invalid string identifier type: accountobjectidentifier"},
{"Account", "invalid string identifier type: Account"},
{"databaseobjectidentifier", "invalid string identifier type: databaseobjectidentifier"},
{"Database", "invalid string identifier type: Database"},
{"schemaobjectidentifier", "invalid string identifier type: schemaobjectidentifier"},
{"Schema", "invalid string identifier type: Schema"},
{"schemaobjectidentifierwitharguments", "invalid string identifier type: schemaobjectidentifierwitharguments"},
{"schemawitharguemnts", "invalid string identifier type: schemawitharguemnts"},
}

for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
_, err := toObjectIdentifierKind(tc.input)
require.ErrorContains(t, err, tc.err)
})
}
}
20 changes: 20 additions & 0 deletions pkg/sdk/poc/generator/template_executors.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,29 @@ func GenerateInterface(writer io.Writer, def *Interface) {
if o.OptsField != nil {
generateOptionsStruct(writer, o)
}

if o.Name == string(OperationKindShow) {
idKind, err := toObjectIdentifierKind(def.IdentifierKind)
if err != nil {
log.Printf("[WARN] for showObjectIdMethod: %v", err)
}
if checkRequiredFieldsForIdMethod(def.NameSingular, o.HelperStructs, idKind) {
generateShowObjectIdMethod(writer, newShowObjectIDMethod(def.NameSingular, idKind))
}

generateShowObjectTypeMethod(writer, newShowObjectTypeMethod(def.NameSingular))
}
}
}

func generateShowObjectIdMethod(writer io.Writer, m *ShowObjectIdMethod) {
printTo(writer, ShowObjectIdMethodTemplate, m)
}

func generateShowObjectTypeMethod(writer io.Writer, m *ShowObjectTypeMethod) {
printTo(writer, ShowObjectTypeMethodTemplate, m)
}

func generateOptionsStruct(writer io.Writer, operation *Operation) {
printTo(writer, OperationStructTemplate, operation)

Expand Down
8 changes: 8 additions & 0 deletions pkg/sdk/poc/generator/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ var (
structTemplateContent string
StructTemplate, _ = template.New("structTemplate").Parse(structTemplateContent)

//go:embed templates/show_object_id_method.tmpl
showObjectIdMethodTemplateContent string
ShowObjectIdMethodTemplate, _ = template.New("showObjectIdMethodTemplate").Parse(showObjectIdMethodTemplateContent)

//go:embed templates/show_object_type_method.tmpl
showObjectTypeMethodTemplateContent string
ShowObjectTypeMethodTemplate, _ = template.New("showObjectTypeMethodTemplate").Parse(showObjectTypeMethodTemplateContent)

//go:embed templates/dto_declarations.tmpl
dtoDeclarationsTemplateContent string
DtoTemplate, _ = template.New("dtoTemplate").Parse(dtoDeclarationsTemplateContent)
Expand Down
5 changes: 5 additions & 0 deletions pkg/sdk/poc/generator/templates/show_object_id_method.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{- /*gotype: github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator.ShowObjectIdMethod*/ -}}

func (v *{{ .StructName }}) ID() {{ .IdentifierKind }} {
return New{{ .IdentifierKind }}({{ range .Args }}v.{{ . }}, {{ end }})
}
5 changes: 5 additions & 0 deletions pkg/sdk/poc/generator/templates/show_object_type_method.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{- /*gotype: github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator.ShowObjectTypeMethod*/ -}}

func (v *{{ .StructName }}) ObjectType() ObjectType {
return ObjectType{{ .StructName }}
}
Loading

0 comments on commit 8e8caac

Please sign in to comment.