[katello-devel] [foreman-dev] Foreman API many-to-many RESTful

Joseph Magen jmagen at redhat.com
Wed Feb 27 15:05:18 UTC 2013


>From my reading, it seems that other API's expose the join table for adding/removing many-to-many relationships.  I was originally against this, but I don't mind which way the API is written.  I'm interested in feedback from those who are consuming the Foreman API.

Here are the options I came up with for adding/removing puppetclasses from hosts/hostgroups:

#1  POST to join table /api/hosts/:id/host_classes and pass the puppetclass_id as a parameter - most RESTful

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X POST -d "{\"host_class\":{\"puppetclass_id\":\"17\"}}" http://0.0.0.0:3000/api/hosts/182/host_classes

#2  same as above, but change the path to puppetclass_ids so there is a common route structure for hosts and hostgroups.  Otherwise, it's hostgroups/:id/hostgroup_classes (following the database)

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X POST -d "{\"puppetclass_id\":\"17\"}}" http://0.0.0.0:3000/api/hosts/182/puppetclass_ids

3)  POST to puppetclasses resource - not exactly RESTful, since it's NOT creating a new puppetclass, but rather a new relationship with host

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X POST -d "{\"puppetclass\":{\"id\":\"17\"}} http://0.0.0.0:3000/api/hosts/182/puppetclasses

4)  same as above #3 but with PUT - also not result, since updating puppetclass_ids on host resource, not puppetclass resource

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X PUT -d "{\"puppetclass\":{\"id\":\"17\"}} http://0.0.0.0:3000/api/hosts/182/puppetclasses

5)  PUT to puppetclasses and include puppetclass_id in URL, not passed as parameter.  Not RESTful, since we are updating the host resource puppetclass_ids, not the puppetclass.

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X PUT -d "{} http://0.0.0.0:3000/api/hosts/182/puppetclasses/17

Any feedback? 

Thanks,

Joseph




----- Original Message -----
From: "Martyn Taylor" <mtaylor at redhat.com>
To: "Joseph Magen" <jmagen at redhat.com>
Cc: foreman-dev at googlegroups.com, katello-devel at redhat.com
Sent: Tuesday, February 26, 2013 4:31:32 PM
Subject: Re: [katello-devel] [foreman-dev] Foreman API many-to-many RESTful


On 02/25/2013 03:28 PM, Joseph Magen wrote: 


Martyn,

thanks for your input. 

doing a GET on /api/hosts/:id/puppetclasses/:id result in retrieving a puppetclass? Yes 

If this is the case then the DELETE would actually delete the resource not the relationship. No. I check if there is a nested resource (ex. host_id).  If there is, I remove the relationship, not the resource. If there is not a nested resource like this, api/puppetclasses/:id, then yes, it deletes the resource.

Joseph Hi Joseph, 

So, my point here is that the expected behaviour of doing a DELETE as outlined by HTTP Specification would result in deleting the actual resource as represented by the URI (not the relationship). http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7 

By not doing that you are firstly modifying the HTTP protocol by changing behaviour of the DELETE verb (you can read Roy Fieldings ranting on this subject here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7 ) 

Secondly you are creating inconsistencies in the API: DELETE means different things depending on which URI you are acting on (even though these URI represent the same resource). 

Given the above and the that you also break the PUT behaviour using Option#1, it seems less than ideal. 

The approach I outlined below by modifying collections on resources strictly adheres to HTTP, which in my opinion is crucial when using REST over HTTP. 

Regards 

Martyn 



----- Original Message -----
From: "Martyn Taylor" <mtaylor at redhat.com> To: "Joseph Magen" <jmagen at redhat.com> Cc: foreman-dev at googlegroups.com , katello-devel at redhat.com Sent: Monday, February 25, 2013 5:00:23 PM
Subject: Re: [katello-devel] [foreman-dev] Foreman API many-to-many RESTful


Hi Joseph. 

This is something we have spoken about in some depth at Aeolus. 

With regards to option #2. Your API need not expose the underlying implementation detail. i.e. the join table is simply a consequence of using a relational database as your backend. Exposing this kind of information is not neccessary in the API. One common mistake people make when implementing APIs is simply exposing the database model. Care should be taken to make sure the API exposes only what a user wants to consume and implemenation details left out. 

Option #1 is also less than idea since it does not conform to the PUT and DELETE verbs as defined by the HTTP specification. 

PUT requires that either a new resource is created at the URI specified or that the resource that resides at the specified URI is updated. 

In your example neither of these happen. 

DELETE method requests that the origin server delete the resource identified by the Request-URI. 

It seems in your example that the URI you PUT to will reference a real resource. i..e does doing a GET on /api/hosts/:id/puppetclasses/:id result in retrieving a puppetclass? If this is the case then the DELETE would actually delete the resource not the relationship. 


An alternate approach is to treat the many relations as collections associated with each resource. You could modify the resource (using PUT or PATCH) by adding or removing relations in the collection. For example: 

Initial state resources: 

<hosts href="hosts/1"> 
<puppetclasses> 
</puppetclasses> 
<hosts> 

<puppetclasses href="puppetclasses/1> 
<hosts> 
</host> 
<hosts> 

Add Relationship 

To add a relationship between the resources simply PUT to either puppetclasses/1 or hosts/1 and add the resource in the collection. 

PUT 
<hosts href="hosts/1"> 
<puppetclasses> 
<puppetclass href="/puppetclasses/1" /> 
</puppetclasses> 
<hosts> 

State after PUT: 
<hosts href="hosts/1"> 
<puppetclasses> 
<puppetclass href="/puppetclasses/1" /> 
</puppetclasses> 
<hosts> 

<puppetclasses href="puppetclasses/1> 
<hosts> 
<host href="hosts/1" /> 
</host> 
<hosts> 

Remove Relationship 
To remove a relationship between the resources PUT to either puppetclasses/1 or hosts/1 and remove the resource in the collection. 

PUT 
<hosts href="hosts/1"> 
<puppetclasses> 
</puppetclasses> 
<hosts> 


States after PUT: 

<hosts href="hosts/1"> 
<puppetclasses> 
</puppetclasses> 
<hosts> 

<puppetclasses href="puppetclasses/1> 
<hosts> 
</host> 
<hosts> 


Hope this helps. 

Regards 

Martyn 
On 02/25/2013 02:30 PM, Joseph Magen wrote: 


Correction.  The pull request is https://github.com/theforeman/foreman/pull/432 ----- Original Message -----
From: "Joseph Magen" <jmagen at redhat.com> To: "foreman-dev" <foreman-dev at googlegroups.com> , katello-devel at redhat.com Sent: Monday, February 25, 2013 4:29:17 PM
Subject: [foreman-dev] Foreman API many-to-many RESTful


I would like to get people's feedback on how to best implement many-to-many relationships in a RESTful way. 

I just pushed a pull request that allows users to add and remove puppetclasses from a host or hostgroup using the API. https://github.com/theforeman/foreman/pull/424 Option #1) Do not expose the join table in the API and use PUT to add a new puppetclass (this is the option that I chose) 

PUT /api/hosts/:id/puppetclasses/:id 
PUT /api/hostgroups/:id/puppetclasses/:id 

curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X PUT -d "{} " http://0.0.0.0:3000/api/hosts/182/puppetclasses/17 

Notice that no data is passed beyond what is in the URL. 

DELETE /api/hosts/:id/puppetclasses/:id 
DELETE /api/hostgroups/:id/puppetclasses/:id 

curl -u admin:secret -H " Content-Type:application/json" -H "Accept:application/json,version=2" -X DELETE http://0.0.0.0:3000/api/hosts/182/puppetclasses/17 Option #2) Expose the join table in the API 

POST /api/hosts/:id/host_classes 
POST /api/hostgroups/:id/hostgroup_classes 

DELETE /api/hosts/:id/host_classes 
DELETE /api/hostgroups/:id/hostgroup_classes 


curl -u admin:secret -H "Content-Type:application/json" -H "Accept:application/json,version=2" -X POST -d "{\"host_class\":{\"puppetclass_id\":\"17\"}}" http://0.0.0.0:3000/api/hosts/182/host_classes I like option #1 better. Any feedback? 

Regards, 

Joseph 




More information about the katello-devel mailing list