blob: 85a73ed98d8506ce48a1e9a3011d309d535d6ea9 [file] [log] [blame]
Jon Perritte7b86d12015-01-16 20:37:11 -07001package services
2
3import (
Jon Perrittb0ab0d12015-01-27 12:12:51 -07004 "fmt"
Jon Perrittb8713ad2015-01-21 15:02:58 -07005 "strings"
6
Jon Perritte7b86d12015-01-16 20:37:11 -07007 "github.com/rackspace/gophercloud"
8 "github.com/rackspace/gophercloud/pagination"
9)
10
11// ListOptsBuilder allows extensions to add additional parameters to the
12// List request.
13type ListOptsBuilder interface {
14 ToCDNServiceListQuery() (string, error)
15}
16
17// ListOpts allows the filtering and sorting of paginated collections through
18// the API. Marker and Limit are used for pagination.
19type ListOpts struct {
20 Marker string `q:"marker"`
21 Limit int `q:"limit"`
22}
23
24// ToCDNServiceListQuery formats a ListOpts into a query string.
25func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
26 q, err := gophercloud.BuildQueryString(opts)
27 if err != nil {
28 return "", err
29 }
30 return q.String(), nil
31}
32
33// List returns a Pager which allows you to iterate over a collection of
34// CDN services. It accepts a ListOpts struct, which allows for pagination via
35// marker and limit.
36func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
37 url := listURL(c)
38 if opts != nil {
39 query, err := opts.ToCDNServiceListQuery()
40 if err != nil {
41 return pagination.Pager{Err: err}
42 }
43 url += query
44 }
45
46 createPage := func(r pagination.PageResult) pagination.Page {
47 p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
48 p.MarkerPageBase.Owner = p
49 return p
50 }
51
52 pager := pagination.NewPager(c, url, createPage)
53 return pager
54}
55
56// CreateOptsBuilder is the interface options structs have to satisfy in order
57// to be used in the main Create operation in this package. Since many
58// extensions decorate or modify the common logic, it is useful for them to
59// satisfy a basic interface in order for them to be used.
60type CreateOptsBuilder interface {
61 ToCDNServiceCreateMap() (map[string]interface{}, error)
62}
63
64// CreateOpts is the common options struct used in this package's Create
65// operation.
66type CreateOpts struct {
67 // REQUIRED. Specifies the name of the service. The minimum length for name is
68 // 3. The maximum length is 256.
69 Name string
70 // REQUIRED. Specifies a list of domains used by users to access their website.
71 Domains []Domain
72 // REQUIRED. Specifies a list of origin domains or IP addresses where the
73 // original assets are stored.
74 Origins []Origin
75 // REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
76 // flavors, see the operation to list the available flavors. The minimum
77 // length for flavor_id is 1. The maximum length is 256.
78 FlavorID string
79 // OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
Jon Perritt0bd23732015-01-19 20:58:57 -070080 Caching []CacheRule
Jon Perritte7b86d12015-01-16 20:37:11 -070081 // OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
82 Restrictions []Restriction
83}
84
85// ToCDNServiceCreateMap casts a CreateOpts struct to a map.
86func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
87 s := make(map[string]interface{})
88
89 if opts.Name == "" {
90 return nil, no("Name")
91 }
92 s["name"] = opts.Name
93
94 if opts.Domains == nil {
95 return nil, no("Domains")
96 }
97 for _, domain := range opts.Domains {
98 if domain.Domain == "" {
99 return nil, no("Domains[].Domain")
100 }
101 }
102 s["domains"] = opts.Domains
103
104 if opts.Origins == nil {
105 return nil, no("Origins")
106 }
107 for _, origin := range opts.Origins {
108 if origin.Origin == "" {
109 return nil, no("Origins[].Origin")
110 }
Jon Perrittb8713ad2015-01-21 15:02:58 -0700111 if origin.Rules == nil && len(opts.Origins) > 1 {
Jon Perritte7b86d12015-01-16 20:37:11 -0700112 return nil, no("Origins[].Rules")
113 }
114 for _, rule := range origin.Rules {
115 if rule.Name == "" {
116 return nil, no("Origins[].Rules[].Name")
117 }
118 if rule.RequestURL == "" {
119 return nil, no("Origins[].Rules[].RequestURL")
120 }
121 }
122 }
123 s["origins"] = opts.Origins
124
125 if opts.FlavorID == "" {
126 return nil, no("FlavorID")
127 }
128 s["flavor_id"] = opts.FlavorID
129
130 if opts.Caching != nil {
131 for _, cache := range opts.Caching {
132 if cache.Name == "" {
133 return nil, no("Caching[].Name")
134 }
135 if cache.Rules != nil {
136 for _, rule := range cache.Rules {
137 if rule.Name == "" {
138 return nil, no("Caching[].Rules[].Name")
139 }
140 if rule.RequestURL == "" {
141 return nil, no("Caching[].Rules[].RequestURL")
142 }
143 }
144 }
145 }
146 s["caching"] = opts.Caching
147 }
148
149 if opts.Restrictions != nil {
150 for _, restriction := range opts.Restrictions {
151 if restriction.Name == "" {
152 return nil, no("Restrictions[].Name")
153 }
154 if restriction.Rules != nil {
155 for _, rule := range restriction.Rules {
156 if rule.Name == "" {
157 return nil, no("Restrictions[].Rules[].Name")
158 }
159 }
160 }
161 }
162 s["restrictions"] = opts.Restrictions
163 }
164
165 return s, nil
166}
167
168// Create accepts a CreateOpts struct and creates a new CDN service using the
169// values provided.
170func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
171 var res CreateResult
172
173 reqBody, err := opts.ToCDNServiceCreateMap()
174 if err != nil {
175 res.Err = err
176 return res
177 }
178
179 // Send request to API
Ash Wilson4bf41a32015-02-12 15:52:44 -0500180 resp, err := c.Request("POST", createURL(c), gophercloud.RequestOpts{
181 JSONBody: &reqBody,
Jon Perritte7b86d12015-01-16 20:37:11 -0700182 })
Ash Wilson2491b4c2015-02-12 16:13:39 -0500183 res.Header = resp.Header
Jon Perrittd21966f2015-01-20 19:22:45 -0700184 res.Err = err
Jon Perritte7b86d12015-01-16 20:37:11 -0700185 return res
186}
187
Jon Perrittb8713ad2015-01-21 15:02:58 -0700188// Get retrieves a specific service based on its URL or its unique ID. For
189// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
190// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
191// are valid options for idOrURL.
192func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
193 var url string
194 if strings.Contains(idOrURL, "/") {
195 url = idOrURL
196 } else {
197 url = getURL(c, idOrURL)
198 }
199
Jon Perritte7b86d12015-01-16 20:37:11 -0700200 var res GetResult
Ash Wilsonf98df8d2015-02-12 15:46:40 -0500201 _, res.Err = c.Request("GET", url, gophercloud.RequestOpts{
202 JSONResponse: &res.Body,
Jon Perritte7b86d12015-01-16 20:37:11 -0700203 })
204 return res
205}
206
Ash Wilsona623ff72015-01-28 15:50:37 -0500207// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
208// or removed.
209type Path struct {
210 baseElement string
211}
212
Ash Wilson05280702015-01-29 11:19:25 -0500213func (p Path) renderRoot() string {
214 return "/" + p.baseElement
215}
216
Ash Wilsona623ff72015-01-28 15:50:37 -0500217func (p Path) renderDash() string {
218 return fmt.Sprintf("/%s/-", p.baseElement)
219}
220
221func (p Path) renderIndex(index int64) string {
222 return fmt.Sprintf("/%s/%d", p.baseElement, index)
223}
224
225var (
226 // PathDomains indicates that an update operation is to be performed on a Domain.
227 PathDomains = Path{baseElement: "domains"}
228
229 // PathOrigins indicates that an update operation is to be performed on an Origin.
230 PathOrigins = Path{baseElement: "origins"}
231
232 // PathCaching indicates that an update operation is to be performed on a CacheRule.
233 PathCaching = Path{baseElement: "caching"}
234)
235
Ash Wilson4ee05012015-01-28 16:13:43 -0500236type value interface {
Ash Wilsonb47ebed2015-01-29 11:08:41 -0500237 toPatchValue() interface{}
Ash Wilson4ee05012015-01-28 16:13:43 -0500238 appropriatePath() Path
Ash Wilson05280702015-01-29 11:19:25 -0500239 renderRootOr(func(p Path) string) string
Ash Wilson4ee05012015-01-28 16:13:43 -0500240}
241
Ash Wilson7b729532015-01-28 16:15:23 -0500242// Patch represents a single update to an existing Service. Multiple updates to a service can be
243// submitted at the same time.
244type Patch interface {
245 ToCDNServiceUpdateMap() map[string]interface{}
246}
247
Ash Wilson299363d2015-01-29 10:49:40 -0500248// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to
249// a Service at a fixed index. Use an Append instead to append the new value to the end of its
250// collection. Pass it to the Update function as part of the Patch slice.
251type Insertion struct {
252 Index int64
Ash Wilson334277c2015-01-29 09:08:52 -0500253 Value value
254}
255
Ash Wilson299363d2015-01-29 10:49:40 -0500256// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
Ash Wilson334277c2015-01-29 09:08:52 -0500257// Update call.
Ash Wilson299363d2015-01-29 10:49:40 -0500258func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
259 return map[string]interface{}{
260 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500261 "path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
Ash Wilson299363d2015-01-29 10:49:40 -0500262 "value": i.Value.toPatchValue(),
263 }
264}
265
266// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a
267// Service at the end of its respective collection. Use an Insertion instead to insert the value
268// at a fixed index within the collection. Pass this to the Update function as part of its
269// Patch slice.
270type Append struct {
271 Value value
272}
273
274// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the
275// Update call.
276func (a Append) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilson334277c2015-01-29 09:08:52 -0500277 return map[string]interface{}{
278 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500279 "path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }),
Ash Wilson334277c2015-01-29 09:08:52 -0500280 "value": a.Value.toPatchValue(),
281 }
282}
283
284// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
285// in-place by index. Pass it to the Update function as part of the Patch slice.
286type Replacement struct {
287 Value value
288 Index int64
289}
290
291// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
292// Update call.
293func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
294 return map[string]interface{}{
295 "op": "replace",
Ash Wilson05280702015-01-29 11:19:25 -0500296 "path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }),
Ash Wilson334277c2015-01-29 09:08:52 -0500297 "value": r.Value.toPatchValue(),
298 }
299}
300
Ash Wilsond842ae62015-01-29 13:11:50 -0500301// NameReplacement specifically updates the Service name. Pass it to the Update function as part
302// of the Patch slice.
303type NameReplacement struct {
304 NewName string
305}
306
307// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the
308// Update call.
309func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} {
310 return map[string]interface{}{
311 "op": "replace",
312 "path": "/name",
313 "value": r.NewName,
314 }
315}
316
Ash Wilson334277c2015-01-29 09:08:52 -0500317// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
318// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
319type Removal struct {
320 Path Path
321 Index int64
Ash Wilsond842ae62015-01-29 13:11:50 -0500322 All bool
Ash Wilson334277c2015-01-29 09:08:52 -0500323}
324
325// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
326// Update call.
327func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilsond842ae62015-01-29 13:11:50 -0500328 result := map[string]interface{}{"op": "remove"}
329 if r.All {
330 result["path"] = r.Path.renderRoot()
331 } else {
332 result["path"] = r.Path.renderIndex(r.Index)
Ash Wilson334277c2015-01-29 09:08:52 -0500333 }
Ash Wilsond842ae62015-01-29 13:11:50 -0500334 return result
Ash Wilson334277c2015-01-29 09:08:52 -0500335}
336
Jon Perritt1bda9c12015-01-29 12:16:08 -0700337type UpdateOpts []Patch
338
Ash Wilson299363d2015-01-29 10:49:40 -0500339// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
340// updates an existing CDN service using the values provided. idOrURL can be either the service's
341// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
Jon Perrittb8713ad2015-01-21 15:02:58 -0700342// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
343// are valid options for idOrURL.
Jon Perritt1bda9c12015-01-29 12:16:08 -0700344func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult {
Jon Perrittb8713ad2015-01-21 15:02:58 -0700345 var url string
346 if strings.Contains(idOrURL, "/") {
347 url = idOrURL
348 } else {
349 url = updateURL(c, idOrURL)
350 }
Jon Perritte7b86d12015-01-16 20:37:11 -0700351
Jon Perritt1bda9c12015-01-29 12:16:08 -0700352 reqBody := make([]map[string]interface{}, len(opts))
353 for i, patch := range opts {
Ash Wilson09d2a282015-01-29 10:05:53 -0500354 reqBody[i] = patch.ToCDNServiceUpdateMap()
Jon Perritte7b86d12015-01-16 20:37:11 -0700355 }
356
Ash Wilson4bf41a32015-02-12 15:52:44 -0500357 resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{
358 JSONBody: &reqBody,
359 OkCodes: []int{202},
Jon Perritte7b86d12015-01-16 20:37:11 -0700360 })
Ash Wilson09d2a282015-01-29 10:05:53 -0500361 var result UpdateResult
Ash Wilson2491b4c2015-02-12 16:13:39 -0500362 result.Header = resp.Header
Ash Wilson09d2a282015-01-29 10:05:53 -0500363 result.Err = err
364 return result
Jon Perritte7b86d12015-01-16 20:37:11 -0700365}
366
Jon Perrittb8713ad2015-01-21 15:02:58 -0700367// Delete accepts a service's ID or its URL and deletes the CDN service
368// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
369// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
370// are valid options for idOrURL.
371func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
372 var url string
373 if strings.Contains(idOrURL, "/") {
374 url = idOrURL
375 } else {
376 url = deleteURL(c, idOrURL)
377 }
378
Jon Perritte7b86d12015-01-16 20:37:11 -0700379 var res DeleteResult
Jamie Hannafordc530ba12015-03-23 17:50:46 +0100380 _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{})
Jon Perritte7b86d12015-01-16 20:37:11 -0700381 return res
382}