Skip to content

Commit

Permalink
Support setting a variable of type Connection using a connection stri…
Browse files Browse the repository at this point in the history
…ng or cloud workspace handle
  • Loading branch information
kaidaguerre authored Oct 22, 2024
1 parent c965b1a commit 440b7e4
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 23 deletions.
6 changes: 6 additions & 0 deletions connection/duckdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const DuckDbConnectionType = "duckdb"
type DuckDbConnection struct {
ConnectionImpl
FileName *string `json:"file_name,omitempty" cty:"file_name" hcl:"file_name,optional"`
// used only to set the connection string from command line variable value with a connection string
ConnectionString *string `json:"connection_string,omitempty" cty:"connection_string"`
}

func NewDuckDbConnection(shortName string, declRange hcl.Range) PipelingConnection {
Expand Down Expand Up @@ -78,6 +80,10 @@ func (c *DuckDbConnection) CtyValue() (cty.Value, error) {
}

func (c *DuckDbConnection) GetConnectionString() string {
if c.ConnectionString != nil {
return *c.ConnectionString
}

return fmt.Sprintf("duckdb://%s", c.getFileName())
}

Expand Down
6 changes: 6 additions & 0 deletions connection/sqllite.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const SqliteConnectionType = "sqlite"
type SqliteConnection struct {
ConnectionImpl
FileName *string `json:"file_name,omitempty" cty:"file_name" hcl:"file_name,optional"`
// used only to set the connection string from command line variable value with a connection string
ConnectionString *string `json:"connection_string,omitempty" cty:"connection_string"`
}

func NewSqliteConnection(shortName string, declRange hcl.Range) PipelingConnection {
Expand Down Expand Up @@ -79,6 +81,10 @@ func (c *SqliteConnection) CtyValue() (cty.Value, error) {
}

func (c *SqliteConnection) GetConnectionString() string {
if c.ConnectionString != nil {
return *c.ConnectionString
}

return fmt.Sprintf("sqlite://%s", c.getFileName())
}

Expand Down
14 changes: 9 additions & 5 deletions inputvars/collect_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func CollectVariableValues(workspacePath string, variableFileArgs []string, vari
name := raw[:eq]
rawVal := raw[eq+1:]

ret[name] = unparsedVariableValueString{
ret[name] = UnparsedVariableValueString{
str: rawVal,
name: name,
sourceType: terraform.ValueFromEnvVar,
Expand Down Expand Up @@ -122,7 +122,7 @@ func CollectVariableValues(workspacePath string, variableFileArgs []string, vari

name := raw[:eq]
rawVal := raw[eq+1:]
ret[name] = unparsedVariableValueString{
ret[name] = UnparsedVariableValueString{
str: rawVal,
name: name,
sourceType: terraform.ValueFromCLIArg,
Expand Down Expand Up @@ -300,17 +300,21 @@ func (v unparsedVariableValueExpression) ParseVariableValue(evalCtx *hcl.EvalCon
}, diags
}

// unparsedVariableValueString is a UnparsedVariableValue
// UnparsedVariableValueString is a UnparsedVariableValue
// implementation that parses its value from a string. This can be used
// to deal with values given directly on the command line and via environment
// variables.
type unparsedVariableValueString struct {
type UnparsedVariableValueString struct {
str string
name string
sourceType terraform.ValueSourceType
}

func (v unparsedVariableValueString) ParseVariableValue(evalCtx *hcl.EvalContext, mode modconfig.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
func (v UnparsedVariableValueString) Raw() string {
return v.str
}

func (v UnparsedVariableValueString) ParseVariableValue(evalCtx *hcl.EvalContext, mode modconfig.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

val, hclDiags := mode.Parse(evalCtx, v.name, v.str)
Expand Down
84 changes: 82 additions & 2 deletions inputvars/unparsed_value.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package inputvars

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/hcl/v2"
"github.com/spf13/viper"
"github.com/turbot/pipe-fittings/backend"
"github.com/turbot/pipe-fittings/connection"
"github.com/turbot/pipe-fittings/constants"
"github.com/turbot/pipe-fittings/error_helpers"
"github.com/turbot/pipe-fittings/modconfig"
"github.com/turbot/pipe-fittings/pipes"
"github.com/turbot/pipe-fittings/steampipeconfig"
"github.com/turbot/terraform-components/terraform"
"github.com/turbot/terraform-components/tfdiags"
"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -61,8 +69,15 @@ func ParseVariableValues(evalContext *hcl.EvalContext, inputValueUnparsed map[st
} else {
mode = modconfig.VariableParseLiteral
}

val, valDiags := unparsedVal.ParseVariableValue(evalContext, mode)
var val *terraform.InputValue
var valDiags tfdiags.Diagnostics
// if the variable is a connection, use special case logic to parse the value
// - handling connection strings and pipes workspace handle
if declared && config.IsConnectionType() {
val, valDiags = parseConnectionVariableValue(evalContext, unparsedVal, mode)
} else {
val, valDiags = unparsedVal.ParseVariableValue(evalContext, mode)
}
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
continue
Expand Down Expand Up @@ -161,6 +176,71 @@ func ParseVariableValues(evalContext *hcl.EvalContext, inputValueUnparsed map[st
return ret, diags
}

// this function handles the case that the value for a connection variable is being set with a connection string or cloud workspace identifier
func parseConnectionVariableValue(evalContext *hcl.EvalContext, unparsedVal UnparsedVariableValue, mode modconfig.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var ctyVal cty.Value
var err error
if unparsedString, ok := unparsedVal.(UnparsedVariableValueString); ok {
str := unparsedString.Raw()
// is the value a workspace handle?
if steampipeconfig.IsPipesWorkspaceIdentifier(str) {
// verify the cloud token was provided
cloudToken := viper.GetString(constants.ArgPipesToken)
if cloudToken == "" {
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Failed to get pipes metadata", error_helpers.MissingCloudTokenError().Error())}
}

pipesMetadata, err := pipes.GetPipesMetadata(context.Background(), str, cloudToken)
if err != nil {
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Failed to get pipes metadata", err.Error())}
}
// create new connection
c := connection.NewSteampipePgConnection(str, hcl.Range{}).(*connection.SteampipePgConnection)
c.ConnectionString = &pipesMetadata.ConnectionString
ctyVal, err = c.CtyValue()
if err != nil {
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Failed to get connection value", err.Error())}
}
return &terraform.InputValue{
Value: ctyVal,
SourceType: terraform.ValueFromCLIArg,
}, nil

} else if backend.HasBackend(str) {
// is the value a connection string?
var c connection.PipelingConnection
switch {
case backend.IsDuckDBConnectionString(str):
c = connection.NewDuckDbConnection("command_arg", hcl.Range{}).(*connection.DuckDbConnection)
c.(*connection.DuckDbConnection).ConnectionString = &str
case backend.IsPostgresConnectionString(str):
c = connection.NewPostgresConnection("command_arg", hcl.Range{})
c.(*connection.PostgresConnection).ConnectionString = &str
case backend.IsMySqlConnectionString(str):
c = connection.NewMysqlConnection("command_arg", hcl.Range{})
c.(*connection.MysqlConnection).ConnectionString = &str
case backend.IsSqliteConnectionString(str):
c = connection.NewSqliteConnection("command_arg", hcl.Range{})
c.(*connection.SqliteConnection).ConnectionString = &str

default:
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Failed to get connection value", fmt.Sprintf("Invalid connection string: %s", str))}
}
ctyVal, err = c.CtyValue()
if err != nil {
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Failed to get connection value", err.Error())}
}

return &terraform.InputValue{
Value: ctyVal,
SourceType: terraform.ValueFromCLIArg,
}, nil
}
}
// fall back to normal parsing
return unparsedVal.ParseVariableValue(evalContext, mode)
}

func getUndeclaredVariableError(name string, variablesMap *modconfig.ModVariableMap) string {
// is this a qualified variable?
if len(strings.Split(name, ".")) == 1 {
Expand Down
17 changes: 17 additions & 0 deletions modconfig/custom_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ func IsCustomType(ty cty.Type) bool {
return isCustomType
}

// IsConnectionType returns true if the given cty.Type is a connection type
func IsConnectionType(ty cty.Type) bool {
// is the value a workspace handle?
encapsulatedGoType, nestedCapsule := hclhelpers.IsNestedCapsuleType(ty)
if !nestedCapsule {
return false
}

encapulatedInstanceNew := reflect.New(encapsulatedGoType)
if _, ok := encapulatedInstanceNew.Interface().(connection.PipelingConnection); ok {
return true
}

var connectionImpl *connection.ConnectionImpl
return encapsulatedGoType.String() == reflect.TypeOf(connectionImpl).String()
}

func ValidateValueMatchesType(ctyVal cty.Value, ty cty.Type, sourceRange *hcl.Range) hcl.Diagnostics {
if ty != cty.DynamicPseudoType {
ctyValType := ctyVal.Type()
Expand Down
14 changes: 1 addition & 13 deletions modconfig/flowpipe_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/hashicorp/hcl/v2"
"github.com/turbot/go-kit/helpers"
"github.com/turbot/pipe-fittings/connection"
"github.com/turbot/pipe-fittings/hclhelpers"
"github.com/turbot/pipe-fittings/options"
"github.com/turbot/pipe-fittings/perr"
Expand Down Expand Up @@ -531,18 +530,7 @@ func (p *PipelineParam) PipelineParamCustomValueValidation(setting cty.Value, ev
}

func (p *PipelineParam) IsConnectionType() bool {
encapsulatedGoType, nestedCapsule := hclhelpers.IsNestedCapsuleType(p.Type)
if !nestedCapsule {
return false
}

encapulatedInstanceNew := reflect.New(encapsulatedGoType)
if _, ok := encapulatedInstanceNew.Interface().(connection.PipelingConnection); ok {
return true
}

var connectionImpl *connection.ConnectionImpl
return encapsulatedGoType.String() == reflect.TypeOf(connectionImpl).String()
return IsConnectionType(p.Type)
}

func (p *PipelineParam) IsNotifierType() bool {
Expand Down
7 changes: 4 additions & 3 deletions modconfig/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package modconfig

import (
"fmt"

"github.com/hashicorp/hcl/v2"
typehelpers "github.com/turbot/go-kit/types"
"github.com/turbot/pipe-fittings/cty_helpers"
Expand All @@ -15,8 +14,6 @@ import (
"github.com/zclconf/go-cty/cty/convert"
)

// TODO check DescriptionSet - still required?

// Variable is a struct representing a Variable resource
type Variable struct {
ResourceWithMetadataImpl
Expand Down Expand Up @@ -182,3 +179,7 @@ func (v *Variable) CtyValue() (cty.Value, error) {
func (v *Variable) IsLateBinding() bool {
return IsLateBindingType(v.Type)
}

func (p *Variable) IsConnectionType() bool {
return IsConnectionType(p.Type)
}

0 comments on commit 440b7e4

Please sign in to comment.