| // Copyright (c) 2016 VMware, Inc. All Rights Reserved. |
| // |
| // This product is licensed to you under the Apache License, Version 2.0 (the "License"). |
| // You may not use this product except in compliance with the License. |
| // |
| // This product may include a number of subcomponents with separate copyright notices and |
| // license terms. Your use of these subcomponents is subject to the terms and conditions |
| // of the subcomponent's license, as noted in the LICENSE file. |
| |
| package photon |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "net/url" |
| "reflect" |
| ) |
| |
| // Reads an error out of the HTTP response, or does nothing if |
| // no error occured. |
| func getError(res *http.Response) (*http.Response, error) { |
| // Do nothing if the response is a successful 2xx |
| if res.StatusCode/100 == 2 { |
| return res, nil |
| } |
| var apiError ApiError |
| // ReadAll is usually a bad practice, but here we need to read the response all |
| // at once because we may attempt to use the data twice. It's preferable to use |
| // methods that take io.Reader, e.g. json.NewDecoder |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| return nil, err |
| } |
| err = json.Unmarshal(body, &apiError) |
| if err != nil { |
| // If deserializing into ApiError fails, return a generic HttpError instead |
| return nil, HttpError{res.StatusCode, string(body[:])} |
| } |
| apiError.HttpStatusCode = res.StatusCode |
| return nil, apiError |
| } |
| |
| // Reads a task object out of the HTTP response. Takes an error argument |
| // so that GetTask can easily wrap GetError. This function will do nothing |
| // if e is not nil. |
| // e.g. res, err := getTask(getError(someApi.Get())) |
| func getTask(res *http.Response, e error) (*Task, error) { |
| if e != nil { |
| return nil, e |
| } |
| var task Task |
| err := json.NewDecoder(res.Body).Decode(&task) |
| if err != nil { |
| return nil, err |
| } |
| if task.State == "ERROR" { |
| // Critical: return task as well, so that it can be examined |
| // for error details. |
| return &task, TaskError{task.ID, getFailedStep(&task)} |
| } |
| return &task, nil |
| } |
| |
| // Converts an options struct into a query string. |
| // E.g. type Foo struct {A int; B int} might return "?a=5&b=10". |
| // Will return an empty string if no options are set. |
| func getQueryString(options interface{}) string { |
| buffer := bytes.Buffer{} |
| buffer.WriteString("?") |
| strct := reflect.ValueOf(options).Elem() |
| typ := strct.Type() |
| for i := 0; i < strct.NumField(); i++ { |
| field := strct.Field(i) |
| value := fmt.Sprint(field.Interface()) |
| if value != "" { |
| buffer.WriteString(typ.Field(i).Tag.Get("urlParam") + "=" + url.QueryEscape(value)) |
| if i < strct.NumField()-1 { |
| buffer.WriteString("&") |
| } |
| } |
| } |
| uri := buffer.String() |
| if uri == "?" { |
| return "" |
| } |
| return uri |
| } |
| |
| // Sets security groups for a given entity (deployment/tenant/project) |
| func setSecurityGroups(client *Client, entityUrl string, securityGroups *SecurityGroupsSpec) (task *Task, err error) { |
| body, err := json.Marshal(securityGroups) |
| if err != nil { |
| return |
| } |
| url := entityUrl + "/set_security_groups" |
| res, err := client.restClient.Post( |
| url, |
| "application/json", |
| bytes.NewReader(body), |
| client.options.TokenOptions) |
| if err != nil { |
| return |
| } |
| defer res.Body.Close() |
| task, err = getTask(getError(res)) |
| return |
| } |