Skip to content

Commit 5eb944c

Browse files
authored
Merge pull request gophercloud#3037 from gebamp/gophercloud-3015
Support list-modify-delete project tags api calls
2 parents 63fe2de + b18f271 commit 5eb944c

7 files changed

Lines changed: 292 additions & 0 deletions

File tree

internal/acceptance/openstack/identity/v3/projects_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,32 @@ func TestProjectsTags(t *testing.T) {
364364
tools.PrintResource(t, updatedProject)
365365
th.AssertEquals(t, len(updatedProject.Tags), 0)
366366
}
367+
368+
func TestProjectsTagsCRUD(t *testing.T) {
369+
clients.RequireAdmin(t)
370+
371+
client, err := clients.NewIdentityV3Client()
372+
th.AssertNoErr(t, err)
373+
374+
createOpts := projects.CreateOpts{
375+
Tags: []string{"Tag1", "Tag2"},
376+
}
377+
378+
projectMain, err := CreateProject(t, client, &createOpts)
379+
th.AssertNoErr(t, err)
380+
defer DeleteProject(t, client, projectMain.ID)
381+
382+
projectTagsList, err := projects.ListTags(context.TODO(), client, projectMain.ID).Extract()
383+
tools.PrintResource(t, projectTagsList)
384+
th.AssertNoErr(t, err)
385+
386+
modifyOpts := projects.ModifyTagsOpts{
387+
Tags: []string{"foo", "bar"},
388+
}
389+
projectTags, err := projects.ModifyTags(context.TODO(), client, projectMain.ID, modifyOpts).Extract()
390+
tools.PrintResource(t, projectTags)
391+
th.AssertNoErr(t, err)
392+
393+
err = projects.DeleteTags(context.TODO(), client, projectMain.ID).ExtractErr()
394+
th.AssertNoErr(t, err)
395+
}

openstack/identity/v3/projects/doc.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,30 @@ Example to Delete a Project
6464
if err != nil {
6565
panic(err)
6666
}
67+
68+
Example to List all tags of a Project
69+
70+
projectID := "966b3c7d36a24facaf20b7e458bf2192"
71+
err := projects.ListTags(context.TODO(), identityClient, projectID).Extract()
72+
if err != nil {
73+
panic(err)
74+
}
75+
76+
Example to modify all tags of a Project
77+
78+
projectID := "966b3c7d36a24facaf20b7e458bf2192"
79+
tags := ["foo", "bar"]
80+
projects, err := projects.ModifyTags(context.TODO(), identityClient, projectID, tags).Extract()
81+
if err != nil {
82+
panic(err)
83+
}
84+
85+
Example to Delete all tags of a Project
86+
87+
projectID := "966b3c7d36a24facaf20b7e458bf2192"
88+
err := projects.DeleteTags(context.TODO(), identityClient, projectID).ExtractErr()
89+
if err != nil {
90+
panic(err)
91+
}
6792
*/
6893
package projects

openstack/identity/v3/projects/requests.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,58 @@ func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, o
243243
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
244244
return
245245
}
246+
247+
// CheckTags lists tags for a project.
248+
func ListTags(ctx context.Context, client *gophercloud.ServiceClient, projectID string) (r ListTagsResult) {
249+
resp, err := client.Get(ctx, listTagsURL(client, projectID), &r.Body, &gophercloud.RequestOpts{
250+
OkCodes: []int{200},
251+
})
252+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
253+
return
254+
}
255+
256+
// Tags represents a list of Tags object.
257+
type ModifyTagsOpts struct {
258+
// Tags is the list of tags associated with the project.
259+
Tags []string `json:"tags,omitempty"`
260+
}
261+
262+
// ModifyTagsOptsBuilder allows extensions to add additional parameters to
263+
// the Modify request.
264+
type ModifyTagsOptsBuilder interface {
265+
ToModifyTagsCreateMap() (map[string]interface{}, error)
266+
}
267+
268+
// ToModifyTagsCreateMap formats a ModifyTagsOpts into a Modify tags request.
269+
func (opts ModifyTagsOpts) ToModifyTagsCreateMap() (map[string]interface{}, error) {
270+
b, err := gophercloud.BuildRequestBody(opts, "")
271+
272+
if err != nil {
273+
return nil, err
274+
}
275+
return b, nil
276+
}
277+
278+
// ModifyTags deletes all tags of a project and adds new ones.
279+
func ModifyTags(ctx context.Context, client *gophercloud.ServiceClient, projectID string, opts ModifyTagsOpts) (r ModifyTagsResult) {
280+
281+
b, err := opts.ToModifyTagsCreateMap()
282+
if err != nil {
283+
r.Err = err
284+
return
285+
}
286+
resp, err := client.Put(ctx, modifyTagsURL(client, projectID), b, &r.Body, &gophercloud.RequestOpts{
287+
OkCodes: []int{200},
288+
})
289+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
290+
return
291+
}
292+
293+
// DeleteTag deletes a tag from a project.
294+
func DeleteTags(ctx context.Context, client *gophercloud.ServiceClient, projectID string) (r DeleteTagsResult) {
295+
resp, err := client.Delete(ctx, deleteTagsURL(client, projectID), &gophercloud.RequestOpts{
296+
OkCodes: []int{204},
297+
})
298+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
299+
return
300+
}

openstack/identity/v3/projects/results.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,49 @@ func (r projectResult) Extract() (*Project, error) {
154154
err := r.ExtractInto(&s)
155155
return s.Project, err
156156
}
157+
158+
// Tags represents a list of Tags object.
159+
type Tags struct {
160+
// Tags is the list of tags associated with the project.
161+
Tags []string `json:"tags,omitempty"`
162+
}
163+
164+
// ListTagsResult is the result of a List Tags request. Call its Extract method to
165+
// interpret it as a list of tags.
166+
type ListTagsResult struct {
167+
gophercloud.Result
168+
}
169+
170+
// Extract interprets any ListTagsResult as a Tags Object.
171+
func (r ListTagsResult) Extract() (*Tags, error) {
172+
var s = &Tags{}
173+
err := r.ExtractInto(&s)
174+
return s, err
175+
}
176+
177+
// ProjectTags represents a list of Tags object.
178+
type ProjectTags struct {
179+
// Tags is the list of tags associated with the project.
180+
Projects []Project `json:"projects,omitempty"`
181+
// Links contains referencing links to the implied_role.
182+
Links map[string]interface{} `json:"links"`
183+
}
184+
185+
// ModifyTagsResLinksult is the result of a Tags request. Call its Extract method to
186+
// interpret it as a project of tags.
187+
type ModifyTagsResult struct {
188+
gophercloud.Result
189+
}
190+
191+
// Extract interprets any ModifyTags as a Tags Object.
192+
func (r ModifyTagsResult) Extract() (*ProjectTags, error) {
193+
var s = &ProjectTags{}
194+
err := r.ExtractInto(&s)
195+
return s, err
196+
}
197+
198+
// DeleteTagsResult is the result of a Delete Tags request. Call its ExtractErr method to
199+
// determine if the request succeeded or failed.
200+
type DeleteTagsResult struct {
201+
gophercloud.ErrResult
202+
}

openstack/identity/v3/projects/testing/fixtures_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,44 @@ const UpdateOutput = `
138138
}
139139
`
140140

141+
// ListTagsOutput provides the output to a ListTags request.
142+
const ListTagsOutput = `
143+
{
144+
"tags": ["foo", "bar"]
145+
}
146+
`
147+
148+
// ModifyProjectTagsRequest provides the input to a ModifyTags request.
149+
const ModifyProjectTagsRequest = `
150+
{
151+
"tags": ["foo", "bar"]
152+
}
153+
`
154+
155+
// ModifyProjectTagsOutput provides the output to a ModifyTags request.
156+
const ModifyProjectTagsOutput = `
157+
{
158+
"links": {
159+
"next": null,
160+
"previous": null,
161+
"self": "http://identity:5000/v3/projects"
162+
},
163+
"projects": [
164+
{
165+
"description": "Test Project",
166+
"domain_id": "default",
167+
"enabled": true,
168+
"id": "3d4c2c82bd5948f0bcab0cf3a7c9b48c",
169+
"links": {
170+
"self": "http://identity:5000/v3/projects/3d4c2c82bd5948f0bcab0cf3a7c9b48c"
171+
},
172+
"name": "demo",
173+
"tags": ["foo", "bar"]
174+
}
175+
]
176+
}
177+
`
178+
141179
// FirstProject is a Project fixture.
142180
var FirstProject = projects.Project{
143181
Description: "my first project",
@@ -212,6 +250,31 @@ var ExpectedAvailableProjectsSlice = []projects.Project{FirstProject, SecondProj
212250
// ExpectedProjectSlice is the slice of projects expected to be returned from ListOutput.
213251
var ExpectedProjectSlice = []projects.Project{RedTeam, BlueTeam}
214252

253+
var ExpectedTags = projects.Tags{
254+
Tags: []string{"foo", "bar"},
255+
}
256+
257+
var ExpectedProjects = projects.ProjectTags{
258+
Projects: []projects.Project{
259+
{
260+
Description: "Test Project",
261+
DomainID: "default",
262+
Enabled: true,
263+
ID: "3d4c2c82bd5948f0bcab0cf3a7c9b48c",
264+
Extra: map[string]interface{}{"links": map[string]interface{}{
265+
"self": "http://identity:5000/v3/projects/3d4c2c82bd5948f0bcab0cf3a7c9b48c",
266+
}},
267+
Name: "demo",
268+
Tags: []string{"foo", "bar"},
269+
},
270+
},
271+
Links: map[string]interface{}{
272+
"next": nil,
273+
"previous": nil,
274+
"self": "http://identity:5000/v3/projects",
275+
},
276+
}
277+
215278
// HandleListAvailableProjectsSuccessfully creates an HTTP handler at `/auth/projects`
216279
// on the test handler mux that responds with a list of two tenants.
217280
func HandleListAvailableProjectsSuccessfully(t *testing.T) {
@@ -290,3 +353,32 @@ func HandleUpdateProjectSuccessfully(t *testing.T) {
290353
fmt.Fprintf(w, UpdateOutput)
291354
})
292355
}
356+
357+
func HandleListProjectTagsSuccessfully(t *testing.T) {
358+
th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) {
359+
th.TestMethod(t, r, "GET")
360+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
361+
362+
w.WriteHeader(http.StatusOK)
363+
fmt.Fprintf(w, ListTagsOutput)
364+
})
365+
}
366+
367+
func HandleModifyProjectTagsSuccessfully(t *testing.T) {
368+
th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) {
369+
th.TestMethod(t, r, "PUT")
370+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
371+
th.TestJSONRequest(t, r, ModifyProjectTagsRequest)
372+
373+
w.WriteHeader(http.StatusOK)
374+
fmt.Fprintf(w, ModifyProjectTagsOutput)
375+
})
376+
}
377+
func HandleDeleteProjectTagsSuccessfully(t *testing.T) {
378+
th.Mux.HandleFunc("/projects/966b3c7d36a24facaf20b7e458bf2192/tags", func(w http.ResponseWriter, r *http.Request) {
379+
th.TestMethod(t, r, "DELETE")
380+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
381+
382+
w.WriteHeader(http.StatusNoContent)
383+
})
384+
}

openstack/identity/v3/projects/testing/requests_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,37 @@ func TestUpdateProject(t *testing.T) {
134134
actual, err := projects.Update(context.TODO(), client.ServiceClient(), "1234", updateOpts).Extract()
135135
th.AssertNoErr(t, err)
136136
th.CheckDeepEquals(t, UpdatedRedTeam, *actual)
137+
t.Log(projects.Update(context.TODO(), client.ServiceClient(), "1234", updateOpts))
138+
}
139+
140+
func TestListProjectTags(t *testing.T) {
141+
th.SetupHTTP()
142+
defer th.TeardownHTTP()
143+
HandleListProjectTagsSuccessfully(t)
144+
145+
actual, err := projects.ListTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192").Extract()
146+
th.AssertNoErr(t, err)
147+
th.CheckDeepEquals(t, ExpectedTags, *actual)
148+
}
149+
150+
func TestModifyProjectTags(t *testing.T) {
151+
th.SetupHTTP()
152+
defer th.TeardownHTTP()
153+
HandleModifyProjectTagsSuccessfully(t)
154+
155+
modifyOpts := projects.ModifyTagsOpts{
156+
Tags: []string{"foo", "bar"},
157+
}
158+
actual, err := projects.ModifyTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192", modifyOpts).Extract()
159+
th.AssertNoErr(t, err)
160+
th.CheckDeepEquals(t, ExpectedProjects, *actual)
161+
}
162+
163+
func TestDeleteTags(t *testing.T) {
164+
th.SetupHTTP()
165+
defer th.TeardownHTTP()
166+
HandleDeleteProjectTagsSuccessfully(t)
167+
168+
err := projects.DeleteTags(context.TODO(), client.ServiceClient(), "966b3c7d36a24facaf20b7e458bf2192").ExtractErr()
169+
th.AssertNoErr(t, err)
137170
}

openstack/identity/v3/projects/urls.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,15 @@ func deleteURL(client *gophercloud.ServiceClient, projectID string) string {
2525
func updateURL(client *gophercloud.ServiceClient, projectID string) string {
2626
return client.ServiceURL("projects", projectID)
2727
}
28+
29+
func listTagsURL(client *gophercloud.ServiceClient, projectID string) string {
30+
return client.ServiceURL("projects", projectID, "tags")
31+
}
32+
33+
func modifyTagsURL(client *gophercloud.ServiceClient, projectID string) string {
34+
return client.ServiceURL("projects", projectID, "tags")
35+
}
36+
37+
func deleteTagsURL(client *gophercloud.ServiceClient, projectID string) string {
38+
return client.ServiceURL("projects", projectID, "tags")
39+
}

0 commit comments

Comments
 (0)