diff --git a/vendor/github.com/ovh/go-ovh/.gitignore b/vendor/github.com/ovh/go-ovh/.gitignore new file mode 100644 index 0000000..7fe0f04 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/.gitignore @@ -0,0 +1,3 @@ +# Temporary edit files +*.swp +*~ diff --git a/vendor/github.com/ovh/go-ovh/.travis.yml b/vendor/github.com/ovh/go-ovh/.travis.yml new file mode 100644 index 0000000..207dfef --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/.travis.yml @@ -0,0 +1,24 @@ +language: go + +go: +- 1.6 +- 1.7 + +before_install: +- go get github.com/axw/gocov/gocov +- go get github.com/mattn/goveralls +- go get github.com/golang/lint/golint +- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi + +script: +# Test Code quality +- go vet ./... +- $HOME/gopath/bin/golint ./... +- go test -covermode=count -coverprofile=profile.cov ./... + +# Test buildable on most common platforms, beyond Linux +- GOOS=darwin go build ./... +- GOOS=windows go build ./... + +# Best effort: notify coveralls. It's too unstable, ignore errors. +- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci || exit 0 diff --git a/vendor/github.com/ovh/go-ovh/CONTRIBUTING.md b/vendor/github.com/ovh/go-ovh/CONTRIBUTING.md new file mode 100644 index 0000000..0d35e34 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing to go-ovh + +## Submitting Modifications: + +So you want to contribute you work? Awesome! We are eager to review it. +To submit your contribution, you must use Github Pull Requests. Your work +does not need to be fully polished before submiting it. Actully, we love +helping people writing a great contribution. Hence, if you are wondering +how to integrate a specific change, feel free to start a discussion in +a Pull Request. + +Before we can actually accept and merge a Pull Request, it will need +to follow the conding guidelines (see below), and each commit shall be +signed to indicate your full agreement with these guidelines and the +DCO (see below). + +To sign a commit, you may use a command like: + +``` +# New commit +git commit -s + +# Previous commit +git commit --amend -s +``` + +If a Pull Request can not be automatically merged, you will probably need +to "rebase" your work on latest project update: + +``` +# Assuming, this project remote is registered as "upstream" +git fetch upstream +git rebase upstream/master +``` + +## Contribution guidelines + +1. your code must follow the coding style rules (see below) +2. your code must be documented +3. you code must be tested +4. your work must be signed (see "Developer Certificate of Origin" below) +5. you may contribute through GitHub Pull Requests + +## Coding and documentation Style: + +- Code must be formated with `gofmt -sw ./` +- Code must pass `go vet ./...` +- Code must pass `golint ./...` + +## Licensing for new files: + +go-ovh is licensed under a (modified) BSD license. Anything contributed to +go-ovh must be released under this license. + +When introducing a new file into the project, please make sure it has a +copyright header making clear under which license it''s being released. + +## Developer Certificate of Origin: + +``` +To improve tracking of contributions to this project we will use a +process modeled on the modified DCO 1.1 and use a "sign-off" procedure +on patches that are being contributed. + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right +to pass it on as an open-source patch. The rules are pretty simple: +if you can certify the below: + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I have + the right to submit it under the open source license indicated in + the file; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source License + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the same open source license (unless I am permitted to submit + under a different license), as indicated in the file; or + +(c) The contribution was provided directly to me by some other person + who certified (a), (b) or (c) and I have not modified it. + +(d) The contribution is made free of any other party''s intellectual + property claims or rights. + +(e) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + + +then you just add a line saying + + Signed-off-by: Random J Developer + +using your real name (sorry, no pseudonyms or anonymous contributions.) +``` + diff --git a/vendor/github.com/ovh/go-ovh/LICENSE b/vendor/github.com/ovh/go-ovh/LICENSE new file mode 100644 index 0000000..d847c8a --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015-2017, OVH SAS. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of OVH SAS nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY OVH SAS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OVH SAS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/ovh/go-ovh/README.md b/vendor/github.com/ovh/go-ovh/README.md new file mode 100644 index 0000000..d44e6aa --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/README.md @@ -0,0 +1,514 @@ +go-ovh +====== + +Lightweight Go wrapper around OVH's APIs. Handles all the hard work including credential creation and requests signing. + +[![GoDoc](https://godoc.org/github.com/ovh/go-ovh/go-ovh?status.svg)](http://godoc.org/github.com/ovh/go-ovh/ovh) +[![Build Status](https://travis-ci.org/ovh/go-ovh.svg?branch=master)](https://travis-ci.org/ovh/go-ovh) +[![Coverage Status](https://coveralls.io/repos/github/ovh/go-ovh/badge.svg?branch=master)](https://coveralls.io/github/ovh/go-ovh?branch=master) +[![Go Report Card](https://goreportcard.com/badge/ovh/go-ovh)](http://goreportcard.com/report/ovh/go-ovh) + +```go +package main + +import ( + "fmt" + "github.com/ovh/go-ovh/ovh" +) + +// PartialMe holds the first name of the currently logged-in user. +// Visit https://api.ovh.com/console/#/me#GET for the full definition +type PartialMe struct { + Firstname string `json:"firstname"` +} + +// Instantiate an OVH client and get the firstname of the currently logged-in user. +// Visit https://api.ovh.com/createToken/index.cgi?GET=/me to get your credentials. +func main() { + var me PartialMe + + client, _ := ovh.NewClient( + "ovh-eu", + YOUR_APPLICATION_KEY, + YOUR_APPLICATION_SECRET, + YOUR_CONSUMER_KEY, + ) + client.Get("/me", &me) + fmt.Printf("Welcome %s!\n", me.Firstname) +} +``` + +## Installation + +The Golang wrapper has been tested with Golang 1.5+. It may worker with older versions although it has not been tested. + +To use it, just include it to your ``import`` and run ``go get``: + +```go +import ( + ... + "github.com/ovh/go-ovh/ovh" +) +``` + +## Configuration + +The straightforward way to use OVH's API keys is to embed them directly in the +application code. While this is very convenient, it lacks of elegance and +flexibility. + +Alternatively it is suggested to use configuration files or environment +variables so that the same code may run seamlessly in multiple environments. +Production and development for instance. + +This wrapper will first look for direct instanciation parameters then +``OVH_ENDPOINT``, ``OVH_APPLICATION_KEY``, ``OVH_APPLICATION_SECRET`` and +``OVH_CONSUMER_KEY`` environment variables. If either of these parameter is not +provided, it will look for a configuration file of the form: + +```ini +[default] +; general configuration: default endpoint +endpoint=ovh-eu + +[ovh-eu] +; configuration specific to 'ovh-eu' endpoint +application_key=my_app_key +application_secret=my_application_secret +consumer_key=my_consumer_key +``` + +Depending on the API you want to use, you may set the ``endpoint`` to: + +* ``ovh-eu`` for OVH Europe API +* ``ovh-us`` for OVH US API +* ``ovh-ca`` for OVH Canada API +* ``soyoustart-eu`` for So you Start Europe API +* ``soyoustart-ca`` for So you Start Canada API +* ``kimsufi-eu`` for Kimsufi Europe API +* ``kimsufi-ca`` for Kimsufi Canada API +* ``runabove-ca`` for RunAbove API +* Or any arbitrary URL to use in a test for example + +The client will successively attempt to locate this configuration file in + +1. Current working directory: ``./ovh.conf`` +2. Current user's home directory ``~/.ovh.conf`` +3. System wide configuration ``/etc/ovh.conf`` + +This lookup mechanism makes it easy to overload credentials for a specific +project or user. + +## Register your app + +OVH's API, like most modern APIs is designed to authenticate both an application and +a user, without requiring the user to provide a password. Your application will be +identified by its "application secret" and "application key" tokens. + +Hence, to use the API, you must first register your application and then ask your +user to authenticate on a specific URL. Once authenticated, you'll have a valid +"consumer key" which will grant your application on specific APIs. + +The user may choose the validity period of its authorization. The default period is +24h. He may also revoke an authorization at any time. Hence, your application should +be prepared to receive 403 HTTP errors and prompt the user to re-authenticated. + +This process is detailed in the following section. Alternatively, you may only need +to build an application for a single user. In this case you may generate all +credentials at once. See below. + +### Use the API on behalf of a user + +Visit [https://eu.api.ovh.com/createApp](https://eu.api.ovh.com/createApp) and create your app +You'll get an application key and an application secret. To use the API you'll need a consumer key. + +The consumer key has two types of restriction: + +* path: eg. only the ```GET``` method on ```/me``` +* time: eg. expire in 1 day + + +Then, get a consumer key. Here's an example on how to generate one. + +First, create a 'ovh.conf' file in the current directory with the application key and +application secret. You can add the consumer key once generated. For alternate +configuration method, please see the [configuration section](#configuration). + +```ini +[ovh-eu] +application_key=my_app_key +application_secret=my_application_secret +; consumer_key=my_consumer_key +``` + +Then, you may use a program like this example to create a consumer key for the application: + +```go +package main + +import ( + "fmt" + + "github.com/ovh/go-ovh/ovh" +) + +func main() { + // Create a client using credentials from config files or environment variables + client, err := ovh.NewEndpointClient("ovh-eu") + if err != nil { + fmt.Printf("Error: %q\n", err) + return + } + ckReq := client.NewCkRequest() + + // Allow GET method on /me + ckReq.AddRules(ovh.ReadOnly, "/me") + + // Allow GET method on /xdsl and all its sub routes + ckReq.AddRecursiveRules(ovh.ReadOnly, "/xdsl") + + // Run the request + response, err := ckReq.Do() + if err != nil { + fmt.Printf("Error: %q\n", err) + return + } + + // Print the validation URL and the Consumer key + fmt.Printf("Generated consumer key: %s\n", response.ConsumerKey) + fmt.Printf("Please visit %s to validate it\n", response.ValidationURL) +} +``` + +### Use the API for a single user + +Alternatively, you may generate all creadentials at once, including the consumer key. You will +typically want to do this when writing automation scripts for a single projects. + +If this case, you may want to directly go to https://eu.api.ovh.com/createToken/ to generate +the 3 tokens at once. Make sure to save them in one of the 'ovh.conf' configuration file. +Please see the [configuration section](#configuration). + +``ovh.conf`` should look like: + +```ini +[ovh-eu] +application_key=my_app_key +application_secret=my_application_secret +consumer_key=my_consumer_key +``` + +## Use the lib + +These examples assume valid credentials are available in the [configuration](#configuration). + +### GET + +```go +package main + +import ( + "fmt" + + "github.com/ovh/go-ovh/ovh" +) + +func main() { + client, err := ovh.NewEndpointClient("ovh-eu") + if err != nil { + fmt.Printf("Error: %q\n", err) + return + } + + // Get all the xdsl services + xdslServices := []string{} + if err := client.Get("/xdsl/", &xdslServices); err != nil { + fmt.Printf("Error: %q\n", err) + return + } + + // xdslAccess represents a xdsl access returned by the API + type xdslAccess struct { + Name string `json:"accessName"` + Status string `json:"status"` + Pairs int `json:"pairsNumber"` + // Insert the other properties here + } + + // Get the details of each service + for i, serviceName := range xdslServices { + access := xdslAccess{} + url := "/xdsl/" + serviceName + + if err := client.Get(url, &access); err != nil { + fmt.Printf("Error: %q\n", err) + return + } + fmt.Printf("#%d : %+v\n", i+1, access) + } +} +``` + +### PUT + +```go +package main + +import ( + "fmt" + + "github.com/ovh/go-ovh/ovh" +) + +func main() { + client, err := ovh.NewEndpointClient("ovh-eu") + if err != nil { + fmt.Printf("Error: %q\n", err) + return + } + + // Params + type AccessPutParams struct { + Description string `json:"description"` + } + + // Update the description of the service + params := &AccessPutParams{Description: "My awesome access"} + if err := client.Put("/xdsl/xdsl-yourservice", params, nil); err != nil { + fmt.Printf("Error: %q\n", err) + return + } + + fmt.Println("Description updated") +} +``` + +## API Documentation + +### Create a client + +- Use ``ovh.NewClient()`` to have full controll over ther authentication +- Use ``ovh.NewEndpointClient()`` to create a client for a specific API and use credentials from config files or environment +- Use ``ovh.NewDefaultClient()`` to create a client unsing endpoint and credentials from config files or environment + +### Query + +Each HTTP verb has its own Client method. Some API methods supports unauthenticated calls. For +these methods, you may want to use the ``*UnAuth`` variant of the Client which will bypass +request signature. + +Each helper accepts a ``method`` and ``resType`` argument. ``method`` is the full URI, including +the query string, and ``resType`` is a reference to an object in which the json response will +be unserialized. + +Additionally, ``Post``, ``Put`` and their ``UnAuth`` variant accept a reqBody which is a +reference to a json serializable object or nil. + +Alternatively, you may directly use the low level ``CallAPI`` method. + +- Use ``client.Get()`` for GET requests +- Use ``client.Post()`` for POST requests +- Use ``client.Put()`` for PUT requests +- Use ``client.Delete()`` for DELETE requests + +Or, for unautenticated requests: + +- Use ``client.GetUnAuth()`` for GET requests +- Use ``client.PostUnAuth()`` for POST requests +- Use ``client.PutUnAuth()`` for PUT requests +- Use ``client.DeleteUnAuth()`` for DELETE requests + +### Request consumer keys + +Consumer keys may be restricted to a subset of the API. This allows to delegate the API to manage +only a specific server or domain name for example. This is called "scoping" a consumer key. + +Rules are simple. They combine an HTTP verb (GET, POST, PUT or DELETE) with a pattern. A pattern +is a plain API method and may contain the '*' wilcard to match "anything". Just like glob on a +Unix machine. + +While this is simple and may be managed directly with the API as-is, this can be cumbersome to do +and we recommend using the ``CkRequest`` helper. It basically manages the list of authorizations +for you and the actual request. + +*example*: Grant on all /sms and identity +```go +client, err := ovh.NewEndpointClient("ovh-eu") +if err == nil { + // Do something +} +req := client.NewCkRequest() +req.AddRules(ovh.ReadOnly, "/me") +req.AddRecursiveRulesRules(ovh.ReadWrite, "/sms") +pendingCk, err := req.Do() +``` + +This example will generate a request for: + +- GET /me +- GET /sms +- GET /sms/* +- POST /sms +- POST /sms/* +- PUT /sms +- PUT /sms/* +- DELETE /sms +- DELETE /sms/* + +Which would be tedious to do by hand... + +*Create a ``CkRequest``*: + +```go +req := client.NewCkRequest() +``` + +*Request access on a specific path and method* (advanced): +```go +// Use this method for fine-grain access control. In most case, you'll +// want to use the methods below. +req.AddRule("VERB", "PATTERN") +``` + +*Request access on specific path*: +```go +// This will generate all patterns for GET PATH +req.AddRules(ovh.ReadOnly, "/PATH") + +// This will generate all patterns for PATH for all HTTP verbs +req.AddRules(ovh.ReadWrite, "/PATH") + +// This will generate all patterns for PATH for all HTTP verbs, except DELETE +req.AddRules(ovh.ReadWriteSafe, "/PATH") +``` + +*Request access on path and all sub-path*: +```go +// This will generate all patterns for GET PATH +req.AddRecursiveRules(ovh.ReadOnly, "/PATH") + +// This will generate all patterns for PATH for all HTTP verbs +req.AddRecursiveRules(ovh.ReadWrite, "/PATH") + +// This will generate all patterns for PATH for all HTTP verbs, except DELETE +req.AddRecusriveRules(ovh.ReadWriteSafe, "/PATH") +``` + +*Create key*: + +```go +pendingCk, err := req.Do() +``` + +This will initiate the consumer key validation process and return both a consumer key and +a validation URL. The consumer key is automatically added to the client which was used to +create the request. It may be used as soon as the user has authenticated the request on the +validation URL. + + +``pendingCk`` contains 3 fields: +- ``ValidationURL`` the URL the user needs to visit to activate the consumer key +- ``ConsumerKey`` the new consumer key. It won't be active until validation +- ``State`` the consumer key state. Always "pendingValidation" at this stage + + +## Hacking + +This wrapper uses standard Go tools, so you should feel at home with it. +Here is a quick outline of what it may look like. + +### Get the sources + +``` +go get github.com/ovh/go-ovh/ovh +cd $GOPATH/src/github.com/ovh/go-ovh/ovh +go get +``` + +You've developed a new cool feature ? Fixed an annoying bug ? We'd be happy +to hear from you ! See [CONTRIBUTING.md](https://github.com/ovh/go-ovh/blob/master/CONTRIBUTING.md) +for more informations + +### Run the tests + +Simply run ``go test``. Since we all love quality, please +note that we do not accept contributions lowering coverage. + +``` +# Run all tests, with coverage +go test -cover + +# Validate code quality +golint ./... +go vet ./... +``` + +## Supported APIs + +### OVH Europe + +- **Documentation**: https://eu.api.ovh.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://eu.api.ovh.com/console +- **Create application credentials**: https://eu.api.ovh.com/createApp/ +- **Create script credentials** (all keys at once): https://eu.api.ovh.com/createToken/ + +### OVH US + +- **Documentation**: https://api.ovh.us/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://api.ovh.us/console/ +- **Create application credentials**: https://api.ovh.us/createApp/ +- **Create script credentials** (all keys at once): https://api.ovh.us/createToken/ + +### OVH Canada + +- **Documentation**: https://ca.api.ovh.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://ca.api.ovh.com/console +- **Create application credentials**: https://ca.api.ovh.com/createApp/ +- **Create script credentials** (all keys at once): https://ca.api.ovh.com/createToken/ + +### So you Start Europe + +- **Documentation**: https://eu.api.soyoustart.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://eu.api.soyoustart.com/console/ +- **Create application credentials**: https://eu.api.soyoustart.com/createApp/ +- **Create script credentials** (all keys at once): https://eu.api.soyoustart.com/createToken/ + +### So you Start Canada + +- **Documentation**: https://ca.api.soyoustart.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://ca.api.soyoustart.com/console/ +- **Create application credentials**: https://ca.api.soyoustart.com/createApp/ +- **Create script credentials** (all keys at once): https://ca.api.soyoustart.com/createToken/ + +### Kimsufi Europe + +- **Documentation**: https://eu.api.kimsufi.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://eu.api.kimsufi.com/console/ +- **Create application credentials**: https://eu.api.kimsufi.com/createApp/ +- **Create script credentials** (all keys at once): https://eu.api.kimsufi.com/createToken/ + +### Kimsufi Canada + +- **Documentation**: https://ca.api.kimsufi.com/ +- **Community support**: api-subscribe@ml.ovh.net +- **Console**: https://ca.api.kimsufi.com/console/ +- **Create application credentials**: https://ca.api.kimsufi.com/createApp/ +- **Create script credentials** (all keys at once): https://ca.api.kimsufi.com/createToken/ + +### Runabove + +- **Community support**: https://community.runabove.com/ +- **Console**: https://api.runabove.com/console/ +- **Create application credentials**: https://api.runabove.com/createApp/ +- **High level SDK**: https://github.com/runabove/python-runabove + +## License + +3-Clause BSD + diff --git a/vendor/github.com/ovh/go-ovh/ovh/configuration.go b/vendor/github.com/ovh/go-ovh/ovh/configuration.go new file mode 100644 index 0000000..016cb29 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/ovh/configuration.go @@ -0,0 +1,123 @@ +package ovh + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + + "gopkg.in/ini.v1" +) + +// Use variables for easier test overload +var ( + systemConfigPath = "/etc/ovh.conf" + userConfigPath = "/.ovh.conf" // prefixed with homeDir + localConfigPath = "./ovh.conf" +) + +// currentUserHome attempts to get current user's home directory +func currentUserHome() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + return usr.HomeDir, nil +} + +// appendConfigurationFile only if it exists. We need to do this because +// ini package will fail to load configuration at all if a configuration +// file is missing. This is racy, but better than always failing. +func appendConfigurationFile(cfg *ini.File, path string) { + if file, err := os.Open(path); err == nil { + file.Close() + cfg.Append(path) + } +} + +// loadConfig loads client configuration from params, environments or configuration +// files (by order of decreasing precedence). +// +// loadConfig will check OVH_CONSUMER_KEY, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET +// and OVH_ENDPOINT environment variables. If any is present, it will take precedence +// over any configuration from file. +// +// Configuration files are ini files. They share the same format as python-ovh, +// node-ovh, php-ovh and all other wrappers. If any wrapper is configured, all +// can re-use the same configuration. loadConfig will check for configuration in: +// +// - ./ovh.conf +// - $HOME/.ovh.conf +// - /etc/ovh.conf +// +func (c *Client) loadConfig(endpointName string) error { + // Load configuration files by order of increasing priority. All configuration + // files are optional. Only load file from user home if home could be resolve + cfg := ini.Empty() + appendConfigurationFile(cfg, systemConfigPath) + if home, err := currentUserHome(); err == nil { + userConfigFullPath := filepath.Join(home, userConfigPath) + appendConfigurationFile(cfg, userConfigFullPath) + } + appendConfigurationFile(cfg, localConfigPath) + + // Canonicalize configuration + if endpointName == "" { + endpointName = getConfigValue(cfg, "default", "endpoint", "ovh-eu") + } + + if c.AppKey == "" { + c.AppKey = getConfigValue(cfg, endpointName, "application_key", "") + } + + if c.AppSecret == "" { + c.AppSecret = getConfigValue(cfg, endpointName, "application_secret", "") + } + + if c.ConsumerKey == "" { + c.ConsumerKey = getConfigValue(cfg, endpointName, "consumer_key", "") + } + + // Load real endpoint URL by name. If endpoint contains a '/', consider it as a URL + if strings.Contains(endpointName, "/") { + c.endpoint = endpointName + } else { + c.endpoint = Endpoints[endpointName] + } + + // If we still have no valid endpoint, AppKey or AppSecret, return an error + if c.endpoint == "" { + return fmt.Errorf("unknown endpoint '%s', consider checking 'Endpoints' list of using an URL", endpointName) + } + if c.AppKey == "" { + return fmt.Errorf("missing application key, please check your configuration or consult the documentation to create one") + } + if c.AppSecret == "" { + return fmt.Errorf("missing application secret, please check your configuration or consult the documentation to create one") + } + + return nil +} + +// getConfigValue returns the value of OVH_ or ``name`` value from ``section``. If +// the value could not be read from either env or any configuration files, return 'def' +func getConfigValue(cfg *ini.File, section, name, def string) string { + // Attempt to load from environment + fromEnv := os.Getenv("OVH_" + strings.ToUpper(name)) + if len(fromEnv) > 0 { + return fromEnv + } + + // Attempt to load from configuration + fromSection := cfg.Section(section) + if fromSection == nil { + return def + } + + fromSectionKey := fromSection.Key(name) + if fromSectionKey == nil { + return def + } + return fromSectionKey.String() +} diff --git a/vendor/github.com/ovh/go-ovh/ovh/consumer_key.go b/vendor/github.com/ovh/go-ovh/ovh/consumer_key.go new file mode 100644 index 0000000..7163755 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/ovh/consumer_key.go @@ -0,0 +1,113 @@ +package ovh + +import ( + "fmt" + "strings" +) + +// Map user friendly access level names to corresponding HTTP verbs +var ( + ReadOnly = []string{"GET"} + ReadWrite = []string{"GET", "POST", "PUT", "DELETE"} + ReadWriteSafe = []string{"GET", "POST", "PUT"} +) + +// AccessRule represents a method allowed for a path +type AccessRule struct { + // Allowed HTTP Method for the requested AccessRule. + // Can be set to GET/POST/PUT/DELETE. + Method string `json:"method"` + // Allowed path. + // Can be an exact string or a string with '*' char. + // Example : + // /me : only /me is authorized + // /* : all calls are authorized + Path string `json:"path"` +} + +// CkValidationState represents the response when asking a new consumerKey. +type CkValidationState struct { + // Consumer key, which need to be validated by customer. + ConsumerKey string `json:"consumerKey"` + // Current status, should be always "pendingValidation". + State string `json:"state"` + // URL to redirect user in order to log in. + ValidationURL string `json:"validationUrl"` +} + +// CkRequest represents the parameters to fill in order to ask a new +// consumerKey. +type CkRequest struct { + client *Client + AccessRules []AccessRule `json:"accessRules"` + Redirection string `json:"redirection,omitempty"` +} + +func (ck *CkValidationState) String() string { + return fmt.Sprintf("CK: %q\nStatus: %q\nValidation URL: %q\n", + ck.ConsumerKey, + ck.State, + ck.ValidationURL, + ) +} + +// NewCkRequest helps create a new ck request +func (c *Client) NewCkRequest() *CkRequest { + return &CkRequest{ + client: c, + AccessRules: []AccessRule{}, + } +} + +// NewCkRequestWithRedirection helps create a new ck request with a redirect URL +func (c *Client) NewCkRequestWithRedirection(redirection string) *CkRequest { + return &CkRequest{ + client: c, + AccessRules: []AccessRule{}, + Redirection: redirection, + } +} + +// AddRule adds a new rule to the ckRequest +func (ck *CkRequest) AddRule(method, path string) { + ck.AccessRules = append(ck.AccessRules, AccessRule{ + Method: method, + Path: path, + }) +} + +// AddRules adds grant requests on "path" for all methods. "ReadOnly", +// "ReadWrite" and "ReadWriteSafe" should be used for "methods" unless +// specific access are required. +func (ck *CkRequest) AddRules(methods []string, path string) { + for _, method := range methods { + ck.AddRule(method, path) + } + +} + +// AddRecursiveRules adds grant requests on "path" and "path/*", for all +// methods "ReadOnly", "ReadWrite" and "ReadWriteSafe" should be used for +// "methods" unless specific access are required. +func (ck *CkRequest) AddRecursiveRules(methods []string, path string) { + path = strings.TrimRight(path, "/") + + // Add rules. Skip base rule when requesting access to "/" + if path != "" { + ck.AddRules(methods, path) + } + ck.AddRules(methods, path+"/*") +} + +// Do executes the request. On success, set the consumer key in the client +// and return the URL the user needs to visit to validate the key +func (ck *CkRequest) Do() (*CkValidationState, error) { + state := CkValidationState{} + err := ck.client.PostUnAuth("/auth/credential", ck, &state) + + if err == nil { + ck.client.ConsumerKey = state.ConsumerKey + } + + return &state, err +} diff --git a/vendor/github.com/ovh/go-ovh/ovh/error.go b/vendor/github.com/ovh/go-ovh/ovh/error.go new file mode 100644 index 0000000..35e3608 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/ovh/error.go @@ -0,0 +1,17 @@ +package ovh + +import "fmt" + +// APIError represents an error that can occurred while calling the API. +type APIError struct { + // Error message. + Message string + // HTTP code. + Code int + // ID of the request + QueryID string +} + +func (err *APIError) Error() string { + return fmt.Sprintf("Error %d: %q", err.Code, err.Message) +} diff --git a/vendor/github.com/ovh/go-ovh/ovh/ovh.go b/vendor/github.com/ovh/go-ovh/ovh/ovh.go new file mode 100644 index 0000000..7ffaf38 --- /dev/null +++ b/vendor/github.com/ovh/go-ovh/ovh/ovh.go @@ -0,0 +1,343 @@ +// Package ovh provides a HTTP wrapper for the OVH API. +package ovh + +import ( + "bytes" + "crypto/sha1" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "sync" + "time" +) + +// DefaultTimeout api requests after 180s +const DefaultTimeout = 180 * time.Second + +// Endpoints +const ( + OvhEU = "https://eu.api.ovh.com/1.0" + OvhCA = "https://ca.api.ovh.com/1.0" + OvhUS = "https://api.ovh.us/1.0" + KimsufiEU = "https://eu.api.kimsufi.com/1.0" + KimsufiCA = "https://ca.api.kimsufi.com/1.0" + SoyoustartEU = "https://eu.api.soyoustart.com/1.0" + SoyoustartCA = "https://ca.api.soyoustart.com/1.0" + RunaboveCA = "https://api.runabove.com/1.0" +) + +// Endpoints conveniently maps endpoints names to their URI for external configuration +var Endpoints = map[string]string{ + "ovh-eu": OvhEU, + "ovh-ca": OvhCA, + "ovh-us": OvhUS, + "kimsufi-eu": KimsufiEU, + "kimsufi-ca": KimsufiCA, + "soyoustart-eu": SoyoustartEU, + "soyoustart-ca": SoyoustartCA, + "runabove-ca": RunaboveCA, +} + +// Errors +var ( + ErrAPIDown = errors.New("go-vh: the OVH API is down, it does't respond to /time anymore") +) + +// Client represents a client to call the OVH API +type Client struct { + // Self generated tokens. Create one by visiting + // https://eu.api.ovh.com/createApp/ + // AppKey holds the Application key + AppKey string + + // AppSecret holds the Application secret key + AppSecret string + + // ConsumerKey holds the user/app specific token. It must have been validated before use. + ConsumerKey string + + // API endpoint + endpoint string + + // Client is the underlying HTTP client used to run the requests. It may be overloaded but a default one is instanciated in ``NewClient`` by default. + Client *http.Client + + // Ensures that the timeDelta function is only ran once + // sync.Once would consider init done, even in case of error + // hence a good old flag + timeDeltaMutex *sync.Mutex + timeDeltaDone bool + timeDelta time.Duration + Timeout time.Duration +} + +// NewClient represents a new client to call the API +func NewClient(endpoint, appKey, appSecret, consumerKey string) (*Client, error) { + client := Client{ + AppKey: appKey, + AppSecret: appSecret, + ConsumerKey: consumerKey, + Client: &http.Client{}, + timeDeltaMutex: &sync.Mutex{}, + timeDeltaDone: false, + Timeout: time.Duration(DefaultTimeout), + } + + // Get and check the configuration + if err := client.loadConfig(endpoint); err != nil { + return nil, err + } + return &client, nil +} + +// NewEndpointClient will create an API client for specified +// endpoint and load all credentials from environment or +// configuration files +func NewEndpointClient(endpoint string) (*Client, error) { + return NewClient(endpoint, "", "", "") +} + +// NewDefaultClient will load all it's parameter from environment +// or configuration files +func NewDefaultClient() (*Client, error) { + return NewClient("", "", "", "") +} + +// +// High level helpers +// + +// Ping performs a ping to OVH API. +// In fact, ping is just a /auth/time call, in order to check if API is up. +func (c *Client) Ping() error { + _, err := c.getTime() + return err +} + +// TimeDelta represents the delay between the machine that runs the code and the +// OVH API. The delay shouldn't change, let's do it only once. +func (c *Client) TimeDelta() (time.Duration, error) { + return c.getTimeDelta() +} + +// Time returns time from the OVH API, by asking GET /auth/time. +func (c *Client) Time() (*time.Time, error) { + return c.getTime() +} + +// +// Common request wrappers +// + +// Get is a wrapper for the GET method +func (c *Client) Get(url string, resType interface{}) error { + return c.CallAPI("GET", url, nil, resType, true) +} + +// GetUnAuth is a wrapper for the unauthenticated GET method +func (c *Client) GetUnAuth(url string, resType interface{}) error { + return c.CallAPI("GET", url, nil, resType, false) +} + +// Post is a wrapper for the POST method +func (c *Client) Post(url string, reqBody, resType interface{}) error { + return c.CallAPI("POST", url, reqBody, resType, true) +} + +// PostUnAuth is a wrapper for the unauthenticated POST method +func (c *Client) PostUnAuth(url string, reqBody, resType interface{}) error { + return c.CallAPI("POST", url, reqBody, resType, false) +} + +// Put is a wrapper for the PUT method +func (c *Client) Put(url string, reqBody, resType interface{}) error { + return c.CallAPI("PUT", url, reqBody, resType, true) +} + +// PutUnAuth is a wrapper for the unauthenticated PUT method +func (c *Client) PutUnAuth(url string, reqBody, resType interface{}) error { + return c.CallAPI("PUT", url, reqBody, resType, false) +} + +// Delete is a wrapper for the DELETE method +func (c *Client) Delete(url string, resType interface{}) error { + return c.CallAPI("DELETE", url, nil, resType, true) +} + +// DeleteUnAuth is a wrapper for the unauthenticated DELETE method +func (c *Client) DeleteUnAuth(url string, resType interface{}) error { + return c.CallAPI("DELETE", url, nil, resType, false) +} + +// timeDelta returns the time delta between the host and the remote API +func (c *Client) getTimeDelta() (time.Duration, error) { + + if !c.timeDeltaDone { + // Ensure only one thread is updating + c.timeDeltaMutex.Lock() + + // Did we wait ? Maybe no more needed + if !c.timeDeltaDone { + ovhTime, err := c.getTime() + if err != nil { + return 0, err + } + + c.timeDelta = time.Since(*ovhTime) + c.timeDeltaDone = true + } + c.timeDeltaMutex.Unlock() + } + + return c.timeDelta, nil +} + +// getTime t returns time from for a given api client endpoint +func (c *Client) getTime() (*time.Time, error) { + var timestamp int64 + + err := c.GetUnAuth("/auth/time", ×tamp) + if err != nil { + return nil, err + } + + serverTime := time.Unix(timestamp, 0) + return &serverTime, nil +} + +// getLocalTime is a function to be overwritten during the tests, it return the time +// on the the local machine +var getLocalTime = func() time.Time { + return time.Now() +} + +// getEndpointForSignature is a function to be overwritten during the tests, it returns a +// the endpoint +var getEndpointForSignature = func(c *Client) string { + return c.endpoint +} + +// NewRequest returns a new HTTP request +func (c *Client) NewRequest(method, path string, reqBody interface{}, needAuth bool) (*http.Request, error) { + var body []byte + var err error + + if reqBody != nil { + body, err = json.Marshal(reqBody) + if err != nil { + return nil, err + } + } + + target := fmt.Sprintf("%s%s", c.endpoint, path) + req, err := http.NewRequest(method, target, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + // Inject headers + if body != nil { + req.Header.Add("Content-Type", "application/json;charset=utf-8") + } + req.Header.Add("X-Ovh-Application", c.AppKey) + req.Header.Add("Accept", "application/json") + + // Inject signature. Some methods do not need authentication, especially /time, + // /auth and some /order methods are actually broken if authenticated. + if needAuth { + timeDelta, err := c.TimeDelta() + if err != nil { + return nil, err + } + + timestamp := getLocalTime().Add(-timeDelta).Unix() + + req.Header.Add("X-Ovh-Timestamp", strconv.FormatInt(timestamp, 10)) + req.Header.Add("X-Ovh-Consumer", c.ConsumerKey) + + h := sha1.New() + h.Write([]byte(fmt.Sprintf("%s+%s+%s+%s%s+%s+%d", + c.AppSecret, + c.ConsumerKey, + method, + getEndpointForSignature(c), + path, + body, + timestamp, + ))) + req.Header.Add("X-Ovh-Signature", fmt.Sprintf("$1$%x", h.Sum(nil))) + } + + // Send the request with requested timeout + c.Client.Timeout = c.Timeout + + return req, nil +} + +// Do sends an HTTP request and returns an HTTP response +func (c *Client) Do(req *http.Request) (*http.Response, error) { + return c.Client.Do(req) +} + +// CallAPI is the lowest level call helper. If needAuth is true, +// inject authentication headers and sign the request. +// +// Request signature is a sha1 hash on following fields, joined by '+': +// - applicationSecret (from Client instance) +// - consumerKey (from Client instance) +// - capitalized method (from arguments) +// - full request url, including any query string argument +// - full serialized request body +// - server current time (takes time delta into account) +// +// Call will automatically assemble the target url from the endpoint +// configured in the client instance and the path argument. If the reqBody +// argument is not nil, it will also serialize it as json and inject +// the required Content-Type header. +// +// If everyrthing went fine, unmarshall response into resType and return nil +// otherwise, return the error +func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error { + req, err := c.NewRequest(method, path, reqBody, needAuth) + if err != nil { + return err + } + response, err := c.Do(req) + if err != nil { + return err + } + return c.UnmarshalResponse(response, resType) + +} + +// UnmarshalResponse checks the response and unmarshals it into the response +// type if needed Helper function, called from CallAPI +func (c *Client) UnmarshalResponse(response *http.Response, resType interface{}) error { + // Read all the response body + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + // < 200 && >= 300 : API error + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + apiError := &APIError{Code: response.StatusCode} + if err = json.Unmarshal(body, apiError); err != nil { + apiError.Message = string(body) + } + apiError.QueryID = response.Header.Get("X-Ovh-QueryID") + + return apiError + } + + // Nothing to unmarshal + if len(body) == 0 || resType == nil { + return nil + } + + return json.Unmarshal(body, &resType) +}