magodo / terraform-provider-restful

Terraform provider to manage RESTful resources
https://registry.terraform.io/providers/magodo/restful
Mozilla Public License 2.0
15 stars 5 forks source link

PUT API repsonse with empty return body #84

Closed wilkejo closed 6 months ago

wilkejo commented 6 months ago

Hello,

I try to use the provider on an API where I want to create users. The API call to create the user is a PUT to this Path /public/v1/users with a simple body like this

{
    "lastName" : "name",
    "firstName" : "first",
    "email" : "first.name@mailaddress.tld"
  }

If I send a get request on the path public/v1/users I get a list of all users. If I sent a get request to public/v1/users/first.name@mailaddress.tld I get the user object or {"title":"User is not found with email first.name@othermail.tld"}

My terraform resource looks like this

resource "restful_resource" "user" {
  path      = "/public/v1/users"
  read_path = "$(path)/$(body.email)"
  read_selector = "#(userId==\"(body.email))\")#.userId"
  body = jsonencode({
    lastName  = "name"
    firstName = "first"
    email     = "first.name@mailaddress.tld"
  })
}

upated the read selector, terraform shows now that it wants to create an object, but the error is still there

My error message is

Error: Modifying `body` during Read

unmarshal the body 

"[{\"userId\":\"a@b.com\",\"firstName\":\"first\",\"lastName\":\"name\",\"fullName\":\"first name\",\"pictureUrl\":null,\"phoneNumber\":null,\"country\":null,\"platformRole\":\"member\",\"groups\":[]},
{\"userId\":\"d.e@f.com\",\"firstName\":null,\"lastName\":null,\"fullName\":\"\",\"pictureUrl\":null,\"phoneNumber\":null,\"country\":null,\"platformRole\":\"admin\",\"groups\":[{\"name\":\"admin\",\"groupId\":\"admin\"}]},
{\"userId\":\"x.y@z.com\",\"firstName\":null,\"lastName\":null,\"fullName\":\"\",\"pictureUrl\":null,\"phoneNumber\":null,\"country\":null,\"platformRole\":\"admin\",\"groups\":[{\"name\":\"admin\",\"groupId\":\"admin\"}]},
{\"userId\":\"r.s@t.com\",\"firstName\":\"rrr\",\"lastName\":\"sss\",\"fullName\":\"rrr\",\"pictureUrl\":null,\"phoneNumber\":null,\"country\":null,\"platformRole\":\"member\",\"groups\":[]}]"
: json: cannot unmarshal array into Go value of type map[string]interface {}

Could you maybe guide me what the issue could be why the providers reads the whole body?

wilkejo commented 6 months ago

this is the response body in json

[
    {
        "userId": "a.b@c.com",
        "firstName": null,
        "lastName": null,
        "fullName": "",
        "pictureUrl": null,
        "phoneNumber": null,
        "country": null,
        "platformRole": "admin",
        "groups": [
            {
                "name": "admin",
                "groupId": "admin"
            }
        ]
    },
    {
        "userId": "d.e@f.com",
        "firstName": null,
        "lastName": null,
        "fullName": "",
        "pictureUrl": null,
        "phoneNumber": null,
        "country": null,
        "platformRole": "admin",
        "groups": [
            {
                "name": "admin",
                "groupId": "admin"
            }
        ]
    }
]
magodo commented 6 months ago

@wilkejo Thank you for reaching out! The read_selector seems incorrectly specified. It is the query used to select the actual user from the array. In this case, it shall be something like: #(userId==\"a.b@c.com\").

While since you've mentioned you can directly get the user by GET against /public/v1/users/first.name@mailaddress.tld, in this case you can just construct the read_path (as you have done) and eliminate the read_selector. The remaining issue is that whether "$(path)/$(body.email)" is proper for the read_selector, in this case the body represents the body from the PUT, if that also returns an array, you'll need to specify create_selector, otherwise, your current setting looks good. Then the provider will use this read_path to construct the resource id. So you can run terraform show to inspect the resource id is something similar to /public/v1/users/first.name@mailaddress.tld.

wilkejo commented 6 months ago

Thank you @magodo, for the quick response. I identified some issues with the state file. I'm now able to create a successfull plan but the apply fails.

╷
│ Error: Provider produced inconsistent result after apply
│ 
│ When applying changes to restful_resource.user, provider "provider[\"registry.terraform.io/magodo/restful\"]" produced an unexpected new value: .body: was cty.StringVal("{\"email\":\"first.name@mailaddress.tld\",\"firstName\":\"first\",\"lastName\":\"name\"}"),
│ but now cty.StringVal("{\"firstName\":\"first\",\"lastName\":\"name\"}").
│ 
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵

From what I can see I think this is an issue from the API. Maybe you can confirm my theory?

A put in the API to create a user only creates a 200 with an empty repsonse. If I query the user with /public/v1/users/{email} there is no email returned. The field name is now userId

{
"userId": "first.name@mailaddress.tld",
"firstName": "first",
"lastName": "name",
"fullName": "First Name",
"pictureUrl": "...",
"phoneNumber": "...",
"country": "...",
"platformRole": "...",
"groups": []
}

Any thoughts on that? Thank you

magodo commented 6 months ago

The provider currently has no way to map a property in request to a different property in the response.. But in this case, you can regard the email as a write only property. In this case, the read response will be modified to compensate/include these implied properties to form the final state.

wilkejo commented 6 months ago

Thank you, that solved my issue. My final configuration, at the end a simple one.

resource "restful_resource" "user" {
  path      = "/public/v1/users"
  read_path = "$(path)/first.name@mailaddress.tld"
  create_method = "PUT"
  body = jsonencode({
    lastName  = "name"
    firstName = "first"
    email     = "first.name@mailaddress.tld"
  })
  write_only_attrs = [
    "email",
  ]
}

Can I send you a coffee somehow? ;)

magodo commented 6 months ago

No problem, it is my pleasure that you are using this provider!