Balena API: create/update a device environment variable in one request

Hello,
I’m working on creating/updating device environment variables via the API (I’d actually like to create device service variables, but that requires more info, including something called a “service_install”, which I can’t find an explanation of… I digress).

If I try to POST a variable to a device that already has it set, I get an error. In order to update that existing variable, I have to know the exact ID of the device environment variable. That turns one request into three:

  1. Try the POST
  2. POST fails, so GET all existing variables and loop through to find the existing one
  3. PATCH the existing variable

I suppose this could be shortened to the following:

  1. GET all variables and look through to see if it is set
  2. POST or PATCH depending on whether or not it exists

But then I’m always doing 2 requests, which is not ideal (though certainly not a show-stopper). Is there a way to do it in one fell swoop? In my opinion, a POST should just clobber anything that is already set.

Semi-related question: some of your API routes (e.g. device GET, device_environment_variable GET) seem to return data nested in an array key d. But this doesn’t seem to hold for other routes, which don’t box the results up at all (e.g. device_environment_variable POST). What’s the difference? Note that I’m using an OData-compatible PHP library to communicate with your API, so maybe it’s throwing stuff in?

The fact that POST device_environment_variable returns an object on the top level, and that object has the key value, seems to be throwing off the OData library I’m using. I don’t know if the problem is on your end (i.e. your API is using an OData reserved keyword) or if the library just makes an assumption that it shouldn’t. The specific line that is messing up is ODataResponse.php line 144, which assumes that any response with the key value has an array at that key.

The response the OData library gets back from POST (sensitive info scrubbed)

(
    [created_at] => 2021-02-08T00:00:00.000Z
    [device] => Array
        (
            [__id] => 123123
            [__deferred] => Array
                (
                    [uri] => /resin/device(@id)?@id=123123
                )

        )

    [name] => MY_VAR
    [id] => 321321
    [value] => lol123asdf
    [__metadata] => Array
        (
            [uri] => /resin/device_environment_variable(@id)?@id=321321
        )

)

Meanwhile, PATCH doesn’t return anything at all, so it doesn’t cause an error. ¯\_(ツ)_/¯

Hi,
Our node sdk already offers a models.device.envVar.set() method which look similar to what you are trying to achieve:

On the other hand I realize that you are probably more interested at using the OData API directly on your PHP project.
You can achieve this by using a PUT request where you use the composite natural key of the resource as id of the request.

curl $'https://api.balena-cloud.com/v6/application_environment_variable(application=<app_id>,name=\'testname\')' \
  -X 'PUT' \
  -H 'authorization: Bearer <TOKEN>' \
  --data-raw '{"value":"test value"}'

Be aware though that updates of records with this approach, currently work like a DELETE and then re-INSERT, which means that you will be getting a fresh date on the created_at field whenever you so a PUT like the above. This might change it the future though to work like an POST/PATCH, but that shouldn’t be a breaking change probably.

The OData specification refers to this using the “Alternate Key” term. Let me also point you to that part of the specification:
https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31360936

Kind regards,
Thodoris

1 Like

Hi Thodoris,
This is great information, thanks! PUT might be exactly what I was looking for. I don’t see it mentioned in the API documentation (and the OData library I’m using doesn’t support it), which is why I hadn’t tried it yet.

I’ll try bypassing the library and making my requests to the API directly, which should allow me to use PUT.

One question about your example: is the name parameter in the composite natural key the name of the device, or the name of the environment variable? It seems like it would be the name of the environment variable, in which case wouldn’t I be updating all instances of that variable (across all devices)? I need to limit my set/update to one device, because each device gets a different value for this variable. I imagine I could just add a filter for device=<id>, right?

@thgreasi I’ve been able to get PUT for device_environment_variable working using an analogue of the composite key you gave for application_environment_variable (i.e. device=<deviceid>,name=<varname>.

However, that request takes over 10 seconds(!!) to complete. Every other Balena API request I’ve ever done is completed in less than 1s, including PATCH with the exact same route and parameters.

Should I switch back to the old method I was doing, or is there a temporary hiccup somewhere that will be ironed out?

Thanks.

EDIT: cURL examples.

root@host:/project$ time curl -X PATCH \
> "https://api.balena-cloud.com/v6/device_environment_variable(device=<deviceid>,name='TEST_VAR')" \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer <token>" \
> --data '{
>     "value": "cURLd PATCH"
> }'
OK
real    0m0.416s
user    0m0.011s
sys     0m0.007s
root@host:/project$ time curl -X PUT \
> "https://api.balena-cloud.com/v6/device_environment_variable(device=<deviceid>,name='TEST_VAR')" \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer <token>" \
> --data '{
>     "value": "cURLd PUT"
> }'
OK
real    0m16.619s
user    0m0.010s
sys     0m0.015s

Hi,

Thanks for reporting this.
I would like to let you know that for first we were able to reproduce what you have been reporting about the PUT requests being considerably slower that the respective POST & PATCH requests.
We also confirmed that on other resources like application tags, so it seems it’s not related to the implementation of application environment variables.
We also realized that the PUT request takes more time even when compared with the total time of a DELETE a POST & a PATCH.
I’ve opened an internal issue to track this and we will let you know once a fix for this is released, but I can’t at the moment provide an estimate for that.

In the mean time if the delay that this operation adds becomes an issue for your implementation, I would suggest you to rely on a POST-PATCH pair checking for 409 as the return status of the POST, or alternatively for a GET-POST-PATCH apporach.

Kind regards,
Thodoris

1 Like

Hi,

I would like to get back on you about our support for PUT requests on our backend, and provide a heads-up that we are soon going to temporarily disable serving PUT requests from our backend.

After further internal discussion and given the decreased performance of PUT requests, we decided that it would be for the best to temporarily disable serving PUT requests and re-enable them once we feel more comfortable with how they work and perform on our platform.
As a result of this change, all third party implementations that rely on PUT requests being supported would stop working.

We are sorry for the inconvenience.

Let us know if we can provide any further help on this matter.

Kind regards,
Thodoris

1 Like

Hi Thodoris,
Thanks for the heads up. I hope you give developers significant notice and allow them time to switch away from using PUT requests before you disable them.

Do you have any estimate of when they might be back up and working?

Regards,
Tommy

Hi Tommy

Unfortunately, it would be difficult to provide an estimate, PUT is not officially something we document at this time and is therefore not a very high priority for us. As Thodoris mentioned, an internal issue has been opened and we will let you know once a fix is released.

Kind regards,
Jasmine