blob: 8b37928e219b738b7baacca3704c64df3e70f142 [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
Jamie Hannaford22aa42b2015-03-24 14:56:04 +0100180 resp, err := c.Post(createURL(c), &reqBody, nil, nil)
Ash Wilson2491b4c2015-02-12 16:13:39 -0500181 res.Header = resp.Header
Jon Perrittd21966f2015-01-20 19:22:45 -0700182 res.Err = err
Jon Perritte7b86d12015-01-16 20:37:11 -0700183 return res
184}
185
Jon Perrittb8713ad2015-01-21 15:02:58 -0700186// Get retrieves a specific service based on its URL or its unique ID. For
187// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
188// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
189// are valid options for idOrURL.
190func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
191 var url string
192 if strings.Contains(idOrURL, "/") {
193 url = idOrURL
194 } else {
195 url = getURL(c, idOrURL)
196 }
197
Jon Perritte7b86d12015-01-16 20:37:11 -0700198 var res GetResult
Jamie Hannaford22aa42b2015-03-24 14:56:04 +0100199 _, res.Err = c.Get(url, &res.Body, nil)
Jon Perritte7b86d12015-01-16 20:37:11 -0700200 return res
201}
202
Ash Wilsona623ff72015-01-28 15:50:37 -0500203// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
204// or removed.
205type Path struct {
206 baseElement string
207}
208
Ash Wilson05280702015-01-29 11:19:25 -0500209func (p Path) renderRoot() string {
210 return "/" + p.baseElement
211}
212
Ash Wilsona623ff72015-01-28 15:50:37 -0500213func (p Path) renderDash() string {
214 return fmt.Sprintf("/%s/-", p.baseElement)
215}
216
217func (p Path) renderIndex(index int64) string {
218 return fmt.Sprintf("/%s/%d", p.baseElement, index)
219}
220
221var (
222 // PathDomains indicates that an update operation is to be performed on a Domain.
223 PathDomains = Path{baseElement: "domains"}
224
225 // PathOrigins indicates that an update operation is to be performed on an Origin.
226 PathOrigins = Path{baseElement: "origins"}
227
228 // PathCaching indicates that an update operation is to be performed on a CacheRule.
229 PathCaching = Path{baseElement: "caching"}
230)
231
Ash Wilson4ee05012015-01-28 16:13:43 -0500232type value interface {
Ash Wilsonb47ebed2015-01-29 11:08:41 -0500233 toPatchValue() interface{}
Ash Wilson4ee05012015-01-28 16:13:43 -0500234 appropriatePath() Path
Ash Wilson05280702015-01-29 11:19:25 -0500235 renderRootOr(func(p Path) string) string
Ash Wilson4ee05012015-01-28 16:13:43 -0500236}
237
Ash Wilson7b729532015-01-28 16:15:23 -0500238// Patch represents a single update to an existing Service. Multiple updates to a service can be
239// submitted at the same time.
240type Patch interface {
241 ToCDNServiceUpdateMap() map[string]interface{}
242}
243
Ash Wilson299363d2015-01-29 10:49:40 -0500244// Insertion is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to
245// a Service at a fixed index. Use an Append instead to append the new value to the end of its
246// collection. Pass it to the Update function as part of the Patch slice.
247type Insertion struct {
248 Index int64
Ash Wilson334277c2015-01-29 09:08:52 -0500249 Value value
250}
251
Ash Wilson299363d2015-01-29 10:49:40 -0500252// ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
Ash Wilson334277c2015-01-29 09:08:52 -0500253// Update call.
Ash Wilson299363d2015-01-29 10:49:40 -0500254func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
255 return map[string]interface{}{
256 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500257 "path": i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
Ash Wilson299363d2015-01-29 10:49:40 -0500258 "value": i.Value.toPatchValue(),
259 }
260}
261
262// Append is a Patch that requests the addition of a value (Domain, Origin, or CacheRule) to a
263// Service at the end of its respective collection. Use an Insertion instead to insert the value
264// at a fixed index within the collection. Pass this to the Update function as part of its
265// Patch slice.
266type Append struct {
267 Value value
268}
269
270// ToCDNServiceUpdateMap converts an Append into a request body fragment suitable for the
271// Update call.
272func (a Append) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilson334277c2015-01-29 09:08:52 -0500273 return map[string]interface{}{
274 "op": "add",
Ash Wilson05280702015-01-29 11:19:25 -0500275 "path": a.Value.renderRootOr(func(p Path) string { return p.renderDash() }),
Ash Wilson334277c2015-01-29 09:08:52 -0500276 "value": a.Value.toPatchValue(),
277 }
278}
279
280// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
281// in-place by index. Pass it to the Update function as part of the Patch slice.
282type Replacement struct {
283 Value value
284 Index int64
285}
286
287// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
288// Update call.
289func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
290 return map[string]interface{}{
291 "op": "replace",
Ash Wilson05280702015-01-29 11:19:25 -0500292 "path": r.Value.renderRootOr(func(p Path) string { return p.renderIndex(r.Index) }),
Ash Wilson334277c2015-01-29 09:08:52 -0500293 "value": r.Value.toPatchValue(),
294 }
295}
296
Ash Wilsond842ae62015-01-29 13:11:50 -0500297// NameReplacement specifically updates the Service name. Pass it to the Update function as part
298// of the Patch slice.
299type NameReplacement struct {
300 NewName string
301}
302
303// ToCDNServiceUpdateMap converts a NameReplacement into a request body fragment suitable for the
304// Update call.
305func (r NameReplacement) ToCDNServiceUpdateMap() map[string]interface{} {
306 return map[string]interface{}{
307 "op": "replace",
308 "path": "/name",
309 "value": r.NewName,
310 }
311}
312
Ash Wilson334277c2015-01-29 09:08:52 -0500313// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
314// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
315type Removal struct {
316 Path Path
317 Index int64
Ash Wilsond842ae62015-01-29 13:11:50 -0500318 All bool
Ash Wilson334277c2015-01-29 09:08:52 -0500319}
320
321// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
322// Update call.
323func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
Ash Wilsond842ae62015-01-29 13:11:50 -0500324 result := map[string]interface{}{"op": "remove"}
325 if r.All {
326 result["path"] = r.Path.renderRoot()
327 } else {
328 result["path"] = r.Path.renderIndex(r.Index)
Ash Wilson334277c2015-01-29 09:08:52 -0500329 }
Ash Wilsond842ae62015-01-29 13:11:50 -0500330 return result
Ash Wilson334277c2015-01-29 09:08:52 -0500331}
332
Jon Perritt1bda9c12015-01-29 12:16:08 -0700333type UpdateOpts []Patch
334
Ash Wilson299363d2015-01-29 10:49:40 -0500335// Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
336// updates an existing CDN service using the values provided. idOrURL can be either the service's
337// URL or its ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
Jon Perrittb8713ad2015-01-21 15:02:58 -0700338// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
339// are valid options for idOrURL.
Jon Perritt1bda9c12015-01-29 12:16:08 -0700340func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOpts) UpdateResult {
Jon Perrittb8713ad2015-01-21 15:02:58 -0700341 var url string
342 if strings.Contains(idOrURL, "/") {
343 url = idOrURL
344 } else {
345 url = updateURL(c, idOrURL)
346 }
Jon Perritte7b86d12015-01-16 20:37:11 -0700347
Jon Perritt1bda9c12015-01-29 12:16:08 -0700348 reqBody := make([]map[string]interface{}, len(opts))
349 for i, patch := range opts {
Ash Wilson09d2a282015-01-29 10:05:53 -0500350 reqBody[i] = patch.ToCDNServiceUpdateMap()
Jon Perritte7b86d12015-01-16 20:37:11 -0700351 }
352
Ash Wilson4bf41a32015-02-12 15:52:44 -0500353 resp, err := c.Request("PATCH", url, gophercloud.RequestOpts{
354 JSONBody: &reqBody,
355 OkCodes: []int{202},
Jon Perritte7b86d12015-01-16 20:37:11 -0700356 })
Ash Wilson09d2a282015-01-29 10:05:53 -0500357 var result UpdateResult
Ash Wilson2491b4c2015-02-12 16:13:39 -0500358 result.Header = resp.Header
Ash Wilson09d2a282015-01-29 10:05:53 -0500359 result.Err = err
360 return result
Jon Perritte7b86d12015-01-16 20:37:11 -0700361}
362
Jon Perrittb8713ad2015-01-21 15:02:58 -0700363// Delete accepts a service's ID or its URL and deletes the CDN service
364// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
365// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
366// are valid options for idOrURL.
367func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
368 var url string
369 if strings.Contains(idOrURL, "/") {
370 url = idOrURL
371 } else {
372 url = deleteURL(c, idOrURL)
373 }
374
Jon Perritte7b86d12015-01-16 20:37:11 -0700375 var res DeleteResult
Jamie Hannaford22aa42b2015-03-24 14:56:04 +0100376 _, res.Err = c.Delete(url, nil)
Jon Perritte7b86d12015-01-16 20:37:11 -0700377 return res
378}