Skip to content

Commit

Permalink
feat(metis): add domains&costobject endpoint handler
Browse files Browse the repository at this point in the history
- remove unused code from projects endpoint
- fix some copy+paste errors
- add domains get and list
- add costobject get and list
  • Loading branch information
IvoGoman committed Sep 14, 2023
1 parent 354a880 commit d9aa27b
Show file tree
Hide file tree
Showing 19 changed files with 803 additions and 89 deletions.
69 changes: 69 additions & 0 deletions metis/v1/identity/costobjects/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2023 SAP SE
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package costobjects

import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"

v1 "github.com/sapcc/gophercloud-sapcc/metis/v1"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToCostObjectListQuery() (string, error)
}

// ListOpts is a structure that holds options for listing costobjects.
type ListOpts struct {
// Project will only return costobjects for the specified project uuid.
Project string `q:"project"`
// Domain will only return costobjects for the specified domain uuid.
Domain string `q:"domain"`
// Limit will limit the number of results returned.
Limit int `q:"limit"`
// UUIDs will only return costobjects with the specified UUIDs.
UUIDs []string `q:"uuids"`
}

// ToCostObjectListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToCostObjectListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}

// List returns a Pager which allows you to iterate over a collection of
// costobjects.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
serviceURL := listURL(client)
if opts != nil {
query, err := opts.ToCostObjectListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
serviceURL += query
}
return pagination.NewPager(client, serviceURL, v1.CreatePage())
}

// Get retrieves a specific costobject based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
resp, err := c.Get(getURL(c, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
67 changes: 67 additions & 0 deletions metis/v1/identity/costobjects/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2023 SAP SE
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package costobjects

import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"

v1 "github.com/sapcc/gophercloud-sapcc/metis/v1"
)

type CostObject struct {
Name string `json:"name"`
Type string `json:"type"`
}

// Extract accepts a Page struct, specifically an v1.CommonPage struct,
// and extracts the elements into a slice of CostObjects structs.
func Extract(r pagination.Page) ([]CostObject, error) {
var s struct {
CostObjects []CostObject `json:"items"`
}
if err := (r.(v1.CommonPage)).ExtractInto(&s); err != nil {
return []CostObject{}, err
}
return s.CostObjects, nil
}

// GetResult represents the result of a get operation. Call its Extract method
// to interpret it as a CostObject.
type GetResult struct {
gophercloud.Result
}

// Extract is a function that accepts a result and extracts a CostObject
// resource.
func (r GetResult) Extract() (*CostObject, error) {
var s struct {
Data struct {
Items []CostObject `json:"items"`
} `json:"data"`
}
err := r.ExtractInto(&s)
if err != nil {
return nil, err
}
if s.Data.Items == nil {
return nil, nil
}
return &s.Data.Items[0], nil
}

func (r GetResult) ExtractInto(v interface{}) error {
return r.Result.ExtractIntoStructPtr(v, "")
}
82 changes: 82 additions & 0 deletions metis/v1/identity/costobjects/testing/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2023 SAP SE
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testing

import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"testing"

th "github.com/gophercloud/gophercloud/testhelper"
fake "github.com/gophercloud/gophercloud/testhelper/client"
)

// HandleGetCostObjectSuccessfully creates an HTTP handler at `/identity/costobject/:costobject_id` on the
// test handler mux that responds with a single costobject.
func HandleGetCostObjectSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/identity/costobject/costobject-1", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

jsonBytes, err := os.ReadFile(filepath.Join("fixtures", "get.json"))
th.AssertNoErr(t, err)
w.Write(jsonBytes) //nolint:errcheck
})
}

// HandleListCostObjectsSuccessfully creates an HTTP handler at `/identity/costobject` on the
// test handler mux that responds with a list of costobjects.
func HandleListCostObjectsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/identity/costobject", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

var jsonBytes []byte
var err error

// ensure that the query filters are present
if !r.URL.Query().Has("domain") && !r.URL.Query().Has("project") {
t.Fatal("HandleListCostObjectsSuccessfully failed missing domain and project query parameters")
}

switch r.URL.Query().Has("limit") && !r.URL.Query().Has("cursor") {
case true:
jsonBytes, err = os.ReadFile(filepath.Join("fixtures", "list_with_next.json"))
th.AssertNoErr(t, err)

var resp struct {
APIVersion string `json:"apiVersion"`
Data map[string]any `json:"data"`
}
err = json.Unmarshal(jsonBytes, &resp)
th.AssertNoErr(t, err)
// adding a nextLink to the pagination response
resp.Data["nextLink"] = th.Endpoint() + "/identity/costobject?domain=foo&project=bar&cursor=dummycursor&limit=1"
jsonBytes, err = json.Marshal(resp)
default:
jsonBytes, err = os.ReadFile(filepath.Join("fixtures", "list.json"))
}
th.AssertNoErr(t, err)
w.Write(jsonBytes) //nolint:errcheck
})
}
13 changes: 13 additions & 0 deletions metis/v1/identity/costobjects/testing/fixtures/get.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"apiVersion": "1.0",
"data": {
"kind": "costobject",
"region": "germany",
"items": [
{
"name": "costobject-1",
"type": "CC"
}
]
}
}
13 changes: 13 additions & 0 deletions metis/v1/identity/costobjects/testing/fixtures/list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"apiVersion": "1.0",
"data": {
"kind": "costobject",
"region": "germany",
"items": [
{
"name": "costobject-2",
"type": "IO"
}
]
}
}
13 changes: 13 additions & 0 deletions metis/v1/identity/costobjects/testing/fixtures/list_with_next.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"apiVersion": "1.0",
"data": {
"kind": "costobject",
"region": "germany",
"items": [
{
"name": "costobject-1",
"type": "CC"
}
]
}
}
69 changes: 69 additions & 0 deletions metis/v1/identity/costobjects/testing/requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2023 SAP SE
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testing

import (
"testing"

th "github.com/gophercloud/gophercloud/testhelper"
fakeclient "github.com/gophercloud/gophercloud/testhelper/client"

"github.com/sapcc/gophercloud-sapcc/metis/v1/identity/costobjects"
)

func TestGetProject(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleGetCostObjectSuccessfully(t)

actual, err := costobjects.Get(fakeclient.ServiceClient(), "costobject-1").Extract()
th.AssertNoErr(t, err)

expected := &costobjects.CostObject{
Name: "costobject-1",
Type: "CC",
}
th.CheckDeepEquals(t, expected, actual)
}

func TestListCostObjects(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListCostObjectsSuccessfully(t)
opts := costobjects.ListOpts{
Limit: 1,
// Set the domain and project options to verify the filtering works
Domain: "foo",
Project: "bar",
}

p, err := costobjects.List(fakeclient.ServiceClient(), opts).AllPages()
th.AssertNoErr(t, err)
actual, err := costobjects.Extract(p)
th.AssertNoErr(t, err)

expected := []costobjects.CostObject{
{
Name: "costobject-1",
Type: "CC",
},
{
Name: "costobject-2",
Type: "IO",
},
}

th.CheckDeepEquals(t, expected, actual)
}
25 changes: 25 additions & 0 deletions metis/v1/identity/costobjects/urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2023 SAP SE
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package costobjects

import "github.com/gophercloud/gophercloud"

func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("identity", "costobject")
}

func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("identity", "costobject", id)
}
Loading

0 comments on commit d9aa27b

Please sign in to comment.