Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions helper/logging/logging_http_transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewLoggingHTTPTransport(t *testing.T) {
reqBody := `An example
multiline
request body`
req, _ := http.NewRequest("GET", "https://www.terraform.io", bytes.NewBufferString(reqBody))
req, _ := http.NewRequest("GET", "https://developer.hashicorp.com/terraform", bytes.NewBufferString(reqBody))
res, err := client.Do(req.WithContext(ctx))
if err != nil {
t.Fatalf("request failed: %v", err)
Expand All @@ -40,7 +40,7 @@ func TestNewLoggingHTTPTransport(t *testing.T) {

entries, err := tflogtest.MultilineJSONDecode(loggerOutput)
if err != nil {
t.Fatalf("log outtput parsing failed: %v", err)
t.Fatalf("log output parsing failed: %v", err)
}

if len(entries) != 2 {
Expand All @@ -67,12 +67,12 @@ func TestNewLoggingHTTPTransport(t *testing.T) {
"@module": "provider",
"tf_http_op_type": "request",
"tf_http_req_method": "GET",
"tf_http_req_uri": "/",
"tf_http_req_uri": "/terraform",
"tf_http_req_version": "HTTP/1.1",
"tf_http_req_body": "An example multiline request body",
"tf_http_trans_id": transId,
"Accept-Encoding": "gzip",
"Host": "www.terraform.io",
"Host": "developer.hashicorp.com",
"User-Agent": "Go-http-client/1.1",
"Content-Length": "37",
}); diff != "" {
Expand Down Expand Up @@ -122,7 +122,7 @@ func TestNewSubsystemLoggingHTTPTransport(t *testing.T) {
reqBody := `An example
multiline
request body`
req, _ := http.NewRequest("GET", "https://www.terraform.io", bytes.NewBufferString(reqBody))
req, _ := http.NewRequest("GET", "https://developer.hashicorp.com/terraform", bytes.NewBufferString(reqBody))
res, err := client.Do(req.WithContext(ctx))
if err != nil {
t.Fatalf("request failed: %v", err)
Expand Down Expand Up @@ -158,12 +158,12 @@ func TestNewSubsystemLoggingHTTPTransport(t *testing.T) {
"@module": "provider.test-subsystem",
"tf_http_op_type": "request",
"tf_http_req_method": "GET",
"tf_http_req_uri": "/",
"tf_http_req_uri": "/terraform",
"tf_http_req_version": "HTTP/1.1",
"tf_http_req_body": "An example multiline request body",
"tf_http_trans_id": transId,
"Accept-Encoding": "gzip",
"Host": "www.terraform.io",
"Host": "developer.hashicorp.com",
"User-Agent": "Go-http-client/1.1",
"Content-Length": "37",
}); diff != "" {
Expand Down Expand Up @@ -201,7 +201,7 @@ func TestNewSubsystemLoggingHTTPTransport(t *testing.T) {
func TestNewLoggingHTTPTransport_LogMasking(t *testing.T) {
ctx, loggerOutput := setupRootLogger()
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "tf_http_op_type")
ctx = tflog.MaskAllFieldValuesRegexes(ctx, regexp.MustCompile(`(?s)<html>.*</html>`))
ctx = tflog.MaskAllFieldValuesRegexes(ctx, regexp.MustCompile(`(?s)<html.*>.*</html>`))
ctx = tflog.MaskMessageStrings(ctx, "Request", "Response")

transport := logging.NewLoggingHTTPTransport(http.DefaultTransport)
Expand All @@ -210,7 +210,7 @@ func TestNewLoggingHTTPTransport_LogMasking(t *testing.T) {
Timeout: 10 * time.Second,
}

req, _ := http.NewRequest("GET", "https://www.terraform.io", nil)
req, _ := http.NewRequest("GET", "https://developer.hashicorp.com/terraform", nil)
res, err := client.Do(req.WithContext(ctx))
if err != nil {
t.Fatalf("request failed: %v", err)
Expand Down Expand Up @@ -266,7 +266,7 @@ func TestNewLoggingHTTPTransport_LogOmitting(t *testing.T) {
Timeout: 10 * time.Second,
}

req, _ := http.NewRequest("GET", "https://www.terraform.io", nil)
req, _ := http.NewRequest("GET", "https://developer.hashicorp.com/terraform", nil)
res, err := client.Do(req.WithContext(ctx))
if err != nil {
t.Fatalf("request failed: %v", err)
Expand Down
52 changes: 50 additions & 2 deletions helper/schema/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,27 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
return resp, nil
}

newInstanceStates, err := s.provider.ImportState(ctx, info, req.ID)
var identity map[string]string
// parse identity data if available
if req.Identity != nil && req.Identity.IdentityData != nil {
// convert req.Identity to flat map identity structure
// Step 1: Turn JSON into cty.Value based on schema
identityBlock, err := s.getResourceIdentitySchemaBlock(req.TypeName)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf("getting identity schema failed for resource '%s': %w", req.TypeName, err))
return resp, nil
}

identityVal, err := msgpack.Unmarshal(req.Identity.IdentityData.MsgPack, identityBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
}
// Step 2: Turn cty.Value into flatmap representation
identity = hcl2shim.FlatmapValueFromHCL2(identityVal)
}

newInstanceStates, err := s.provider.ImportStateWithIdentity(ctx, info, req.ID, identity)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
Expand Down Expand Up @@ -1622,12 +1642,40 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
return resp, nil
}

var identityData *tfprotov5.ResourceIdentityData
if is.Identity != nil {
identityBlock, err := s.getResourceIdentitySchemaBlock(resourceType)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf("getting identity schema failed for resource '%s': %w", req.TypeName, err))
return resp, nil
}

newIdentityVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Identity, identityBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
}

newIdentityMP, err := msgpack.Marshal(newIdentityVal, identityBlock.ImpliedType())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err)
return resp, nil
}

identityData = &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: newIdentityMP,
},
}
}

importedResource := &tfprotov5.ImportedResource{
TypeName: resourceType,
State: &tfprotov5.DynamicValue{
MsgPack: newStateMP,
},
Private: meta,
Private: meta,
Identity: identityData,
}

resp.ImportedResources = append(resp.ImportedResources, importedResource)
Expand Down
163 changes: 163 additions & 0 deletions helper/schema/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7715,6 +7715,169 @@ func TestImportResourceState(t *testing.T) {
},
},
},
"basic-import-from-identity": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
"test": {
SchemaVersion: 1,
Schema: map[string]*Schema{
"id": {
Type: TypeString,
Required: true,
},
"test_string": {
Type: TypeString,
Computed: true,
},
},
Identity: &ResourceIdentity{
Version: 1,
SchemaFunc: func() map[string]*Schema {
return map[string]*Schema{
"id": {
Type: TypeString,
RequiredForImport: true,
},
}
},
},
Importer: &ResourceImporter{
StateContext: func(ctx context.Context, d *ResourceData, meta interface{}) ([]*ResourceData, error) {
identity, err := d.Identity()
if err != nil {
t.Fatalf("failed to get identity: %v", err)
}
result, exists := identity.GetOk("id")
if !exists {
t.Fatalf("expected id to exist in identity")
}

err = d.Set("test_string", "new-imported-val")
if err != nil {
return nil, err
}

d.SetId(result.(string))

return []*ResourceData{d}, nil
},
},
},
},
}),
req: &tfprotov5.ImportResourceStateRequest{
TypeName: "test",
Identity: &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("imported-id"),
}),
),
},
},
},
expected: &tfprotov5.ImportResourceStateResponse{
ImportedResources: []*tfprotov5.ImportedResource{
{
TypeName: "test",
State: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
"test_string": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("imported-id"),
"test_string": cty.StringVal("new-imported-val"),
}),
),
},
Private: []byte(`{"schema_version":"1"}`),
Identity: &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("imported-id"),
}),
),
},
},
},
},
},
},
"basic-import-from-identity-no-id": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
"test": {
SchemaVersion: 1,
Schema: map[string]*Schema{
"id": {
Type: TypeString,
Required: true,
},
"test_string": {
Type: TypeString,
Computed: true,
},
},
Identity: &ResourceIdentity{
Version: 1,
SchemaFunc: func() map[string]*Schema {
return map[string]*Schema{
"id": {
Type: TypeString,
RequiredForImport: true,
},
}
},
},
Importer: &ResourceImporter{
// Note: this does not set the Id on the ResourceData which results in an error that this test expects
StateContext: func(ctx context.Context, d *ResourceData, meta interface{}) ([]*ResourceData, error) {
err := d.Set("test_string", "new-imported-val")
if err != nil {
return nil, err
}

return []*ResourceData{d}, nil
},
},
},
},
}),
req: &tfprotov5.ImportResourceStateRequest{
TypeName: "test",
Identity: &tfprotov5.ResourceIdentityData{
IdentityData: &tfprotov5.DynamicValue{
MsgPack: mustMsgpackMarshal(
cty.Object(map[string]cty.Type{
"id": cty.String,
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("imported-id"),
}),
),
},
},
},
expected: &tfprotov5.ImportResourceStateResponse{
ImportedResources: nil,
Diagnostics: []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "The provider returned a resource missing an identifier during ImportResourceState. This is generally a bug in the resource implementation for import. Resource import code should not call d.SetId(\"\") or create an empty ResourceData. If the resource is missing, instead return an error. Please report this to the provider developers.",
},
},
},
},
"resource-doesnt-exist": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
Expand Down
18 changes: 18 additions & 0 deletions helper/schema/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,14 @@ func (p *Provider) ImportState(
ctx context.Context,
info *terraform.InstanceInfo,
id string) ([]*terraform.InstanceState, error) {
return p.ImportStateWithIdentity(ctx, info, id, nil)
}

func (p *Provider) ImportStateWithIdentity(
ctx context.Context,
info *terraform.InstanceInfo,
id string,
identity map[string]string) ([]*terraform.InstanceState, error) {
// Find the resource
r, ok := p.ResourcesMap[info.Type]
if !ok {
Expand All @@ -492,6 +500,16 @@ func (p *Provider) ImportState(
data.SetId(id)
data.SetType(info.Type)

if data.identitySchema != nil {
identityData, err := data.Identity()
if err != nil {
return nil, err // this should not happen, as we checked above
}
identityData.raw = identity // is this too hacky / unexpected?
} else if identity != nil {
return nil, fmt.Errorf("resource %s doesn't support identity import", info.Type)
}

// Call the import function
results := []*ResourceData{data}
if r.Importer.State != nil || r.Importer.StateContext != nil {
Expand Down
Loading
Loading