├── .travis.yml ├── Godeps ├── LICENSE ├── README.md ├── account.go ├── account_feature.go ├── addon.go ├── addon_service.go ├── app.go ├── app_feature.go ├── app_test.go ├── app_transfer.go ├── collaborator.go ├── config_var.go ├── doc.go ├── domain.go ├── dyno.go ├── formation.go ├── gen ├── gen.rb └── schema.json ├── heroku.go ├── heroku_test.go ├── key.go ├── log_drain.go ├── log_session.go ├── oauth_authorization.go ├── oauth_client.go ├── oauth_token.go ├── organization.go ├── organization_app.go ├── organization_app_collaborator.go ├── organization_member.go ├── plan.go ├── rate_limit.go ├── region.go ├── release.go ├── slug.go ├── ssl_endpoint.go └── stack.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - tip 6 | install: 7 | - export PATH=$PATH:$HOME/gopath/bin 8 | - go get -v github.com/kr/godep 9 | - godep get 10 | - godep go build -v ./... 11 | script: 12 | - godep go test -i ./... 13 | - godep go test ./... 14 | - test -z "$(go fmt ./...)" 15 | -------------------------------------------------------------------------------- /Godeps: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/bgentry/heroku-go", 3 | "GoVersion": "go1.2", 4 | "Deps": [ 5 | { 6 | "ImportPath": "code.google.com/p/go-uuid/uuid", 7 | "Comment": "null-10", 8 | "Rev": "5fac954758f5aa282478dc07fbb8709fe9575184" 9 | }, 10 | { 11 | "ImportPath": "github.com/bgentry/testnet", 12 | "Rev": "05450cdcf16c84d5b08dc9bb617250aa7b63c8ff" 13 | }, 14 | { 15 | "ImportPath": "github.com/stretchr/testify/assert", 16 | "Rev": "4c55a02a9da3f9b9daf583332a2a82c38a4521be" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Blake Gentry. 4 | 5 | Portions of the base API client (main.go) are Copyright (c) 2013 Keith Rarick. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # heroku-go 2 | 3 | An API client interface for Heroku for the Go (golang) programming language. 4 | 5 | [![Build Status](https://travis-ci.org/bgentry/heroku-go.png)](https://travis-ci.org/bgentry/heroku-go) 6 | [![GoDoc](https://godoc.org/github.com/bgentry/heroku-go?status.png)][godoc] 7 | 8 | ## Background 9 | 10 | This package provides a complete interface to all of the Heroku Platform API v3 11 | actions, and is almost entirely auto-generated based on the API's JSON Schema. 12 | The exceptions are the files `heroku.go`, `heroku_test.go`, and `app_test.go`, 13 | as well as the generator itself. All models are auto-generated by the Ruby 14 | script in `gen/gen.rb`. 15 | 16 | The client leverages Go's powerful `net/http` Client. That means that, 17 | out-of-the-box, it has keep-alive support and the ability to handle many 18 | concurrent requests from different goroutines. 19 | 20 | You should have at least some understanding of Heroku and its 21 | [Platform API][platform-api]. 22 | 23 | ## Installation 24 | 25 | This package is targeted towards Go 1.2 or later, though it may work on 26 | earlier versions as well. 27 | 28 | Run `go get github.com/bgentry/heroku-go` to download, build, and install the 29 | package. 30 | 31 | ## Getting Started 32 | 33 | To use the client, first add it to your Go file's imports list: 34 | 35 | ```go 36 | import ( 37 | "github.com/bgentry/heroku-go" 38 | ) 39 | ``` 40 | 41 | Then create a `Client` object and make calls to it: 42 | 43 | ```go 44 | client := heroku.Client{Username: "email@me.com", Password: "my-api-key"} 45 | 46 | // pass nil for options if you don't need to set any optional params 47 | app, err := client.AppCreate(nil) 48 | if err != nil { 49 | panic(err) 50 | } 51 | fmt.Println("Created", app.Name) 52 | 53 | // Output: 54 | // Created dodging-samurai-42 55 | ``` 56 | 57 | That's it! Here is a more advanced example that also sets some options on the 58 | new app: 59 | 60 | ```go 61 | name := "myapp" 62 | region := "region" 63 | 64 | // Optional values need to be provided as pointers. If a field in an option 65 | // struct is nil (not provided), the option is omitted from the API request. 66 | opts := heroku.AppCreateOpts{Name: &name, Region: ®ion} 67 | 68 | // Create an app with options set: 69 | app2, err := client.AppCreate(&opts) 70 | if err != nil { 71 | // if this is a heroku.Error, it will contain details about the error 72 | if hkerr, ok := err.(heroku.Error); ok { 73 | panic(fmt.Sprintf("Error id=%s message=%q", hkerr.Id, hkerr)) 74 | } 75 | } 76 | fmt.Printf("created app2: name=%s region=%s", app2.Name, app2.Region.Name) 77 | 78 | // Output: 79 | // created app2: name=myapp region=eu 80 | ``` 81 | 82 | ## Optional Parameters 83 | 84 | Many of the Heroku Platform API actions have optional parameters. For example, 85 | when creating an app, you can either specify a custom name, or allow the API to 86 | choose a random haiku name for your app. 87 | 88 | Optional parameters in heroku-go are always provided to functions as a pointer 89 | to a struct, such as `AppCreateOpts` for the function `AppCreate()`. If you do 90 | not wish to set any optional parameters, simply provide a `nil` in place of the 91 | options struct, and the options will be omitted from the API request entirely. 92 | For any individual options that you don't want to set, simply leave them as 93 | `nil`, and they will be omitted from the API request. 94 | 95 | ## List Ranges & Sorting 96 | 97 | Results from the Heroku API are paginated. You can specify a field for sorting 98 | and adjust the maximum number of records returned by providing a `ListRange` to 99 | API calls that list objects: 100 | 101 | ```go 102 | apps, err = client.AppList(&heroku.ListRange{Field: "name", Max: 1000}) 103 | ``` 104 | 105 | Note `Field` [is required][range-docs] when setting any range options. 106 | 107 | ## Documentation 108 | 109 | More detailed documentation is available on [godoc][godoc]. 110 | 111 | ## Thank You 112 | 113 | A special thanks goes out to [Keith Rarick](https://github.com/kr) for writing 114 | much of the [base API client](https://github.com/bgentry/heroku-go/blob/master/main.go) 115 | as part of [hk](https://github.com/heroku/hk). I also want to thank the Heroku 116 | API team for making their API available in JSON schema and for the early version 117 | of [heroics](https://github.com/heroku/heroics), on which this generator is 118 | based. 119 | 120 | [godoc]: https://godoc.org/github.com/bgentry/heroku-go "heroku-go on Godoc.org" 121 | [platform-api]: https://devcenter.heroku.com/articles/platform-api-reference "Heroku Platform API" 122 | [range-docs]: https://devcenter.heroku.com/articles/platform-api-reference#ranges "Request Ranges" 123 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An account represents an individual signed up to use the Heroku platform. 12 | type Account struct { 13 | // whether to allow third party web activity tracking 14 | AllowTracking bool `json:"allow_tracking"` 15 | 16 | // whether allowed to utilize beta Heroku features 17 | Beta bool `json:"beta"` 18 | 19 | // when account was created 20 | CreatedAt time.Time `json:"created_at"` 21 | 22 | // unique email address of account 23 | Email string `json:"email"` 24 | 25 | // unique identifier of an account 26 | Id string `json:"id"` 27 | 28 | // when account last authorized with Heroku 29 | LastLogin time.Time `json:"last_login"` 30 | 31 | // full name of the account owner 32 | Name *string `json:"name"` 33 | 34 | // when account was updated 35 | UpdatedAt time.Time `json:"updated_at"` 36 | 37 | // whether account has been verified with billing information 38 | Verified bool `json:"verified"` 39 | } 40 | 41 | // Info for account. 42 | func (c *Client) AccountInfo() (*Account, error) { 43 | var account Account 44 | return &account, c.Get(&account, "/account") 45 | } 46 | 47 | // Update account. 48 | // 49 | // password is the current password on the account. options is the struct of 50 | // optional parameters for this action. 51 | func (c *Client) AccountUpdate(password string, options *AccountUpdateOpts) (*Account, error) { 52 | params := struct { 53 | Password string `json:"password"` 54 | AllowTracking *bool `json:"allow_tracking,omitempty"` 55 | Beta *bool `json:"beta,omitempty"` 56 | Name *string `json:"name,omitempty"` 57 | }{ 58 | Password: password, 59 | } 60 | if options != nil { 61 | params.AllowTracking = options.AllowTracking 62 | params.Beta = options.Beta 63 | params.Name = options.Name 64 | } 65 | var accountRes Account 66 | return &accountRes, c.Patch(&accountRes, "/account", params) 67 | } 68 | 69 | // AccountUpdateOpts holds the optional parameters for AccountUpdate 70 | type AccountUpdateOpts struct { 71 | // whether to allow third party web activity tracking 72 | AllowTracking *bool `json:"allow_tracking,omitempty"` 73 | // whether allowed to utilize beta Heroku features 74 | Beta *bool `json:"beta,omitempty"` 75 | // full name of the account owner 76 | Name *string `json:"name,omitempty"` 77 | } 78 | 79 | // Change Email for account. 80 | // 81 | // password is the current password on the account. email is the unique email 82 | // address of account. 83 | func (c *Client) AccountChangeEmail(password string, email string) (*Account, error) { 84 | params := struct { 85 | Password string `json:"password"` 86 | Email string `json:"email"` 87 | }{ 88 | Password: password, 89 | Email: email, 90 | } 91 | var accountRes Account 92 | return &accountRes, c.Patch(&accountRes, "/account", params) 93 | } 94 | 95 | // Change Password for account. 96 | // 97 | // newPassword is the the new password for the account when changing the 98 | // password. password is the current password on the account. 99 | func (c *Client) AccountChangePassword(newPassword string, password string) (*Account, error) { 100 | params := struct { 101 | NewPassword string `json:"new_password"` 102 | Password string `json:"password"` 103 | }{ 104 | NewPassword: newPassword, 105 | Password: password, 106 | } 107 | var accountRes Account 108 | return &accountRes, c.Patch(&accountRes, "/account", params) 109 | } 110 | -------------------------------------------------------------------------------- /account_feature.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An account feature represents a Heroku labs capability that can be enabled or 12 | // disabled for an account on Heroku. 13 | type AccountFeature struct { 14 | // when account feature was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // description of account feature 18 | Description string `json:"description"` 19 | 20 | // documentation URL of account feature 21 | DocURL string `json:"doc_url"` 22 | 23 | // whether or not account feature has been enabled 24 | Enabled bool `json:"enabled"` 25 | 26 | // unique identifier of account feature 27 | Id string `json:"id"` 28 | 29 | // unique name of account feature 30 | Name string `json:"name"` 31 | 32 | // state of account feature 33 | State string `json:"state"` 34 | 35 | // when account feature was updated 36 | UpdatedAt time.Time `json:"updated_at"` 37 | } 38 | 39 | // Info for an existing account feature. 40 | // 41 | // accountFeatureIdentity is the unique identifier of the AccountFeature. 42 | func (c *Client) AccountFeatureInfo(accountFeatureIdentity string) (*AccountFeature, error) { 43 | var accountFeature AccountFeature 44 | return &accountFeature, c.Get(&accountFeature, "/account/features/"+accountFeatureIdentity) 45 | } 46 | 47 | // List existing account features. 48 | // 49 | // lr is an optional ListRange that sets the Range options for the paginated 50 | // list of results. 51 | func (c *Client) AccountFeatureList(lr *ListRange) ([]AccountFeature, error) { 52 | req, err := c.NewRequest("GET", "/account/features", nil) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if lr != nil { 58 | lr.SetHeader(req) 59 | } 60 | 61 | var accountFeaturesRes []AccountFeature 62 | return accountFeaturesRes, c.DoReq(req, &accountFeaturesRes) 63 | } 64 | 65 | // Update an existing account feature. 66 | // 67 | // accountFeatureIdentity is the unique identifier of the AccountFeature. 68 | // enabled is the whether or not account feature has been enabled. 69 | func (c *Client) AccountFeatureUpdate(accountFeatureIdentity string, enabled bool) (*AccountFeature, error) { 70 | params := struct { 71 | Enabled bool `json:"enabled"` 72 | }{ 73 | Enabled: enabled, 74 | } 75 | var accountFeatureRes AccountFeature 76 | return &accountFeatureRes, c.Patch(&accountFeatureRes, "/account/features/"+accountFeatureIdentity, params) 77 | } 78 | -------------------------------------------------------------------------------- /addon.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Add-ons represent add-ons that have been provisioned for an app. 12 | type Addon struct { 13 | // config vars associated with this application 14 | ConfigVars []string `json:"config_vars"` 15 | 16 | // when add-on was updated 17 | CreatedAt time.Time `json:"created_at"` 18 | 19 | // unique identifier of add-on 20 | Id string `json:"id"` 21 | 22 | // name of the add-on unique within its app 23 | Name string `json:"name"` 24 | 25 | // identity of add-on plan 26 | Plan struct { 27 | Id string `json:"id"` 28 | Name string `json:"name"` 29 | } `json:"plan"` 30 | 31 | // id of this add-on with its provider 32 | ProviderId string `json:"provider_id"` 33 | 34 | // when add-on was updated 35 | UpdatedAt time.Time `json:"updated_at"` 36 | } 37 | 38 | // Create a new add-on. 39 | // 40 | // appIdentity is the unique identifier of the Addon's App. plan is the unique 41 | // identifier of this plan or unique name of this plan. options is the struct of 42 | // optional parameters for this action. 43 | func (c *Client) AddonCreate(appIdentity string, plan string, options *AddonCreateOpts) (*Addon, error) { 44 | params := struct { 45 | Plan string `json:"plan"` 46 | Config *map[string]string `json:"config,omitempty"` 47 | }{ 48 | Plan: plan, 49 | } 50 | if options != nil { 51 | params.Config = options.Config 52 | } 53 | var addonRes Addon 54 | return &addonRes, c.Post(&addonRes, "/apps/"+appIdentity+"/addons", params) 55 | } 56 | 57 | // AddonCreateOpts holds the optional parameters for AddonCreate 58 | type AddonCreateOpts struct { 59 | // custom add-on provisioning options 60 | Config *map[string]string `json:"config,omitempty"` 61 | } 62 | 63 | // Delete an existing add-on. 64 | // 65 | // appIdentity is the unique identifier of the Addon's App. addonIdentity is the 66 | // unique identifier of the Addon. 67 | func (c *Client) AddonDelete(appIdentity string, addonIdentity string) error { 68 | return c.Delete("/apps/" + appIdentity + "/addons/" + addonIdentity) 69 | } 70 | 71 | // Info for an existing add-on. 72 | // 73 | // appIdentity is the unique identifier of the Addon's App. addonIdentity is the 74 | // unique identifier of the Addon. 75 | func (c *Client) AddonInfo(appIdentity string, addonIdentity string) (*Addon, error) { 76 | var addon Addon 77 | return &addon, c.Get(&addon, "/apps/"+appIdentity+"/addons/"+addonIdentity) 78 | } 79 | 80 | // List existing add-ons. 81 | // 82 | // appIdentity is the unique identifier of the Addon's App. lr is an optional 83 | // ListRange that sets the Range options for the paginated list of results. 84 | func (c *Client) AddonList(appIdentity string, lr *ListRange) ([]Addon, error) { 85 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/addons", nil) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | if lr != nil { 91 | lr.SetHeader(req) 92 | } 93 | 94 | var addonsRes []Addon 95 | return addonsRes, c.DoReq(req, &addonsRes) 96 | } 97 | 98 | // Change add-on plan. Some add-ons may not support changing plans. In that 99 | // case, an error will be returned. 100 | // 101 | // appIdentity is the unique identifier of the Addon's App. addonIdentity is the 102 | // unique identifier of the Addon. plan is the unique identifier of this plan or 103 | // unique name of this plan. 104 | func (c *Client) AddonUpdate(appIdentity string, addonIdentity string, plan string) (*Addon, error) { 105 | params := struct { 106 | Plan string `json:"plan"` 107 | }{ 108 | Plan: plan, 109 | } 110 | var addonRes Addon 111 | return &addonRes, c.Patch(&addonRes, "/apps/"+appIdentity+"/addons/"+addonIdentity, params) 112 | } 113 | -------------------------------------------------------------------------------- /addon_service.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Add-on services represent add-ons that may be provisioned for apps. 12 | type AddonService struct { 13 | // when addon-service was created 14 | CreatedAt time.Time `json:"created_at"` 15 | 16 | // unique identifier of this addon-service 17 | Id string `json:"id"` 18 | 19 | // unique name of this addon-service 20 | Name string `json:"name"` 21 | 22 | // when addon-service was updated 23 | UpdatedAt time.Time `json:"updated_at"` 24 | } 25 | 26 | // Info for existing addon-service. 27 | // 28 | // addonServiceIdentity is the unique identifier of the AddonService. 29 | func (c *Client) AddonServiceInfo(addonServiceIdentity string) (*AddonService, error) { 30 | var addonService AddonService 31 | return &addonService, c.Get(&addonService, "/addon-services/"+addonServiceIdentity) 32 | } 33 | 34 | // List existing addon-services. 35 | // 36 | // lr is an optional ListRange that sets the Range options for the paginated 37 | // list of results. 38 | func (c *Client) AddonServiceList(lr *ListRange) ([]AddonService, error) { 39 | req, err := c.NewRequest("GET", "/addon-services", nil) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | if lr != nil { 45 | lr.SetHeader(req) 46 | } 47 | 48 | var addonServicesRes []AddonService 49 | return addonServicesRes, c.DoReq(req, &addonServicesRes) 50 | } 51 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An app represents the program that you would like to deploy and run on 12 | // Heroku. 13 | type App struct { 14 | // when app was archived 15 | ArchivedAt *time.Time `json:"archived_at"` 16 | 17 | // description from buildpack of app 18 | BuildpackProvidedDescription *string `json:"buildpack_provided_description"` 19 | 20 | // when app was created 21 | CreatedAt time.Time `json:"created_at"` 22 | 23 | // git repo URL of app 24 | GitURL string `json:"git_url"` 25 | 26 | // unique identifier of app 27 | Id string `json:"id"` 28 | 29 | // maintenance status of app 30 | Maintenance bool `json:"maintenance"` 31 | 32 | // unique name of app 33 | Name string `json:"name"` 34 | 35 | // identity of app owner 36 | Owner struct { 37 | Email string `json:"email"` 38 | Id string `json:"id"` 39 | } `json:"owner"` 40 | 41 | // identity of app region 42 | Region struct { 43 | Id string `json:"id"` 44 | Name string `json:"name"` 45 | } `json:"region"` 46 | 47 | // when app was released 48 | ReleasedAt *time.Time `json:"released_at"` 49 | 50 | // git repo size in bytes of app 51 | RepoSize *int `json:"repo_size"` 52 | 53 | // slug size in bytes of app 54 | SlugSize *int `json:"slug_size"` 55 | 56 | // identity of app stack 57 | Stack struct { 58 | Id string `json:"id"` 59 | Name string `json:"name"` 60 | } `json:"stack"` 61 | 62 | // when app was updated 63 | UpdatedAt time.Time `json:"updated_at"` 64 | 65 | // web URL of app 66 | WebURL string `json:"web_url"` 67 | } 68 | 69 | // Create a new app. 70 | // 71 | // options is the struct of optional parameters for this action. 72 | func (c *Client) AppCreate(options *AppCreateOpts) (*App, error) { 73 | var appRes App 74 | return &appRes, c.Post(&appRes, "/apps", options) 75 | } 76 | 77 | // AppCreateOpts holds the optional parameters for AppCreate 78 | type AppCreateOpts struct { 79 | // unique name of app 80 | Name *string `json:"name,omitempty"` 81 | // identity of app region 82 | Region *string `json:"region,omitempty"` 83 | // identity of app stack 84 | Stack *string `json:"stack,omitempty"` 85 | } 86 | 87 | // Delete an existing app. 88 | // 89 | // appIdentity is the unique identifier of the App. 90 | func (c *Client) AppDelete(appIdentity string) error { 91 | return c.Delete("/apps/" + appIdentity) 92 | } 93 | 94 | // Info for existing app. 95 | // 96 | // appIdentity is the unique identifier of the App. 97 | func (c *Client) AppInfo(appIdentity string) (*App, error) { 98 | var app App 99 | return &app, c.Get(&app, "/apps/"+appIdentity) 100 | } 101 | 102 | // List existing apps. 103 | // 104 | // lr is an optional ListRange that sets the Range options for the paginated 105 | // list of results. 106 | func (c *Client) AppList(lr *ListRange) ([]App, error) { 107 | req, err := c.NewRequest("GET", "/apps", nil) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | if lr != nil { 113 | lr.SetHeader(req) 114 | } 115 | 116 | var appsRes []App 117 | return appsRes, c.DoReq(req, &appsRes) 118 | } 119 | 120 | // Update an existing app. 121 | // 122 | // appIdentity is the unique identifier of the App. options is the struct of 123 | // optional parameters for this action. 124 | func (c *Client) AppUpdate(appIdentity string, options *AppUpdateOpts) (*App, error) { 125 | var appRes App 126 | return &appRes, c.Patch(&appRes, "/apps/"+appIdentity, options) 127 | } 128 | 129 | // AppUpdateOpts holds the optional parameters for AppUpdate 130 | type AppUpdateOpts struct { 131 | // maintenance status of app 132 | Maintenance *bool `json:"maintenance,omitempty"` 133 | // unique name of app 134 | Name *string `json:"name,omitempty"` 135 | } 136 | -------------------------------------------------------------------------------- /app_feature.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An app feature represents a Heroku labs capability that can be enabled or 12 | // disabled for an app on Heroku. 13 | type AppFeature struct { 14 | // when app feature was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // description of app feature 18 | Description string `json:"description"` 19 | 20 | // documentation URL of app feature 21 | DocURL string `json:"doc_url"` 22 | 23 | // whether or not app feature has been enabled 24 | Enabled bool `json:"enabled"` 25 | 26 | // unique identifier of app feature 27 | Id string `json:"id"` 28 | 29 | // unique name of app feature 30 | Name string `json:"name"` 31 | 32 | // state of app feature 33 | State string `json:"state"` 34 | 35 | // when app feature was updated 36 | UpdatedAt time.Time `json:"updated_at"` 37 | } 38 | 39 | // Info for an existing app feature. 40 | // 41 | // appIdentity is the unique identifier of the AppFeature's App. 42 | // appFeatureIdentity is the unique identifier of the AppFeature. 43 | func (c *Client) AppFeatureInfo(appIdentity string, appFeatureIdentity string) (*AppFeature, error) { 44 | var appFeature AppFeature 45 | return &appFeature, c.Get(&appFeature, "/apps/"+appIdentity+"/features/"+appFeatureIdentity) 46 | } 47 | 48 | // List existing app features. 49 | // 50 | // appIdentity is the unique identifier of the AppFeature's App. lr is an 51 | // optional ListRange that sets the Range options for the paginated list of 52 | // results. 53 | func (c *Client) AppFeatureList(appIdentity string, lr *ListRange) ([]AppFeature, error) { 54 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/features", nil) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | if lr != nil { 60 | lr.SetHeader(req) 61 | } 62 | 63 | var appFeaturesRes []AppFeature 64 | return appFeaturesRes, c.DoReq(req, &appFeaturesRes) 65 | } 66 | 67 | // Update an existing app feature. 68 | // 69 | // appIdentity is the unique identifier of the AppFeature's App. 70 | // appFeatureIdentity is the unique identifier of the AppFeature. enabled is the 71 | // whether or not app feature has been enabled. 72 | func (c *Client) AppFeatureUpdate(appIdentity string, appFeatureIdentity string, enabled bool) (*AppFeature, error) { 73 | params := struct { 74 | Enabled bool `json:"enabled"` 75 | }{ 76 | Enabled: enabled, 77 | } 78 | var appFeatureRes AppFeature 79 | return &appFeatureRes, c.Patch(&appFeatureRes, "/apps/"+appIdentity+"/features/"+appFeatureIdentity, params) 80 | } 81 | -------------------------------------------------------------------------------- /app_test.go: -------------------------------------------------------------------------------- 1 | package heroku 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/bgentry/testnet" 9 | ) 10 | 11 | // 12 | // AppInfo() 13 | // 14 | 15 | const appMarshaled = `{ 16 | "archived_at": "2012-01-01T12:00:00Z", 17 | "buildpack_provided_description": "Ruby/Rack", 18 | "created_at": "2012-01-01T12:00:00Z", 19 | "git_url": "git@heroku.com/example.git", 20 | "id": "01234567-89ab-cdef-0123-456789abcdef", 21 | "maintenance": true, 22 | "name": "example", 23 | "owner": { 24 | "email": "username@example.com", 25 | "id": "01234567-89ab-cdef-0123-456789abcdef" 26 | }, 27 | "region": { 28 | "id": "01234567-89ab-cdef-0123-456789abcdef", 29 | "name": "us" 30 | }, 31 | "released_at": "2012-01-01T12:00:00Z", 32 | "repo_size": 1, 33 | "slug_size": 1, 34 | "stack": { 35 | "id": "01234567-89ab-cdef-0123-456789abcdef", 36 | "name": "cedar" 37 | }, 38 | "updated_at": "2012-01-01T12:00:00Z", 39 | "web_url": "http://example.herokuapp.com" 40 | }` 41 | 42 | func TestAppUnmarshal(t *testing.T) { 43 | var app App 44 | err := json.Unmarshal([]byte(appMarshaled), &app) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | if app.ArchivedAt == nil { 50 | t.Error("expected ArchivedAt to be set, was nil") 51 | } else if *app.ArchivedAt != time.Unix(int64(1325419200), int64(0)).UTC() { 52 | t.Errorf("expected ArchivedAt to be 2012-01-01T12:00:00Z, was %s", *app.ArchivedAt) 53 | } 54 | testStringsEqual(t, "app.Id", "01234567-89ab-cdef-0123-456789abcdef", app.Id) 55 | testStringsEqual(t, "app.Name", "example", app.Name) 56 | testStringsEqual(t, "app.Owner.Email", "username@example.com", app.Owner.Email) 57 | testStringsEqual(t, "app.Region.Name", "us", app.Region.Name) 58 | testStringsEqual(t, "app.Stack.Name", "cedar", app.Stack.Name) 59 | } 60 | 61 | var appInfoResponse = testnet.TestResponse{ 62 | Status: 200, 63 | Body: appMarshaled, 64 | } 65 | var appInfoRequest = newTestRequest("GET", "/apps/example", "", appInfoResponse) 66 | 67 | func TestAppInfoSuccess(t *testing.T) { 68 | ts, handler, c := newTestServerAndClient(t, appInfoRequest) 69 | defer ts.Close() 70 | 71 | app, err := c.AppInfo("example") 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if !handler.AllRequestsCalled() { 76 | t.Errorf("not all expected requests were called") 77 | } 78 | if app == nil { 79 | t.Fatal("no app object returned") 80 | } 81 | var emptyapp App 82 | if *app == emptyapp { 83 | t.Errorf("returned app is empty") 84 | } 85 | } 86 | 87 | // 88 | // AppList() 89 | // 90 | 91 | var appListResponse = testnet.TestResponse{ 92 | Status: 200, 93 | Body: "[" + appMarshaled + "]", 94 | } 95 | var appListRequest = newTestRequest("GET", "/apps", "", appListResponse) 96 | 97 | func TestAppListSuccess(t *testing.T) { 98 | appListRequest.Header.Set("Range", "..; max=1") 99 | 100 | ts, handler, c := newTestServerAndClient(t, appListRequest) 101 | defer ts.Close() 102 | 103 | lr := ListRange{Max: 1} 104 | apps, err := c.AppList(&lr) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | if !handler.AllRequestsCalled() { 109 | t.Errorf("not all expected requests were called") 110 | } 111 | if len(apps) != 1 { 112 | t.Fatalf("expected 1 app, got %d", len(apps)) 113 | } 114 | var emptyapp App 115 | if apps[0] == emptyapp { 116 | t.Errorf("returned app is empty") 117 | } 118 | } 119 | 120 | // 121 | // AppCreate() 122 | // 123 | 124 | func TestAppCreateMarshal(t *testing.T) { 125 | nameval := "example" 126 | regionval := "us" 127 | stackval := "cedar" 128 | 129 | var marshalTests = []struct { 130 | values AppCreateOpts 131 | output string 132 | }{ 133 | {AppCreateOpts{}, `{}`}, 134 | {AppCreateOpts{Name: &nameval}, `{"name":"example"}`}, 135 | {AppCreateOpts{Region: ®ionval}, `{"region":"us"}`}, 136 | {AppCreateOpts{Stack: &stackval}, `{"stack":"cedar"}`}, 137 | {AppCreateOpts{ 138 | Name: &nameval, 139 | Region: ®ionval, 140 | Stack: &stackval, 141 | }, `{"name":"example","region":"us","stack":"cedar"}`}, 142 | } 143 | 144 | for _, body := range marshalTests { 145 | bytes, err := json.Marshal(body.values) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | if string(bytes) != body.output { 150 | t.Errorf("expected %s, got %s", body.output, string(bytes)) 151 | } 152 | } 153 | } 154 | 155 | func TestAppCreateSuccess(t *testing.T) { 156 | appCreateResponse := testnet.TestResponse{ 157 | Status: 201, 158 | Body: appMarshaled, 159 | } 160 | appCreateRequest := newTestRequest("POST", "/apps", "", appCreateResponse) 161 | appCreateRequest.Matcher = testnet.RequestBodyMatcherWithContentType("", "") 162 | 163 | ts, handler, c := newTestServerAndClient(t, appCreateRequest) 164 | defer ts.Close() 165 | 166 | app, err := c.AppCreate(nil) 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | if app == nil { 171 | t.Fatal("no app object returned") 172 | } 173 | var emptyapp App 174 | if *app == emptyapp { 175 | t.Errorf("returned app is empty") 176 | } 177 | 178 | if !handler.AllRequestsCalled() { 179 | t.Errorf("not all expected requests were called") 180 | } 181 | } 182 | 183 | func TestAppCreateSuccessWithOpts(t *testing.T) { 184 | appCreateResponse := testnet.TestResponse{ 185 | Status: 201, 186 | Body: appMarshaled, 187 | } 188 | appCreateRequestBody := `{"name":"example", "region":"us", "stack":"cedar"}` 189 | appCreateRequest := newTestRequest("POST", "/apps", appCreateRequestBody, appCreateResponse) 190 | 191 | nameval := "example" 192 | regionval := "us" 193 | stackval := "cedar" 194 | appCreateRequestOptions := AppCreateOpts{Name: &nameval, Region: ®ionval, Stack: &stackval} 195 | 196 | ts, handler, c := newTestServerAndClient(t, appCreateRequest) 197 | defer ts.Close() 198 | 199 | app, err := c.AppCreate(&appCreateRequestOptions) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | if app == nil { 204 | t.Fatal("no app object returned") 205 | } 206 | var emptyapp App 207 | if *app == emptyapp { 208 | t.Errorf("returned app is empty") 209 | } 210 | 211 | if !handler.AllRequestsCalled() { 212 | t.Errorf("not all expected requests were called") 213 | } 214 | } 215 | 216 | // 217 | // AppDelete() 218 | // 219 | 220 | var appDeleteResponse = testnet.TestResponse{ 221 | Status: 200, 222 | Body: appMarshaled, 223 | } 224 | var appDeleteRequest = newTestRequest("DELETE", "/apps/example", "", appDeleteResponse) 225 | 226 | func TestAppDeleteSuccess(t *testing.T) { 227 | ts, handler, c := newTestServerAndClient(t, appDeleteRequest) 228 | defer ts.Close() 229 | 230 | err := c.AppDelete("example") 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | if !handler.AllRequestsCalled() { 235 | t.Errorf("not all expected requests were called") 236 | } 237 | } 238 | 239 | // 240 | // AppUpdate() 241 | // 242 | 243 | func TestAppUpdateMarshal(t *testing.T) { 244 | maintval := true 245 | nameval := "example" 246 | 247 | var marshalTests = []struct { 248 | values AppUpdateOpts 249 | output string 250 | }{ 251 | {AppUpdateOpts{Maintenance: &maintval}, `{"maintenance":true}`}, 252 | {AppUpdateOpts{Name: &nameval}, `{"name":"example"}`}, 253 | {AppUpdateOpts{Maintenance: &maintval, Name: &nameval}, `{"maintenance":true,"name":"example"}`}, 254 | } 255 | 256 | for _, body := range marshalTests { 257 | bytes, err := json.Marshal(body.values) 258 | if err != nil { 259 | t.Fatal(err) 260 | } 261 | if string(bytes) != body.output { 262 | t.Errorf("expected %s, got %s", body.output, string(bytes)) 263 | } 264 | } 265 | } 266 | 267 | func TestAppUpdateSuccess(t *testing.T) { 268 | appUpdateResponse := testnet.TestResponse{ 269 | Status: 201, 270 | Body: appMarshaled, 271 | } 272 | appUpdateRequestBody := `{"maintenance":true, "name":"example"}` 273 | appUpdateRequest := newTestRequest("PATCH", "/apps/example", appUpdateRequestBody, appUpdateResponse) 274 | 275 | maintval := true 276 | nameval := "example" 277 | appUpdateRequestOptions := AppUpdateOpts{Maintenance: &maintval, Name: &nameval} 278 | 279 | ts, handler, c := newTestServerAndClient(t, appUpdateRequest) 280 | defer ts.Close() 281 | 282 | app, err := c.AppUpdate("example", &appUpdateRequestOptions) 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | if app == nil { 287 | t.Fatal("no app object returned") 288 | } 289 | var emptyapp App 290 | if *app == emptyapp { 291 | t.Errorf("returned app is empty") 292 | } 293 | 294 | if !handler.AllRequestsCalled() { 295 | t.Errorf("not all expected requests were called") 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /app_transfer.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An app transfer represents a two party interaction for transferring ownership 12 | // of an app. 13 | type AppTransfer struct { 14 | // app involved in the transfer 15 | App struct { 16 | Name string `json:"name"` 17 | Id string `json:"id"` 18 | } `json:"app"` 19 | 20 | // when app transfer was created 21 | CreatedAt time.Time `json:"created_at"` 22 | 23 | // unique identifier of app transfer 24 | Id string `json:"id"` 25 | 26 | // identity of the owner of the transfer 27 | Owner struct { 28 | Email string `json:"email"` 29 | Id string `json:"id"` 30 | } `json:"owner"` 31 | 32 | // identity of the recipient of the transfer 33 | Recipient struct { 34 | Email string `json:"email"` 35 | Id string `json:"id"` 36 | } `json:"recipient"` 37 | 38 | // the current state of an app transfer 39 | State string `json:"state"` 40 | 41 | // when app transfer was updated 42 | UpdatedAt time.Time `json:"updated_at"` 43 | } 44 | 45 | // Create a new app transfer. 46 | // 47 | // app is the unique identifier of app or unique name of app. recipient is the 48 | // unique email address of account or unique identifier of an account. 49 | func (c *Client) AppTransferCreate(app string, recipient string) (*AppTransfer, error) { 50 | params := struct { 51 | App string `json:"app"` 52 | Recipient string `json:"recipient"` 53 | }{ 54 | App: app, 55 | Recipient: recipient, 56 | } 57 | var appTransferRes AppTransfer 58 | return &appTransferRes, c.Post(&appTransferRes, "/account/app-transfers", params) 59 | } 60 | 61 | // Delete an existing app transfer 62 | // 63 | // appTransferIdentity is the unique identifier of the AppTransfer. 64 | func (c *Client) AppTransferDelete(appTransferIdentity string) error { 65 | return c.Delete("/account/app-transfers/" + appTransferIdentity) 66 | } 67 | 68 | // Info for existing app transfer. 69 | // 70 | // appTransferIdentity is the unique identifier of the AppTransfer. 71 | func (c *Client) AppTransferInfo(appTransferIdentity string) (*AppTransfer, error) { 72 | var appTransfer AppTransfer 73 | return &appTransfer, c.Get(&appTransfer, "/account/app-transfers/"+appTransferIdentity) 74 | } 75 | 76 | // List existing apps transfers. 77 | // 78 | // lr is an optional ListRange that sets the Range options for the paginated 79 | // list of results. 80 | func (c *Client) AppTransferList(lr *ListRange) ([]AppTransfer, error) { 81 | req, err := c.NewRequest("GET", "/account/app-transfers", nil) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | if lr != nil { 87 | lr.SetHeader(req) 88 | } 89 | 90 | var appTransfersRes []AppTransfer 91 | return appTransfersRes, c.DoReq(req, &appTransfersRes) 92 | } 93 | 94 | // Update an existing app transfer. 95 | // 96 | // appTransferIdentity is the unique identifier of the AppTransfer. state is the 97 | // the current state of an app transfer. 98 | func (c *Client) AppTransferUpdate(appTransferIdentity string, state string) (*AppTransfer, error) { 99 | params := struct { 100 | State string `json:"state"` 101 | }{ 102 | State: state, 103 | } 104 | var appTransferRes AppTransfer 105 | return &appTransferRes, c.Patch(&appTransferRes, "/account/app-transfers/"+appTransferIdentity, params) 106 | } 107 | -------------------------------------------------------------------------------- /collaborator.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // A collaborator represents an account that has been given access to an app on 12 | // Heroku. 13 | type Collaborator struct { 14 | // when collaborator was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // unique identifier of collaborator 18 | Id string `json:"id"` 19 | 20 | // when collaborator was updated 21 | UpdatedAt time.Time `json:"updated_at"` 22 | 23 | // identity of collaborated account 24 | User struct { 25 | Email string `json:"email"` 26 | Id string `json:"id"` 27 | } `json:"user"` 28 | } 29 | 30 | // Create a new collaborator. 31 | // 32 | // appIdentity is the unique identifier of the Collaborator's App. user is the 33 | // unique email address of account or unique identifier of an account. options 34 | // is the struct of optional parameters for this action. 35 | func (c *Client) CollaboratorCreate(appIdentity string, user string, options *CollaboratorCreateOpts) (*Collaborator, error) { 36 | params := struct { 37 | User string `json:"user"` 38 | Silent *bool `json:"silent,omitempty"` 39 | }{ 40 | User: user, 41 | } 42 | if options != nil { 43 | params.Silent = options.Silent 44 | } 45 | var collaboratorRes Collaborator 46 | return &collaboratorRes, c.Post(&collaboratorRes, "/apps/"+appIdentity+"/collaborators", params) 47 | } 48 | 49 | // CollaboratorCreateOpts holds the optional parameters for CollaboratorCreate 50 | type CollaboratorCreateOpts struct { 51 | // whether to suppress email invitation when creating collaborator 52 | Silent *bool `json:"silent,omitempty"` 53 | } 54 | 55 | // Delete an existing collaborator. 56 | // 57 | // appIdentity is the unique identifier of the Collaborator's App. 58 | // collaboratorIdentity is the unique identifier of the Collaborator. 59 | func (c *Client) CollaboratorDelete(appIdentity string, collaboratorIdentity string) error { 60 | return c.Delete("/apps/" + appIdentity + "/collaborators/" + collaboratorIdentity) 61 | } 62 | 63 | // Info for existing collaborator. 64 | // 65 | // appIdentity is the unique identifier of the Collaborator's App. 66 | // collaboratorIdentity is the unique identifier of the Collaborator. 67 | func (c *Client) CollaboratorInfo(appIdentity string, collaboratorIdentity string) (*Collaborator, error) { 68 | var collaborator Collaborator 69 | return &collaborator, c.Get(&collaborator, "/apps/"+appIdentity+"/collaborators/"+collaboratorIdentity) 70 | } 71 | 72 | // List existing collaborators. 73 | // 74 | // appIdentity is the unique identifier of the Collaborator's App. lr is an 75 | // optional ListRange that sets the Range options for the paginated list of 76 | // results. 77 | func (c *Client) CollaboratorList(appIdentity string, lr *ListRange) ([]Collaborator, error) { 78 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/collaborators", nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | if lr != nil { 84 | lr.SetHeader(req) 85 | } 86 | 87 | var collaboratorsRes []Collaborator 88 | return collaboratorsRes, c.DoReq(req, &collaboratorsRes) 89 | } 90 | -------------------------------------------------------------------------------- /config_var.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | // Get config-vars for app. 8 | // 9 | // appIdentity is the unique identifier of the ConfigVar's App. 10 | func (c *Client) ConfigVarInfo(appIdentity string) (map[string]string, error) { 11 | var configVar map[string]string 12 | return configVar, c.Get(&configVar, "/apps/"+appIdentity+"/config-vars") 13 | } 14 | 15 | // Update config-vars for app. You can update existing config-vars by setting 16 | // them again, and remove by setting it to nil. 17 | // 18 | // appIdentity is the unique identifier of the ConfigVar's App. options is the 19 | // hash of config changes – update values or delete by seting it to nil. 20 | func (c *Client) ConfigVarUpdate(appIdentity string, options map[string]*string) (map[string]string, error) { 21 | var configVarRes map[string]string 22 | return configVarRes, c.Patch(&configVarRes, "/apps/"+appIdentity+"/config-vars", options) 23 | } 24 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Blake Gentry. All rights reserved. Use of 2 | // this source code is governed by an MIT license that can be 3 | // found in the LICENSE file. 4 | 5 | /* 6 | Package heroku is a client interface to the Heroku API. 7 | 8 | Background 9 | 10 | This package provides a complete interface to all of the Heroku Platform API v3 11 | actions, and is almost entirely auto-generated based on the API's JSON Schema. 12 | The exceptions are the files heroku.go, heroku_test.go, and app_test.go, as well 13 | as the generator itself. All models are auto-generated by the Ruby script in 14 | gen/gen.rb. 15 | 16 | The client leverages Go's powerful net/http Client. That means that, 17 | out-of-the-box, it has keep-alive support and the ability to handle many 18 | concurrent requests from different goroutines. 19 | 20 | You should have at least some understanding of Heroku and its Platform API: 21 | https://devcenter.heroku.com/articles/platform-api-reference 22 | 23 | Installation 24 | 25 | This package is targeted towards Go 1.2 or later, though it may work on 26 | earlier versions as well. 27 | 28 | Run `go get github.com/bgentry/heroku-go` to download, build, and install the 29 | package. 30 | 31 | Getting Started 32 | 33 | To use the client, first add it to your Go file's imports list: 34 | 35 | import ( 36 | "github.com/bgentry/heroku-go" 37 | ) 38 | 39 | Then create a Client object and make calls to it: 40 | 41 | client := heroku.Client{Username: "email@me.com", Password: "my-api-key"} 42 | 43 | // pass nil for options if you don't need to set any optional params 44 | app, err := client.AppCreate(nil) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Println("Created", app.Name) 49 | 50 | // Output: 51 | // Created dodging-samurai-42 52 | 53 | That's it! Here is a more advanced example that also sets some options on the 54 | new app: 55 | 56 | name := "myapp" 57 | region := "region" 58 | 59 | // Optional values need to be provided as pointers. If a field in an option 60 | // struct is nil (not provided), the option is omitted from the API request. 61 | opts := heroku.AppCreateOpts{Name: &name, Region: ®ion} 62 | 63 | // Create an app with options set: 64 | app2, err := client.AppCreate(&opts) 65 | if err != nil { 66 | // if this is a heroku.Error, it will contain details about the error 67 | if hkerr, ok := err.(heroku.Error); ok { 68 | panic(fmt.Sprintf("Error id=%s message=%q", hkerr.Id, hkerr)) 69 | } 70 | } 71 | fmt.Printf("created app2: name=%s region=%s", app2.Name, app2.Region.Name) 72 | 73 | // Output: 74 | // created app2: name=myapp region=eu 75 | 76 | Optional Parameters 77 | 78 | Many of the Heroku Platform API actions have optional parameters. For example, 79 | when creating an app, you can either specify a custom name, or allow the API to 80 | choose a random haiku name for your app. 81 | 82 | Optional parameters in heroku-go are always provided to functions as a pointer 83 | to a struct, such as AppCreateOpts for the function AppCreate(). If you do not 84 | wish to set any optional parameters, simply provide a nil in place of the 85 | options struct, and the options will be omitted from the API request entirely. 86 | For any individual options that you don't want to set, simply leave them as nil, 87 | and they will be omitted from the API request. 88 | 89 | List Ranges & Sorting 90 | 91 | Results from the Heroku API are paginated. You can specify a field for sorting 92 | and adjust the maximum number of records returned by providing a ListRange to 93 | API calls that list objects: 94 | 95 | apps, err = client.AppList(&heroku.ListRange{Field: "name", Max: 1000}) 96 | 97 | Note Field is required when setting any range options. 98 | */ 99 | package heroku 100 | -------------------------------------------------------------------------------- /domain.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Domains define what web routes should be routed to an app on Heroku. 12 | type Domain struct { 13 | // when domain was created 14 | CreatedAt time.Time `json:"created_at"` 15 | 16 | // full hostname 17 | Hostname string `json:"hostname"` 18 | 19 | // unique identifier of this domain 20 | Id string `json:"id"` 21 | 22 | // when domain was updated 23 | UpdatedAt time.Time `json:"updated_at"` 24 | } 25 | 26 | // Create a new domain. 27 | // 28 | // appIdentity is the unique identifier of the Domain's App. hostname is the 29 | // full hostname. 30 | func (c *Client) DomainCreate(appIdentity string, hostname string) (*Domain, error) { 31 | params := struct { 32 | Hostname string `json:"hostname"` 33 | }{ 34 | Hostname: hostname, 35 | } 36 | var domainRes Domain 37 | return &domainRes, c.Post(&domainRes, "/apps/"+appIdentity+"/domains", params) 38 | } 39 | 40 | // Delete an existing domain 41 | // 42 | // appIdentity is the unique identifier of the Domain's App. domainIdentity is 43 | // the unique identifier of the Domain. 44 | func (c *Client) DomainDelete(appIdentity string, domainIdentity string) error { 45 | return c.Delete("/apps/" + appIdentity + "/domains/" + domainIdentity) 46 | } 47 | 48 | // Info for existing domain. 49 | // 50 | // appIdentity is the unique identifier of the Domain's App. domainIdentity is 51 | // the unique identifier of the Domain. 52 | func (c *Client) DomainInfo(appIdentity string, domainIdentity string) (*Domain, error) { 53 | var domain Domain 54 | return &domain, c.Get(&domain, "/apps/"+appIdentity+"/domains/"+domainIdentity) 55 | } 56 | 57 | // List existing domains. 58 | // 59 | // appIdentity is the unique identifier of the Domain's App. lr is an optional 60 | // ListRange that sets the Range options for the paginated list of results. 61 | func (c *Client) DomainList(appIdentity string, lr *ListRange) ([]Domain, error) { 62 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/domains", nil) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | if lr != nil { 68 | lr.SetHeader(req) 69 | } 70 | 71 | var domainsRes []Domain 72 | return domainsRes, c.DoReq(req, &domainsRes) 73 | } 74 | -------------------------------------------------------------------------------- /dyno.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Dynos encapsulate running processes of an app on Heroku. 12 | type Dyno struct { 13 | // a URL to stream output from for attached processes or null for non-attached processes 14 | AttachURL *string `json:"attach_url"` 15 | 16 | // command used to start this process 17 | Command string `json:"command"` 18 | 19 | // when dyno was created 20 | CreatedAt time.Time `json:"created_at"` 21 | 22 | // unique identifier of this dyno 23 | Id string `json:"id"` 24 | 25 | // the name of this process on this dyno 26 | Name string `json:"name"` 27 | 28 | // app release of the dyno 29 | Release struct { 30 | Id string `json:"id"` 31 | Version int `json:"version"` 32 | } `json:"release"` 33 | 34 | // dyno size (default: "1X") 35 | Size string `json:"size"` 36 | 37 | // current status of process (either: crashed, down, idle, starting, or up) 38 | State string `json:"state"` 39 | 40 | // type of process 41 | Type string `json:"type"` 42 | 43 | // when process last changed state 44 | UpdatedAt time.Time `json:"updated_at"` 45 | } 46 | 47 | // Create a new dyno. 48 | // 49 | // appIdentity is the unique identifier of the Dyno's App. command is the 50 | // command used to start this process. options is the struct of optional 51 | // parameters for this action. 52 | func (c *Client) DynoCreate(appIdentity string, command string, options *DynoCreateOpts) (*Dyno, error) { 53 | params := struct { 54 | Command string `json:"command"` 55 | Attach *bool `json:"attach,omitempty"` 56 | Env *map[string]string `json:"env,omitempty"` 57 | Size *string `json:"size,omitempty"` 58 | }{ 59 | Command: command, 60 | } 61 | if options != nil { 62 | params.Attach = options.Attach 63 | params.Env = options.Env 64 | params.Size = options.Size 65 | } 66 | var dynoRes Dyno 67 | return &dynoRes, c.Post(&dynoRes, "/apps/"+appIdentity+"/dynos", params) 68 | } 69 | 70 | // DynoCreateOpts holds the optional parameters for DynoCreate 71 | type DynoCreateOpts struct { 72 | // whether to stream output or not 73 | Attach *bool `json:"attach,omitempty"` 74 | // custom environment to add to the dyno config vars 75 | Env *map[string]string `json:"env,omitempty"` 76 | // dyno size (default: "1X") 77 | Size *string `json:"size,omitempty"` 78 | } 79 | 80 | // Restart dyno. 81 | // 82 | // appIdentity is the unique identifier of the Dyno's App. dynoIdentity is the 83 | // unique identifier of the Dyno. 84 | func (c *Client) DynoRestart(appIdentity string, dynoIdentity string) error { 85 | return c.Delete("/apps/" + appIdentity + "/dynos/" + dynoIdentity) 86 | } 87 | 88 | // Restart all dynos 89 | // 90 | // appIdentity is the unique identifier of the Dyno's App. 91 | func (c *Client) DynoRestartAll(appIdentity string) error { 92 | return c.Delete("/apps/" + appIdentity + "/dynos") 93 | } 94 | 95 | // Info for existing dyno. 96 | // 97 | // appIdentity is the unique identifier of the Dyno's App. dynoIdentity is the 98 | // unique identifier of the Dyno. 99 | func (c *Client) DynoInfo(appIdentity string, dynoIdentity string) (*Dyno, error) { 100 | var dyno Dyno 101 | return &dyno, c.Get(&dyno, "/apps/"+appIdentity+"/dynos/"+dynoIdentity) 102 | } 103 | 104 | // List existing dynos. 105 | // 106 | // appIdentity is the unique identifier of the Dyno's App. lr is an optional 107 | // ListRange that sets the Range options for the paginated list of results. 108 | func (c *Client) DynoList(appIdentity string, lr *ListRange) ([]Dyno, error) { 109 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/dynos", nil) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if lr != nil { 115 | lr.SetHeader(req) 116 | } 117 | 118 | var dynosRes []Dyno 119 | return dynosRes, c.DoReq(req, &dynosRes) 120 | } 121 | -------------------------------------------------------------------------------- /formation.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // The formation of processes that should be maintained for an app. Update the 12 | // formation to scale processes or change dyno sizes. Available process type 13 | // names and commands are defined by the process_types attribute for the slug 14 | // currently released on an app. 15 | type Formation struct { 16 | // command to use to launch this process 17 | Command string `json:"command"` 18 | 19 | // when process type was created 20 | CreatedAt time.Time `json:"created_at"` 21 | 22 | // unique identifier of this process type 23 | Id string `json:"id"` 24 | 25 | // number of processes to maintain 26 | Quantity int `json:"quantity"` 27 | 28 | // dyno size (default: "1X") 29 | Size string `json:"size"` 30 | 31 | // type of process to maintain 32 | Type string `json:"type"` 33 | 34 | // when dyno type was updated 35 | UpdatedAt time.Time `json:"updated_at"` 36 | } 37 | 38 | // Info for a process type 39 | // 40 | // appIdentity is the unique identifier of the Formation's App. 41 | // formationIdentity is the unique identifier of the Formation. 42 | func (c *Client) FormationInfo(appIdentity string, formationIdentity string) (*Formation, error) { 43 | var formation Formation 44 | return &formation, c.Get(&formation, "/apps/"+appIdentity+"/formation/"+formationIdentity) 45 | } 46 | 47 | // List process type formation 48 | // 49 | // appIdentity is the unique identifier of the Formation's App. lr is an 50 | // optional ListRange that sets the Range options for the paginated list of 51 | // results. 52 | func (c *Client) FormationList(appIdentity string, lr *ListRange) ([]Formation, error) { 53 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/formation", nil) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if lr != nil { 59 | lr.SetHeader(req) 60 | } 61 | 62 | var formationsRes []Formation 63 | return formationsRes, c.DoReq(req, &formationsRes) 64 | } 65 | 66 | // Batch update process types 67 | // 68 | // appIdentity is the unique identifier of the Formation's App. updates is the 69 | // Array with formation updates. Each element must have "process", the id or 70 | // name of the process type to be updated, and can optionally update its 71 | // "quantity" or "size". 72 | func (c *Client) FormationBatchUpdate(appIdentity string, updates []FormationBatchUpdateOpts) ([]Formation, error) { 73 | params := struct { 74 | Updates []FormationBatchUpdateOpts `json:"updates"` 75 | }{ 76 | Updates: updates, 77 | } 78 | var formationsRes []Formation 79 | return formationsRes, c.Patch(&formationsRes, "/apps/"+appIdentity+"/formation", params) 80 | } 81 | 82 | type FormationBatchUpdateOpts struct { 83 | // unique identifier of this process type 84 | Process string `json:"process"` 85 | 86 | // number of processes to maintain 87 | Quantity *int `json:"quantity,omitempty"` 88 | 89 | // dyno size (default: "1X") 90 | Size *string `json:"size,omitempty"` 91 | } 92 | 93 | // Update process type 94 | // 95 | // appIdentity is the unique identifier of the Formation's App. 96 | // formationIdentity is the unique identifier of the Formation. options is the 97 | // struct of optional parameters for this action. 98 | func (c *Client) FormationUpdate(appIdentity string, formationIdentity string, options *FormationUpdateOpts) (*Formation, error) { 99 | var formationRes Formation 100 | return &formationRes, c.Patch(&formationRes, "/apps/"+appIdentity+"/formation/"+formationIdentity, options) 101 | } 102 | 103 | // FormationUpdateOpts holds the optional parameters for FormationUpdate 104 | type FormationUpdateOpts struct { 105 | // number of processes to maintain 106 | Quantity *int `json:"quantity,omitempty"` 107 | // dyno size (default: "1X") 108 | Size *string `json:"size,omitempty"` 109 | } 110 | -------------------------------------------------------------------------------- /gen/gen.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'erubis' 4 | require 'multi_json' 5 | 6 | RESOURCE_TEMPLATE = <<-RESOURCE_TEMPLATE 7 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 8 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 9 | // script rather than the generated files. 10 | 11 | package heroku 12 | 13 | <%- if schemas[key]['properties'] && schemas[key]['properties'].any?{|p, v| resolve_typedef(v).end_with?("time.Time") } %> 14 | import ( 15 | "time" 16 | ) 17 | <%- end %> 18 | 19 | <%- if definition['properties'] %> 20 | <%- description = markdown_free(definition["description"] || "") %> 21 | <%- word_wrap(description, line_width: 77).split("\n").each do |line| %> 22 | // <%= line %> 23 | <%- end %> 24 | type <%= resource_class %> struct { 25 | <%- definition['properties'].each do |propname, propdef| %> 26 | <%- resolved_propdef = resolve_propdef(propdef) %> 27 | // <%= resolved_propdef["description"] %> 28 | <%- type = resolve_typedef(resolved_propdef) %> 29 | <%= Erubis::Eruby.new(STRUCT_FIELD_TEMPLATE).result({type: type, propdef: propdef, resolved_propdef: resolved_propdef, propname: propname}).strip %> 30 | 31 | <%- end %> 32 | } 33 | <%- end %> 34 | 35 | <%- definition["links"].each do |link| %> 36 | <%- func_name = titlecase(key.downcase. + "-" + link["title"]) %> 37 | <%- func_args = [] %> 38 | <%- func_args += func_args_from_model_and_link(definition, key, link) %> 39 | <%- return_values = return_values_from_link(key, link) %> 40 | <%- path = link['href'] %> 41 | <%- if parent_resource_instance %> 42 | <%- path = path.gsub("{(%23%2Fdefinitions%2F" + parent_resource_instance + "%2Fdefinitions%2Fidentity)}", '" + ' + variablecase(parent_resource_instance) + 'Identity + "') %> 43 | <%- end %> 44 | <%- path = substitute_resource_identity(path) %> 45 | <%- path = ensure_balanced_end_quote(ensure_open_quote(path)) %> 46 | 47 | <%- word_wrap(markdown_free(link["description"]), line_width: 77).split("\n").each do |line| %> 48 | // <%= line %> 49 | <%- end %> 50 | <%- func_arg_comments = [] %> 51 | <%- func_arg_comments += func_arg_comments_from_model_and_link(definition, key, link) %> 52 | <%- unless func_arg_comments.empty? %> 53 | // 54 | <%- end %> 55 | <%- word_wrap(func_arg_comments.join(" "), line_width: 77).split("\n").each do |comment| %> 56 | // <%= comment %> 57 | <%- end %> 58 | <%- flat_postval = link["schema"] && link["schema"]["additionalProperties"] == false %> 59 | <%- required = (link["schema"] && link["schema"]["required"]) || [] %> 60 | <%- optional = ((link["schema"] && link["schema"]["properties"]) || {}).keys - required %> 61 | <%- postval = if flat_postval %> 62 | <%- "options" %> 63 | <%- elsif required.empty? && optional.empty? %> 64 | <%- "nil" %> 65 | <%- elsif required.empty? %> 66 | <%- "options" %> 67 | <%- else %> 68 | <%- "params" %> 69 | <%- end %> 70 | <%- hasCustomType = !schemas[key]["properties"].nil? %> 71 | func (c *Client) <%= func_name + "(" + func_args.join(', ') %>) <%= return_values %> { 72 | <%- method = link['method'].downcase.capitalize %> 73 | <%- case link["rel"] %> 74 | <%- when "create" %> 75 | <%- if !required.empty? %> 76 | <%= Erubis::Eruby.new(LINK_PARAMS_TEMPLATE).result({modelname: key, link: link, required: required, optional: optional}).strip %> 77 | <%- end %> 78 | var <%= variablecase(key + '-res') %> <%= titlecase(key) %> 79 | <%- puts link if key =~ /org/ %> 80 | return &<%= variablecase(key + '-res') %>, c.<%= method %>(&<%= variablecase(key + '-res') %>, <%= path %>, <%= postval %>) 81 | <%- when "self" %> 82 | var <%= variablecase(key) %> <%= hasCustomType ? titlecase(key) : "map[string]string" %> 83 | return <%= "&" if hasCustomType%><%= variablecase(key) %>, c.<%= method %>(&<%= variablecase(key) %>, <%= path %>) 84 | <%- when "destroy", "empty" %> 85 | return c.Delete(<%= path %>) 86 | <%- when "update" %> 87 | <%- if !required.empty? %> 88 | <%= Erubis::Eruby.new(LINK_PARAMS_TEMPLATE).result({modelname: key, link: link, required: required, optional: optional}).strip %> 89 | <%- end %> 90 | <%- if link["title"].include?("Batch") %> 91 | var <%= variablecase(key + 's-res') %> []<%= titlecase(key) %> 92 | return <%= variablecase(key + 's-res') %>, c.<%= method %>(&<%= variablecase(key + 's-res') %>, <%= path %>, <%= postval %>) 93 | <%- else %> 94 | var <%= variablecase(key + '-res') %> <%= hasCustomType ? titlecase(key) : "map[string]string" %> 95 | return <%= "&" if hasCustomType%><%= variablecase(key + '-res') %>, c.<%= method %>(&<%= variablecase(key + '-res') %>, <%= path %>, <%= postval %>) 96 | <%- end %> 97 | <%- when "instances" %> 98 | req, err := c.NewRequest("GET", <%= path %>, nil) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | if lr != nil { 104 | lr.SetHeader(req) 105 | } 106 | 107 | var <%= variablecase(key + 's-res') %> []<%= titlecase(key) %> 108 | return <%= variablecase(key + 's-res') %>, c.DoReq(req, &<%= variablecase(key + 's-res') %>) 109 | <%- end %> 110 | } 111 | 112 | <%- if %w{create update}.include?(link["rel"]) && link["schema"] && link["schema"]["properties"] %> 113 | <%- if !required.empty? %> 114 | <%- structs = required.select {|p| resolve_typedef(link["schema"]["properties"][p]) == "struct" } %> 115 | <%- structs.each do |propname| %> 116 | <%- typename = titlecase([key, link["title"], propname].join("-")) %> 117 | // <%= typename %> used in <%= func_name %> as the <%= definition["properties"][propname]["description"] %> 118 | type <%= typename %> struct { 119 | <%- link["schema"]["properties"][propname]["properties"].each do |subpropname, subval| %> 120 | <%- propdef = definition["properties"][propname]["properties"][subpropname] %> 121 | <%- description = resolve_propdef(propdef)["description"] %> 122 | <%- word_wrap(description, line_width: 77).split("\n").each do |line| %> 123 | // <%= line %> 124 | <%- end %> 125 | <%= titlecase(subpropname) %> <%= resolve_typedef(subval) %> `json:"<%= subpropname %>"` 126 | 127 | <%- end %> 128 | } 129 | <%- end %> 130 | <%- arr_structs = required.select {|p| resolve_typedef(link["schema"]["properties"][p]) == "[]struct" } %> 131 | <%- arr_structs.each do |propname| %> 132 | <%- # special case for arrays of structs (like FormationBulkUpdate) %> 133 | <%- typename = titlecase([key, link["title"], "opts"].join("-")) %> 134 | <%- typedef = resolve_propdef(link["schema"]["properties"][propname]["items"]) %> 135 | 136 | type <%= typename %> struct { 137 | <%- typedef["properties"].each do |subpropname, subref| %> 138 | <%- propdef = resolve_propdef(subref) %> 139 | <%- description = resolve_propdef(propdef)["description"] %> 140 | <%- is_required = typedef["required"].include?(subpropname) %> 141 | <%- word_wrap(description, line_width: 77).split("\n").each do |line| %> 142 | // <%= line %> 143 | <%- end %> 144 | <%= titlecase(subpropname) %> <%= "*" unless is_required %><%= resolve_typedef(propdef) %> `json:"<%= subpropname %><%= ",omitempty" unless is_required %>"` 145 | 146 | <%- end %> 147 | } 148 | <%- end %> 149 | <%- end %> 150 | <%- if !optional.empty? %> 151 | // <%= func_name %>Opts holds the optional parameters for <%= func_name %> 152 | type <%= func_name %>Opts struct { 153 | <%- optional.each do |propname| %> 154 | <%- if definition['properties'][propname] && definition['properties'][propname]['description'] %> 155 | // <%= definition['properties'][propname]['description'] %> 156 | <%- elsif definition["definitions"][propname] %> 157 | // <%= definition["definitions"][propname]["description"] %> 158 | <%- elsif link["schema"]["properties"][propname]["$ref"] %> 159 | // <%= resolve_propdef(link["schema"]["properties"][propname])["description"] %> 160 | <%- else %> 161 | // <%= link["schema"]["properties"][propname]["description"] %> 162 | <%- end %> 163 | <%= titlecase(propname) %> <%= type_for_link_opts_field(link, propname) %> `json:"<%= propname %>,omitempty"` 164 | <%- end %> 165 | } 166 | <%- end %> 167 | <%- end %> 168 | 169 | <%- end %> 170 | RESOURCE_TEMPLATE 171 | 172 | STRUCT_FIELD_TEMPLATE = <<-STRUCT_FIELD_TEMPLATE 173 | <%- if type =~ /\\*?struct/ %> 174 | <%- is_array = propdef["type"].is_a?(Array) && propdef["type"].first == "array" %> 175 | <%= titlecase(propname) %> <%= "[]" if is_array %><%= type %> { 176 | <%- resolved_propdef["properties"].each do |subpropname, subpropdef| %> 177 | <%- resolved_subpropdef = resolve_propdef(subpropdef) %> 178 | <%- subtype = resolve_typedef(resolved_subpropdef) %> 179 | <%- if subtype =~ /\\*?struct/ %> 180 | <%= Erubis::Eruby.new(STRUCT_FIELD_TEMPLATE).result({type: subtype, propdef: subpropdef, resolved_propdef: resolved_subpropdef, propname: subpropname}).strip %> 181 | <%- else %> 182 | <%= Erubis::Eruby.new(FLAT_STRUCT_FIELD_TEMPLATE).result({type: subtype, propname: subpropname}).strip %> 183 | <%- end %> 184 | <%- end %> 185 | } `json:"<%= propname %>"` 186 | <%- else %> 187 | <%= Erubis::Eruby.new(FLAT_STRUCT_FIELD_TEMPLATE).result({type: type, propname: propname}).strip %> 188 | <%- end %> 189 | STRUCT_FIELD_TEMPLATE 190 | 191 | FLAT_STRUCT_FIELD_TEMPLATE = <<-FLAT_STRUCT_FIELD_TEMPLATE 192 | <%= titlecase(propname) %> <%= type %> `json:"<%= propname %>"` 193 | FLAT_STRUCT_FIELD_TEMPLATE 194 | 195 | LINK_PARAMS_TEMPLATE = <<-LINK_PARAMS_TEMPLATE 196 | params := struct { 197 | <%- required.each do |propname| %> 198 | <%- type = resolve_typedef(link["schema"]["properties"][propname]) %> 199 | <%- if type == "[]struct" %> 200 | <%- type = type.gsub("struct", titlecase([modelname, link["title"], "opts"].join("-"))) %> 201 | <%- elsif type == "struct" %> 202 | <%- type = titlecase([modelname, link["title"], propname].join("-")) %> 203 | <%- end %> 204 | <%= titlecase(propname) %> <%= type %> `json:"<%= propname %>"` 205 | <%- end %> 206 | <%- optional.each do |propname| %> 207 | <%= titlecase(propname) %> <%= type_for_link_opts_field(link, propname) %> `json:"<%= propname %>,omitempty"` 208 | <%- end %> 209 | }{ 210 | <%- required.each do |propname| %> 211 | <%= titlecase(propname) %>: <%= variablecase(propname) %>, 212 | <%- end %> 213 | } 214 | <%- if optional.count > 0 %> 215 | if options != nil { 216 | <%- optional.each do |propname| %> 217 | params.<%= titlecase(propname) %> = options.<%= titlecase(propname) %> 218 | <%- end %> 219 | } 220 | <%- end %> 221 | LINK_PARAMS_TEMPLATE 222 | 223 | # definition: data, 224 | # key: modelname, 225 | # parent_resource_class: parent_resource_class, 226 | # parent_resource_identity: parent_resource_identity, 227 | # parent_resource_instance: parent_resource_instance, 228 | # resource_class: resource_class, 229 | # resource_instance: resource_instance, 230 | # resource_proxy_class: resource_proxy_class, 231 | # resource_proxy_instance: resource_proxy_instance 232 | 233 | module Generator 234 | extend self 235 | 236 | def ensure_open_quote(str) 237 | str[0] == '"' ? str : "\"#{str}" 238 | end 239 | 240 | def ensure_balanced_end_quote(str) 241 | (str.count('"') % 2) == 1 ? "#{str}\"" : str 242 | end 243 | 244 | def strip_unnecessary_end_plusquote(str) 245 | str.gsub(/ \+ "$/, "") 246 | end 247 | 248 | def must_end_with(str, ending) 249 | str.end_with?(ending) ? str : "#{str}#{ending}" 250 | end 251 | 252 | def word_wrap(text, options = {}) 253 | line_width = options.fetch(:line_width, 80) 254 | 255 | text.split("\n").collect do |line| 256 | line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line 257 | end * "\n" 258 | end 259 | 260 | def markdown_free(text) 261 | text.gsub(/\[(?[^\]]*)\](?\(.*\))/, '\k'). 262 | gsub(/`(?[^\]]*)`/, '\k').gsub("NULL", "nil") 263 | end 264 | 265 | def variablecase(str) 266 | words = str.gsub('_','-').gsub(' ','-').split('-') 267 | (words[0...1] + words[1..-1].map {|k| k[0...1].upcase + k[1..-1]}).join 268 | end 269 | 270 | def titlecase(str) 271 | str.gsub('_','-').gsub(' ','-').split('-').map do |k| 272 | # special case so Url becomes URL, Ssl becomes SSL 273 | if %w{url ssl}.include?(k.downcase) 274 | k.upcase 275 | elsif k.downcase == "oauth" # special case so Oauth becomes OAuth 276 | "OAuth" 277 | else 278 | k[0...1].upcase + k[1..-1] 279 | end 280 | end.join 281 | end 282 | 283 | def resolve_typedef(propdef) 284 | if types = propdef["type"] 285 | null = types.include?("null") 286 | tname = case (types - ["null"]).first 287 | when "boolean" 288 | "bool" 289 | when "integer" 290 | "int" 291 | when "string" 292 | format = propdef["format"] 293 | format && format == "date-time" ? "time.Time" : "string" 294 | when "object" 295 | if propdef["additionalProperties"] == false 296 | if propdef["patternProperties"] 297 | "map[string]string" 298 | else 299 | # special case for arrays of structs (like FormationBulkUpdate) 300 | "struct" 301 | end 302 | else 303 | "struct" 304 | end 305 | when "array" 306 | arraytype = if propdef["items"]["$ref"] 307 | resolve_typedef(propdef["items"]) 308 | else 309 | propdef["items"]["type"] 310 | end 311 | arraytype = arraytype.first if arraytype.is_a?(Array) 312 | "[]#{arraytype}" 313 | else 314 | types.first 315 | end 316 | null ? "*#{tname}" : tname 317 | elsif propdef["anyOf"] 318 | # identity cross-reference, cheat because these are always strings atm 319 | "string" 320 | elsif propdef["additionalProperties"] == false 321 | # inline object 322 | propdef 323 | elsif ref = propdef["$ref"] 324 | matches = ref.match(/\/definitions\/([\w-]+)\/definitions\/([\w-]+)/) 325 | schemaname, fieldname = matches[1..2] 326 | resolve_typedef(schemas[schemaname]["definitions"][fieldname]) 327 | else 328 | raise "WTF #{propdef}" 329 | end 330 | end 331 | 332 | def type_for_link_opts_field(link, propname, nullable = true) 333 | resulttype = resolve_typedef(link["schema"]["properties"][propname]) 334 | if nullable && !resulttype.start_with?("*") 335 | resulttype = "*#{resulttype}" 336 | elsif !nullable 337 | resulttype = resulttype.gsub("*", "") 338 | end 339 | resulttype 340 | end 341 | 342 | def type_from_types_and_format(types, format) 343 | case types.first 344 | when "boolean" 345 | "bool" 346 | when "integer" 347 | "int" 348 | when "string" 349 | format && format == "date-time" ? "time.Time" : "string" 350 | else 351 | types.first 352 | end 353 | end 354 | 355 | def return_values_from_link(modelname, link) 356 | if !schemas[modelname]["properties"] 357 | # structless type like ConfigVar 358 | "(map[string]string, error)" 359 | else 360 | case link["rel"] 361 | when "destroy", "empty" 362 | "error" 363 | when "instances" 364 | "([]#{titlecase(modelname)}, error)" 365 | else 366 | if link["title"].include?("Batch") 367 | "([]#{titlecase(modelname)}, error)" 368 | else 369 | "(*#{titlecase(modelname)}, error)" 370 | end 371 | end 372 | end 373 | end 374 | 375 | def func_args_from_model_and_link(definition, modelname, link) 376 | args = [] 377 | required = (link["schema"] && link["schema"]["required"]) || [] 378 | optional = ((link["schema"] && link["schema"]["properties"]) || {}).keys - required 379 | 380 | # get all of the model identities required by this link's href 381 | reg = /{\(%23%2Fdefinitions%2F(?[\w-]+)%2Fdefinitions%2Fidentity\)}/ 382 | link["href"].scan(reg) do |match| 383 | args << "#{variablecase(match.first)}Identity string" 384 | end 385 | 386 | if %w{create update}.include?(link["rel"]) 387 | if link["schema"]["additionalProperties"] == false 388 | # handle ConfigVar update 389 | args << "options map[string]*string" 390 | else 391 | required.each do |propname| 392 | type = type_for_link_opts_field(link, propname, false) 393 | if type == "[]struct" 394 | type = type.gsub("struct", titlecase([modelname, link["title"], "Opts"].join("-"))) 395 | elsif type == "struct" 396 | type = type.gsub("struct", titlecase([modelname, link["title"], propname].join("-"))) 397 | end 398 | args << "#{variablecase(propname)} #{type}" 399 | end 400 | end 401 | args << "options *#{titlecase(modelname)}#{link["rel"].capitalize}Opts" unless optional.empty? 402 | end 403 | 404 | if "instances" == link["rel"] 405 | args << "lr *ListRange" 406 | end 407 | 408 | args 409 | end 410 | 411 | def resolve_propdef(propdef) 412 | resolve_all_propdefs(propdef).first 413 | end 414 | 415 | def resolve_all_propdefs(propdef) 416 | if propdef["description"] 417 | [propdef] 418 | elsif ref = propdef["$ref"] 419 | # handle embedded structs 420 | if matches = ref.match(/#\/definitions\/([\w-]+)\/definitions\/([\w-]+)\/definitions\/([\w-]+)/) 421 | schemaname, structname, fieldname = matches[1..3] 422 | resolve_all_propdefs(schemas[schemaname]["definitions"][structname]["definitions"][fieldname]) 423 | else 424 | matches = ref.match(/#\/definitions\/([\w-]+)\/definitions\/([\w-]+)/) 425 | schemaname, fieldname = matches[1..2] 426 | resolve_all_propdefs(schemas[schemaname]["definitions"][fieldname]) 427 | end 428 | elsif anyof = propdef["anyOf"] 429 | # Identity 430 | anyof.map do |refhash| 431 | matches = refhash["$ref"].match(/#\/definitions\/([\w-]+)\/definitions\/([\w-]+)/) 432 | schemaname, fieldname = matches[1..2] 433 | resolve_all_propdefs(schemas[schemaname]["definitions"][fieldname]) 434 | end.flatten 435 | elsif propdef["items"] && ref = propdef["items"]["$ref"] 436 | # special case for params which are embedded structs, like build-result 437 | matches = ref.match(/#\/definitions\/([\w-]+)\/definitions\/([\w-]+)/) 438 | schemaname, fieldname = matches[1..2] 439 | resolve_all_propdefs(schemas[schemaname]["definitions"][fieldname]) 440 | elsif propdef["type"] && propdef["type"].is_a?(Array) && propdef["type"].first == "object" 441 | # special case for params which are nested objects, like oauth-grant 442 | [propdef] 443 | else 444 | raise "WTF #{propdef}" 445 | end 446 | end 447 | 448 | def func_arg_comments_from_model_and_link(definition, modelname, link) 449 | # <%- func_arg_comments << (variablecase(parent_resource_instance) + "Identity is the unique identifier of the " + key + "'s " + parent_resource_instance + ".") if parent_resource_instance %> 450 | args = [] 451 | flat_postval = link["schema"] && link["schema"]["additionalProperties"] == false 452 | properties = (link["schema"] && link["schema"]["properties"]) || {} 453 | required_keys = (link["schema"] && link["schema"]["required"]) || [] 454 | optional_keys = properties.keys - required_keys 455 | 456 | # get all of the model identities required by this link's href 457 | reg = /{\(%23%2Fdefinitions%2F(?[\w-]+)%2Fdefinitions%2Fidentity\)}/ 458 | link["href"].scan(reg) do |match| 459 | if match.first == modelname 460 | args << "#{variablecase(modelname)}Identity is the unique identifier of the #{titlecase(modelname)}." 461 | else 462 | args << "#{variablecase(match.first)}Identity is the unique identifier of the #{titlecase(modelname)}'s #{titlecase(match.first)}." 463 | end 464 | end 465 | 466 | if flat_postval 467 | # special case for ConfigVar update w/ flat param struct 468 | desc = markdown_free(link["schema"]["description"]) 469 | args << "options is the #{desc}." 470 | end 471 | 472 | if %w{create update}.include?(link["rel"]) 473 | required_keys.each do |propname| 474 | rpresults = resolve_all_propdefs(link["schema"]["properties"][propname]) 475 | if rpresults.size == 1 476 | if rpresults.first["properties"] 477 | # special case for things like OAuthToken with nested objects 478 | rpresults = resolve_all_propdefs(definition["properties"][propname]) 479 | end 480 | args << "#{variablecase(propname)} is the #{must_end_with(rpresults.first["description"] || "", ".")}" 481 | elsif rpresults.size == 2 482 | args << "#{variablecase(propname)} is the #{rpresults.first["description"]} or #{must_end_with(rpresults.last["description"] || "", ".")}" 483 | else 484 | raise "Didn't expect 3 rpresults" 485 | end 486 | end 487 | args << "options is the struct of optional parameters for this action." unless optional_keys.empty? 488 | end 489 | 490 | if "instances" == link["rel"] 491 | args << "lr is an optional ListRange that sets the Range options for the paginated list of results." 492 | end 493 | 494 | case link["rel"] 495 | when "create" 496 | ["options is the struct of optional parameters for this action."] 497 | when "update" 498 | ["#{variablecase(modelname)}Identity is the unique identifier of the #{titlecase(modelname)}.", 499 | "options is the struct of optional parameters for this action."] 500 | when "destroy", "self", "empty" 501 | ["#{variablecase(modelname)}Identity is the unique identifier of the #{titlecase(modelname)}."] 502 | when "instances" 503 | ["lr is an optional ListRange that sets the Range options for the paginated list of results."] 504 | else 505 | [] 506 | end 507 | args 508 | end 509 | 510 | def substitute_resource_identity(path) 511 | reg = /{\(%23%2Fdefinitions%2F(?[\w-]+)%2Fdefinitions%2Fidentity\)}/ 512 | matches = path.match(reg) 513 | return path unless matches 514 | 515 | gsubbed = path.gsub(reg, '"+' + variablecase(matches['keyname']) + 'Identity + "') 516 | strip_unnecessary_end_plusquote(gsubbed) 517 | end 518 | 519 | def resource_instance_from_model(modelname) 520 | modelname.downcase.split('-').join('_') 521 | end 522 | 523 | def schemas 524 | @@schemas ||= {} 525 | end 526 | 527 | def load_schema 528 | schema_path = File.expand_path("./schema.json") 529 | schema = MultiJson.load(File.read(schema_path)) 530 | schema["definitions"].each do |modelname, val| 531 | schemas[modelname] = val 532 | end 533 | end 534 | 535 | def generate_model(modelname) 536 | if !schemas[modelname] 537 | puts "no schema for #{modelname}" && return 538 | end 539 | if schemas[modelname]['links'].empty? 540 | puts "no links for #{modelname}" 541 | end 542 | 543 | resource_class = titlecase(modelname) 544 | resource_instance = resource_instance_from_model(modelname) 545 | 546 | resource_proxy_class = resource_class + 's' 547 | resource_proxy_instance = resource_instance + 's' 548 | 549 | parent_resource_class, parent_resource_identity, parent_resource_instance = if schemas[modelname]['links'].all? {|link| link['href'].include?('{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fidentity)}')} 550 | ['App', 'app_identity', 'app'] 551 | elsif schemas[modelname]['links'].all? {|link| link['href'].include?('{(%23%2Fdefinitions%2Faddon-service%2Fdefinitions%2Fidentity)}')} 552 | ['AddonService', 'addon_service', 'addon-service'] 553 | end 554 | 555 | data = Erubis::Eruby.new(RESOURCE_TEMPLATE).result({ 556 | definition: schemas[modelname], 557 | key: modelname, 558 | parent_resource_class: parent_resource_class, 559 | parent_resource_identity: parent_resource_identity, 560 | parent_resource_instance: parent_resource_instance, 561 | resource_class: resource_class, 562 | resource_instance: resource_instance, 563 | resource_proxy_class: resource_proxy_class, 564 | resource_proxy_instance: resource_proxy_instance 565 | }) 566 | 567 | path = File.expand_path(File.join(File.dirname(__FILE__), '..', "#{modelname.gsub('-', '_')}.go")) 568 | File.open(path, 'w') do |file| 569 | file.write(data) 570 | end 571 | %x( go fmt #{path} ) 572 | end 573 | end 574 | 575 | include Generator 576 | 577 | puts "Loading schema..." 578 | Generator.load_schema 579 | 580 | schemas.keys.each do |modelname| 581 | puts "Generating #{modelname}..." 582 | if (Generator.schemas[modelname]["links"] || []).empty? && Generator.schemas[modelname]["properties"].empty? 583 | puts "-- skipping #{modelname} because it has no links or properties" 584 | else 585 | Generator.generate_model(modelname) 586 | end 587 | end 588 | -------------------------------------------------------------------------------- /heroku.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Blake Gentry. All rights reserved. Use of 2 | // this source code is governed by an MIT license that can be 3 | // found in the LICENSE file. 4 | 5 | package heroku 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "log" 14 | "net/http" 15 | "net/http/httputil" 16 | "os" 17 | "reflect" 18 | "runtime" 19 | "strings" 20 | 21 | "github.com/pborman/uuid" 22 | ) 23 | 24 | const ( 25 | Version = "0.10.2" 26 | DefaultAPIURL = "https://api.heroku.com" 27 | DefaultUserAgent = "heroku-go/" + Version + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")" 28 | ) 29 | 30 | // A Client is a Heroku API client. Its zero value is a usable client that uses 31 | // default settings for the Heroku API. The Client has an internal HTTP client 32 | // (HTTP) which defaults to http.DefaultClient. 33 | // 34 | // As with all http.Clients, this Client's Transport has internal state (cached 35 | // HTTP connections), so Clients should be reused instead of created as needed. 36 | // Clients are safe for use by multiple goroutines. 37 | type Client struct { 38 | // HTTP is the Client's internal http.Client, handling HTTP requests to the 39 | // Heroku API. 40 | HTTP *http.Client 41 | 42 | // The URL of the Heroku API to communicate with. Defaults to 43 | // "https://api.heroku.com". 44 | URL string 45 | 46 | // Username is the HTTP basic auth username for API calls made by this Client. 47 | Username string 48 | 49 | // Password is the HTTP basic auth password for API calls made by this Client. 50 | Password string 51 | 52 | // UserAgent to be provided in API requests. Set to DefaultUserAgent if not 53 | // specified. 54 | UserAgent string 55 | 56 | // Debug mode can be used to dump the full request and response to stdout. 57 | Debug bool 58 | 59 | // AdditionalHeaders are extra headers to add to each HTTP request sent by 60 | // this Client. 61 | AdditionalHeaders http.Header 62 | 63 | // Path to the Unix domain socket or a running heroku-agent. 64 | HerokuAgentSocket string 65 | } 66 | 67 | func (c *Client) Get(v interface{}, path string) error { 68 | return c.APIReq(v, "GET", path, nil) 69 | } 70 | 71 | func (c *Client) Patch(v interface{}, path string, body interface{}) error { 72 | return c.APIReq(v, "PATCH", path, body) 73 | } 74 | 75 | func (c *Client) Post(v interface{}, path string, body interface{}) error { 76 | return c.APIReq(v, "POST", path, body) 77 | } 78 | 79 | func (c *Client) Put(v interface{}, path string, body interface{}) error { 80 | return c.APIReq(v, "PUT", path, body) 81 | } 82 | 83 | func (c *Client) Delete(path string) error { 84 | return c.APIReq(nil, "DELETE", path, nil) 85 | } 86 | 87 | // Generates an HTTP request for the Heroku API, but does not 88 | // perform the request. The request's Accept header field will be 89 | // set to: 90 | // 91 | // Accept: application/vnd.heroku+json; version=3 92 | // 93 | // The Request-Id header will be set to a random UUID. The User-Agent header 94 | // will be set to the Client's UserAgent, or DefaultUserAgent if UserAgent is 95 | // not set. 96 | // 97 | // The type of body determines how to encode the request: 98 | // 99 | // nil no body 100 | // io.Reader body is sent verbatim 101 | // else body is encoded as application/json 102 | func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) { 103 | var ctype string 104 | var rbody io.Reader 105 | 106 | switch t := body.(type) { 107 | case nil: 108 | case string: 109 | rbody = bytes.NewBufferString(t) 110 | case io.Reader: 111 | rbody = t 112 | default: 113 | v := reflect.ValueOf(body) 114 | if !v.IsValid() { 115 | break 116 | } 117 | if v.Type().Kind() == reflect.Ptr { 118 | v = reflect.Indirect(v) 119 | if !v.IsValid() { 120 | break 121 | } 122 | } 123 | 124 | j, err := json.Marshal(body) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | rbody = bytes.NewReader(j) 129 | ctype = "application/json" 130 | } 131 | apiURL := strings.TrimRight(c.URL, "/") 132 | if apiURL == "" { 133 | apiURL = DefaultAPIURL 134 | } 135 | req, err := http.NewRequest(method, apiURL+path, rbody) 136 | if err != nil { 137 | return nil, err 138 | } 139 | // If we're talking to heroku-agent over a local Unix socket, downgrade to 140 | // HTTP; heroku-agent will establish a secure connection between itself and 141 | // the Heroku API. 142 | if c.HerokuAgentSocket != "" { 143 | req.URL.Scheme = "http" 144 | } 145 | req.Header.Set("Accept", "application/vnd.heroku+json; version=3") 146 | req.Header.Set("Request-Id", uuid.New()) 147 | useragent := c.UserAgent 148 | if useragent == "" { 149 | useragent = DefaultUserAgent 150 | } 151 | req.Header.Set("User-Agent", useragent) 152 | if ctype != "" { 153 | req.Header.Set("Content-Type", ctype) 154 | } 155 | req.SetBasicAuth(c.Username, c.Password) 156 | for k, v := range c.AdditionalHeaders { 157 | req.Header[k] = v 158 | } 159 | return req, nil 160 | } 161 | 162 | // Sends a Heroku API request and decodes the response into v. As 163 | // described in NewRequest(), the type of body determines how to 164 | // encode the request body. As described in DoReq(), the type of 165 | // v determines how to handle the response body. 166 | func (c *Client) APIReq(v interface{}, meth, path string, body interface{}) error { 167 | req, err := c.NewRequest(meth, path, body) 168 | if err != nil { 169 | return err 170 | } 171 | return c.DoReq(req, v) 172 | } 173 | 174 | // Submits an HTTP request, checks its response, and deserializes 175 | // the response into v. The type of v determines how to handle 176 | // the response body: 177 | // 178 | // nil body is discarded 179 | // io.Writer body is copied directly into v 180 | // else body is decoded into v as json 181 | // 182 | func (c *Client) DoReq(req *http.Request, v interface{}) error { 183 | if c.Debug { 184 | dump, err := httputil.DumpRequestOut(req, true) 185 | if err != nil { 186 | log.Println(err) 187 | } else { 188 | os.Stderr.Write(dump) 189 | os.Stderr.Write([]byte{'\n', '\n'}) 190 | } 191 | } 192 | 193 | httpClient := c.HTTP 194 | if httpClient == nil { 195 | httpClient = http.DefaultClient 196 | } 197 | 198 | res, err := httpClient.Do(req) 199 | if err != nil { 200 | return err 201 | } 202 | defer res.Body.Close() 203 | if c.Debug { 204 | dump, err := httputil.DumpResponse(res, true) 205 | if err != nil { 206 | log.Println(err) 207 | } else { 208 | os.Stderr.Write(dump) 209 | os.Stderr.Write([]byte{'\n'}) 210 | } 211 | } 212 | if err = checkResp(res); err != nil { 213 | return err 214 | } 215 | switch t := v.(type) { 216 | case nil: 217 | case io.Writer: 218 | _, err = io.Copy(t, res.Body) 219 | default: 220 | err = json.NewDecoder(res.Body).Decode(v) 221 | } 222 | return err 223 | } 224 | 225 | // An Error represents a Heroku API error. 226 | type Error struct { 227 | error 228 | Id string 229 | URL string 230 | } 231 | 232 | type errorResp struct { 233 | Message string 234 | Id string 235 | URL string `json:"url"` 236 | } 237 | 238 | func checkResp(res *http.Response) error { 239 | if res.StatusCode/100 != 2 { // 200, 201, 202, etc 240 | var e errorResp 241 | err := json.NewDecoder(res.Body).Decode(&e) 242 | if err != nil { 243 | return errors.New("Unexpected error: " + res.Status) 244 | } 245 | return Error{error: errors.New(e.Message), Id: e.Id, URL: e.URL} 246 | } 247 | if msg := res.Header.Get("X-Heroku-Warning"); msg != "" { 248 | fmt.Fprintln(os.Stderr, strings.TrimSpace(msg)) 249 | } 250 | return nil 251 | } 252 | 253 | type ListRange struct { 254 | Field string 255 | Max int 256 | Descending bool 257 | FirstId string 258 | LastId string 259 | } 260 | 261 | func (lr *ListRange) SetHeader(req *http.Request) { 262 | var hdrval string 263 | if lr.Field != "" { 264 | hdrval += lr.Field + " " 265 | } 266 | hdrval += lr.FirstId + ".." + lr.LastId 267 | if lr.Max != 0 { 268 | hdrval += fmt.Sprintf("; max=%d", lr.Max) 269 | if lr.Descending { 270 | hdrval += ", " 271 | } 272 | } 273 | 274 | if lr.Descending { 275 | hdrval += ", order=desc" 276 | } 277 | 278 | req.Header.Set("Range", hdrval) 279 | return 280 | } 281 | -------------------------------------------------------------------------------- /heroku_test.go: -------------------------------------------------------------------------------- 1 | package heroku 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/bgentry/testnet" 13 | ) 14 | 15 | // Tests 16 | 17 | func TestAdditionalHeaders(t *testing.T) { 18 | multival := []string{"awesome", "multival"} 19 | c := &Client{AdditionalHeaders: http.Header{ 20 | "Fake-Header": []string{"value"}, 21 | "X-Heroku-Header": multival, 22 | }} 23 | req, err := c.NewRequest("GET", "/", nil) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if val := req.Header.Get("Fake-Header"); val != "value" { 28 | t.Errorf("Fake-Header expected %q, got %q", "value", val) 29 | } 30 | val := req.Header["X-Heroku-Header"] 31 | if len(val) != len(multival) { 32 | t.Errorf("X-Heroku-Header len expected %d, got %d", len(multival), len(val)) 33 | } 34 | for i, v := range val { 35 | if v != multival[i] { 36 | t.Errorf("X-Heroku-Header value[%d] expected %q, got %q", i, multival[i], v) 37 | } 38 | } 39 | } 40 | 41 | func TestHerokuAgent(t *testing.T) { 42 | c := &Client{ 43 | HerokuAgentSocket: "~/.heroku-agent.sock", 44 | } 45 | req, err := c.NewRequest("GET", "/", nil) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | if req.URL.Scheme != "http" { 50 | t.Error("Expected http scheme, got %s", req.URL.Scheme) 51 | } 52 | } 53 | 54 | func TestRequestId(t *testing.T) { 55 | c := &Client{} 56 | req, err := c.NewRequest("GET", "/", nil) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if req.Header.Get("Request-Id") == "" { 61 | t.Error("Request-Id not set") 62 | } 63 | } 64 | 65 | func TestUserAgent(t *testing.T) { 66 | c := &Client{} 67 | req, err := c.NewRequest("GET", "/", nil) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | if ua := req.Header.Get("User-Agent"); ua != DefaultUserAgent { 72 | t.Errorf("Default User-Agent expected %q, got %q", DefaultUserAgent, ua) 73 | } 74 | 75 | // try a custom User-Agent 76 | customAgent := "custom-client 2.1 " + DefaultUserAgent 77 | c.UserAgent = customAgent 78 | req, err = c.NewRequest("GET", "/", nil) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | if ua := req.Header.Get("User-Agent"); ua != customAgent { 83 | t.Errorf("User-Agent expected %q, got %q", customAgent, ua) 84 | } 85 | } 86 | 87 | func TestGet(t *testing.T) { 88 | resp := testnet.TestResponse{ 89 | Status: http.StatusOK, 90 | Body: `{"omg": "wtf"}`, 91 | } 92 | req := newTestRequest("GET", "/", "", resp) 93 | 94 | ts, _, c := newTestServerAndClient(t, req) 95 | defer ts.Close() 96 | 97 | var respBody struct { 98 | Omg string 99 | } 100 | err := c.Get(&respBody, "/") 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | if respBody.Omg != "wtf" { 105 | t.Errorf("expected %q, got %q", "wtf", respBody.Omg) 106 | } 107 | } 108 | 109 | func TestPost(t *testing.T) { 110 | resp := testnet.TestResponse{ 111 | Status: http.StatusOK, 112 | Body: `{"omg": "wtf"}`, 113 | } 114 | req := newTestRequest("POST", "/", `{"Wtf": "bbq"}`, resp) 115 | 116 | ts, _, c := newTestServerAndClient(t, req) 117 | defer ts.Close() 118 | 119 | var reqBody struct { 120 | Wtf string 121 | } 122 | reqBody.Wtf = "bbq" 123 | var respBody struct { 124 | Omg string 125 | } 126 | err := c.Post(&respBody, "/", reqBody) 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | if respBody.Omg != "wtf" { 131 | t.Errorf("expected %q, got %q", "wtf", respBody.Omg) 132 | } 133 | } 134 | 135 | type respTest struct { 136 | Response http.Response 137 | Expected error 138 | } 139 | 140 | func newTestResponse(statuscode int, body string) http.Response { 141 | return http.Response{ 142 | StatusCode: statuscode, 143 | Status: http.StatusText(statuscode), 144 | ContentLength: int64(len(body)), 145 | Body: ioutil.NopCloser(bytes.NewBufferString(body)), 146 | } 147 | } 148 | 149 | var respTests = []respTest{ 150 | {newTestResponse(200, `{"code": "OK"}`), nil}, 151 | {newTestResponse(201, `{"code": "OK"}`), nil}, 152 | { 153 | newTestResponse(403, `{"id": "forbidden", "message": "You do not have access to the app myapp."}`), 154 | Error{ 155 | error: errors.New("You do not have access to the app myapp."), 156 | Id: "forbidden", 157 | URL: "", 158 | }, 159 | }, 160 | { 161 | newTestResponse(401, `{"id": "unauthorized", "message": "Long error message."}`), 162 | Error{ 163 | error: errors.New("Long error message."), 164 | Id: "unauthorized", 165 | URL: "", 166 | }, 167 | }, 168 | { 169 | newTestResponse(422, `{"id": "invalid_params", "message": "Cannot scale to more than 5 PX size dynos per process type.", "url": "https://bit.ly/1gK1TvU"}`), 170 | Error{ 171 | error: errors.New("Cannot scale to more than 5 PX size dynos per process type."), 172 | Id: "invalid_params", 173 | URL: "https://bit.ly/1gK1TvU", 174 | }, 175 | }, 176 | { 177 | newTestResponse(500, `not valid json {} ""`), 178 | errors.New("Unexpected error: Internal Server Error"), 179 | }, 180 | } 181 | 182 | func TestCheckResp(t *testing.T) { 183 | for i, rt := range respTests { 184 | resp := checkResp(&rt.Response) 185 | if !reflect.DeepEqual(rt.Expected, resp) { 186 | t.Errorf("checkResp respTests[%d] expected %v, got %v", i, rt.Expected, resp) 187 | } 188 | } 189 | } 190 | 191 | // test helpers 192 | 193 | func newTestRequest(method, path, body string, resp testnet.TestResponse) testnet.TestRequest { 194 | headers := http.Header{} 195 | headers.Set("Accept", "application/vnd.heroku+json; version=3") 196 | req := testnet.TestRequest{ 197 | Method: method, 198 | Path: path, 199 | Response: resp, 200 | Header: headers, 201 | } 202 | if method != "GET" && body != "" { 203 | req.Matcher = testnet.RequestBodyMatcher(body) 204 | } 205 | return req 206 | } 207 | 208 | func newTestServerAndClient(t *testing.T, requests ...testnet.TestRequest) (*httptest.Server, *testnet.Handler, *Client) { 209 | ts, handler := testnet.NewServer(t, requests) 210 | c := &Client{} 211 | c.URL = ts.URL 212 | 213 | return ts, handler, c 214 | } 215 | 216 | func testStringsEqual(t *testing.T, fieldName, expected, actual string) { 217 | if actual != expected { 218 | t.Errorf("%s expected %s, got %s", fieldName, expected, actual) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /key.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Keys represent public SSH keys associated with an account and are used to 12 | // authorize accounts as they are performing git operations. 13 | type Key struct { 14 | // comment on the key 15 | Comment string `json:"comment"` 16 | 17 | // when key was created 18 | CreatedAt time.Time `json:"created_at"` 19 | 20 | // deprecated. Please refer to 'comment' instead 21 | Email string `json:"email"` 22 | 23 | // a unique identifying string based on contents 24 | Fingerprint string `json:"fingerprint"` 25 | 26 | // unique identifier of this key 27 | Id string `json:"id"` 28 | 29 | // full public_key as uploaded 30 | PublicKey string `json:"public_key"` 31 | 32 | // when key was updated 33 | UpdatedAt time.Time `json:"updated_at"` 34 | } 35 | 36 | // Create a new key. 37 | // 38 | // publicKey is the full public_key as uploaded. 39 | func (c *Client) KeyCreate(publicKey string) (*Key, error) { 40 | params := struct { 41 | PublicKey string `json:"public_key"` 42 | }{ 43 | PublicKey: publicKey, 44 | } 45 | var keyRes Key 46 | return &keyRes, c.Post(&keyRes, "/account/keys", params) 47 | } 48 | 49 | // Delete an existing key 50 | // 51 | // keyIdentity is the unique identifier of the Key. 52 | func (c *Client) KeyDelete(keyIdentity string) error { 53 | return c.Delete("/account/keys/" + keyIdentity) 54 | } 55 | 56 | // Info for existing key. 57 | // 58 | // keyIdentity is the unique identifier of the Key. 59 | func (c *Client) KeyInfo(keyIdentity string) (*Key, error) { 60 | var key Key 61 | return &key, c.Get(&key, "/account/keys/"+keyIdentity) 62 | } 63 | 64 | // List existing keys. 65 | // 66 | // lr is an optional ListRange that sets the Range options for the paginated 67 | // list of results. 68 | func (c *Client) KeyList(lr *ListRange) ([]Key, error) { 69 | req, err := c.NewRequest("GET", "/account/keys", nil) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | if lr != nil { 75 | lr.SetHeader(req) 76 | } 77 | 78 | var keysRes []Key 79 | return keysRes, c.DoReq(req, &keysRes) 80 | } 81 | -------------------------------------------------------------------------------- /log_drain.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Log drains provide a way to forward your Heroku logs to an external syslog 12 | // server for long-term archiving. This external service must be configured to 13 | // receive syslog packets from Heroku, whereupon its URL can be added to an app 14 | // using this API. Some addons will add a log drain when they are provisioned to 15 | // an app. These drains can only be removed by removing the add-on. 16 | type LogDrain struct { 17 | // addon that created the drain 18 | Addon *struct { 19 | Id string `json:"id"` 20 | } `json:"addon"` 21 | 22 | // when log drain was created 23 | CreatedAt time.Time `json:"created_at"` 24 | 25 | // unique identifier of this log drain 26 | Id string `json:"id"` 27 | 28 | // token associated with the log drain 29 | Token string `json:"token"` 30 | 31 | // when log drain was updated 32 | UpdatedAt time.Time `json:"updated_at"` 33 | 34 | // url associated with the log drain 35 | URL string `json:"url"` 36 | } 37 | 38 | // Create a new log drain. 39 | // 40 | // appIdentity is the unique identifier of the LogDrain's App. url is the url 41 | // associated with the log drain. 42 | func (c *Client) LogDrainCreate(appIdentity string, url string) (*LogDrain, error) { 43 | params := struct { 44 | URL string `json:"url"` 45 | }{ 46 | URL: url, 47 | } 48 | var logDrainRes LogDrain 49 | return &logDrainRes, c.Post(&logDrainRes, "/apps/"+appIdentity+"/log-drains", params) 50 | } 51 | 52 | // Delete an existing log drain. Log drains added by add-ons can only be removed 53 | // by removing the add-on. 54 | // 55 | // appIdentity is the unique identifier of the LogDrain's App. logDrainIdentity 56 | // is the unique identifier of the LogDrain. 57 | func (c *Client) LogDrainDelete(appIdentity string, logDrainIdentity string) error { 58 | return c.Delete("/apps/" + appIdentity + "/log-drains/" + logDrainIdentity) 59 | } 60 | 61 | // Info for existing log drain. 62 | // 63 | // appIdentity is the unique identifier of the LogDrain's App. logDrainIdentity 64 | // is the unique identifier of the LogDrain. 65 | func (c *Client) LogDrainInfo(appIdentity string, logDrainIdentity string) (*LogDrain, error) { 66 | var logDrain LogDrain 67 | return &logDrain, c.Get(&logDrain, "/apps/"+appIdentity+"/log-drains/"+logDrainIdentity) 68 | } 69 | 70 | // List existing log drains. 71 | // 72 | // appIdentity is the unique identifier of the LogDrain's App. lr is an optional 73 | // ListRange that sets the Range options for the paginated list of results. 74 | func (c *Client) LogDrainList(appIdentity string, lr *ListRange) ([]LogDrain, error) { 75 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/log-drains", nil) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | if lr != nil { 81 | lr.SetHeader(req) 82 | } 83 | 84 | var logDrainsRes []LogDrain 85 | return logDrainsRes, c.DoReq(req, &logDrainsRes) 86 | } 87 | -------------------------------------------------------------------------------- /log_session.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // A log session is a reference to the http based log stream for an app. 12 | type LogSession struct { 13 | // when log connection was created 14 | CreatedAt time.Time `json:"created_at"` 15 | 16 | // unique identifier of this log session 17 | Id string `json:"id"` 18 | 19 | // URL for log streaming session 20 | LogplexURL string `json:"logplex_url"` 21 | 22 | // when log session was updated 23 | UpdatedAt time.Time `json:"updated_at"` 24 | } 25 | 26 | // Create a new log session. 27 | // 28 | // appIdentity is the unique identifier of the LogSession's App. options is the 29 | // struct of optional parameters for this action. 30 | func (c *Client) LogSessionCreate(appIdentity string, options *LogSessionCreateOpts) (*LogSession, error) { 31 | var logSessionRes LogSession 32 | return &logSessionRes, c.Post(&logSessionRes, "/apps/"+appIdentity+"/log-sessions", options) 33 | } 34 | 35 | // LogSessionCreateOpts holds the optional parameters for LogSessionCreate 36 | type LogSessionCreateOpts struct { 37 | // dyno to limit results to 38 | Dyno *string `json:"dyno,omitempty"` 39 | // number of log lines to stream at once 40 | Lines *int `json:"lines,omitempty"` 41 | // log source to limit results to 42 | Source *string `json:"source,omitempty"` 43 | // whether to stream ongoing logs 44 | Tail *bool `json:"tail,omitempty"` 45 | } 46 | -------------------------------------------------------------------------------- /oauth_authorization.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // OAuth authorizations represent clients that a Heroku user has authorized to 12 | // automate, customize or extend their usage of the platform. For more 13 | // information please refer to the Heroku OAuth documentation 14 | type OAuthAuthorization struct { 15 | // access token for this authorization 16 | AccessToken *struct { 17 | ExpiresIn *int `json:"expires_in"` 18 | Id string `json:"id"` 19 | Token string `json:"token"` 20 | } `json:"access_token"` 21 | 22 | // identifier of the client that obtained this authorization, if any 23 | Client *struct { 24 | Id string `json:"id"` 25 | Name string `json:"name"` 26 | RedirectUri string `json:"redirect_uri"` 27 | } `json:"client"` 28 | 29 | // when OAuth authorization was created 30 | CreatedAt time.Time `json:"created_at"` 31 | 32 | // this authorization's grant 33 | Grant *struct { 34 | Code string `json:"code"` 35 | ExpiresIn int `json:"expires_in"` 36 | Id string `json:"id"` 37 | } `json:"grant"` 38 | 39 | // unique identifier of OAuth authorization 40 | Id string `json:"id"` 41 | 42 | // refresh token for this authorization 43 | RefreshToken *struct { 44 | ExpiresIn *int `json:"expires_in"` 45 | Id string `json:"id"` 46 | Token string `json:"token"` 47 | } `json:"refresh_token"` 48 | 49 | // The scope of access OAuth authorization allows 50 | Scope []string `json:"scope"` 51 | 52 | // when OAuth authorization was updated 53 | UpdatedAt time.Time `json:"updated_at"` 54 | } 55 | 56 | // Create a new OAuth authorization. 57 | // 58 | // scope is the The scope of access OAuth authorization allows. options is the 59 | // struct of optional parameters for this action. 60 | func (c *Client) OAuthAuthorizationCreate(scope []string, options *OAuthAuthorizationCreateOpts) (*OAuthAuthorization, error) { 61 | params := struct { 62 | Scope []string `json:"scope"` 63 | Client *string `json:"client,omitempty"` 64 | Description *string `json:"description,omitempty"` 65 | ExpiresIn *int `json:"expires_in,omitempty"` 66 | }{ 67 | Scope: scope, 68 | } 69 | if options != nil { 70 | params.Client = options.Client 71 | params.Description = options.Description 72 | params.ExpiresIn = options.ExpiresIn 73 | } 74 | var oauthAuthorizationRes OAuthAuthorization 75 | return &oauthAuthorizationRes, c.Post(&oauthAuthorizationRes, "/oauth/authorizations", params) 76 | } 77 | 78 | // OAuthAuthorizationCreateOpts holds the optional parameters for OAuthAuthorizationCreate 79 | type OAuthAuthorizationCreateOpts struct { 80 | // identifier of the client that obtained this authorization, if any 81 | Client *string `json:"client,omitempty"` 82 | // human-friendly description of this OAuth authorization 83 | Description *string `json:"description,omitempty"` 84 | // seconds until OAuth token expires; may be `null` for tokens with indefinite lifetime 85 | ExpiresIn *int `json:"expires_in,omitempty"` 86 | } 87 | 88 | // Delete OAuth authorization. 89 | // 90 | // oauthAuthorizationIdentity is the unique identifier of the 91 | // OAuthAuthorization. 92 | func (c *Client) OAuthAuthorizationDelete(oauthAuthorizationIdentity string) error { 93 | return c.Delete("/oauth/authorizations/" + oauthAuthorizationIdentity) 94 | } 95 | 96 | // Info for an OAuth authorization. 97 | // 98 | // oauthAuthorizationIdentity is the unique identifier of the 99 | // OAuthAuthorization. 100 | func (c *Client) OAuthAuthorizationInfo(oauthAuthorizationIdentity string) (*OAuthAuthorization, error) { 101 | var oauthAuthorization OAuthAuthorization 102 | return &oauthAuthorization, c.Get(&oauthAuthorization, "/oauth/authorizations/"+oauthAuthorizationIdentity) 103 | } 104 | 105 | // List OAuth authorizations. 106 | // 107 | // lr is an optional ListRange that sets the Range options for the paginated 108 | // list of results. 109 | func (c *Client) OAuthAuthorizationList(lr *ListRange) ([]OAuthAuthorization, error) { 110 | req, err := c.NewRequest("GET", "/oauth/authorizations", nil) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | if lr != nil { 116 | lr.SetHeader(req) 117 | } 118 | 119 | var oauthAuthorizationsRes []OAuthAuthorization 120 | return oauthAuthorizationsRes, c.DoReq(req, &oauthAuthorizationsRes) 121 | } 122 | -------------------------------------------------------------------------------- /oauth_client.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // OAuth clients are applications that Heroku users can authorize to automate, 12 | // customize or extend their usage of the platform. For more information please 13 | // refer to the Heroku OAuth documentation. 14 | type OAuthClient struct { 15 | // when OAuth client was created 16 | CreatedAt time.Time `json:"created_at"` 17 | 18 | // unique identifier of this OAuth client 19 | Id string `json:"id"` 20 | 21 | // whether the client is still operable given a delinquent account 22 | IgnoresDelinquent *bool `json:"ignores_delinquent"` 23 | 24 | // OAuth client name 25 | Name string `json:"name"` 26 | 27 | // endpoint for redirection after authorization with OAuth client 28 | RedirectUri string `json:"redirect_uri"` 29 | 30 | // secret used to obtain OAuth authorizations under this client 31 | Secret string `json:"secret"` 32 | 33 | // when OAuth client was updated 34 | UpdatedAt time.Time `json:"updated_at"` 35 | } 36 | 37 | // Create a new OAuth client. 38 | // 39 | // name is the OAuth client name. redirectUri is the endpoint for redirection 40 | // after authorization with OAuth client. 41 | func (c *Client) OAuthClientCreate(name string, redirectUri string) (*OAuthClient, error) { 42 | params := struct { 43 | Name string `json:"name"` 44 | RedirectUri string `json:"redirect_uri"` 45 | }{ 46 | Name: name, 47 | RedirectUri: redirectUri, 48 | } 49 | var oauthClientRes OAuthClient 50 | return &oauthClientRes, c.Post(&oauthClientRes, "/oauth/clients", params) 51 | } 52 | 53 | // Delete OAuth client. 54 | // 55 | // oauthClientIdentity is the unique identifier of the OAuthClient. 56 | func (c *Client) OAuthClientDelete(oauthClientIdentity string) error { 57 | return c.Delete("/oauth/clients/" + oauthClientIdentity) 58 | } 59 | 60 | // Info for an OAuth client 61 | // 62 | // oauthClientIdentity is the unique identifier of the OAuthClient. 63 | func (c *Client) OAuthClientInfo(oauthClientIdentity string) (*OAuthClient, error) { 64 | var oauthClient OAuthClient 65 | return &oauthClient, c.Get(&oauthClient, "/oauth/clients/"+oauthClientIdentity) 66 | } 67 | 68 | // List OAuth clients 69 | // 70 | // lr is an optional ListRange that sets the Range options for the paginated 71 | // list of results. 72 | func (c *Client) OAuthClientList(lr *ListRange) ([]OAuthClient, error) { 73 | req, err := c.NewRequest("GET", "/oauth/clients", nil) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | if lr != nil { 79 | lr.SetHeader(req) 80 | } 81 | 82 | var oauthClientsRes []OAuthClient 83 | return oauthClientsRes, c.DoReq(req, &oauthClientsRes) 84 | } 85 | 86 | // Update OAuth client 87 | // 88 | // oauthClientIdentity is the unique identifier of the OAuthClient. options is 89 | // the struct of optional parameters for this action. 90 | func (c *Client) OAuthClientUpdate(oauthClientIdentity string, options *OAuthClientUpdateOpts) (*OAuthClient, error) { 91 | var oauthClientRes OAuthClient 92 | return &oauthClientRes, c.Patch(&oauthClientRes, "/oauth/clients/"+oauthClientIdentity, options) 93 | } 94 | 95 | // OAuthClientUpdateOpts holds the optional parameters for OAuthClientUpdate 96 | type OAuthClientUpdateOpts struct { 97 | // OAuth client name 98 | Name *string `json:"name,omitempty"` 99 | // endpoint for redirection after authorization with OAuth client 100 | RedirectUri *string `json:"redirect_uri,omitempty"` 101 | } 102 | -------------------------------------------------------------------------------- /oauth_token.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // OAuth tokens provide access for authorized clients to act on behalf of a 12 | // Heroku user to automate, customize or extend their usage of the platform. For 13 | // more information please refer to the Heroku OAuth documentation 14 | type OAuthToken struct { 15 | // current access token 16 | AccessToken struct { 17 | ExpiresIn *int `json:"expires_in"` 18 | Id string `json:"id"` 19 | Token string `json:"token"` 20 | } `json:"access_token"` 21 | 22 | // authorization for this set of tokens 23 | Authorization struct { 24 | Id string `json:"id"` 25 | } `json:"authorization"` 26 | 27 | // OAuth client secret used to obtain token 28 | Client *struct { 29 | Secret string `json:"secret"` 30 | } `json:"client"` 31 | 32 | // when OAuth token was created 33 | CreatedAt time.Time `json:"created_at"` 34 | 35 | // grant used on the underlying authorization 36 | Grant struct { 37 | Code string `json:"code"` 38 | Type string `json:"type"` 39 | } `json:"grant"` 40 | 41 | // unique identifier of OAuth token 42 | Id string `json:"id"` 43 | 44 | // refresh token for this authorization 45 | RefreshToken struct { 46 | ExpiresIn *int `json:"expires_in"` 47 | Id string `json:"id"` 48 | Token string `json:"token"` 49 | } `json:"refresh_token"` 50 | 51 | // OAuth session using this token 52 | Session struct { 53 | Id string `json:"id"` 54 | } `json:"session"` 55 | 56 | // when OAuth token was updated 57 | UpdatedAt time.Time `json:"updated_at"` 58 | 59 | // Reference to the user associated with this token 60 | User struct { 61 | Id string `json:"id"` 62 | } `json:"user"` 63 | } 64 | 65 | // Create a new OAuth token. 66 | // 67 | // grant is the grant used on the underlying authorization. client is the OAuth 68 | // client secret used to obtain token. refreshToken is the refresh token for 69 | // this authorization. 70 | func (c *Client) OAuthTokenCreate(grant OAuthTokenCreateGrant, client OAuthTokenCreateClient, refreshToken OAuthTokenCreateRefreshToken) (*OAuthToken, error) { 71 | params := struct { 72 | Grant OAuthTokenCreateGrant `json:"grant"` 73 | Client OAuthTokenCreateClient `json:"client"` 74 | RefreshToken OAuthTokenCreateRefreshToken `json:"refresh_token"` 75 | }{ 76 | Grant: grant, 77 | Client: client, 78 | RefreshToken: refreshToken, 79 | } 80 | var oauthTokenRes OAuthToken 81 | return &oauthTokenRes, c.Post(&oauthTokenRes, "/oauth/tokens", params) 82 | } 83 | 84 | // OAuthTokenCreateGrant used in OAuthTokenCreate as the grant used on the underlying authorization 85 | type OAuthTokenCreateGrant struct { 86 | // grant code received from OAuth web application authorization 87 | Code string `json:"code"` 88 | 89 | // type of grant requested, one of `authorization_code` or `refresh_token` 90 | Type string `json:"type"` 91 | } 92 | 93 | // OAuthTokenCreateClient used in OAuthTokenCreate as the OAuth client secret used to obtain token 94 | type OAuthTokenCreateClient struct { 95 | // secret used to obtain OAuth authorizations under this client 96 | Secret string `json:"secret"` 97 | } 98 | 99 | // OAuthTokenCreateRefreshToken used in OAuthTokenCreate as the refresh token for this authorization 100 | type OAuthTokenCreateRefreshToken struct { 101 | // contents of the token to be used for authorization 102 | Token string `json:"token"` 103 | } 104 | -------------------------------------------------------------------------------- /organization.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | // Organizations allow you to manage access to a shared group of applications 8 | // across your development team. 9 | type Organization struct { 10 | // whether charges incurred by the org are paid by credit card. 11 | CreditCardCollections bool `json:"credit_card_collections"` 12 | 13 | // whether to use this organization when none is specified 14 | Default bool `json:"default"` 15 | 16 | // unique name of organization 17 | Name string `json:"name"` 18 | 19 | // whether the org is provisioned licenses by salesforce. 20 | ProvisionedLicenses bool `json:"provisioned_licenses"` 21 | 22 | // role in the organization 23 | Role string `json:"role"` 24 | } 25 | 26 | // List organizations in which you are a member. 27 | // 28 | // lr is an optional ListRange that sets the Range options for the paginated 29 | // list of results. 30 | func (c *Client) OrganizationList(lr *ListRange) ([]Organization, error) { 31 | req, err := c.NewRequest("GET", "/organizations", nil) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if lr != nil { 37 | lr.SetHeader(req) 38 | } 39 | 40 | var organizationsRes []Organization 41 | return organizationsRes, c.DoReq(req, &organizationsRes) 42 | } 43 | 44 | // Set or Unset the organization as your default organization. 45 | // 46 | // organizationIdentity is the unique identifier of the Organization. options is 47 | // the struct of optional parameters for this action. 48 | func (c *Client) OrganizationUpdate(organizationIdentity string, options *OrganizationUpdateOpts) (*Organization, error) { 49 | var organizationRes Organization 50 | return &organizationRes, c.Patch(&organizationRes, "/organizations/"+organizationIdentity, options) 51 | } 52 | 53 | // OrganizationUpdateOpts holds the optional parameters for OrganizationUpdate 54 | type OrganizationUpdateOpts struct { 55 | // whether to use this organization when none is specified 56 | Default *bool `json:"default,omitempty"` 57 | } 58 | -------------------------------------------------------------------------------- /organization_app.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An organization app encapsulates the organization specific functionality of 12 | // Heroku apps. 13 | type OrganizationApp struct { 14 | // when app was archived 15 | ArchivedAt *time.Time `json:"archived_at"` 16 | 17 | // description from buildpack of app 18 | BuildpackProvidedDescription *string `json:"buildpack_provided_description"` 19 | 20 | // when app was created 21 | CreatedAt time.Time `json:"created_at"` 22 | 23 | // git repo URL of app 24 | GitURL string `json:"git_url"` 25 | 26 | // unique identifier of app 27 | Id string `json:"id"` 28 | 29 | // is the current member a collaborator on this app. 30 | Joined bool `json:"joined"` 31 | 32 | // are other organization members forbidden from joining this app. 33 | Locked bool `json:"locked"` 34 | 35 | // maintenance status of app 36 | Maintenance bool `json:"maintenance"` 37 | 38 | // unique name of app 39 | Name string `json:"name"` 40 | 41 | // organization that owns this app 42 | Organization *struct { 43 | Name string `json:"name"` 44 | } `json:"organization"` 45 | 46 | // identity of app owner 47 | Owner *struct { 48 | Email string `json:"email"` 49 | Id string `json:"id"` 50 | } `json:"owner"` 51 | 52 | // identity of app region 53 | Region struct { 54 | Id string `json:"id"` 55 | Name string `json:"name"` 56 | } `json:"region"` 57 | 58 | // when app was released 59 | ReleasedAt *time.Time `json:"released_at"` 60 | 61 | // git repo size in bytes of app 62 | RepoSize *int `json:"repo_size"` 63 | 64 | // slug size in bytes of app 65 | SlugSize *int `json:"slug_size"` 66 | 67 | // identity of app stack 68 | Stack struct { 69 | Id string `json:"id"` 70 | Name string `json:"name"` 71 | } `json:"stack"` 72 | 73 | // when app was updated 74 | UpdatedAt time.Time `json:"updated_at"` 75 | 76 | // web URL of app 77 | WebURL string `json:"web_url"` 78 | } 79 | 80 | // Create a new app in the specified organization, in the default organization 81 | // if unspecified, or in personal account, if default organization is not set. 82 | // 83 | // options is the struct of optional parameters for this action. 84 | func (c *Client) OrganizationAppCreate(options *OrganizationAppCreateOpts) (*OrganizationApp, error) { 85 | var organizationAppRes OrganizationApp 86 | return &organizationAppRes, c.Post(&organizationAppRes, "/organizations/apps", options) 87 | } 88 | 89 | // OrganizationAppCreateOpts holds the optional parameters for OrganizationAppCreate 90 | type OrganizationAppCreateOpts struct { 91 | // are other organization members forbidden from joining this app. 92 | Locked *bool `json:"locked,omitempty"` 93 | // unique name of app 94 | Name *string `json:"name,omitempty"` 95 | // organization that owns this app 96 | Organization *string `json:"organization,omitempty"` 97 | // force creation of the app in the user account even if a default org is set. 98 | Personal *bool `json:"personal,omitempty"` 99 | // identity of app region 100 | Region *string `json:"region,omitempty"` 101 | // identity of app stack 102 | Stack *string `json:"stack,omitempty"` 103 | } 104 | 105 | // List apps in the default organization, or in personal account, if default 106 | // organization is not set. 107 | // 108 | // lr is an optional ListRange that sets the Range options for the paginated 109 | // list of results. 110 | func (c *Client) OrganizationAppList(lr *ListRange) ([]OrganizationApp, error) { 111 | req, err := c.NewRequest("GET", "/organizations/apps", nil) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | if lr != nil { 117 | lr.SetHeader(req) 118 | } 119 | 120 | var organizationAppsRes []OrganizationApp 121 | return organizationAppsRes, c.DoReq(req, &organizationAppsRes) 122 | } 123 | 124 | // List organization apps. 125 | // 126 | // organizationIdentity is the unique identifier of the OrganizationApp's 127 | // Organization. lr is an optional ListRange that sets the Range options for the 128 | // paginated list of results. 129 | func (c *Client) OrganizationAppListForOrganization(organizationIdentity string, lr *ListRange) ([]OrganizationApp, error) { 130 | req, err := c.NewRequest("GET", "/organizations/"+organizationIdentity+"/apps", nil) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | if lr != nil { 136 | lr.SetHeader(req) 137 | } 138 | 139 | var organizationAppsRes []OrganizationApp 140 | return organizationAppsRes, c.DoReq(req, &organizationAppsRes) 141 | } 142 | 143 | // Info for an organization app. 144 | // 145 | // appIdentity is the unique identifier of the OrganizationApp's App. 146 | func (c *Client) OrganizationAppInfo(appIdentity string) (*OrganizationApp, error) { 147 | var organizationApp OrganizationApp 148 | return &organizationApp, c.Get(&organizationApp, "/organizations/apps/"+appIdentity) 149 | } 150 | 151 | // Lock or unlock an organization app. 152 | // 153 | // appIdentity is the unique identifier of the OrganizationApp's App. locked is 154 | // the are other organization members forbidden from joining this app. 155 | func (c *Client) OrganizationAppUpdateLocked(appIdentity string, locked bool) (*OrganizationApp, error) { 156 | params := struct { 157 | Locked bool `json:"locked"` 158 | }{ 159 | Locked: locked, 160 | } 161 | var organizationAppRes OrganizationApp 162 | return &organizationAppRes, c.Patch(&organizationAppRes, "/organizations/apps/"+appIdentity, params) 163 | } 164 | 165 | // Transfer an existing organization app to another Heroku account. 166 | // 167 | // appIdentity is the unique identifier of the OrganizationApp's App. owner is 168 | // the unique email address of account or unique identifier of an account. 169 | func (c *Client) OrganizationAppTransferToAccount(appIdentity string, owner string) (*OrganizationApp, error) { 170 | params := struct { 171 | Owner string `json:"owner"` 172 | }{ 173 | Owner: owner, 174 | } 175 | var organizationAppRes OrganizationApp 176 | return &organizationAppRes, c.Patch(&organizationAppRes, "/organizations/apps/"+appIdentity, params) 177 | } 178 | 179 | // Transfer an existing organization app to another organization. 180 | // 181 | // appIdentity is the unique identifier of the OrganizationApp's App. owner is 182 | // the unique name of organization. 183 | func (c *Client) OrganizationAppTransferToOrganization(appIdentity string, owner string) (*OrganizationApp, error) { 184 | params := struct { 185 | Owner string `json:"owner"` 186 | }{ 187 | Owner: owner, 188 | } 189 | var organizationAppRes OrganizationApp 190 | return &organizationAppRes, c.Patch(&organizationAppRes, "/organizations/apps/"+appIdentity, params) 191 | } 192 | -------------------------------------------------------------------------------- /organization_app_collaborator.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An organization collaborator represents an account that has been given access 12 | // to an organization app on Heroku. 13 | type OrganizationAppCollaborator struct { 14 | // when collaborator was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // unique identifier of collaborator 18 | Id string `json:"id"` 19 | 20 | // role in the organization 21 | Role string `json:"role"` 22 | 23 | // when collaborator was updated 24 | UpdatedAt time.Time `json:"updated_at"` 25 | 26 | // identity of collaborated account 27 | User struct { 28 | Email string `json:"email"` 29 | Id string `json:"id"` 30 | } `json:"user"` 31 | } 32 | 33 | // Create a new collaborator on an organization app. Use this endpoint instead 34 | // of the /apps/{app_id_or_name}/collaborator endpoint when you want the 35 | // collaborator to be granted [privileges] 36 | // (https://devcenter.heroku.com/articles/org-users-access#roles) according to 37 | // their role in the organization. 38 | // 39 | // appIdentity is the unique identifier of the OrganizationAppCollaborator's 40 | // App. user is the unique email address of account or unique identifier of an 41 | // account. options is the struct of optional parameters for this action. 42 | func (c *Client) OrganizationAppCollaboratorCreate(appIdentity string, user string, options *OrganizationAppCollaboratorCreateOpts) (*OrganizationAppCollaborator, error) { 43 | params := struct { 44 | User string `json:"user"` 45 | Silent *bool `json:"silent,omitempty"` 46 | }{ 47 | User: user, 48 | } 49 | if options != nil { 50 | params.Silent = options.Silent 51 | } 52 | var organizationAppCollaboratorRes OrganizationAppCollaborator 53 | return &organizationAppCollaboratorRes, c.Post(&organizationAppCollaboratorRes, "/organizations/apps/"+appIdentity+"/collaborators", params) 54 | } 55 | 56 | // OrganizationAppCollaboratorCreateOpts holds the optional parameters for OrganizationAppCollaboratorCreate 57 | type OrganizationAppCollaboratorCreateOpts struct { 58 | // whether to suppress email invitation when creating collaborator 59 | Silent *bool `json:"silent,omitempty"` 60 | } 61 | 62 | // Delete an existing collaborator from an organization app. 63 | // 64 | // appIdentity is the unique identifier of the OrganizationAppCollaborator's 65 | // App. collaboratorIdentity is the unique identifier of the 66 | // OrganizationAppCollaborator's Collaborator. 67 | func (c *Client) OrganizationAppCollaboratorDelete(appIdentity string, collaboratorIdentity string) error { 68 | return c.Delete("/organizations/apps/" + appIdentity + "/collaborators/" + collaboratorIdentity) 69 | } 70 | 71 | // Info for a collaborator on an organization app. 72 | // 73 | // appIdentity is the unique identifier of the OrganizationAppCollaborator's 74 | // App. collaboratorIdentity is the unique identifier of the 75 | // OrganizationAppCollaborator's Collaborator. 76 | func (c *Client) OrganizationAppCollaboratorInfo(appIdentity string, collaboratorIdentity string) (*OrganizationAppCollaborator, error) { 77 | var organizationAppCollaborator OrganizationAppCollaborator 78 | return &organizationAppCollaborator, c.Get(&organizationAppCollaborator, "/organizations/apps/"+appIdentity+"/collaborators/"+collaboratorIdentity) 79 | } 80 | 81 | // List collaborators on an organization app. 82 | // 83 | // appIdentity is the unique identifier of the OrganizationAppCollaborator's 84 | // App. lr is an optional ListRange that sets the Range options for the 85 | // paginated list of results. 86 | func (c *Client) OrganizationAppCollaboratorList(appIdentity string, lr *ListRange) ([]OrganizationAppCollaborator, error) { 87 | req, err := c.NewRequest("GET", "/organizations/apps/"+appIdentity+"/collaborators", nil) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | if lr != nil { 93 | lr.SetHeader(req) 94 | } 95 | 96 | var organizationAppCollaboratorsRes []OrganizationAppCollaborator 97 | return organizationAppCollaboratorsRes, c.DoReq(req, &organizationAppCollaboratorsRes) 98 | } 99 | -------------------------------------------------------------------------------- /organization_member.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // An organization member is an individual with access to an organization. 12 | type OrganizationMember struct { 13 | // when organization-member was created 14 | CreatedAt time.Time `json:"created_at"` 15 | 16 | // email address of the organization member 17 | Email string `json:"email"` 18 | 19 | // role in the organization 20 | Role string `json:"role"` 21 | 22 | // when organization-member was updated 23 | UpdatedAt time.Time `json:"updated_at"` 24 | } 25 | 26 | // Create a new organization member, or update their role. 27 | // 28 | // organizationIdentity is the unique identifier of the OrganizationMember's 29 | // Organization. email is the email address of the organization member. role is 30 | // the role in the organization. 31 | func (c *Client) OrganizationMemberCreateOrUpdate(organizationIdentity string, email string, role string) (*OrganizationMember, error) { 32 | params := struct { 33 | Email string `json:"email"` 34 | Role string `json:"role"` 35 | }{ 36 | Email: email, 37 | Role: role, 38 | } 39 | var organizationMemberRes OrganizationMember 40 | return &organizationMemberRes, c.Put(&organizationMemberRes, "/organizations/"+organizationIdentity+"/members", params) 41 | } 42 | 43 | // Remove a member from the organization. 44 | // 45 | // organizationIdentity is the unique identifier of the OrganizationMember's 46 | // Organization. organizationMemberIdentity is the unique identifier of the 47 | // OrganizationMember. 48 | func (c *Client) OrganizationMemberDelete(organizationIdentity string, organizationMemberIdentity string) error { 49 | return c.Delete("/organizations/" + organizationIdentity + "/members/" + organizationIdentity) 50 | } 51 | 52 | // List members of the organization. 53 | // 54 | // organizationIdentity is the unique identifier of the OrganizationMember's 55 | // Organization. lr is an optional ListRange that sets the Range options for the 56 | // paginated list of results. 57 | func (c *Client) OrganizationMemberList(organizationIdentity string, lr *ListRange) ([]OrganizationMember, error) { 58 | req, err := c.NewRequest("GET", "/organizations/"+organizationIdentity+"/members", nil) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | if lr != nil { 64 | lr.SetHeader(req) 65 | } 66 | 67 | var organizationMembersRes []OrganizationMember 68 | return organizationMembersRes, c.DoReq(req, &organizationMembersRes) 69 | } 70 | -------------------------------------------------------------------------------- /plan.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Plans represent different configurations of add-ons that may be added to 12 | // apps. 13 | type Plan struct { 14 | // when plan was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // whether this plan is the default for its addon service 18 | Default bool `json:"default"` 19 | 20 | // description of plan 21 | Description string `json:"description"` 22 | 23 | // unique identifier of this plan 24 | Id string `json:"id"` 25 | 26 | // unique name of this plan 27 | Name string `json:"name"` 28 | 29 | // price 30 | Price struct { 31 | Cents int `json:"cents"` 32 | Unit string `json:"unit"` 33 | } `json:"price"` 34 | 35 | // release status for plan 36 | State string `json:"state"` 37 | 38 | // when plan was updated 39 | UpdatedAt time.Time `json:"updated_at"` 40 | } 41 | 42 | // Info for existing plan. 43 | // 44 | // addonServiceIdentity is the unique identifier of the Plan's AddonService. 45 | // planIdentity is the unique identifier of the Plan. 46 | func (c *Client) PlanInfo(addonServiceIdentity string, planIdentity string) (*Plan, error) { 47 | var plan Plan 48 | return &plan, c.Get(&plan, "/addon-services/"+addonServiceIdentity+"/plans/"+planIdentity) 49 | } 50 | 51 | // List existing plans. 52 | // 53 | // addonServiceIdentity is the unique identifier of the Plan's AddonService. lr 54 | // is an optional ListRange that sets the Range options for the paginated list 55 | // of results. 56 | func (c *Client) PlanList(addonServiceIdentity string, lr *ListRange) ([]Plan, error) { 57 | req, err := c.NewRequest("GET", "/addon-services/"+addonServiceIdentity+"/plans", nil) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | if lr != nil { 63 | lr.SetHeader(req) 64 | } 65 | 66 | var plansRes []Plan 67 | return plansRes, c.DoReq(req, &plansRes) 68 | } 69 | -------------------------------------------------------------------------------- /rate_limit.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | // Rate Limit represents the number of request tokens each account holds. 8 | // Requests to this endpoint do not count towards the rate limit. 9 | type RateLimit struct { 10 | // allowed requests remaining in current interval 11 | Remaining int `json:"remaining"` 12 | } 13 | 14 | // Info for rate limits. 15 | func (c *Client) RateLimitInfo() (*RateLimit, error) { 16 | var rateLimit RateLimit 17 | return &rateLimit, c.Get(&rateLimit, "/account/rate-limits") 18 | } 19 | -------------------------------------------------------------------------------- /region.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // A region represents a geographic location in which your application may run. 12 | type Region struct { 13 | // when region was created 14 | CreatedAt time.Time `json:"created_at"` 15 | 16 | // description of region 17 | Description string `json:"description"` 18 | 19 | // unique identifier of region 20 | Id string `json:"id"` 21 | 22 | // unique name of region 23 | Name string `json:"name"` 24 | 25 | // when region was updated 26 | UpdatedAt time.Time `json:"updated_at"` 27 | } 28 | 29 | // Info for existing region. 30 | // 31 | // regionIdentity is the unique identifier of the Region. 32 | func (c *Client) RegionInfo(regionIdentity string) (*Region, error) { 33 | var region Region 34 | return ®ion, c.Get(®ion, "/regions/"+regionIdentity) 35 | } 36 | 37 | // List existing regions. 38 | // 39 | // lr is an optional ListRange that sets the Range options for the paginated 40 | // list of results. 41 | func (c *Client) RegionList(lr *ListRange) ([]Region, error) { 42 | req, err := c.NewRequest("GET", "/regions", nil) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | if lr != nil { 48 | lr.SetHeader(req) 49 | } 50 | 51 | var regionsRes []Region 52 | return regionsRes, c.DoReq(req, ®ionsRes) 53 | } 54 | -------------------------------------------------------------------------------- /release.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // A release represents a combination of code, config vars and add-ons for an 12 | // app on Heroku. 13 | type Release struct { 14 | // when release was created 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // description of changes in this release 18 | Description string `json:"description"` 19 | 20 | // unique identifier of release 21 | Id string `json:"id"` 22 | 23 | // when release was updated 24 | UpdatedAt time.Time `json:"updated_at"` 25 | 26 | // slug running in this release 27 | Slug *struct { 28 | Id string `json:"id"` 29 | } `json:"slug"` 30 | 31 | // user that created the release 32 | User struct { 33 | Id string `json:"id"` 34 | Email string `json:"email"` 35 | } `json:"user"` 36 | 37 | // unique version assigned to the release 38 | Version int `json:"version"` 39 | } 40 | 41 | // Info for existing release. 42 | // 43 | // appIdentity is the unique identifier of the Release's App. releaseIdentity is 44 | // the unique identifier of the Release. 45 | func (c *Client) ReleaseInfo(appIdentity string, releaseIdentity string) (*Release, error) { 46 | var release Release 47 | return &release, c.Get(&release, "/apps/"+appIdentity+"/releases/"+releaseIdentity) 48 | } 49 | 50 | // List existing releases. 51 | // 52 | // appIdentity is the unique identifier of the Release's App. lr is an optional 53 | // ListRange that sets the Range options for the paginated list of results. 54 | func (c *Client) ReleaseList(appIdentity string, lr *ListRange) ([]Release, error) { 55 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/releases", nil) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | if lr != nil { 61 | lr.SetHeader(req) 62 | } 63 | 64 | var releasesRes []Release 65 | return releasesRes, c.DoReq(req, &releasesRes) 66 | } 67 | 68 | // Create new release. The API cannot be used to create releases on Bamboo apps. 69 | // 70 | // appIdentity is the unique identifier of the Release's App. slug is the unique 71 | // identifier of slug. options is the struct of optional parameters for this 72 | // action. 73 | func (c *Client) ReleaseCreate(appIdentity string, slug string, options *ReleaseCreateOpts) (*Release, error) { 74 | params := struct { 75 | Slug string `json:"slug"` 76 | Description *string `json:"description,omitempty"` 77 | }{ 78 | Slug: slug, 79 | } 80 | if options != nil { 81 | params.Description = options.Description 82 | } 83 | var releaseRes Release 84 | return &releaseRes, c.Post(&releaseRes, "/apps/"+appIdentity+"/releases", params) 85 | } 86 | 87 | // ReleaseCreateOpts holds the optional parameters for ReleaseCreate 88 | type ReleaseCreateOpts struct { 89 | // description of changes in this release 90 | Description *string `json:"description,omitempty"` 91 | } 92 | 93 | // Rollback to an existing release. 94 | // 95 | // appIdentity is the unique identifier of the Release's App. release is the 96 | // unique identifier of release. 97 | func (c *Client) ReleaseRollback(appIdentity string, release string) (*Release, error) { 98 | params := struct { 99 | Release string `json:"release"` 100 | }{ 101 | Release: release, 102 | } 103 | var releaseRes Release 104 | return &releaseRes, c.Post(&releaseRes, "/apps/"+appIdentity+"/releases", params) 105 | } 106 | -------------------------------------------------------------------------------- /slug.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // A slug is a snapshot of your application code that is ready to run on the 12 | // platform. 13 | type Slug struct { 14 | // pointer to the url where clients can fetch or store the actual release binary 15 | Blob struct { 16 | Method string `json:"method"` 17 | URL string `json:"url"` 18 | } `json:"blob"` 19 | 20 | // description from buildpack of slug 21 | BuildpackProvidedDescription *string `json:"buildpack_provided_description"` 22 | 23 | // identification of the code with your version control system (eg: SHA of the git HEAD) 24 | Commit *string `json:"commit"` 25 | 26 | // when slug was created 27 | CreatedAt time.Time `json:"created_at"` 28 | 29 | // unique identifier of slug 30 | Id string `json:"id"` 31 | 32 | // hash mapping process type names to their respective command 33 | ProcessTypes map[string]string `json:"process_types"` 34 | 35 | // size of slug, in bytes 36 | Size *int `json:"size"` 37 | 38 | // when slug was updated 39 | UpdatedAt time.Time `json:"updated_at"` 40 | } 41 | 42 | // Info for existing slug. 43 | // 44 | // appIdentity is the unique identifier of the Slug's App. slugIdentity is the 45 | // unique identifier of the Slug. 46 | func (c *Client) SlugInfo(appIdentity string, slugIdentity string) (*Slug, error) { 47 | var slug Slug 48 | return &slug, c.Get(&slug, "/apps/"+appIdentity+"/slugs/"+slugIdentity) 49 | } 50 | 51 | // Create a new slug. For more information please refer to Deploying Slugs using 52 | // the Platform API. 53 | // 54 | // appIdentity is the unique identifier of the Slug's App. processTypes is the 55 | // hash mapping process type names to their respective command. options is the 56 | // struct of optional parameters for this action. 57 | func (c *Client) SlugCreate(appIdentity string, processTypes map[string]string, options *SlugCreateOpts) (*Slug, error) { 58 | params := struct { 59 | ProcessTypes map[string]string `json:"process_types"` 60 | BuildpackProvidedDescription *string `json:"buildpack_provided_description,omitempty"` 61 | Commit *string `json:"commit,omitempty"` 62 | }{ 63 | ProcessTypes: processTypes, 64 | } 65 | if options != nil { 66 | params.BuildpackProvidedDescription = options.BuildpackProvidedDescription 67 | params.Commit = options.Commit 68 | } 69 | var slugRes Slug 70 | return &slugRes, c.Post(&slugRes, "/apps/"+appIdentity+"/slugs", params) 71 | } 72 | 73 | // SlugCreateOpts holds the optional parameters for SlugCreate 74 | type SlugCreateOpts struct { 75 | // description from buildpack of slug 76 | BuildpackProvidedDescription *string `json:"buildpack_provided_description,omitempty"` 77 | // identification of the code with your version control system (eg: SHA of the git HEAD) 78 | Commit *string `json:"commit,omitempty"` 79 | } 80 | -------------------------------------------------------------------------------- /ssl_endpoint.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // SSL Endpoint is a public address serving custom SSL cert for HTTPS traffic to 12 | // a Heroku app. Note that an app must have the ssl:endpoint addon installed 13 | // before it can provision an SSL Endpoint using these APIs. 14 | type SSLEndpoint struct { 15 | // raw contents of the public certificate chain (eg: .crt or .pem file) 16 | CertificateChain string `json:"certificate_chain"` 17 | 18 | // canonical name record, the address to point a domain at 19 | Cname string `json:"cname"` 20 | 21 | // when endpoint was created 22 | CreatedAt time.Time `json:"created_at"` 23 | 24 | // unique identifier of this SSL endpoint 25 | Id string `json:"id"` 26 | 27 | // unique name for SSL endpoint 28 | Name string `json:"name"` 29 | 30 | // when endpoint was updated 31 | UpdatedAt time.Time `json:"updated_at"` 32 | } 33 | 34 | // Create a new SSL endpoint. 35 | // 36 | // appIdentity is the unique identifier of the SSLEndpoint's App. 37 | // certificateChain is the raw contents of the public certificate chain (eg: 38 | // .crt or .pem file). privateKey is the contents of the private key (eg .key 39 | // file). options is the struct of optional parameters for this action. 40 | func (c *Client) SSLEndpointCreate(appIdentity string, certificateChain string, privateKey string, options *SSLEndpointCreateOpts) (*SSLEndpoint, error) { 41 | params := struct { 42 | CertificateChain string `json:"certificate_chain"` 43 | PrivateKey string `json:"private_key"` 44 | Preprocess *bool `json:"preprocess,omitempty"` 45 | }{ 46 | CertificateChain: certificateChain, 47 | PrivateKey: privateKey, 48 | } 49 | if options != nil { 50 | params.Preprocess = options.Preprocess 51 | } 52 | var sslEndpointRes SSLEndpoint 53 | return &sslEndpointRes, c.Post(&sslEndpointRes, "/apps/"+appIdentity+"/ssl-endpoints", params) 54 | } 55 | 56 | // SSLEndpointCreateOpts holds the optional parameters for SSLEndpointCreate 57 | type SSLEndpointCreateOpts struct { 58 | // allow Heroku to modify an uploaded public certificate chain if deemed advantageous by adding missing intermediaries, stripping unnecessary ones, etc. 59 | Preprocess *bool `json:"preprocess,omitempty"` 60 | } 61 | 62 | // Delete existing SSL endpoint. 63 | // 64 | // appIdentity is the unique identifier of the SSLEndpoint's App. 65 | // sslEndpointIdentity is the unique identifier of the SSLEndpoint. 66 | func (c *Client) SSLEndpointDelete(appIdentity string, sslEndpointIdentity string) error { 67 | return c.Delete("/apps/" + appIdentity + "/ssl-endpoints/" + sslEndpointIdentity) 68 | } 69 | 70 | // Info for existing SSL endpoint. 71 | // 72 | // appIdentity is the unique identifier of the SSLEndpoint's App. 73 | // sslEndpointIdentity is the unique identifier of the SSLEndpoint. 74 | func (c *Client) SSLEndpointInfo(appIdentity string, sslEndpointIdentity string) (*SSLEndpoint, error) { 75 | var sslEndpoint SSLEndpoint 76 | return &sslEndpoint, c.Get(&sslEndpoint, "/apps/"+appIdentity+"/ssl-endpoints/"+sslEndpointIdentity) 77 | } 78 | 79 | // List existing SSL endpoints. 80 | // 81 | // appIdentity is the unique identifier of the SSLEndpoint's App. lr is an 82 | // optional ListRange that sets the Range options for the paginated list of 83 | // results. 84 | func (c *Client) SSLEndpointList(appIdentity string, lr *ListRange) ([]SSLEndpoint, error) { 85 | req, err := c.NewRequest("GET", "/apps/"+appIdentity+"/ssl-endpoints", nil) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | if lr != nil { 91 | lr.SetHeader(req) 92 | } 93 | 94 | var sslEndpointsRes []SSLEndpoint 95 | return sslEndpointsRes, c.DoReq(req, &sslEndpointsRes) 96 | } 97 | 98 | // Update an existing SSL endpoint. 99 | // 100 | // appIdentity is the unique identifier of the SSLEndpoint's App. 101 | // sslEndpointIdentity is the unique identifier of the SSLEndpoint. options is 102 | // the struct of optional parameters for this action. 103 | func (c *Client) SSLEndpointUpdate(appIdentity string, sslEndpointIdentity string, options *SSLEndpointUpdateOpts) (*SSLEndpoint, error) { 104 | var sslEndpointRes SSLEndpoint 105 | return &sslEndpointRes, c.Patch(&sslEndpointRes, "/apps/"+appIdentity+"/ssl-endpoints/"+sslEndpointIdentity, options) 106 | } 107 | 108 | // SSLEndpointUpdateOpts holds the optional parameters for SSLEndpointUpdate 109 | type SSLEndpointUpdateOpts struct { 110 | // raw contents of the public certificate chain (eg: .crt or .pem file) 111 | CertificateChain *string `json:"certificate_chain,omitempty"` 112 | // allow Heroku to modify an uploaded public certificate chain if deemed advantageous by adding missing intermediaries, stripping unnecessary ones, etc. 113 | Preprocess *bool `json:"preprocess,omitempty"` 114 | // contents of the private key (eg .key file) 115 | PrivateKey *string `json:"private_key,omitempty"` 116 | // indicates that a rollback should be performed 117 | Rollback *bool `json:"rollback,omitempty"` 118 | } 119 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | // WARNING: This code is auto-generated from the Heroku Platform API JSON Schema 2 | // by a Ruby script (gen/gen.rb). Changes should be made to the generation 3 | // script rather than the generated files. 4 | 5 | package heroku 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Stacks are the different application execution environments available in the 12 | // Heroku platform. 13 | type Stack struct { 14 | // when stack was introduced 15 | CreatedAt time.Time `json:"created_at"` 16 | 17 | // unique identifier of stack 18 | Id string `json:"id"` 19 | 20 | // unique name of stack 21 | Name string `json:"name"` 22 | 23 | // availability of this stack: beta, deprecated or public 24 | State string `json:"state"` 25 | 26 | // when stack was last modified 27 | UpdatedAt time.Time `json:"updated_at"` 28 | } 29 | 30 | // Stack info. 31 | // 32 | // stackIdentity is the unique identifier of the Stack. 33 | func (c *Client) StackInfo(stackIdentity string) (*Stack, error) { 34 | var stack Stack 35 | return &stack, c.Get(&stack, "/stacks/"+stackIdentity) 36 | } 37 | 38 | // List available stacks. 39 | // 40 | // lr is an optional ListRange that sets the Range options for the paginated 41 | // list of results. 42 | func (c *Client) StackList(lr *ListRange) ([]Stack, error) { 43 | req, err := c.NewRequest("GET", "/stacks", nil) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | if lr != nil { 49 | lr.SetHeader(req) 50 | } 51 | 52 | var stacksRes []Stack 53 | return stacksRes, c.DoReq(req, &stacksRes) 54 | } 55 | --------------------------------------------------------------------------------