blob: ad1e1da462d4818ee5fdbbf99fc6a45574492f47 [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/racker/perigee"
8 "github.com/rackspace/gophercloud"
9 "github.com/rackspace/gophercloud/pagination"
10)
11
12// ListOptsBuilder allows extensions to add additional parameters to the
13// List request.
14type ListOptsBuilder interface {
15 ToCDNServiceListQuery() (string, error)
16}
17
18// ListOpts allows the filtering and sorting of paginated collections through
19// the API. Marker and Limit are used for pagination.
20type ListOpts struct {
21 Marker string `q:"marker"`
22 Limit int `q:"limit"`
23}
24
25// ToCDNServiceListQuery formats a ListOpts into a query string.
26func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
27 q, err := gophercloud.BuildQueryString(opts)
28 if err != nil {
29 return "", err
30 }
31 return q.String(), nil
32}
33
34// List returns a Pager which allows you to iterate over a collection of
35// CDN services. It accepts a ListOpts struct, which allows for pagination via
36// marker and limit.
37func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
38 url := listURL(c)
39 if opts != nil {
40 query, err := opts.ToCDNServiceListQuery()
41 if err != nil {
42 return pagination.Pager{Err: err}
43 }
44 url += query
45 }
46
47 createPage := func(r pagination.PageResult) pagination.Page {
48 p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
49 p.MarkerPageBase.Owner = p
50 return p
51 }
52
53 pager := pagination.NewPager(c, url, createPage)
54 return pager
55}
56
57// CreateOptsBuilder is the interface options structs have to satisfy in order
58// to be used in the main Create operation in this package. Since many
59// extensions decorate or modify the common logic, it is useful for them to
60// satisfy a basic interface in order for them to be used.
61type CreateOptsBuilder interface {
62 ToCDNServiceCreateMap() (map[string]interface{}, error)
63}
64
65// CreateOpts is the common options struct used in this package's Create
66// operation.
67type CreateOpts struct {
68 // REQUIRED. Specifies the name of the service. The minimum length for name is
69 // 3. The maximum length is 256.
70 Name string
71 // REQUIRED. Specifies a list of domains used by users to access their website.
72 Domains []Domain
73 // REQUIRED. Specifies a list of origin domains or IP addresses where the
74 // original assets are stored.
75 Origins []Origin
76 // REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
77 // flavors, see the operation to list the available flavors. The minimum
78 // length for flavor_id is 1. The maximum length is 256.
79 FlavorID string
80 // OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
Jon Perritt0bd23732015-01-19 20:58:57 -070081 Caching []CacheRule
Jon Perritte7b86d12015-01-16 20:37:11 -070082 // OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
83 Restrictions []Restriction
84}
85
86// ToCDNServiceCreateMap casts a CreateOpts struct to a map.
87func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
88 s := make(map[string]interface{})
89
90 if opts.Name == "" {
91 return nil, no("Name")
92 }
93 s["name"] = opts.Name
94
95 if opts.Domains == nil {
96 return nil, no("Domains")
97 }
98 for _, domain := range opts.Domains {
99 if domain.Domain == "" {
100 return nil, no("Domains[].Domain")
101 }
102 }
103 s["domains"] = opts.Domains
104
105 if opts.Origins == nil {
106 return nil, no("Origins")
107 }
108 for _, origin := range opts.Origins {
109 if origin.Origin == "" {
110 return nil, no("Origins[].Origin")
111 }
Jon Perrittb8713ad2015-01-21 15:02:58 -0700112 if origin.Rules == nil && len(opts.Origins) > 1 {
Jon Perritte7b86d12015-01-16 20:37:11 -0700113 return nil, no("Origins[].Rules")
114 }
115 for _, rule := range origin.Rules {
116 if rule.Name == "" {
117 return nil, no("Origins[].Rules[].Name")
118 }
119 if rule.RequestURL == "" {
120 return nil, no("Origins[].Rules[].RequestURL")
121 }
122 }
123 }
124 s["origins"] = opts.Origins
125
126 if opts.FlavorID == "" {
127 return nil, no("FlavorID")
128 }
129 s["flavor_id"] = opts.FlavorID
130
131 if opts.Caching != nil {
132 for _, cache := range opts.Caching {
133 if cache.Name == "" {
134 return nil, no("Caching[].Name")
135 }
136 if cache.Rules != nil {
137 for _, rule := range cache.Rules {
138 if rule.Name == "" {
139 return nil, no("Caching[].Rules[].Name")
140 }
141 if rule.RequestURL == "" {
142 return nil, no("Caching[].Rules[].RequestURL")
143 }
144 }
145 }
146 }
147 s["caching"] = opts.Caching
148 }
149
150 if opts.Restrictions != nil {
151 for _, restriction := range opts.Restrictions {
152 if restriction.Name == "" {
153 return nil, no("Restrictions[].Name")
154 }
155 if restriction.Rules != nil {
156 for _, rule := range restriction.Rules {
157 if rule.Name == "" {
158 return nil, no("Restrictions[].Rules[].Name")
159 }
160 }
161 }
162 }
163 s["restrictions"] = opts.Restrictions
164 }
165
166 return s, nil
167}
168
169// Create accepts a CreateOpts struct and creates a new CDN service using the
170// values provided.
171func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
172 var res CreateResult
173
174 reqBody, err := opts.ToCDNServiceCreateMap()
175 if err != nil {
176 res.Err = err
177 return res
178 }
179
180 // Send request to API
Jon Perrittd21966f2015-01-20 19:22:45 -0700181 resp, err := perigee.Request("POST", createURL(c), perigee.Options{
Jon Perritte7b86d12015-01-16 20:37:11 -0700182 MoreHeaders: c.AuthenticatedHeaders(),
183 ReqBody: &reqBody,
184 OkCodes: []int{202},
185 })
Jon Perrittd21966f2015-01-20 19:22:45 -0700186 res.Header = resp.HttpResponse.Header
187 res.Err = err
Jon Perritte7b86d12015-01-16 20:37:11 -0700188 return res
189}
190
Jon Perrittb8713ad2015-01-21 15:02:58 -0700191// Get retrieves a specific service based on its URL or its unique ID. For
192// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
193// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
194// are valid options for idOrURL.
195func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
196 var url string
197 if strings.Contains(idOrURL, "/") {
198 url = idOrURL
199 } else {
200 url = getURL(c, idOrURL)
201 }
202
Jon Perritte7b86d12015-01-16 20:37:11 -0700203 var res GetResult
Jon Perrittb8713ad2015-01-21 15:02:58 -0700204 _, res.Err = perigee.Request("GET", url, perigee.Options{
Jon Perritte7b86d12015-01-16 20:37:11 -0700205 MoreHeaders: c.AuthenticatedHeaders(),
206 Results: &res.Body,
207 OkCodes: []int{200},
208 })
209 return res
210}
211
Ash Wilsona623ff72015-01-28 15:50:37 -0500212// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
213// or removed.
214type Path struct {
215 baseElement string
216}
217
Ash Wilson05280702015-01-29 11:19:25 -0500218func (p Path) renderRoot() string {
219 return "/" + p.baseElement
220}
221
Ash Wilsona623ff72015-01-28 15:50:37 -0500222func (p Path) renderDash() string {
223 return fmt.Sprintf("/%s/-", p.baseElement)
224}
225
226func (p Path) renderIndex(index int64) string {
227 return fmt.Sprintf("/%s/%d", p.baseElement, index)
228}
229
230var (
231 // PathDomains indicates that an update operation is to be performed on a Domain.
232 PathDomains = Path{baseElement: "domains"}
233
234 // PathOrigins indicates that an update operation is to be performed on an Origin.
235 PathOrigins = Path{baseElement: "origins"}
236
237 // PathCaching indicates that an update operation is to be performed on a CacheRule.
238 PathCaching = Path{baseElement: "caching"}
239)
240
Ash Wilson4ee05012015-01-28 16:13:43 -0500241type value interface {
Ash Wilsonb47ebed2015-01-29 11:08:41 -0500242 toPatchValue() interface{}
Ash Wilson4ee05012015-01-28 16:13:43 -0500243 appropriatePath() Path
Ash Wilson05280702015-01-29 11:19:25 -0500244 renderRootOr(func(p Path) string) string
Ash Wilson4ee05012015-01-28 16:13:43 -0500245}
246
Ash Wilson7b729532015-01-28 16:15:23 -0500247// Patch represents a single update to an existing Service. Multiple updates to a service can be
248// submitted at the same time.
249type Patch interface {
250 ToCDNServiceUpdateMap() map[string]interface{}
251}
252
Ash Wilson299363d2015-01-29 10:49:40 -0500253// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to
254// a Service at a fixed index. Use an Append instead to append the new value to the end of its
255// collection. Pass it to the Update function as part of the Patch slice.
256type Insertion struct {
257 Index int64
Ash Wilson334277c2015-01-29 09:08:52 -0500258 Value value
259}
260
Ash Wilson299363d2015-01-29 10:49:40 -0500261// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
Ash Wilson334277c2015-01-29 09:08:52 -0500262// Update call.
Ash Wilson299363d2015-01-29 10:49:40 -0500263func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
264 return map[string]interface{}{
265 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500266 "path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
Ash Wilson299363d2015-01-29 10:49:40 -0500267 "value": i.Value.toPatchValue(),
268 }
269}
270
271// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a
272// Service at the end of its respective collection. Use an Insertion instead to insert the value
273// at a fixed index within the collection. Pass this to the Update function as part of its
274// Patch slice.
275type Append struct {
276 Value value
277}
278
279// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the
280// Update call.
281func (a Append) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilson334277c2015-01-29 09:08:52 -0500282 return map[string]interface{}{
283 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500284 "path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }),
Ash Wilson334277c2015-01-29 09:08:52 -0500285 "value": a.Value.toPatchValue(),
286 }
287}
288
289// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
290// in-place by index. Pass it to the Update function as part of the Patch slice.
291type Replacement struct {
292 Value value
293 Index int64
294}
295
296// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
297// Update call.
298func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
299 return map[string]interface{}{
300 "op": "replace",
Ash Wilson05280702015-01-29 11:19:25 -0500301 "path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }),
Ash Wilson334277c2015-01-29 09:08:52 -0500302 "value": r.Value.toPatchValue(),
303 }
304}
305
Ash Wilsond842ae62015-01-29 13:11:50 -0500306// NameReplacement specifically updates the Service name. Pass it to the Update function as part
307// of the Patch slice.
308type NameReplacement struct {
309 NewName string
310}
311
312// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the
313// Update call.
314func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} {
315 return map[string]interface{}{
316 "op": "replace",
317 "path": "/name",
318 "value": r.NewName,
319 }
320}
321
Ash Wilson334277c2015-01-29 09:08:52 -0500322// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
323// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
324type Removal struct {
325 Path Path
326 Index int64
Ash Wilsond842ae62015-01-29 13:11:50 -0500327 All bool
Ash Wilson334277c2015-01-29 09:08:52 -0500328}
329
330// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
331// Update call.
332func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilsond842ae62015-01-29 13:11:50 -0500333 result := map[string]interface{}{"op": "remove"}
334 if r.All {
335 result["path"] = r.Path.renderRoot()
336 } else {
337 result["path"] = r.Path.renderIndex(r.Index)
Ash Wilson334277c2015-01-29 09:08:52 -0500338 }
Ash Wilsond842ae62015-01-29 13:11:50 -0500339 return result
Ash Wilson334277c2015-01-29 09:08:52 -0500340}
341
Jon Perritt1bda9c12015-01-29 12:16:08 -0700342type UpdateOpts []Patch
343
Ash Wilson299363d2015-01-29 10:49:40 -0500344// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
345// updates an existing CDN service using the values provided. idOrURL can be either the service's
346// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
Jon Perrittb8713ad2015-01-21 15:02:58 -0700347// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
348// are valid options for idOrURL.
Jon Perritt1bda9c12015-01-29 12:16:08 -0700349func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult {
Jon Perrittb8713ad2015-01-21 15:02:58 -0700350 var url string
351 if strings.Contains(idOrURL, "/") {
352 url = idOrURL
353 } else {
354 url = updateURL(c, idOrURL)
355 }
Jon Perritte7b86d12015-01-16 20:37:11 -0700356
Jon Perritt1bda9c12015-01-29 12:16:08 -0700357 reqBody := make([]map[string]interface{}, len(opts))
358 for i, patch := range opts {
Ash Wilson09d2a282015-01-29 10:05:53 -0500359 reqBody[i] = patch.ToCDNServiceUpdateMap()
Jon Perritte7b86d12015-01-16 20:37:11 -0700360 }
361
Jon Perrittb8713ad2015-01-21 15:02:58 -0700362 resp, err := perigee.Request("PATCH", url, perigee.Options{
Jon Perritte7b86d12015-01-16 20:37:11 -0700363 MoreHeaders: c.AuthenticatedHeaders(),
364 ReqBody: &reqBody,
365 OkCodes: []int{202},
366 })
Ash Wilson09d2a282015-01-29 10:05:53 -0500367 var result UpdateResult
368 result.Header = resp.HttpResponse.Header
369 result.Err = err
370 return result
Jon Perritte7b86d12015-01-16 20:37:11 -0700371}
372
Jon Perrittb8713ad2015-01-21 15:02:58 -0700373// Delete accepts a service's ID or its URL and deletes the CDN service
374// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
375// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
376// are valid options for idOrURL.
377func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
378 var url string
379 if strings.Contains(idOrURL, "/") {
380 url = idOrURL
381 } else {
382 url = deleteURL(c, idOrURL)
383 }
384
Jon Perritte7b86d12015-01-16 20:37:11 -0700385 var res DeleteResult
Jon Perrittb8713ad2015-01-21 15:02:58 -0700386 _, res.Err = perigee.Request("DELETE", url, perigee.Options{
Jon Perritte7b86d12015-01-16 20:37:11 -0700387 MoreHeaders: c.AuthenticatedHeaders(),
388 OkCodes: []int{202},
389 })
390 return res
391}