Skip to content

Commit

Permalink
Add a doc for numeric IDs
Browse files Browse the repository at this point in the history
Fixes #352
  • Loading branch information
iwahbe committed Jul 24, 2024
1 parent 33763d1 commit b16751b
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 16 deletions.
51 changes: 51 additions & 0 deletions docs/resource-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Resource IDs

Every Pulumi resource must have an `"id"` field. `"id"` must be a string, and it must be
set by the provider (`Computed` and *not* `Optional` in Terraform parlance).

## SDKv1 and SDKv2 Based Providers

The ID requirement is easy to satisfy for SDKv{1,2} based providers since both SDKs
require an ID field of type string set on the provider. If the provider being bridged is
based on SDKv1 or SDKv2, then the bridge handles Pulumi's ID field without intervention.

## PF Based Providers

Most PF based providers have an attribute called `"id"` of the right kind for the bridge
to use. If your provider doesn't, then you will see an error when `make tfgen` is
run. Each error has a different kind of resolution.

### ID of the wrong type
```
error: Resource dnsimple_email_forward has a problem: "id" attribute is of type "int", expected type "string". To map this resource consider overriding the SchemaInfo.Type field or specifying ResourceInfo.ComputeID.
```

This error happens when the upstream resource has an ID but it's not a string. You can fix
it by setting the `SchemaInfo.Type` override for the `"id"` field:

```go
"dnsimple_email_forward": {
Fields: map[string]*tfbridge.SchemaInfo{
"id": {Type: "string"},
},
},
```


For providers[^1] where every resource's ID has the wrong type, you can use a `for` loop to apply this:

```go
prov.P.ResourcesMap().Range(func(key string, value shim.Resource) bool {
if value.Schema().Get("id").Type() != shim.TypeString {
r := prov.Resources[key]
if r.Fields == nil {
r.Fields = make(map[string]*tfbridge.SchemaInfo, 1)
}
r.Fields["id"] = &tfbridge.SchemaInfo{Type: "string"}
}
return true
})
```


[^1]: https://github.com/pulumi/pulumi-dnsimple/blob/7d7e5f3d88082306f15c3600f3481516ae19454a/provider/resources.go#L126-L140
82 changes: 66 additions & 16 deletions pf/internal/check/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"fmt"

"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"

"github.com/pulumi/pulumi-terraform-bridge/pf/internal/muxer"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
Expand Down Expand Up @@ -58,15 +57,13 @@ func checkIDProperties(sink diag.Sink, info tfbridge.ProviderInfo, isPFResource
if resourceHasComputeID(info, rname) {
return true
}
ok, reason := resourceHasRegularID(resource, info.Resources[rname])
if ok {
err := resourceHasRegularID(rname, resource, info.Resources[rname])
if err == nil {
return true
}
m := fmt.Sprintf("Resource %s has a problem: %s. "+
"To map this resource consider specifying ResourceInfo.ComputeID",
rname, reason)

errors++
sink.Errorf(&diag.Diag{Message: m})
sink.Errorf(&diag.Diag{Message: resourceError{rname, err}.Error()})

return true
})
Expand All @@ -78,26 +75,79 @@ func checkIDProperties(sink diag.Sink, info tfbridge.ProviderInfo, isPFResource
return nil
}

func resourceHasRegularID(resource shim.Resource, resourceInfo *tfbridge.ResourceInfo) (bool, string) {
type resourceError struct {
token string
err error
}

func (err resourceError) Error() string {
msg := fmt.Sprintf("Resource %s has a problem", err.token)
if err.err != nil {
msg += ": " + err.err.Error()
}
return msg
}

type errSensativeID struct {
token string
}

func (err errSensativeID) Error() string {
msg := `"id" attribute is sensitive, but cannot be kept secret.`
if err.token != "" {
msg += fmt.Sprintf(
" To accept exposing ID, set `ProviderInfo.Resources[%q].Fields[%q].Secret = tfbridge.True()`",
err.token, "id")
}
return msg
}

type errWrongIDType struct {
actualType string
}

func (err errWrongIDType) Error() string {
msg := `"id" attribute is not of type "string"`
const postfix = ". To map this resource consider overriding the SchemaInfo.Type" +
" field or specifying ResourceInfo.ComputeID"
if err.actualType != "" {
msg = fmt.Sprintf(
`"id" attribute is of type %q, expected type "string"`,
err.actualType)
}
return msg + postfix
}

type errMissingIDAttribute struct{}

func (errMissingIDAttribute) Error() string {
return `no "id" attribute. To map this resource consider specifying ResourceInfo.ComputeID`
}

func resourceHasRegularID(rname string, resource shim.Resource, resourceInfo *tfbridge.ResourceInfo) error {
idSchema, gotID := resource.Schema().GetOk("id")
if !gotID {
return false, `no "id" attribute`
return errMissingIDAttribute{}
}
var typeOverride tokens.Type
var info tfbridge.SchemaInfo
if resourceInfo != nil {
if id := resourceInfo.Fields["id"]; id != nil {
typeOverride = id.Type
info = *id
}
}

// If the user over-rode the type to be a string, don't reject.
if idSchema.Type() != shim.TypeString && typeOverride != "string" {
return false, `"id" attribute is not of type String`
if idSchema.Type() != shim.TypeString && info.Type != "string" {
actual := idSchema.Type().String()
if info.Type != "" {
actual = string(info.Type)
}
return errWrongIDType{actualType: actual}
}
if idSchema.Sensitive() {
return false, `"id" attribute is sensitive`
if idSchema.Sensitive() && (info.Secret == nil || !*info.Secret) {
return errSensativeID{rname}
}
return true, ""
return nil
}

func resourceHasComputeID(info tfbridge.ProviderInfo, resname string) bool {
Expand Down

0 comments on commit b16751b

Please sign in to comment.