blob: 73a94363b15fecfb491713c21f09bf8b445ecfa0 [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,
182 OkCodes: []int{202},
Jon Perritte7b86d12015-01-16 20:37:11 -0700183 })
Jon Perrittd21966f2015-01-20 19:22:45 -0700184 res.Header = resp.HttpResponse.Header
185 res.Err = err
Jon Perritte7b86d12015-01-16 20:37:11 -0700186 return res
187}
188
Jon Perrittb8713ad2015-01-21 15:02:58 -0700189// Get retrieves a specific service based on its URL or its unique ID. For
190// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
191// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
192// are valid options for idOrURL.
193func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
194 var url string
195 if strings.Contains(idOrURL, "/") {
196 url = idOrURL
197 } else {
198 url = getURL(c, idOrURL)
199 }
200
Jon Perritte7b86d12015-01-16 20:37:11 -0700201 var res GetResult
Ash Wilsonf98df8d2015-02-12 15:46:40 -0500202 _, res.Err = c.Request("GET", url, gophercloud.RequestOpts{
203 JSONResponse: &res.Body,
204 OkCodes: []int{200},
Jon Perritte7b86d12015-01-16 20:37:11 -0700205 })
206 return res
207}
208
Ash Wilsona623ff72015-01-28 15:50:37 -0500209// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
210// or removed.
211type Path struct {
212 baseElement string
213}
214
Ash Wilson05280702015-01-29 11:19:25 -0500215func (p Path) renderRoot() string {
216 return "/" + p.baseElement
217}
218
Ash Wilsona623ff72015-01-28 15:50:37 -0500219func (p Path) renderDash() string {
220 return fmt.Sprintf("/%s/-", p.baseElement)
221}
222
223func (p Path) renderIndex(index int64) string {
224 return fmt.Sprintf("/%s/%d", p.baseElement, index)
225}
226
227var (
228 // PathDomains indicates that an update operation is to be performed on a Domain.
229 PathDomains = Path{baseElement: "domains"}
230
231 // PathOrigins indicates that an update operation is to be performed on an Origin.
232 PathOrigins = Path{baseElement: "origins"}
233
234 // PathCaching indicates that an update operation is to be performed on a CacheRule.
235 PathCaching = Path{baseElement: "caching"}
236)
237
Ash Wilson4ee05012015-01-28 16:13:43 -0500238type value interface {
Ash Wilsonb47ebed2015-01-29 11:08:41 -0500239 toPatchValue() interface{}
Ash Wilson4ee05012015-01-28 16:13:43 -0500240 appropriatePath() Path
Ash Wilson05280702015-01-29 11:19:25 -0500241 renderRootOr(func(p Path) string) string
Ash Wilson4ee05012015-01-28 16:13:43 -0500242}
243
Ash Wilson7b729532015-01-28 16:15:23 -0500244// Patch represents a single update to an existing Service. Multiple updates to a service can be
245// submitted at the same time.
246type Patch interface {
247 ToCDNServiceUpdateMap() map[string]interface{}
248}
249
Ash Wilson299363d2015-01-29 10:49:40 -0500250// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to
251// a Service at a fixed index. Use an Append instead to append the new value to the end of its
252// collection. Pass it to the Update function as part of the Patch slice.
253type Insertion struct {
254 Index int64
Ash Wilson334277c2015-01-29 09:08:52 -0500255 Value value
256}
257
Ash Wilson299363d2015-01-29 10:49:40 -0500258// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
Ash Wilson334277c2015-01-29 09:08:52 -0500259// Update call.
Ash Wilson299363d2015-01-29 10:49:40 -0500260func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
261 return map[string]interface{}{
262 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500263 "path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
Ash Wilson299363d2015-01-29 10:49:40 -0500264 "value": i.Value.toPatchValue(),
265 }
266}
267
268// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a
269// Service at the end of its respective collection. Use an Insertion instead to insert the value
270// at a fixed index within the collection. Pass this to the Update function as part of its
271// Patch slice.
272type Append struct {
273 Value value
274}
275
276// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the
277// Update call.
278func (a Append) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilson334277c2015-01-29 09:08:52 -0500279 return map[string]interface{}{
280 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500281 "path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }),
Ash Wilson334277c2015-01-29 09:08:52 -0500282 "value": a.Value.toPatchValue(),
283 }
284}
285
286// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
287// in-place by index. Pass it to the Update function as part of the Patch slice.
288type Replacement struct {
289 Value value
290 Index int64
291}
292
293// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
294// Update call.
295func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
296 return map[string]interface{}{
297 "op": "replace",
Ash Wilson05280702015-01-29 11:19:25 -0500298 "path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }),
Ash Wilson334277c2015-01-29 09:08:52 -0500299 "value": r.Value.toPatchValue(),
300 }
301}
302
Ash Wilsond842ae62015-01-29 13:11:50 -0500303// NameReplacement specifically updates the Service name. Pass it to the Update function as part
304// of the Patch slice.
305type NameReplacement struct {
306 NewName string
307}
308
309// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the
310// Update call.
311func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} {
312 return map[string]interface{}{
313 "op": "replace",
314 "path": "/name",
315 "value": r.NewName,
316 }
317}
318
Ash Wilson334277c2015-01-29 09:08:52 -0500319// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
320// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
321type Removal struct {
322 Path Path
323 Index int64
Ash Wilsond842ae62015-01-29 13:11:50 -0500324 All bool
Ash Wilson334277c2015-01-29 09:08:52 -0500325}
326
327// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
328// Update call.
329func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilsond842ae62015-01-29 13:11:50 -0500330 result := map[string]interface{}{"op": "remove"}
331 if r.All {
332 result["path"] = r.Path.renderRoot()
333 } else {
334 result["path"] = r.Path.renderIndex(r.Index)
Ash Wilson334277c2015-01-29 09:08:52 -0500335 }
Ash Wilsond842ae62015-01-29 13:11:50 -0500336 return result
Ash Wilson334277c2015-01-29 09:08:52 -0500337}
338
Jon Perritt1bda9c12015-01-29 12:16:08 -0700339type UpdateOpts []Patch
340
Ash Wilson299363d2015-01-29 10:49:40 -0500341// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
342// updates an existing CDN service using the values provided. idOrURL can be either the service's
343// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
Jon Perrittb8713ad2015-01-21 15:02:58 -0700344// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
345// are valid options for idOrURL.
Jon Perritt1bda9c12015-01-29 12:16:08 -0700346func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult {
Jon Perrittb8713ad2015-01-21 15:02:58 -0700347 var url string
348 if strings.Contains(idOrURL, "/") {
349 url = idOrURL
350 } else {
351 url = updateURL(c, idOrURL)
352 }
Jon Perritte7b86d12015-01-16 20:37:11 -0700353
Jon Perritt1bda9c12015-01-29 12:16:08 -0700354 reqBody := make([]map[string]interface{}, len(opts))
355 for i, patch := range opts {
Ash Wilson09d2a282015-01-29 10:05:53 -0500356 reqBody[i] = patch.ToCDNServiceUpdateMap()
Jon Perritte7b86d12015-01-16 20:37:11 -0700357 }
358
Ash Wilson4bf41a32015-02-12 15:52:44 -0500359 resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{
360 JSONBody: &reqBody,
361 OkCodes: []int{202},
Jon Perritte7b86d12015-01-16 20:37:11 -0700362 })
Ash Wilson09d2a282015-01-29 10:05:53 -0500363 var result UpdateResult
364 result.Header = resp.HttpResponse.Header
365 result.Err = err
366 return result
Jon Perritte7b86d12015-01-16 20:37:11 -0700367}
368
Jon Perrittb8713ad2015-01-21 15:02:58 -0700369// Delete accepts a service's ID or its URL and deletes the CDN service
370// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
371// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
372// are valid options for idOrURL.
373func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
374 var url string
375 if strings.Contains(idOrURL, "/") {
376 url = idOrURL
377 } else {
378 url = deleteURL(c, idOrURL)
379 }
380
Jon Perritte7b86d12015-01-16 20:37:11 -0700381 var res DeleteResult
Ash Wilsonf98df8d2015-02-12 15:46:40 -0500382 _, res.Err = c.Request("DELETE", url, gophercloud.RequestOpts{
383 OkCodes: []int{202},
Jon Perritte7b86d12015-01-16 20:37:11 -0700384 })
385 return res
386}