Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #36015 - API endpoints to add/remove single roles #597

Merged
merged 1 commit into from May 8, 2023

Conversation

ofedoren
Copy link
Member

No description provided.

@ofedoren ofedoren force-pushed the feat-36015-add-role-by-one branch 2 times, most recently from 186eda3 to 8434de4 Compare February 17, 2023 12:32
api :DELETE, '/hostgroups/:id/remove_ansible_roles',
N_('Remove directly assigned Ansible roles from a hostgroup')
param :id, :identifier, :required => true
param :ansible_role_ids, Array,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you pass an array of ansible_role_ids? I wasn't able to do that (but it works with just one ansible_role_id)

Copy link
Member Author

@ofedoren ofedoren Feb 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simply use JSON object with array: { "ansible_role_ids": [1,2,3] }. Accuracy depends on what you use. Since curl needs to be pretty verbose, I use Postman Chrome extension which makes API testing quite easy and straightforwad.

host = FactoryBot.create(:host,
:managed => false,
:ansible_role_ids => [@ansible_role3.id, @ansible_role2.id, @ansible_role1.id])
post :remove_ansible_roles,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I couldn't figure out yet is how the tests are passing even though it is a post request (same for hostgroup). Should it be a delete request?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question... :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @adamruzicka describes it pretty well here

Copy link
Member

@ekohl ekohl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not commenting inline, since /add_X and remove_x are used in both the hostgroup and the hosts controllers. It's not a very REST thing include the action in the endpoint: the HTTP verb describes what you want.

In REST you have collections and resources. A collection is /hosts and a resource is /hosts/:id. Then the HTTP verb describes the action.

More practical, that means you have a collection /hosts/:id/ansible/roles. You can GET them to get a listing, POST to it to create a new link, which you also direct you to the individual resources.

The individual resources would likely be on /hosts/:host_id/ansible/roles/:role_id, which you can GETto view andDELETEto remove. If you want to update it,PUT` is typically used.

https://restfulapi.net/resource-naming/ does mention that sometimes you do have a more procedural concept, but in this case it maps very well to collections and resources.

Now I will note you here add and delete collections, but https://restfulapi.net/http-methods/#delete really focuses on individual resources. That's what REST is all about. The PR summary describes you say add/remove single roles, so per resource is probably sufficient for that. If that's really what you want to support bulk actions, I'm not sure REST is correct. If you want it in the UI, the graphql API may be more appropriate than REST.

:required => true

def add_ansible_role
process_response @hostgroup.ansible_roles << @ansible_role
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the role is already in there? Is ansible_roles a set or a list?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the role is already assigned, it will return

{
    "error": {
        "message": "Validation failed: Ansible role has already been taken"
    }
}

with 500 error code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A HTTP 500 sounds bad. I'd consider making it 422 if you consider it wrong or 200 if you think it's OK (and I'd use 200 Ok).

You can also consider an API endpoint PUT /hostgroups/:id/ansible_roles/:ansible_role_id which should be idempotent: it ensures it's part of the set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that 500 code is weird for such response, but Rails will treat this as a validation error anyway. I left 422 instead of suppressing the error. If you still find this wrong, I can change it to suppress the ActiveRecord::RecordInvalid and return 200.

:ansible_role_id => [@ansible_role2.id]
},
:session => set_session_user
assert_response :success
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect HTTP 201 Created if it was added, not HTTP 200 Success

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server responds with correct 201.

P.S. Apparently post and others are expecting :action instead of route from routes definition. ofedoren.experience += 1. Also, assert_response here considers both 200 and 201 as successful via mapping, that's why the test doesn't fail, but I've changed to use the specific number instead.
P.P.S. I hate test frameworks :/

:ansible_role_id => [@ansible_role2.id]
},
:session => set_session_user
assert_response :success
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP 204 (No Content) is common after a DELETE. If it's 200 Success, I wonder what's in the content.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the same behaviour with responses almost everywhere else since we use the same logic/methods. In this case the server will respond with 200 and the modified entity (hostgroup in this case).

assert_equal assigns('hostgroup').ansible_roles, [@ansible_role1, @ansible_role2]
end

test 'should remove only specified roles from a hostgroup' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another use case: what if the role doesn't exist? Does it return 404 or 200/204 since it's not part of the role collection anymore? I'd expect the latter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server will respond with 200.

@ofedoren ofedoren force-pushed the feat-36015-add-role-by-one branch 2 times, most recently from e6eddf4 to c1d6084 Compare March 31, 2023 12:59
@ofedoren
Copy link
Member Author

ofedoren commented Apr 11, 2023

[test foreman_ansible]

Oh, right, github actions... There really should be a button/bot command to re-run the tests :/

Copy link
Contributor

@nofaralfasi nofaralfasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Everything works as expected.
@ekohl can I merge? :)

@nofaralfasi
Copy link
Contributor

I will merge it to ensure it's included in the next released version.
If there are any additional changes needed, we can open a RM issue to address them separately.
Thanks @ofedoren!

@nofaralfasi nofaralfasi merged commit f347fbe into theforeman:master May 8, 2023
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants