upgrade linodego to v0.14.0 (#9395)

* upgrade linodego to v0.14.0

* fix builder/linode linter errors

* Update go.mod

Co-authored-by: Adrien Delorme <adrien.delorme@icloud.com>
This commit is contained in:
Charlie Kenney 2020-06-12 05:36:54 -04:00 committed by GitHub
parent 4afcc794be
commit 70a2c7d364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 14716 additions and 12239 deletions

View File

@ -34,8 +34,6 @@ type Config struct {
RootSSHKey string `mapstructure:"root_ssh_key"`
ImageLabel string `mapstructure:"image_label"`
Description string `mapstructure:"image_description"`
interCtx interpolate.Context
}
func createRandomRootPassword() (string, error) {
@ -130,7 +128,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
for _, t := range c.Tags {
if !tagRe.MatchString(t) {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("invalid tag: %s", t)))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("invalid tag: %s", t))
}
}

7
go.mod
View File

@ -34,7 +34,6 @@ require (
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
github.com/digitalocean/godo v1.11.1
github.com/dnaeon/go-vcr v1.0.1 // indirect
github.com/docker/docker v0.0.0-20180422163414-57142e89befe // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect
@ -45,6 +44,7 @@ require (
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.25.4
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-resty/resty/v2 v2.3.0 // indirect
github.com/gobwas/glob v0.2.3
github.com/gocolly/colly v1.2.0
github.com/gofrs/flock v0.7.1
@ -92,7 +92,7 @@ require (
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f // indirect
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
github.com/linode/linodego v0.7.1
github.com/linode/linodego v0.14.0
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939
@ -102,6 +102,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-testing-interface v1.0.3 // indirect
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/iochan v1.0.0
github.com/mitchellh/mapstructure v1.2.3
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
@ -145,7 +146,7 @@ require (
github.com/zclconf/go-cty-yaml v1.0.1
golang.org/x/crypto v0.0.0-20200422194213-44a606286825
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd

14
go.sum
View File

@ -205,6 +205,10 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
@ -343,6 +347,7 @@ github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
@ -429,6 +434,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE=
github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY=
github.com/linode/linodego v0.14.0 h1:0APKMjiVGyry2TTUVDiok72H6cWpFNMMrFWBFn14aFU=
github.com/linode/linodego v0.14.0/go.mod h1:2ce3S00NrDqJfp4i55ZuSlT0U3cKNELNYACWBPI8Tnw=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c h1:FMUOnVGy8nWk1cvlMCAoftRItQGMxI0vzJ3dQjeZTCE=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
@ -482,6 +489,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -707,6 +716,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -715,6 +725,9 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -904,6 +917,7 @@ gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=

View File

@ -26,3 +26,6 @@ _testmain.go
coverage.out
coverage.txt
go.sum
# Exclude intellij IDE folders
.idea/*

View File

@ -2,23 +2,16 @@ language: go
sudo: false
go:
# - 1.3
# - 1.4
# - 1.5
# - 1.6
# - 1.7
# - 1.8.x
# - 1.9.x
- 1.10.x
- 1.11.x
go: # use travis ci resource effectively, keep always latest 2 versions and tip :)
- 1.14.x
- 1.13.x
- tip
install:
- go get -v -t ./...
script:
- go test ./... -coverprofile=coverage.txt -covermode=atomic
- go test ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -6,7 +6,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
gazelle(
name = "gazelle",
command = "fix",
prefix = "gopkg.in/resty.v1",
prefix = "github.com/go-resty/resty/v2",
)
go_library(
@ -15,7 +15,7 @@ go_library(
["*.go"],
exclude = ["*_test.go"],
),
importpath = "gopkg.in/resty.v1",
importpath = "github.com/go-resty/resty/v2",
visibility = ["//visibility:public"],
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
)
@ -29,7 +29,7 @@ go_test(
),
data = glob([".testdata/*"]),
embed = [":go_default_library"],
importpath = "gopkg.in/resty.v1",
importpath = "github.com/go-resty/resty/v2",
deps = [
"@org_golang_x_net//proxy:go_default_library",
],

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-2019 Jeevanandam M., https://myjeeva.com <jeeva@myjeeva.com>
Copyright (c) 2015-2020 Jeevanandam M., https://myjeeva.com <jeeva@myjeeva.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -4,54 +4,51 @@
<p align="center"><a href="#features">Features</a> section describes in detail about Resty capabilities</p>
</p>
<p align="center">
<p align="center"><a href="https://travis-ci.org/go-resty/resty"><img src="https://travis-ci.org/go-resty/resty.svg?branch=master" alt="Build Status"></a> <a href="https://codecov.io/gh/go-resty/resty/branch/master"><img src="https://codecov.io/gh/go-resty/resty/branch/master/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-1.12.0-blue.svg" alt="Release Version"></a> <a href="https://godoc.org/gopkg.in/resty.v1"><img src="https://godoc.org/gopkg.in/resty.v1?status.svg" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a></p>
<p align="center"><a href="https://travis-ci.org/go-resty/resty"><img src="https://travis-ci.org/go-resty/resty.svg?branch=master" alt="Build Status"></a> <a href="https://codecov.io/gh/go-resty/resty/branch/master"><img src="https://codecov.io/gh/go-resty/resty/branch/master/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-2.3.0-blue.svg" alt="Release Version"></a> <a href="https://pkg.go.dev/github.com/go-resty/resty/v2"><img src="https://godoc.org/github.com/go-resty/resty?status.svg" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a> <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a></p>
</p>
<p align="center">
<h4 align="center">Resty Communication Channels</h4>
<p align="center"><a href="https://gitter.im/go_resty/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/go_resty/community.svg" alt="Chat on Gitter - Resty Community"></a> <a href="https://twitter.com/go_resty"><img src="https://img.shields.io/badge/twitter-@go_resty-55acee.svg" alt="Twitter @go_resty"></a></p>
<p align="center"><a href="https://gitter.im/go_resty/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/go_resty/community.svg" alt="Chat on Gitter - Resty Community"></a> <a href="https://twitter.com/go_resty"><img src="https://img.shields.io/badge/twitter-@go__resty-55acee.svg" alt="Twitter @go_resty"></a></p>
</p>
## News
* Resty `v2` development is in-progress :smile:
* v2.3.0 [released](https://github.com/go-resty/resty/releases/tag/v2.3.0) and tagged on May 20, 2020.
* v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
* v1.11.0 [released](https://github.com/go-resty/resty/releases/tag/v1.11.0) and tagged on Jan 06, 2019.
* v1.10.3 [released](https://github.com/go-resty/resty/releases/tag/v1.10.3) and tagged on Dec 04, 2018.
* v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).
## Features
* GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
* Simple and chainable methods for settings and request
* Request Body can be `string`, `[]byte`, `struct`, `map`, `slice` and `io.Reader` too
* [Request](https://godoc.org/github.com/go-resty/resty#Request) Body can be `string`, `[]byte`, `struct`, `map`, `slice` and `io.Reader` too
* Auto detects `Content-Type`
* Buffer less processing for `io.Reader`
* [Response](https://godoc.org/gopkg.in/resty.v1#Response) object gives you more possibility
* Request Body can be read multiple times via `Request.RawRequest.GetBody()`
* [Response](https://godoc.org/github.com/go-resty/resty#Response) object gives you more possibility
* Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()`
* Know your `response.Time()` and when we `response.ReceivedAt()`
* Automatic marshal and unmarshal for `JSON` and `XML` content type
* Default is `JSON`, if you supply `struct/map` without header `Content-Type`
* For auto-unmarshal, refer to -
- Success scenario [Request.SetResult()](https://godoc.org/gopkg.in/resty.v1#Request.SetResult) and [Response.Result()](https://godoc.org/gopkg.in/resty.v1#Response.Result).
- Error scenario [Request.SetError()](https://godoc.org/gopkg.in/resty.v1#Request.SetError) and [Response.Error()](https://godoc.org/gopkg.in/resty.v1#Response.Error).
- Success scenario [Request.SetResult()](https://godoc.org/github.com/go-resty/resty#Request.SetResult) and [Response.Result()](https://godoc.org/github.com/go-resty/resty#Response.Result).
- Error scenario [Request.SetError()](https://godoc.org/github.com/go-resty/resty#Request.SetError) and [Response.Error()](https://godoc.org/github.com/go-resty/resty#Response.Error).
- Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json` & `application/problem+xml`
* Easy to upload one or more file(s) via `multipart/form-data`
* Auto detects file content type
* Request URL [Path Params (aka URI Params)](https://godoc.org/gopkg.in/resty.v1#Request.SetPathParams)
* Request URL [Path Params (aka URI Params)](https://godoc.org/github.com/go-resty/resty#Request.SetPathParams)
* Backoff Retry Mechanism with retry condition function [reference](retry_test.go)
* resty client HTTP & REST [Request](https://godoc.org/gopkg.in/resty.v1#Client.OnBeforeRequest) and [Response](https://godoc.org/gopkg.in/resty.v1#Client.OnAfterResponse) middlewares
* `Request.SetContext` supported `go1.7` and above
* Resty client HTTP & REST [Request](https://godoc.org/github.com/go-resty/resty#Client.OnBeforeRequest) and [Response](https://godoc.org/github.com/go-resty/resty#Client.OnAfterResponse) middlewares
* `Request.SetContext` supported
* Authorization option of `BasicAuth` and `Bearer` token
* Set request `ContentLength` value for all request or particular request
* Choose between HTTP and REST mode. Default is `REST`
* `HTTP` - default up to 10 redirects and no automatic response unmarshal
* `REST` - defaults to no redirects and automatic response marshal/unmarshal for `JSON` & `XML`
* Custom [Root Certificates](https://godoc.org/gopkg.in/resty.v1#Client.SetRootCertificate) and Client [Certificates](https://godoc.org/gopkg.in/resty.v1#Client.SetCertificates)
* Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://godoc.org/gopkg.in/resty.v1#Client.SetOutputDirectory) & [SetOutput](https://godoc.org/gopkg.in/resty.v1#Request.SetOutput).
* Custom [Root Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetRootCertificate) and Client [Certificates](https://godoc.org/github.com/go-resty/resty#Client.SetCertificates)
* Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://godoc.org/github.com/go-resty/resty#Client.SetOutputDirectory) & [SetOutput](https://godoc.org/github.com/go-resty/resty#Request.SetOutput).
* Cookies for your request and CookieJar support
* SRV Record based request instead of Host URL
* Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc.
* Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://godoc.org/gopkg.in/resty.v1#Client.SetAllowGetMethodPayload)
* Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://godoc.org/github.com/go-resty/resty#Client.SetAllowGetMethodPayload)
* Supports registering external JSON library into resty, see [how to use](https://github.com/go-resty/resty/issues/76#issuecomment-314015250)
* Exposes Response reader without reading response (no auto-unmarshaling) if need be, see [how to use](https://github.com/go-resty/resty/issues/87#issuecomment-322100604)
* Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer to [#92](https://github.com/go-resty/resty/issues/92)
@ -59,18 +56,16 @@
* Have client level settings & options and also override at Request level if you want to
* Request and Response middlewares
* Create Multiple clients if you want to `resty.New()`
* Supports `http.RoundTripper` implementation, see [SetTransport](https://godoc.org/gopkg.in/resty.v1#Client.SetTransport)
* Supports `http.RoundTripper` implementation, see [SetTransport](https://godoc.org/github.com/go-resty/resty#Client.SetTransport)
* goroutine concurrent safe
* REST and HTTP modes
* Resty Client trace, see [Client.EnableTrace](https://godoc.org/github.com/go-resty/resty#Client.EnableTrace) and [Request.EnableTrace](https://godoc.org/github.com/go-resty/resty#Request.EnableTrace)
* Debug mode - clean and informative logging presentation
* Gzip - Go does it automatically also resty has fallback handling too
* Works fine with `HTTP/2` and `HTTP/1.1`
* [Bazel support](#bazel-support)
* Easily mock resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
* Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library)
* Well tested client library
Resty works with `go1.3` and above.
### Included Batteries
* Redirect Policies - see [how to use](#redirect-policy)
@ -84,26 +79,16 @@ Resty works with `go1.3` and above.
* SRV Record based request instead of Host URL [how to use](resty_test.go#L1412)
* etc (upcoming - throw your idea's [here](https://github.com/go-resty/resty/issues)).
## Installation
#### Stable Version - Production Ready
#### Supported Go Versions
Please refer section [Versioning](#versioning) for detailed info.
Initially Resty started supporting `go modules` since `v1.10.0` release.
##### go.mod
Starting Resty v2 and higher versions, it fully embraces [go modules](https://github.com/golang/go/wiki/Modules) package release. It requires a Go version capable of understanding `/vN` suffixed imports:
```bash
require gopkg.in/resty.v1 v1.12.0
```
##### go get
```bash
go get -u gopkg.in/resty.v1
```
#### Heads up for upcoming Resty v2
Resty v2 release will be moving away from `gopkg.in` proxy versioning. It will completely follow and adpating Go Mod versioning recommendation. For e.g.: module definition would be `module github.com/go-resty/resty/v2`.
- 1.9.7+
- 1.10.3+
- 1.11+
## It might be beneficial for your project :smile:
@ -111,58 +96,102 @@ Resty v2 release will be moving away from `gopkg.in` proxy versioning. It will c
Resty author also published following projects for Go Community.
* [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework.
* [THUMBAI](https://thumbai.app), [Source Code](https://github.com/thumbai/thumbai) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
* [THUMBAI](https://thumbai.app) - Go Mod Repository, Go Vanity Service and Simple Proxy Server.
* [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for Go `struct`.
## Installation
```bash
# Go Modules
require github.com/go-resty/resty/v2 v2.3.0
```
## Usage
The following samples will assist you to become as comfortable as possible with resty library. Resty comes with ready to use DefaultClient.
Import resty into your code and refer it as `resty`.
The following samples will assist you to become as comfortable as possible with resty library.
```go
import "gopkg.in/resty.v1"
// Import resty into your code and refer it as `resty`.
import "github.com/go-resty/resty/v2"
```
#### Simple GET
```go
// GET request
resp, err := resty.R().Get("http://httpbin.org/get")
// Create a Resty Client
client := resty.New()
// explore response object
fmt.Printf("\nError: %v", err)
fmt.Printf("\nResponse Status Code: %v", resp.StatusCode())
fmt.Printf("\nResponse Status: %v", resp.Status())
fmt.Printf("\nResponse Time: %v", resp.Time())
fmt.Printf("\nResponse Received At: %v", resp.ReceivedAt())
fmt.Printf("\nResponse Body: %v", resp) // or resp.String() or string(resp.Body())
// more...
resp, err := client.R().
EnableTrace().
Get("https://httpbin.org/get")
// Explore response object
fmt.Println("Response Info:")
fmt.Println("Error :", err)
fmt.Println("Status Code:", resp.StatusCode())
fmt.Println("Status :", resp.Status())
fmt.Println("Proto :", resp.Proto())
fmt.Println("Time :", resp.Time())
fmt.Println("Received At:", resp.ReceivedAt())
fmt.Println("Body :\n", resp)
fmt.Println()
// Explore trace info
fmt.Println("Request Trace Info:")
ti := resp.Request.TraceInfo()
fmt.Println("DNSLookup :", ti.DNSLookup)
fmt.Println("ConnTime :", ti.ConnTime)
fmt.Println("TCPConnTime :", ti.TCPConnTime)
fmt.Println("TLSHandshake :", ti.TLSHandshake)
fmt.Println("ServerTime :", ti.ServerTime)
fmt.Println("ResponseTime :", ti.ResponseTime)
fmt.Println("TotalTime :", ti.TotalTime)
fmt.Println("IsConnReused :", ti.IsConnReused)
fmt.Println("IsConnWasIdle:", ti.IsConnWasIdle)
fmt.Println("ConnIdleTime :", ti.ConnIdleTime)
/* Output
Error: <nil>
Response Status Code: 200
Response Status: 200 OK
Response Time: 160.1151ms
Response Received At: 2018-10-16 16:28:34.8595663 -0700 PDT m=+0.166119401
Response Body: {
Response Info:
Error : <nil>
Status Code: 200
Status : 200 OK
Proto : HTTP/2.0
Time : 475.611189ms
Received At: 2020-05-19 00:11:06.828188 -0700 PDT m=+0.476510773
Body :
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "go-resty/1.10.0 (https://github.com/go-resty/resty)"
"User-Agent": "go-resty/2.3.0 (https://github.com/go-resty/resty)"
},
"origin": "0.0.0.0",
"url": "http://httpbin.org/get"
"url": "https://httpbin.org/get"
}
Request Trace Info:
DNSLookup : 4.870246ms
ConnTime : 393.95373ms
TCPConnTime : 78.360432ms
TLSHandshake : 310.032859ms
ServerTime : 81.648284ms
ResponseTime : 124.266µs
TotalTime : 475.611189ms
IsConnReused : false
IsConnWasIdle: false
ConnIdleTime : 0s
*/
```
#### Enhanced GET
```go
resp, err := resty.R().
// Create a Resty Client
client := resty.New()
resp, err := client.R().
SetQueryParams(map[string]string{
"page_no": "1",
"limit": "20",
@ -176,7 +205,7 @@ resp, err := resty.R().
// Sample of using Request.SetQueryString method
resp, err := resty.R().
resp, err := client.R().
SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
SetHeader("Accept", "application/json").
SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
@ -186,9 +215,12 @@ resp, err := resty.R().
#### Various POST method combinations
```go
// Create a Resty Client
client := resty.New()
// POST JSON string
// No need to set content type, if you have client level setting
resp, err := resty.R().
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"username":"testuser", "password":"testpass"}`).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
@ -196,21 +228,21 @@ resp, err := resty.R().
// POST []byte array
// No need to set content type, if you have client level setting
resp, err := resty.R().
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
Post("https://myapp.com/login")
// POST Struct, default is JSON content type. No need to set one
resp, err := resty.R().
resp, err := client.R().
SetBody(User{Username: "testuser", Password: "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
Post("https://myapp.com/login")
// POST Map, default is JSON content type. No need to set one
resp, err := resty.R().
resp, err := client.R().
SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
SetError(&AuthError{}). // or SetError(AuthError{}).
@ -220,7 +252,7 @@ resp, err := resty.R().
fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
resp, err := resty.R().
resp, err := client.R().
SetBody(fileBytes).
SetContentLength(true). // Dropbox expects this value
SetAuthToken("<your-auth-token>").
@ -239,9 +271,12 @@ You can use various combinations of `PUT` method call like demonstrated for `POS
```go
// Note: This is one sample of PUT method usage, refer POST for more combination
// Create a Resty Client
client := resty.New()
// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetBody(Article{
Title: "go-resty",
Content: "This is my article content, oh ya!",
@ -260,9 +295,12 @@ You can use various combinations of `PATCH` method call like demonstrated for `P
```go
// Note: This is one sample of PUT method usage, refer POST for more combination
// Create a Resty Client
client := resty.New()
// Request goes as JSON content type
// No need to set auth token, error, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetBody(Article{
Tags: []string{"new tag1", "new tag2"},
}).
@ -274,16 +312,19 @@ resp, err := resty.R().
#### Sample DELETE, HEAD, OPTIONS
```go
// Create a Resty Client
client := resty.New()
// DELETE a article
// No need to set auth token, error, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
SetError(&Error{}). // or SetError(Error{}).
Delete("https://myapp.com/articles/1234")
// DELETE a articles with payload/body as a JSON string
// No need to set auth token, error, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
SetError(&Error{}). // or SetError(Error{}).
SetHeader("Content-Type", "application/json").
@ -292,13 +333,13 @@ resp, err := resty.R().
// HEAD of resource
// No need to set auth token, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
Head("https://myapp.com/videos/hi-res-video")
// OPTIONS of resource
// No need to set auth token, if you have client level settings
resp, err := resty.R().
resp, err := client.R().
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
Options("https://myapp.com/servers/nyc-dc-01")
```
@ -311,7 +352,10 @@ resp, err := resty.R().
profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")
resp, err := resty.R().
// Create a Resty Client
client := resty.New()
resp, err := client.R().
SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
SetFormData(map[string]string{
@ -324,13 +368,16 @@ resp, err := resty.R().
#### Using File directly from Path
```go
// Create a Resty Client
client := resty.New()
// Single file scenario
resp, err := resty.R().
resp, err := client.R().
SetFile("profile_img", "/Users/jeeva/test-img.png").
Post("http://myapp.com/upload")
// Multiple files scenario
resp, err := resty.R().
resp, err := client.R().
SetFiles(map[string]string{
"profile_img": "/Users/jeeva/test-img.png",
"notes": "/Users/jeeva/text-file.txt",
@ -338,7 +385,7 @@ resp, err := resty.R().
Post("http://myapp.com/upload")
// Multipart of form fields and files
resp, err := resty.R().
resp, err := client.R().
SetFiles(map[string]string{
"profile_img": "/Users/jeeva/test-img.png",
"notes": "/Users/jeeva/text-file.txt",
@ -356,9 +403,12 @@ resp, err := resty.R().
#### Sample Form submission
```go
// Create a Resty Client
client := resty.New()
// just mentioning about POST as an example with simple flow
// User Login
resp, err := resty.R().
resp, err := client.R().
SetFormData(map[string]string{
"username": "jeeva",
"password": "mypass",
@ -366,7 +416,7 @@ resp, err := resty.R().
Post("http://myapp.com/login")
// Followed by profile update
resp, err := resty.R().
resp, err := client.R().
SetFormData(map[string]string{
"first_name": "Jeevanandam",
"last_name": "M",
@ -379,27 +429,30 @@ resp, err := resty.R().
criteria := url.Values{
"search_criteria": []string{"book", "glass", "pencil"},
}
resp, err := resty.R().
SetMultiValueFormData(criteria).
resp, err := client.R().
SetFormDataFromValues(criteria).
Post("http://myapp.com/search")
```
#### Save HTTP Response into File
```go
// Create a Resty Client
client := resty.New()
// Setting output directory path, If directory not exists then resty creates one!
// This is optional one, if you're planning using absoule path in
// `Request.SetOutput` and can used together.
resty.SetOutputDirectory("/Users/jeeva/Downloads")
client.SetOutputDirectory("/Users/jeeva/Downloads")
// HTTP response gets saved into file, similar to curl -o flag
_, err := resty.R().
_, err := client.R().
SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
Get("http://bit.ly/1LouEKr")
// OR using absolute path
// Note: output directory path is not used for absoulte path
_, err := resty.R().
// Note: output directory path is not used for absolute path
_, err := client.R().
SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
Get("http://bit.ly/1LouEKr")
```
@ -409,7 +462,10 @@ _, err := resty.R().
Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level params value can be overridden at request level.
```go
resty.R().SetPathParams(map[string]string{
// Create a Resty Client
client := resty.New()
client.R().SetPathParams(map[string]string{
"userId": "sample@sample.com",
"subAccountId": "100002",
}).
@ -424,8 +480,11 @@ Get("/v1/users/{userId}/{subAccountId}/details")
Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.
```go
// Create a Resty Client
client := resty.New()
// Registering Request Middleware
resty.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
// Now you have access to Client and current Request object
// manipulate it as per your need
@ -433,7 +492,7 @@ resty.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
})
// Registering Response Middleware
resty.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
// Now you have access to Client and current Response object
// manipulate it as per your need
@ -446,11 +505,14 @@ resty.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error {
Resty provides few ready to use redirect policy(s) also it supports multiple policies together.
```go
// Create a Resty Client
client := resty.New()
// Assign Client Redirect Policy. Create one as per you need
resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
// Wanna multiple policies such as redirect count, domain name check, etc
resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net"))
```
@ -459,8 +521,11 @@ resty.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20),
Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a look [redirect.go](redirect.go) for more information.
```go
// Create a Resty Client
client := resty.New()
// Using raw func into resty.SetRedirectPolicy
resty.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
// Implement your logic here
// return nil for continue redirect otherwise return error to stop/prevent redirect
@ -482,16 +547,19 @@ func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) err
}
// Registering in resty
resty.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
```
#### Custom Root Certificates and Client Certificates
```go
// Create a Resty Client
client := resty.New()
// Custom Root certificates, just supply .pem file.
// you can add one or more root certificates, its get appended
resty.SetRootCertificate("/path/to/root/pemFile1.pem")
resty.SetRootCertificate("/path/to/root/pemFile2.pem")
client.SetRootCertificate("/path/to/root/pemFile1.pem")
client.SetRootCertificate("/path/to/root/pemFile2.pem")
// ... and so on!
// Adding Client Certificates, you add one or more certificates
@ -504,7 +572,30 @@ if err != nil {
// ...
// You add one or more certificates
resty.SetCertificates(cert1, cert2, cert3)
client.SetCertificates(cert1, cert2, cert3)
```
#### Custom Root Certificates and Client Certificates from string
```go
// Custom Root certificates from string
// You can pass you certificates throught env variables as strings
// you can add one or more root certificates, its get appended
client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")
// ... and so on!
// Adding Client Certificates, you add one or more certificates
// Sample for creating certificate object
// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data.
cert1, err := tls.X509KeyPair([]byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"), []byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"))
if err != nil {
log.Fatalf("ERROR client certificate: %s", err)
}
// ...
// You add one or more certificates
client.SetCertificates(cert1, cert2, cert3)
```
#### Proxy Settings - Client as well as at Request Level
@ -515,11 +606,14 @@ Choose as per your need.
**Client Level Proxy** settings applied to all the request
```go
// Create a Resty Client
client := resty.New()
// Setting a Proxy URL and Port
resty.SetProxy("http://proxyserver:8888")
client.SetProxy("http://proxyserver:8888")
// Want to remove proxy setting
resty.RemoveProxy()
client.RemoveProxy()
```
#### Retries
@ -530,8 +624,11 @@ to increase retry intervals after each attempt.
Usage example:
```go
// Create a Resty Client
client := resty.New()
// Retries are configured per client
resty.
client.
// Set retry count to non zero to enable retries
SetRetryCount(3).
// You can override initial retry wait time.
@ -539,7 +636,12 @@ resty.
SetRetryWaitTime(5 * time.Second).
// MaxWaitTime can be overridden as well.
// Default is 2 seconds.
SetRetryMaxWaitTime(20 * time.Second)
SetRetryMaxWaitTime(20 * time.Second).
// SetRetryAfter sets callback to calculate wait time between retries.
// Default (nil) implies exponential backoff with jitter
SetRetryAfter(func(client *Client, resp *Response) (time.Duration, error) {
return 0, errors.New("quota exceeded")
})
```
Above setup will result in resty retrying requests returned non nil error up to
@ -548,12 +650,14 @@ Above setup will result in resty retrying requests returned non nil error up to
You can optionally provide client with custom retry conditions:
```go
resty.AddRetryCondition(
// Condition function will be provided with *resty.Response as a
// parameter. It is expected to return (bool, error) pair. Resty will retry
// in case condition returns true or non nil error.
func(r *resty.Response) (bool, error) {
return r.StatusCode() == http.StatusTooManyRequests, nil
// Create a Resty Client
client := resty.New()
client.AddRetryCondition(
// RetryConditionFunc type is for retry condition function
// input: non-nil Response OR request execution error
func(r *resty.Response, err error) bool {
return r.StatusCode() == http.StatusTooManyRequests
},
)
```
@ -566,21 +670,14 @@ Multiple retry conditions can be added.
It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios
implemented. [Reference](retry_test.go).
#### Choose REST or HTTP mode
```go
// REST mode. This is Default.
resty.SetRESTMode()
// HTTP mode
resty.SetHTTPMode()
```
#### Allow GET request with Payload
```go
// Create a Resty Client
client := resty.New()
// Allow GET request with Payload. This is disabled by default.
resty.SetAllowGetMethodPayload(true)
client.SetAllowGetMethodPayload(true)
```
#### Wanna Multiple Clients
@ -603,40 +700,39 @@ client2.R().Head("http://httpbin.org")
#### Remaining Client Settings & its Options
```go
// Create a Resty Client
client := resty.New()
// Unique settings at Client level
//--------------------------------
// Enable debug mode
resty.SetDebug(true)
// Using you custom log writer
logFile, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
resty.SetLogger(logFile)
client.SetDebug(true)
// Assign Client TLSClientConfig
// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
resty.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
client.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
// or One can disable security check (https)
resty.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
// Set client timeout as per your need
resty.SetTimeout(1 * time.Minute)
client.SetTimeout(1 * time.Minute)
// You can override all below settings and options at request level if you want to
//--------------------------------------------------------------------------------
// Host URL for all request. So you can use relative URL in the request
resty.SetHostURL("http://httpbin.org")
client.SetHostURL("http://httpbin.org")
// Headers for all request
resty.SetHeader("Accept", "application/json")
resty.SetHeaders(map[string]string{
client.SetHeader("Accept", "application/json")
client.SetHeaders(map[string]string{
"Content-Type": "application/json",
"User-Agent": "My custom User Agent String",
})
// Cookies for all request
resty.SetCookie(&http.Cookie{
client.SetCookie(&http.Cookie{
Name:"go-resty",
Value:"This is cookie value",
Path: "/",
@ -645,32 +741,32 @@ resty.SetCookie(&http.Cookie{
HttpOnly: true,
Secure: false,
})
resty.SetCookies(cookies)
client.SetCookies(cookies)
// URL query parameters for all request
resty.SetQueryParam("user_id", "00001")
resty.SetQueryParams(map[string]string{ // sample of those who use this manner
client.SetQueryParam("user_id", "00001")
client.SetQueryParams(map[string]string{ // sample of those who use this manner
"api_key": "api-key-here",
"api_secert": "api-secert",
})
resty.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
// Form data for all request. Typically used with POST and PUT
resty.SetFormData(map[string]string{
client.SetFormData(map[string]string{
"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
})
// Basic Auth for all request
resty.SetBasicAuth("myuser", "mypass")
client.SetBasicAuth("myuser", "mypass")
// Bearer Auth Token for all request
resty.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
// Enabling Content length value for all request
resty.SetContentLength(true)
client.SetContentLength(true)
// Registering global Error object structure for JSON/XML request
resty.SetError(&Error{}) // or resty.SetError(Error{})
client.SetError(&Error{}) // or resty.SetError(Error{})
```
#### Unix Socket
@ -685,12 +781,15 @@ transport := http.Transport{
},
}
// Create a Resty Client
client := resty.New()
// Set the previous transport that we created, set the scheme of the communication to the
// socket and set the unixSocket as the HostURL.
r := resty.New().SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
// No need to write the host's URL on the request, just the path.
r.R().Get("/index.html")
client.R().Get("/index.html")
```
#### Bazel support
@ -710,16 +809,22 @@ could use the `httpmock` library.
When using the default resty client, you should pass the client to the library as follow:
```go
httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
// Create a Resty Client
client := resty.New()
// Get the underlying HTTP Client and set it to Mock
httpmock.ActivateNonDefault(client.GetClient())
```
More detailed example of mocking resty http requests using ginko could be found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example).
## Versioning
resty releases versions according to [Semantic Versioning](http://semver.org)
Resty releases versions according to [Semantic Versioning](http://semver.org)
* `gopkg.in/resty.vX` points to appropriate tagged versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`.
* Resty v2 does not use `gopkg.in` service for library versioning.
* Resty fully adapted to `go mod` capabilities since `v1.10.0` release.
* Resty v1 series was using `gopkg.in` to provide versioning. `gopkg.in/resty.vX` points to appropriate tagged versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`.
* Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to fix a bug.
## Contribution
@ -732,6 +837,10 @@ BTW, I'd like to know what you think about `Resty`. Kindly open an issue or send
[Jeevanandam M.](https://github.com/jeevatkm) (jeeva@myjeeva.com)
## Core Team
Have a look on [Members](https://github.com/orgs/go-resty/teams/core/members) page.
## Contributors
Have a look on [Contributors](https://github.com/go-resty/resty/graphs/contributors) page.

File diff suppressed because it is too large Load Diff

5
vendor/github.com/go-resty/resty/v2/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/go-resty/resty/v2
require golang.org/x/net v0.0.0-20200513185701-a91f0712d120
go 1.11

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
@ -20,9 +21,11 @@ import (
"time"
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
const debugRequestLogKey = "__restyDebugRequestLog"
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Middleware(s)
//___________________________________
//_______________________________________________________________________
func parseRequestURL(c *Client, r *Request) error {
// GitHub #103 Path Params
@ -104,7 +107,7 @@ func parseRequestHeader(c *Client, r *Request) error {
}
if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
hdr.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
hdr.Set(hdrUserAgentKey, hdrUserAgentValue)
}
ct := hdr.Get(hdrContentTypeKey)
@ -176,19 +179,58 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
// Add headers into http request
r.RawRequest.Header = r.Header
// Add cookies into http request
// Add cookies from client instance into http request
for _, cookie := range c.Cookies {
r.RawRequest.AddCookie(cookie)
}
// Add cookies from request instance into http request
for _, cookie := range r.Cookies {
r.RawRequest.AddCookie(cookie)
}
// it's for non-http scheme option
if r.RawRequest.URL != nil && r.RawRequest.URL.Scheme == "" {
r.RawRequest.URL.Scheme = c.scheme
r.RawRequest.URL.Host = r.URL
}
// Enable trace
if c.trace || r.trace {
r.clientTrace = &clientTrace{}
r.ctx = r.clientTrace.createContext(r.Context())
}
// Use context if it was specified
r.addContextIfAvailable()
if r.ctx != nil {
r.RawRequest = r.RawRequest.WithContext(r.ctx)
}
// assign get body func for the underlying raw request instance
r.RawRequest.GetBody = func() (io.ReadCloser, error) {
// If r.bodyBuf present, return the copy
if r.bodyBuf != nil {
return ioutil.NopCloser(bytes.NewReader(r.bodyBuf.Bytes())), nil
}
// Maybe body is `io.Reader`.
// Note: Resty user have to watchout for large body size of `io.Reader`
if r.RawRequest.Body != nil {
b, err := ioutil.ReadAll(r.RawRequest.Body)
if err != nil {
return nil, err
}
// Restore the Body
closeq(r.RawRequest.Body)
r.RawRequest.Body = ioutil.NopCloser(bytes.NewBuffer(b))
// Return the Body bytes
return ioutil.NopCloser(bytes.NewBuffer(b)), nil
}
return nil, nil
}
return
}
@ -206,15 +248,25 @@ func addCredentials(c *Client, r *Request) error {
if !c.DisableWarn {
if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
c.Log.Println("WARNING - Using Basic Auth in HTTP mode is not secure.")
c.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS")
}
}
// Token Auth
// Set the Authorization Header Scheme
var authScheme string
if !IsStringEmpty(r.AuthScheme) {
authScheme = r.AuthScheme
} else if !IsStringEmpty(c.AuthScheme) {
authScheme = c.AuthScheme
} else {
authScheme = "Bearer"
}
// Build the Token Auth header
if !IsStringEmpty(r.Token) { // takes precedence
r.RawRequest.Header.Set(hdrAuthorizationKey, "Bearer "+r.Token)
r.RawRequest.Header.Set(hdrAuthorizationKey, authScheme+" "+r.Token)
} else if !IsStringEmpty(c.Token) {
r.RawRequest.Header.Set(hdrAuthorizationKey, "Bearer "+c.Token)
r.RawRequest.Header.Set(hdrAuthorizationKey, authScheme+" "+c.Token)
}
return nil
@ -223,30 +275,32 @@ func addCredentials(c *Client, r *Request) error {
func requestLogger(c *Client, r *Request) error {
if c.Debug {
rr := r.RawRequest
rl := &RequestLog{Header: copyHeaders(rr.Header), Body: r.fmtBodyString()}
rl := &RequestLog{Header: copyHeaders(rr.Header), Body: r.fmtBodyString(c.debugBodySizeLimit)}
if c.requestLog != nil {
if err := c.requestLog(rl); err != nil {
return err
}
}
// fmt.Sprintf("COOKIES:\n%s\n", composeCookies(c.GetClient().Jar, *rr.URL)) +
reqLog := "\n---------------------- REQUEST LOG -----------------------\n" +
reqLog := "\n==============================================================================\n" +
"~~~ REQUEST ~~~\n" +
fmt.Sprintf("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) +
fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
fmt.Sprintf("HEADERS:\n") +
composeHeaders(rl.Header) + "\n" +
fmt.Sprintf("HEADERS:\n%s\n", composeHeaders(c, r, rl.Header)) +
fmt.Sprintf("BODY :\n%v\n", rl.Body) +
"----------------------------------------------------------\n"
"------------------------------------------------------------------------------\n"
c.Log.Print(reqLog)
r.initValuesMap()
r.values[debugRequestLogKey] = reqLog
}
return nil
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response Middleware(s)
//___________________________________
//_______________________________________________________________________
func responseLogger(c *Client, res *Response) error {
if c.Debug {
@ -257,20 +311,22 @@ func responseLogger(c *Client, res *Response) error {
}
}
resLog := "\n---------------------- RESPONSE LOG -----------------------\n" +
debugLog := res.Request.values[debugRequestLogKey].(string)
debugLog += "~~~ RESPONSE ~~~\n" +
fmt.Sprintf("STATUS : %s\n", res.Status()) +
fmt.Sprintf("PROTO : %s\n", res.RawResponse.Proto) +
fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
fmt.Sprintf("RESPONSE TIME : %v\n", res.Time()) +
"HEADERS:\n" +
composeHeaders(rl.Header) + "\n"
fmt.Sprintf("TIME DURATION: %v\n", res.Time()) +
"HEADERS :\n" +
composeHeaders(c, res.Request, rl.Header) + "\n"
if res.Request.isSaveResponse {
resLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
debugLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
} else {
resLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
}
resLog += "----------------------------------------------------------\n"
debugLog += "==============================================================================\n"
c.Log.Print(resLog)
c.log.Debugf("%s", debugLog)
}
return nil
@ -281,10 +337,11 @@ func parseResponseBody(c *Client, res *Response) (err error) {
return
}
// Handles only JSON or XML content type
ct := firstNonEmpty(res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType)
ct := firstNonEmpty(res.Request.forceContentType, res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType)
if IsJSONType(ct) || IsXMLType(ct) {
// HTTP status code > 199 and < 300, considered as Result
if res.IsSuccess() {
res.Request.Error = nil
if res.Request.Result != nil {
err = Unmarshalc(c, ct, res.body, res.Request.Result)
return
@ -398,7 +455,7 @@ func handleRequestBody(c *Client, r *Request) (err error) {
r.bodyBuf = nil
if reader, ok := r.Body.(io.Reader); ok {
if c.setContentLength || r.setContentLength { // keep backward compability
if c.setContentLength || r.setContentLength { // keep backward compatibility
r.bodyBuf = acquireBuffer()
_, err = r.bodyBuf.ReadFrom(reader)
r.Body = nil

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
@ -47,9 +47,7 @@ func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
if len(via) >= noOfRedirect {
return fmt.Errorf("stopped after %d redirects", noOfRedirect)
}
checkHostAndAddHeaders(req, via[0])
return nil
})
}
@ -74,6 +72,10 @@ func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
return fn
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Unexported methods
//_______________________________________________________________________
func getHostname(host string) (hostname string) {
if strings.Index(host, ":") > 0 {
host, _, _ = net.SplitHostPort(host)
@ -85,7 +87,7 @@ func getHostname(host string) (hostname string) {
// By default Golang will not redirect request headers
// after go throughing various discussion comments from thread
// https://github.com/golang/go/issues/4800
// go-resty will add all the headers during a redirect for the same host
// Resty will add all the headers during a redirect for the same host
func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
curHostname := getHostname(cur.URL.Host)
preHostname := getHostname(pre.URL.Host)
@ -94,6 +96,6 @@ func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
cur.Header[key] = val
}
} else { // only library User-Agent header is added
cur.Header.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
cur.Header.Set(hdrUserAgentKey, hdrUserAgentValue)
}
}

View File

@ -1,66 +1,124 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"encoding/base64"
"bytes"
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net"
"net/http"
"net/url"
"reflect"
"strings"
"time"
)
// SRVRecord holds the data to query the SRV record for the following service
type SRVRecord struct {
Service string
Domain string
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request struct and methods
//_______________________________________________________________________
// Request struct is used to compose and fire individual request from
// resty client. Request provides an options to override client level
// settings and also an options for the request composition.
type Request struct {
URL string
Method string
Token string
AuthScheme string
QueryParam url.Values
FormData url.Values
Header http.Header
Time time.Time
Body interface{}
Result interface{}
Error interface{}
RawRequest *http.Request
SRV *SRVRecord
UserInfo *User
Cookies []*http.Cookie
isMultiPart bool
isFormData bool
setContentLength bool
isSaveResponse bool
notParseResponse bool
jsonEscapeHTML bool
trace bool
outputFile string
fallbackContentType string
forceContentType string
ctx context.Context
pathParams map[string]string
values map[string]interface{}
client *Client
bodyBuf *bytes.Buffer
clientTrace *clientTrace
multipartFiles []*File
multipartFields []*MultipartField
}
// Context method returns the Context if its already set in request
// otherwise it creates new one using `context.Background()`.
func (r *Request) Context() context.Context {
if r.ctx == nil {
return context.Background()
}
return r.ctx
}
// SetContext method sets the context.Context for current Request. It allows
// to interrupt the request execution if ctx.Done() channel is closed.
// See https://blog.golang.org/context article and the "context" package
// documentation.
func (r *Request) SetContext(ctx context.Context) *Request {
r.ctx = ctx
return r
}
// SetHeader method is to set a single header field and its value in the current request.
// Example: To set `Content-Type` and `Accept` as `application/json`.
// resty.R().
//
// For Example: To set `Content-Type` and `Accept` as `application/json`.
// client.R().
// SetHeader("Content-Type", "application/json").
// SetHeader("Accept", "application/json")
//
// Also you can override header value, which was set at client instance level.
//
func (r *Request) SetHeader(header, value string) *Request {
r.Header.Set(header, value)
return r
}
// SetHeaders method sets multiple headers field and its values at one go in the current request.
// Example: To set `Content-Type` and `Accept` as `application/json`
//
// resty.R().
// For Example: To set `Content-Type` and `Accept` as `application/json`
//
// client.R().
// SetHeaders(map[string]string{
// "Content-Type": "application/json",
// "Accept": "application/json",
// })
// Also you can override header value, which was set at client instance level.
//
func (r *Request) SetHeaders(headers map[string]string) *Request {
for h, v := range headers {
r.SetHeader(h, v)
}
return r
}
// SetQueryParam method sets single parameter and its value in the current request.
// It will be formed as query string for the request.
// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
// resty.R().
//
// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
// client.R().
// SetQueryParam("search", "kitchen papers").
// SetQueryParam("size", "large")
// Also you can override query params value, which was set at client instance level
//
// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParam(param, value string) *Request {
r.QueryParam.Set(param, value)
return r
@ -68,47 +126,45 @@ func (r *Request) SetQueryParam(param, value string) *Request {
// SetQueryParams method sets multiple parameters and its values at one go in the current request.
// It will be formed as query string for the request.
// Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
// resty.R().
//
// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.
// client.R().
// SetQueryParams(map[string]string{
// "search": "kitchen papers",
// "size": "large",
// })
// Also you can override query params value, which was set at client instance level
//
// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParams(params map[string]string) *Request {
for p, v := range params {
r.SetQueryParam(p, v)
}
return r
}
// SetMultiValueQueryParams method appends multiple parameters with multi-value
// at one go in the current request. It will be formed as query string for the request.
// Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
// resty.R().
// SetMultiValueQueryParams(url.Values{
// SetQueryParamsFromValues method appends multiple parameters with multi-value
// (`url.Values`) at one go in the current request. It will be formed as
// query string for the request.
//
// For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
// client.R().
// SetQueryParamsFromValues(url.Values{
// "status": []string{"pending", "approved", "open"},
// })
// Also you can override query params value, which was set at client instance level
//
func (r *Request) SetMultiValueQueryParams(params url.Values) *Request {
// Also you can override query params value, which was set at client instance level.
func (r *Request) SetQueryParamsFromValues(params url.Values) *Request {
for p, v := range params {
for _, pv := range v {
r.QueryParam.Add(p, pv)
}
}
return r
}
// SetQueryString method provides ability to use string as an input to set URL query string for the request.
//
// Using String as an input
// resty.R().
// client.R().
// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
//
func (r *Request) SetQueryString(query string) *Request {
params, err := url.ParseQuery(strings.TrimSpace(query))
if err == nil {
@ -118,7 +174,7 @@ func (r *Request) SetQueryString(query string) *Request {
}
}
} else {
r.client.Log.Printf("ERROR %v", err)
r.client.log.Errorf("%v", err)
}
return r
}
@ -126,36 +182,32 @@ func (r *Request) SetQueryString(query string) *Request {
// SetFormData method sets Form parameters and their values in the current request.
// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
// `application/x-www-form-urlencoded`.
// resty.R().
// client.R().
// SetFormData(map[string]string{
// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
// "user_id": "3455454545",
// })
// Also you can override form data value, which was set at client instance level
//
// Also you can override form data value, which was set at client instance level.
func (r *Request) SetFormData(data map[string]string) *Request {
for k, v := range data {
r.FormData.Set(k, v)
}
return r
}
// SetMultiValueFormData method appends multiple form parameters with multi-value
// at one go in the current request.
// resty.R().
// SetMultiValueFormData(url.Values{
// SetFormDataFromValues method appends multiple form parameters with multi-value
// (`url.Values`) at one go in the current request.
// client.R().
// SetFormDataFromValues(url.Values{
// "search_criteria": []string{"book", "glass", "pencil"},
// })
// Also you can override form data value, which was set at client instance level
//
func (r *Request) SetMultiValueFormData(params url.Values) *Request {
for k, v := range params {
// Also you can override form data value, which was set at client instance level.
func (r *Request) SetFormDataFromValues(data url.Values) *Request {
for k, v := range data {
for _, kv := range v {
r.FormData.Add(k, kv)
}
}
return r
}
@ -166,17 +218,15 @@ func (r *Request) SetMultiValueFormData(params url.Values) *Request {
//
// Note: `io.Reader` is processed as bufferless mode while sending request.
//
// Example:
//
// Struct as a body input, based on content type, it will be marshalled.
// resty.R().
// For Example: Struct as a body input, based on content type, it will be marshalled.
// client.R().
// SetBody(User{
// Username: "jeeva@myjeeva.com",
// Password: "welcome2resty",
// })
//
// Map as a body input, based on content type, it will be marshalled.
// resty.R().
// client.R().
// SetBody(map[string]interface{}{
// "username": "jeeva@myjeeva.com",
// "password": "welcome2resty",
@ -190,57 +240,53 @@ func (r *Request) SetMultiValueFormData(params url.Values) *Request {
// })
//
// String as a body input. Suitable for any need as a string input.
// resty.R().
// client.R().
// SetBody(`{
// "username": "jeeva@getrightcare.com",
// "password": "admin"
// }`)
//
// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
// resty.R().
// client.R().
// SetBody([]byte("This is my raw request, sent as-is"))
//
func (r *Request) SetBody(body interface{}) *Request {
r.Body = body
return r
}
// SetResult method is to register the response `Result` object for automatic unmarshalling in the RESTful mode
// SetResult method is to register the response `Result` object for automatic unmarshalling for the request,
// if response status code is between 200 and 299 and content type either JSON or XML.
//
// Note: Result object can be pointer or non-pointer.
// resty.R().SetResult(&AuthToken{})
// client.R().SetResult(&AuthToken{})
// // OR
// resty.R().SetResult(AuthToken{})
// client.R().SetResult(AuthToken{})
//
// Accessing a result value
// Accessing a result value from response instance.
// response.Result().(*AuthToken)
//
func (r *Request) SetResult(res interface{}) *Request {
r.Result = getPointer(res)
return r
}
// SetError method is to register the request `Error` object for automatic unmarshalling in the RESTful mode
// SetError method is to register the request `Error` object for automatic unmarshalling for the request,
// if response status code is greater than 399 and content type either JSON or XML.
//
// Note: Error object can be pointer or non-pointer.
// resty.R().SetError(&AuthError{})
// client.R().SetError(&AuthError{})
// // OR
// resty.R().SetError(AuthError{})
// client.R().SetError(AuthError{})
//
// Accessing a error value
// Accessing a error value from response instance.
// response.Error().(*AuthError)
//
func (r *Request) SetError(err interface{}) *Request {
r.Error = getPointer(err)
return r
}
// SetFile method is to set single file field name and its path for multipart upload.
// resty.R().
// client.R().
// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
//
func (r *Request) SetFile(param, filePath string) *Request {
r.isMultiPart = true
r.FormData.Set("@"+param, filePath)
@ -248,28 +294,24 @@ func (r *Request) SetFile(param, filePath string) *Request {
}
// SetFiles method is to set multiple file field name and its path for multipart upload.
// resty.R().
// client.R().
// SetFiles(map[string]string{
// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
// })
//
func (r *Request) SetFiles(files map[string]string) *Request {
r.isMultiPart = true
for f, fp := range files {
r.FormData.Set("@"+f, fp)
}
return r
}
// SetFileReader method is to set single file using io.Reader for multipart upload.
// resty.R().
// client.R().
// SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
//
func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
r.isMultiPart = true
r.multipartFiles = append(r.multipartFiles, &File{
@ -280,6 +322,15 @@ func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Reque
return r
}
// SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`
func (r *Request) SetMultipartFormData(data map[string]string) *Request {
for k, v := range data {
r = r.SetMultipartField(k, "", "", strings.NewReader(v))
}
return r
}
// SetMultipartField method is to set custom data using io.Reader for multipart upload.
func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
r.isMultiPart = true
@ -293,8 +344,9 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
}
// SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.
// Example:
// resty.R().SetMultipartFields(
//
// For Example:
// client.R().SetMultipartFields(
// &resty.MultipartField{
// Param: "uploadManifest1",
// FileName: "upload-file-1.json",
@ -309,7 +361,7 @@ func (r *Request) SetMultipartField(param, fileName, contentType string, reader
// })
//
// If you have slice already, then simply call-
// resty.R().SetMultipartFields(fields...)
// client.R().SetMultipartFields(fields...)
func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
r.isMultiPart = true
r.multipartFields = append(r.multipartFields, fields...)
@ -317,48 +369,69 @@ func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
}
// SetContentLength method sets the HTTP header `Content-Length` value for current request.
// By default go-resty won't set `Content-Length`. Also you have an option to enable for every
// request. See `resty.SetContentLength`
// resty.R().SetContentLength(true)
// By default Resty won't set `Content-Length`. Also you have an option to enable for every
// request.
//
// See `Client.SetContentLength`
// client.R().SetContentLength(true)
func (r *Request) SetContentLength(l bool) *Request {
r.setContentLength = true
return r
}
// SetBasicAuth method sets the basic authentication header in the current HTTP request.
// For Header example:
//
// For Example:
// Authorization: Basic <base64-encoded-value>
//
// To set the header for username "go-resty" and password "welcome"
// resty.R().SetBasicAuth("go-resty", "welcome")
//
// This method overrides the credentials set by method `resty.SetBasicAuth`.
// client.R().SetBasicAuth("go-resty", "welcome")
//
// This method overrides the credentials set by method `Client.SetBasicAuth`.
func (r *Request) SetBasicAuth(username, password string) *Request {
r.UserInfo = &User{Username: username, Password: password}
return r
}
// SetAuthToken method sets bearer auth token header in the current HTTP request. Header example:
// SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
// Authorization: Bearer <auth-token-value-comes-here>
//
// Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
//
// resty.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
//
// This method overrides the Auth token set by method `resty.SetAuthToken`.
// client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
//
// This method overrides the Auth token set by method `Client.SetAuthToken`.
func (r *Request) SetAuthToken(token string) *Request {
r.Token = token
return r
}
// SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:
// Authorization: <auth-scheme-value-set-here> <auth-token-value>
//
// For Example: To set the scheme to use OAuth
//
// client.R().SetAuthScheme("OAuth")
//
// This auth header scheme gets added to all the request rasied from this client instance.
// Also it can be overridden or set one at the request level is supported.
//
// Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing
// the currently defined official authentication schemes:
// https://tools.ietf.org/html/rfc7235
// https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
//
// This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.
func (r *Request) SetAuthScheme(scheme string) *Request {
r.AuthScheme = scheme
return r
}
// SetOutput method sets the output file for current HTTP request. Current HTTP response will be
// saved into given file. It is similar to `curl -o` flag. Absolute path or relative path can be used.
// If is it relative path then output file goes under the output directory, as mentioned
// in the `Client.SetOutputDirectory`.
// resty.R().
// client.R().
// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
// Get("http://bit.ly/1LouEKr")
//
@ -371,7 +444,7 @@ func (r *Request) SetOutput(file string) *Request {
// SetSRV method sets the details to query the service SRV record and execute the
// request.
// resty.R().
// client.R().
// SetSRV(SRVRecord{"web", "testservice.com"}).
// Get("/get")
func (r *Request) SetSRV(srv *SRVRecord) *Request {
@ -383,7 +456,7 @@ func (r *Request) SetSRV(srv *SRVRecord) *Request {
// Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
// otherwise you might get into connection leaks, no connection reuse.
//
// Please Note: Response middlewares are not applicable, if you use this option. Basically you have
// Note: Response middlewares are not applicable, if you use this option. Basically you have
// taken over the control of response parsing from `Resty`.
func (r *Request) SetDoNotParseResponse(parse bool) *Request {
r.notParseResponse = parse
@ -391,8 +464,8 @@ func (r *Request) SetDoNotParseResponse(parse bool) *Request {
}
// SetPathParams method sets multiple URL path key-value pairs at one go in the
// resty current request instance.
// resty.R().SetPathParams(map[string]string{
// Resty current request instance.
// client.R().SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// })
@ -416,17 +489,122 @@ func (r *Request) ExpectContentType(contentType string) *Request {
return r
}
// ForceContentType method provides a strong sense of response `Content-Type` for automatic unmarshalling.
// Resty will respect it with higher priority; even response `Content-Type` response header value is available.
func (r *Request) ForceContentType(contentType string) *Request {
r.forceContentType = contentType
return r
}
// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
//
// NOTE: This option only applicable to standard JSON Marshaller.
// Note: This option only applicable to standard JSON Marshaller.
func (r *Request) SetJSONEscapeHTML(b bool) *Request {
r.jsonEscapeHTML = b
return r
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// SetCookie method appends a single cookie in the current request instance.
// client.R().SetCookie(&http.Cookie{
// Name:"go-resty",
// Value:"This is cookie value",
// })
//
// Note: Method appends the Cookie value into existing Cookie if already existing.
//
// Since v2.1.0
func (r *Request) SetCookie(hc *http.Cookie) *Request {
r.Cookies = append(r.Cookies, hc)
return r
}
// SetCookies method sets an array of cookies in the current request instance.
// cookies := []*http.Cookie{
// &http.Cookie{
// Name:"go-resty-1",
// Value:"This is cookie 1 value",
// },
// &http.Cookie{
// Name:"go-resty-2",
// Value:"This is cookie 2 value",
// },
// }
//
// // Setting a cookies into resty's current request
// client.R().SetCookies(cookies)
//
// Note: Method appends the Cookie value into existing Cookie if already existing.
//
// Since v2.1.0
func (r *Request) SetCookies(rs []*http.Cookie) *Request {
r.Cookies = append(r.Cookies, rs...)
return r
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// HTTP request tracing
//_______________________________________________________________________
// EnableTrace method enables trace for the current request
// using `httptrace.ClientTrace` and provides insights.
//
// client := resty.New()
//
// resp, err := client.R().EnableTrace().Get("https://httpbin.org/get")
// fmt.Println("Error:", err)
// fmt.Println("Trace Info:", resp.Request.TraceInfo())
//
// See `Client.EnableTrace` available too to get trace info for all requests.
//
// Since v2.0.0
func (r *Request) EnableTrace() *Request {
r.trace = true
return r
}
// TraceInfo method returns the trace info for the request.
// If either the Client or Request EnableTrace function has not been called
// prior to the request being made, an empty TraceInfo object will be returned.
//
// Since v2.0.0
func (r *Request) TraceInfo() TraceInfo {
ct := r.clientTrace
if ct == nil {
return TraceInfo{}
}
ti := TraceInfo{
DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
TotalTime: ct.endTime.Sub(ct.dnsStart),
IsConnReused: ct.gotConnInfo.Reused,
IsConnWasIdle: ct.gotConnInfo.WasIdle,
ConnIdleTime: ct.gotConnInfo.IdleTime,
}
// Only calcuate on successful connections
if !ct.connectDone.IsZero() {
ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone)
}
// Only calcuate on successful connections
if !ct.gotConn.IsZero() {
ti.ConnTime = ct.gotConn.Sub(ct.getConn)
}
// Only calcuate on successful connections
if !ct.gotFirstResponseByte.IsZero() {
ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte)
}
return ti
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// HTTP verb method starts here
//___________________________________
//_______________________________________________________________________
// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
func (r *Request) Get(url string) (*Response, error) {
@ -463,15 +641,25 @@ func (r *Request) Patch(url string) (*Response, error) {
return r.Execute(MethodPatch, url)
}
// Send method performs the HTTP request using the method and URL already defined
// for current `Request`.
// req := client.R()
// req.Method = resty.GET
// req.URL = "http://httpbin.org/get"
// resp, err := client.R().Send()
func (r *Request) Send() (*Response, error) {
return r.Execute(r.Method, r.URL)
}
// Execute method performs the HTTP request with given HTTP method and URL
// for current `Request`.
// resp, err := resty.R().Execute(resty.GET, "http://httpbin.org/get")
//
// resp, err := client.R().Execute(resty.GET, "http://httpbin.org/get")
func (r *Request) Execute(method, url string) (*Response, error) {
var addrs []*net.SRV
var resp *Response
var err error
if r.isMultiPart && !(method == MethodPost || method == MethodPut) {
if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) {
return nil, fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method)
}
@ -486,12 +674,12 @@ func (r *Request) Execute(method, url string) (*Response, error) {
r.URL = r.selectAddr(addrs, url, 0)
if r.client.RetryCount == 0 {
return r.client.execute(r)
resp, err = r.client.execute(r)
return resp, unwrapNoRetryErr(err)
}
var resp *Response
attempt := 0
_ = Backoff(
err = Backoff(
func() (*Response, error) {
attempt++
@ -499,12 +687,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
resp, err = r.client.execute(r)
if err != nil {
r.client.Log.Printf("ERROR %v, Attempt %v", err, attempt)
if r.isContextCancelledIfAvailable() {
// stop Backoff from retrying request if request has been
// canceled by context
return resp, nil
}
r.client.log.Errorf("%v, Attempt %v", err, attempt)
}
return resp, err
@ -515,16 +698,30 @@ func (r *Request) Execute(method, url string) (*Response, error) {
RetryConditions(r.client.RetryConditions),
)
return resp, err
return resp, unwrapNoRetryErr(err)
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Unexported methods
//___________________________________
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// SRVRecord struct
//_______________________________________________________________________
func (r *Request) fmtBodyString() (body string) {
// SRVRecord struct holds the data to query the SRV record for the
// following service.
type SRVRecord struct {
Service string
Domain string
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Unexported methods
//_______________________________________________________________________
func (r *Request) fmtBodyString(sl int64) (body string) {
body = "***** NO CONTENT *****"
if isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
return
}
if _, ok := r.Body.(io.Reader); ok {
body = "***** BODY IS io.Reader *****"
return
@ -532,6 +729,11 @@ func (r *Request) fmtBodyString() (body string) {
// multipart or form-data
if r.isMultiPart || r.isFormData {
bodySize := int64(r.bodyBuf.Len())
if bodySize > sl {
body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
return
}
body = r.bodyBuf.String()
return
}
@ -559,15 +761,21 @@ func (r *Request) fmtBodyString() (body string) {
}
} else {
body = b
return
}
} else if b, ok := r.Body.([]byte); ok {
body = base64.StdEncoding.EncodeToString(b)
body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
return
}
if prtBodyBytes != nil && err == nil {
body = string(prtBodyBytes)
}
if len(body) > 0 {
bodySize := int64(len([]byte(body)))
if bodySize > sl {
body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
}
}
return
@ -584,3 +792,18 @@ func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string
return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
}
func (r *Request) initValuesMap() {
if r.values == nil {
r.values = make(map[string]interface{})
}
}
var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
buf := acquireBuffer()
defer releaseBuffer(buf)
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(v)
return buf.Bytes(), err
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
@ -13,7 +13,11 @@ import (
"time"
)
// Response is an object represents executed request and its values.
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response struct and methods
//_______________________________________________________________________
// Response struct holds response values of executed request.
type Response struct {
Request *Request
RawResponse *http.Response
@ -24,6 +28,7 @@ type Response struct {
}
// Body method returns HTTP response as []byte array for the executed request.
//
// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
func (r *Response) Body() []byte {
if r.RawResponse == nil {
@ -38,7 +43,6 @@ func (r *Response) Status() string {
if r.RawResponse == nil {
return ""
}
return r.RawResponse.Status
}
@ -48,10 +52,17 @@ func (r *Response) StatusCode() int {
if r.RawResponse == nil {
return 0
}
return r.RawResponse.StatusCode
}
// Proto method returns the HTTP response protocol used for the request.
func (r *Response) Proto() string {
if r.RawResponse == nil {
return ""
}
return r.RawResponse.Proto
}
// Result method returns the response value as an object if it has one
func (r *Response) Result() interface{} {
return r.Request.Result
@ -67,7 +78,6 @@ func (r *Response) Header() http.Header {
if r.RawResponse == nil {
return http.Header{}
}
return r.RawResponse.Header
}
@ -76,7 +86,6 @@ func (r *Response) Cookies() []*http.Cookie {
if r.RawResponse == nil {
return make([]*http.Cookie, 0)
}
return r.RawResponse.Cookies()
}
@ -85,14 +94,17 @@ func (r *Response) String() string {
if r.body == nil {
return ""
}
return strings.TrimSpace(string(r.body))
}
// Time method returns the time of HTTP response time that from request we sent and received a request.
// See `response.ReceivedAt` to know when client recevied response and see `response.Request.Time` to know
//
// See `Response.ReceivedAt` to know when client recevied response and see `Response.Request.Time` to know
// when client sent a request.
func (r *Response) Time() time.Duration {
if r.Request.clientTrace != nil {
return r.Request.TraceInfo().TotalTime
}
return r.receivedAt.Sub(r.Request.Time)
}
@ -120,16 +132,27 @@ func (r *Response) RawBody() io.ReadCloser {
return r.RawResponse.Body
}
// IsSuccess method returns true if HTTP status code >= 200 and <= 299 otherwise false.
// IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false.
func (r *Response) IsSuccess() bool {
return r.StatusCode() > 199 && r.StatusCode() < 300
}
// IsError method returns true if HTTP status code >= 400 otherwise false.
// IsError method returns true if HTTP status `code >= 400` otherwise false.
func (r *Response) IsError() bool {
return r.StatusCode() > 399
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response Unexported methods
//_______________________________________________________________________
func (r *Response) setReceivedAt() {
r.receivedAt = time.Now()
if r.Request.clientTrace != nil {
r.Request.clientTrace.endTime = r.receivedAt
}
}
func (r *Response) fmtBodyString(sl int64) string {
if r.body != nil {
if int64(len(r.body)) > sl {
@ -139,9 +162,11 @@ func (r *Response) fmtBodyString(sl int64) string {
if IsJSONType(ct) {
out := acquireBuffer()
defer releaseBuffer(out)
if err := json.Indent(out, r.body, "", " "); err == nil {
return out.String()
err := json.Indent(out, r.body, "", " ")
if err != nil {
return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String())
}
return out.String()
}
return r.String()
}

40
vendor/github.com/go-resty/resty/v2/resty.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// Package resty provides Simple HTTP and REST client library for Go.
package resty
import (
"net"
"net/http"
"net/http/cookiejar"
"golang.org/x/net/publicsuffix"
)
// Version # of resty
const Version = "2.3.0"
// New method creates a new Resty client.
func New() *Client {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
return createClient(&http.Client{
Jar: cookieJar,
})
}
// NewWithClient method creates a new Resty client with given `http.Client`.
func NewWithClient(hc *http.Client) *Client {
return createClient(hc)
}
// NewWithLocalAddr method creates a new Resty client with given Local Address
// to dial from.
func NewWithLocalAddr(localAddr net.Addr) *Client {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
return createClient(&http.Client{
Jar: cookieJar,
Transport: createTransport(localAddr),
})
}

181
vendor/github.com/go-resty/resty/v2/retry.go generated vendored Normal file
View File

@ -0,0 +1,181 @@
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"context"
"math"
"math/rand"
"time"
)
const (
defaultMaxRetries = 3
defaultWaitTime = time.Duration(100) * time.Millisecond
defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
)
type (
// Option is to create convenient retry options like wait time, max retries, etc.
Option func(*Options)
// RetryConditionFunc type is for retry condition function
// input: non-nil Response OR request execution error
RetryConditionFunc func(*Response, error) bool
// RetryAfterFunc returns time to wait before retry
// For example, it can parse HTTP Retry-After header
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
// Non-nil error is returned if it is found that request is not retryable
// (0, nil) is a special result means 'use default algorithm'
RetryAfterFunc func(*Client, *Response) (time.Duration, error)
// Options struct is used to hold retry settings.
Options struct {
maxRetries int
waitTime time.Duration
maxWaitTime time.Duration
retryConditions []RetryConditionFunc
}
)
// Retries sets the max number of retries
func Retries(value int) Option {
return func(o *Options) {
o.maxRetries = value
}
}
// WaitTime sets the default wait time to sleep between requests
func WaitTime(value time.Duration) Option {
return func(o *Options) {
o.waitTime = value
}
}
// MaxWaitTime sets the max wait time to sleep between requests
func MaxWaitTime(value time.Duration) Option {
return func(o *Options) {
o.maxWaitTime = value
}
}
// RetryConditions sets the conditions that will be checked for retry.
func RetryConditions(conditions []RetryConditionFunc) Option {
return func(o *Options) {
o.retryConditions = conditions
}
}
// Backoff retries with increasing timeout duration up until X amount of retries
// (Default is 3 attempts, Override with option Retries(n))
func Backoff(operation func() (*Response, error), options ...Option) error {
// Defaults
opts := Options{
maxRetries: defaultMaxRetries,
waitTime: defaultWaitTime,
maxWaitTime: defaultMaxWaitTime,
retryConditions: []RetryConditionFunc{},
}
for _, o := range options {
o(&opts)
}
var (
resp *Response
err error
)
for attempt := 0; attempt <= opts.maxRetries; attempt++ {
resp, err = operation()
ctx := context.Background()
if resp != nil && resp.Request.ctx != nil {
ctx = resp.Request.ctx
}
if ctx.Err() != nil {
return err
}
err1 := unwrapNoRetryErr(err) // raw error, it used for return users callback.
needsRetry := err != nil && err == err1 // retry on a few operation errors by default
for _, condition := range opts.retryConditions {
needsRetry = condition(resp, err1)
if needsRetry {
break
}
}
if !needsRetry {
return err
}
waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
if err2 != nil {
if err == nil {
err = err2
}
return err
}
select {
case <-time.After(waitTime):
case <-ctx.Done():
return ctx.Err()
}
}
return err
}
func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
const maxInt = 1<<31 - 1 // max int for arch 386
if max < 0 {
max = maxInt
}
if resp == nil {
goto defaultCase
}
// 1. Check for custom callback
if retryAfterFunc := resp.Request.client.RetryAfter; retryAfterFunc != nil {
result, err := retryAfterFunc(resp.Request.client, resp)
if err != nil {
return 0, err // i.e. 'API quota exceeded'
}
if result == 0 {
goto defaultCase
}
if result < 0 || max < result {
result = max
}
if result < min {
result = min
}
return result, nil
}
// 2. Return capped exponential backoff with jitter
// http://www.awsarchitectureblog.com/2015/03/backoff.html
defaultCase:
base := float64(min)
capLevel := float64(max)
temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
ri := int(temp / 2)
if ri <= 0 {
ri = maxInt // max int for arch 386
}
result := time.Duration(math.Abs(float64(ri + rand.Intn(ri))))
if result < min {
result = min
}
return result, nil
}

122
vendor/github.com/go-resty/resty/v2/trace.go generated vendored Normal file
View File

@ -0,0 +1,122 @@
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"context"
"crypto/tls"
"net/http/httptrace"
"time"
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// TraceInfo struct
//_______________________________________________________________________
// TraceInfo struct is used provide request trace info such as DNS lookup
// duration, Connection obtain duration, Server processing duration, etc.
//
// Since v2.0.0
type TraceInfo struct {
// DNSLookup is a duration that transport took to perform
// DNS lookup.
DNSLookup time.Duration
// ConnTime is a duration that took to obtain a successful connection.
ConnTime time.Duration
// TCPConnTime is a duration that took to obtain the TCP connection.
TCPConnTime time.Duration
// TLSHandshake is a duration that TLS handshake took place.
TLSHandshake time.Duration
// ServerTime is a duration that server took to respond first byte.
ServerTime time.Duration
// ResponseTime is a duration since first response byte from server to
// request completion.
ResponseTime time.Duration
// TotalTime is a duration that total request took end-to-end.
TotalTime time.Duration
// IsConnReused is whether this connection has been previously
// used for another HTTP request.
IsConnReused bool
// IsConnWasIdle is whether this connection was obtained from an
// idle pool.
IsConnWasIdle bool
// ConnIdleTime is a duration how long the connection was previously
// idle, if IsConnWasIdle is true.
ConnIdleTime time.Duration
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// CientTrace struct and its methods
//_______________________________________________________________________
// tracer struct maps the `httptrace.ClientTrace` hooks into Fields
// with same naming for easy understanding. Plus additional insights
// Request.
type clientTrace struct {
getConn time.Time
dnsStart time.Time
dnsDone time.Time
connectDone time.Time
tlsHandshakeStart time.Time
tlsHandshakeDone time.Time
gotConn time.Time
gotFirstResponseByte time.Time
endTime time.Time
gotConnInfo httptrace.GotConnInfo
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Trace unexported methods
//_______________________________________________________________________
func (t *clientTrace) createContext(ctx context.Context) context.Context {
return httptrace.WithClientTrace(
ctx,
&httptrace.ClientTrace{
DNSStart: func(_ httptrace.DNSStartInfo) {
t.dnsStart = time.Now()
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
t.dnsDone = time.Now()
},
ConnectStart: func(_, _ string) {
if t.dnsDone.IsZero() {
t.dnsDone = time.Now()
}
if t.dnsStart.IsZero() {
t.dnsStart = t.dnsDone
}
},
ConnectDone: func(net, addr string, err error) {
t.connectDone = time.Now()
},
GetConn: func(_ string) {
t.getConn = time.Now()
},
GotConn: func(ci httptrace.GotConnInfo) {
t.gotConn = time.Now()
t.gotConnInfo = ci
},
GotFirstResponseByte: func() {
t.gotFirstResponseByte = time.Now()
},
TLSHandshakeStart: func() {
t.tlsHandshakeStart = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
t.tlsHandshakeDone = time.Now()
},
},
)
}

35
vendor/github.com/go-resty/resty/v2/transport.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
// +build go1.13
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"net"
"net/http"
"runtime"
"time"
)
func createTransport(localAddr net.Addr) *http.Transport {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
if localAddr != nil {
dialer.LocalAddr = localAddr
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
}

34
vendor/github.com/go-resty/resty/v2/transport112.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
// +build !go1.13
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"net"
"net/http"
"runtime"
"time"
)
func createTransport(localAddr net.Addr) *http.Transport {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
if localAddr != nil {
dialer.LocalAddr = localAddr
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
@ -6,7 +6,6 @@ package resty
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
@ -22,9 +21,52 @@ import (
"strings"
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Logger interface
//_______________________________________________________________________
// Logger interface is to abstract the logging from Resty. Gives control to
// the Resty users, choice of the logger.
type Logger interface {
Errorf(format string, v ...interface{})
Warnf(format string, v ...interface{})
Debugf(format string, v ...interface{})
}
func createLogger() *logger {
l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
return l
}
var _ Logger = (*logger)(nil)
type logger struct {
l *log.Logger
}
func (l *logger) Errorf(format string, v ...interface{}) {
l.output("ERROR RESTY "+format, v...)
}
func (l *logger) Warnf(format string, v ...interface{}) {
l.output("WARN RESTY "+format, v...)
}
func (l *logger) Debugf(format string, v ...interface{}) {
l.output("DEBUG RESTY "+format, v...)
}
func (l *logger) output(format string, v ...interface{}) {
if len(v) == 0 {
l.l.Print(format)
return
}
l.l.Printf(format, v...)
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Helper methods
//___________________________________
//_______________________________________________________________________
// IsStringEmpty method tells whether given string is empty or not
func IsStringEmpty(str string) bool {
@ -61,18 +103,6 @@ func IsXMLType(ct string) bool {
return xmlCheck.MatchString(ct)
}
// Unmarshal content into object from JSON or XML
// Deprecated: kept for backward compatibility
func Unmarshal(ct string, b []byte, d interface{}) (err error) {
if IsJSONType(ct) {
err = json.Unmarshal(b, d)
} else if IsXMLType(ct) {
err = xml.Unmarshal(b, d)
}
return
}
// Unmarshalc content into object from JSON or XML
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
if IsJSONType(ct) {
@ -84,9 +114,9 @@ func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
return
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// RequestLog and ResponseLog type
//___________________________________
//_______________________________________________________________________
// RequestLog struct is used to collected information from resty request
// instance for debug logging. It sent to request log callback before resty
@ -104,9 +134,9 @@ type ResponseLog struct {
Body string
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Unexported methods
//___________________________________
//_______________________________________________________________________
// way to disable the HTML escape as opt-in
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
@ -127,10 +157,6 @@ func firstNonEmpty(v ...string) string {
return ""
}
func getLogger(w io.Writer) *log.Logger {
return log.New(w, "RESTY ", log.LstdFlags)
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
@ -139,9 +165,19 @@ func escapeQuotes(s string) string {
func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
hdr := make(textproto.MIMEHeader)
hdr.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes(param), escapeQuotes(fileName)))
hdr.Set("Content-Type", contentType)
var contentDispositionValue string
if IsStringEmpty(fileName) {
contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
} else {
contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
param, escapeQuotes(fileName))
}
hdr.Set("Content-Disposition", contentDispositionValue)
if !IsStringEmpty(contentType) {
hdr.Set(hdrContentTypeKey, contentType)
}
return hdr
}
@ -249,22 +285,40 @@ func releaseBuffer(buf *bytes.Buffer) {
func closeq(v interface{}) {
if c, ok := v.(io.Closer); ok {
sliently(c.Close())
silently(c.Close())
}
}
func sliently(_ ...interface{}) {}
func silently(_ ...interface{}) {}
func composeHeaders(hdrs http.Header) string {
var str []string
func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
str := make([]string, 0, len(hdrs))
for _, k := range sortHeaderKeys(hdrs) {
str = append(str, fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
var v string
if k == "Cookie" {
cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
if c.GetClient().Jar != nil {
for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
if cv != "" {
cv = cv + "; " + c.String()
} else {
cv = c.String()
}
}
}
v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
} else {
v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
}
if v != "" {
str = append(str, "\t"+v)
}
}
return strings.Join(str, "\n")
}
func sortHeaderKeys(hdrs http.Header) []string {
var keys []string
keys := make([]string, 0, len(hdrs))
for key := range hdrs {
keys = append(keys, key)
}
@ -279,3 +333,25 @@ func copyHeaders(hdrs http.Header) http.Header {
}
return nh
}
type noRetryErr struct {
err error
}
func (e *noRetryErr) Error() string {
return e.err.Error()
}
func wrapNoRetryErr(err error) error {
if err != nil {
err = &noRetryErr{err: err}
}
return err
}
func unwrapNoRetryErr(err error) error {
if e, ok := err.(*noRetryErr); ok {
err = e.err
}
return err
}

47
vendor/github.com/linode/linodego/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,47 @@
run:
tests: false
linters-settings:
errcheck:
check-type-assertions: true
check-blank: true
govet:
check-shadowing: true
enable:
- atomicalign
enable-all: false
disable:
- shadow
disable-all: false
golint:
min-confidence: 0.8
gocyclo:
min-complexity: 30
gocognit:
min-complexity: 30
maligned:
suggest-new: true
dupl:
threshold: 100
linters:
enable-all: true
disable:
- vetshadow
- gocyclo
- unparam
- nakedret
- lll
- dupl
- gosec
- gochecknoinits
- gochecknoglobals
- errcheck
- staticcheck
- stylecheck
- wsl
- interfacer
- gomnd
fast: false

View File

@ -4,19 +4,18 @@ matrix:
allow_failures:
- go: tip
go:
- "1.10"
- tip
env:
- GO111MODULE=on
install:
- go get -u gopkg.in/alecthomas/gometalinter.v2
- gometalinter.v2 --install
go:
- "1.13"
script:
- touch .env
- make test ARGS='-v -race -count=2 -coverprofile=coverage.txt -covermode=atomic ./...'
- gometalinter.v2 --enable-all --disable=vetshadow --disable=gocyclo --disable=unparam --disable=nakedret --disable=lll --disable=dupl --disable=gosec --disable=gochecknoinits --disable=gochecknoglobals --disable=test --deadline=120s
- gometalinter.v2 --disable-all --enable=vetshadow --enable=gocyclo --enable=unparam --enable=nakedret --enable=lll --enable=dupl --enable=gosec --enable=gochecknoinits --enable=gochecknoglobals --deadline=120s || true
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0
- go mod download
- go mod vendor
- make test ARGS='-race -count=2 -coverprofile=coverage.txt -covermode=atomic ./...'
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +1,19 @@
# API Support
This document tracks LinodeGo support for the features of the [Linode API](https://developers.linode.com/changelog/api/).
Endpoints are implemented as needed, by need or user-request. As new features are added (as reported in the [Linode API Changelog](https://developers.linode.com/changelog/api/)) this document should be updated to reflect any missing endpoints. New or deprecated fields should also be indicated below the affected HTTP method, for example:
```markdown
- `/fake/endpoint`
- [x] `GET`
* 4.0.29 field X is not implemented <http://...>
- [ ] `POST`
* 4.0.30 added support to create Fake things <http://...>
```
See `template.go` and `template_test.go` for tips on adding new endpoints.
## Linodes
- `/linode/instances`
@ -68,11 +82,11 @@
### IPs
- `/linode/instances/$id/ips`
- [ ] `GET`
- [ ] `POST`
- [X] `GET`
- [X] `POST`
- `/linode/instances/$id/ips/$ip_address`
- [ ] `GET`
- [ ] `PUT`
- [X] `GET`
- [X] `PUT`
- [ ] `DELETE`
- `/linode/instances/$id/ips/sharing`
- [ ] `POST`
@ -97,9 +111,9 @@
### Stats
- `/linode/instances/$id/stats`
- [ ] `GET`
- [X] `GET`
- `/linode/instances/$id/stats/$year/$month`
- [ ] `GET`
- [X] `GET`
### Types
@ -127,6 +141,27 @@
- [X] `PUT`
- [X] `DELETE`
## LKE
- `/lke/clusters`
- [X] `POST`
- [X] `GET`
- [X] `PUT`
- [X] `DELETE`
- `/lke/clusters/$id/pools`
- [X] `POST`
- [X] `GET`
- [X] `PUT`
- [X] `DELETE`
- `/lke/clusters/$id/api-endpoint`
- [X] `GET`
- `/lke/clusters/$id/kubeconfig`
- [X] `GET`
- `/lke/clusters/$id/versions`
- [X] `GET`
- `/lke/clusters/$id/versions/$id`
- [X] `GET`
## Longview
- `/longview/clients`
@ -153,6 +188,8 @@
- [X] `GET`
- [X] `PUT`
- [X] `DELETE`
- `/nodebalancers/$id/stats`
- [X] `GET`
### NodeBalancer Configs
@ -181,7 +218,7 @@
- [ ] `POST`
- `/networking/ips/$address`
- [X] `GET`
- [ ] `PUT`
- [X] `PUT`
- [ ] `DELETE`
### IPv6
@ -191,9 +228,9 @@
- `/networking/ips/$address`
- [X] `GET`
- [ ] `PUT`
- /networking/ipv6/ranges
- `/networking/ipv6/ranges`
- [X] `GET`
- /networking/ipv6/pools
- `/networking/ipv6/pools`
- [X] `GET`
## Regions
@ -255,25 +292,50 @@
### OAuth Clients
- `/account/oauth-clients`
- [ ] `GET`
- [ ] `POST`
- [X] `GET`
- [X] `POST`
- `/account/oauth-clients/$id`
- [ ] `GET`
- [ ] `PUT`
- [ ] `DELETE`
- [X] `GET`
- [X] `PUT`
- [X] `DELETE`
- `/account/oauth-clients/$id/reset_secret`
- [ ] `POST`
- `/account/oauth-clients/$id/thumbnail`
- [ ] `GET`
- [ ] `PUT`
### Object Storage Keys
- `/object-storage/keys`
- [X] `GET`
- [X] `POST`
- `/object-storage/keys/$id`
- [X] `GET`
- [X] `PUT`
- [X] `DELETE`
### Object Storage Clusters
- `/object-storage/clusters`
- [X] `GET`
- `/object-storage/clusters/$id`
- [X] `GET`
### Object Storage Buckets
- `/object-storage/buckets`
- [X] `GET`
- [X] `POST`
- `/object-storage/buckets/$id/$id`
- [X] `GET`
- [X] `DELETE`
### Payments
- `/account/payments`
- [ ] `GET`
- [ ] `POST`
- [X] `GET`
- [X] `POST`
- `/account/payments/$id`
- [ ] `GET`
- [X] `GET`
- `/account/payments/paypal`
- [ ] `GET`
- `/account/payments/paypal/execute`
@ -282,8 +344,8 @@
### Settings
- `/account/settings`
- [ ] `GET`
- [ ] `PUT`
- [X] `GET`
- [X] `PUT`
### Users

View File

@ -1,239 +1 @@
# Change Log
## Unreleased
### Fixes
### Features
<a name-"v0.7.1"></a>
## [v0.7.1](https://github.com/linode/linodego/compare/v0.7.0..v0.7.1) (2018-02-05)
### Features
* add `ClassDedicated` constant (`dedicated`) for use in `LinodeType` `Class` values
See the [Dedicated CPU Announcement](https://blog.linode.com/2019/02/05/introducing-linode-dedicated-cpu-instances/)
<a name-"v0.7.0"></a>
## [v0.7.0](https://github.com/linode/linodego/compare/v0.6.2..v0.7.0) (2018-12-03)
### Features
* add `Tags` field in: `NodeBalancer`, `Domain`, `Volume`
* add `UpdateIPAddress` (for setting RDNS)
### Fixes
* invalid URL for `/v4/networking/` enpoints (IPv6 Ranges and Pools) has been correcrted
<a name-"v0.6.2"></a>
## [v0.6.2](https://github.com/linode/linodego/compare/v0.6.1..v0.6.2) (2018-10-26)
### Fixes
* add missing `Account` fields: `address_1`, `address_2`, `phone`
<a name-"v0.6.1"></a>
## [v0.6.1](https://github.com/linode/linodego/compare/v0.6.0..v0.6.1) (2018-10-26)
### Features
* Adds support for fetching and updating basic Profile information
<a name-"v0.6.0"></a>
## [v0.6.0](https://github.com/linode/linodego/compare/v0.5.1..v0.6.0) (2018-10-25)
### Fixes
* Fixes Image date handling
* Fixes broken example code in README
* Fixes WaitForEventFinished when encountering events without entity
* Fixes ResizeInstanceDisk which was executing CloneInstanceDisk
* Fixes go-resty import path to gopkg.in version for future go module support
### Features
* Adds support for user account operations
* Adds support for profile tokens
* Adds support for Tags
* Adds PasswordResetInstanceDisk
* Adds DiskStatus constants
* Adds WaitForInstanceDiskStatus
* Adds SetPollDelay for configuring poll duration
* Reduced polling time to millisecond granularity
* Change polling default to 3s to avoid 429 conditions
* Use poll delay in waitfor functions
<a name="v0.5.1"></a>
## [v0.5.1](https://github.com/linode/linodego/compare/v0.5.0...v0.5.1) (2018-09-10)
### Fixes
* Domain.Status was not imported from API responses correctly
<a name="v0.5.0"></a>
## [v0.5.0](https://github.com/linode/linodego/compare/v0.4.0...v0.5.0) (2018-09-09)
### Breaking Changes
* List functions return slice of thing instead of slice of pointer to thing
### Feature
* add SSHKeys methods to client (also affects InstanceCreate, InstanceDiskCreate)
* add RebuildNodeBalancerConfig (and CreateNodeBalancerConfig with Nodes)
### Fixes
* Event.TimeRemaining wouldn't parse all possible API value
* Tests no longer rely on known/special instance and volume ids
<a name="0.4.0"></a>
## [0.4.0](https://github.com/linode/linodego/compare/v0.3.0...0.4.0) (2018-08-27)
### Breaking Changes
Replaces bool, error results with error results, for:
* instance\_snapshots.go: EnableInstanceBackups
* instance\_snapshots.go: CancelInstanceBackups
* instance\_snapshots.go: RestoreInstanceBackup
* instances.go: BootInstance
* instances.go: RebootInstance
* instances.go: MutateInstance
* instances.go: RescueInstance
* instances.go: ResizeInstance
* instances.go: ShutdownInstance
* volumes.go: DetachVolume
* volumes.go: ResizeVolume
### Docs
* reword text about breaking changes until first tag
### Feat
* added MigrateInstance and InstanceResizing from 4.0.1-4.0.3 API Changelog
* added gometalinter to travis builds
* added missing function and type comments as reported by linting tools
* supply json values for all fields, useful for mocking responses using linodego types
* use context channels in WaitFor\* functions
* add LinodeTypeClass type (enum)
* add TicketStatus type (enum)
* update template thing and add a test template
### Fix
* TransferQuota was TransferQuote (and not parsed from the api correctly)
* stackscripts udf was not parsed correctly
* add InstanceCreateOptions.PrivateIP
* check the WaitFor timeout before sleeping to avoid extra sleep
* various linting warnings and unhandled err results as reported by linting tools
* fix GetStackscript 404 handling
<a name="0.3.0"></a>
## [0.3.0](https://github.com/linode/linodego/compare/v0.2.0...0.3.0) (2018-08-15)
### Breaking Changes
* WaitForVolumeLinodeID return fetch volume for consistency with out WaitFors
* Moved linodego from chiefy to github.com/linode. Thanks [@chiefy](https://github.com/chiefy)!
<a name="v0.2.0"></a>
## [v0.2.0](https://github.com/linode/linodego/compare/v0.1.1...v0.2.0) (2018-08-11)
### Breaking Changes
* WaitFor\* should be client methods
*use `client.WaitFor...` rather than `linodego.WaitFor(..., client, ...)`*
* remove ListInstanceSnapshots (does not exist in the API)
*this never worked, so shouldn't cause a problem*
* Changes UpdateOptions and CreateOptions and similar Options parameters to values instead of pointers
*these were never optional and the function never updated any values in the Options structures*
* fixed various optional/zero Update and Create options
*some values are now pointers, and vice-versa*
* Changes InstanceUpdateOptions to use pointers for optional fields Backups and Alerts
* Changes InstanceClone's Disks and Configs to ints instead of strings
* using new enum string aliased types where appropriate
*`InstanceSnapshotStatus`, `DiskFilesystem`, `NodeMode`*
### Feature
* add RescueInstance and RescueInstanceOptions
* add CreateImage, UpdateImage, DeleteImage
* add EnableInstanceBackups, CancelInstanceBackups, RestoreInstanceBackup
* add WatchdogEnabled to InstanceUpdateOptions
### Fix
* return Volume from AttachVolume instead of bool
* add more boilerplate to template.go
* nodebalancers and domain records had no pagination support
* NodeBalancer transfer stats are not int
### Tests
* add fixtures and tests for NodeBalancerNodes
* fix nodebalancer tests to handle changes due to random labels
* add tests for nodebalancers and nodebalancer configs
* added tests for Backups flow
* TestListInstanceBackups fixture is hand tweaked because repeated polled events
appear to get the tests stuck
### Deps
* update all dependencies to latest
<a name="v0.1.1"></a>
## [v0.1.1](https://github.com/linode/linodego/compare/v0.0.1...v0.1.0) (2018-07-30)
Adds more Domain handling
### Fixed
* go-resty doesnt pass errors when content-type is not set
* Domain, DomainRecords, tests and fixtures
### Added
* add CreateDomainRecord, UpdateDomainRecord, and DeleteDomainRecord
<a name="v0.1.0"></a>
## [v0.1.0](https://github.com/linode/linodego/compare/v0.0.1...v0.1.0) (2018-07-23)
Deals with NewClient and context for all http requests
### Breaking Changes
* changed `NewClient(token, *http.RoundTripper)` to `NewClient(*http.Client)`
* changed all `Client` `Get`, `List`, `Create`, `Update`, `Delete`, and `Wait` calls to take context as the first parameter
### Fixed
* fixed docs should now show Examples for more functions
### Added
* added `Client.SetBaseURL(url string)`
<a name="v0.0.1"></a>
## v0.0.1 (2018-07-20)
### Changed
* Initial tagged release
Release notes for this project are kept here: https://github.com/linode/linodego/releases

View File

@ -1,111 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:6e1c13bc32e58ccb4afa1115a3ba4fc071d918ed897b40dfa323ffb3fcc6619d"
name = "github.com/dnaeon/go-vcr"
packages = [
"cassette",
"recorder",
]
pruneopts = "UT"
revision = "aafff18a5cc28fa0b2f26baf6a14472cda9b54c6"
[[projects]]
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
name = "github.com/golang/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:33b9d71d1dde2106309484a388eb7ba53cd1f67014e34a71f7b3dbc20bd186e5"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
"idna",
"publicsuffix",
]
pruneopts = "UT"
revision = "8a410e7b638dca158bf9e766925842f6651ff828"
[[projects]]
branch = "master"
digest = "1:363b547c971a2b07474c598b6e9ebcb238d556d8a27f37b3895ad20cd50e7281"
name = "golang.org/x/oauth2"
packages = [
".",
"internal",
]
pruneopts = "UT"
revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
digest = "1:328b5e4f197d928c444a51a75385f4b978915c0e75521f0ad6a3db976c97a7d3"
name = "google.golang.org/appengine"
packages = [
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch",
]
pruneopts = "UT"
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
version = "v1.1.0"
[[projects]]
digest = "1:b7fc4c3fd91df516486f53cc86f4b55a0c815782dbe852c5a19cce8e6c577aac"
name = "gopkg.in/resty.v1"
packages = ["."]
pruneopts = "UT"
revision = "d4920dcf5b7689548a6db640278a9b35a5b48ec6"
version = "v1.9.1"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/dnaeon/go-vcr/recorder",
"golang.org/x/oauth2",
"gopkg.in/resty.v1",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,29 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[prune]
go-tests = true
unused-packages = true

View File

@ -1,35 +1,61 @@
include .env
-include .env
BIN_DIR := $(GOPATH)/bin
.PHONY: vendor example refresh-fixtures clean-fixtures
GOLANGCILINT := golangci-lint
GOLANGCILINT_IMG := golangci/golangci-lint:v1.23-alpine
GOLANGCILINT_ARGS := run
.PHONY: test
test: vendor
PACKAGES := $(shell go list ./... | grep -v integration)
SKIP_LINT ?= 0
.PHONY: build vet test refresh-fixtures clean-fixtures lint run_fixtures sanitize fixtures godoc testint testunit
test: testunit testint
citest: lint test
testunit: build lint
go test -v $(PACKAGES) $(ARGS)
testint: build lint
@LINODE_FIXTURE_MODE="play" \
LINODE_TOKEN="awesometokenawesometokenawesometoken" \
go test $(ARGS)
LINODE_API_VERSION="v4beta" \
GO111MODULE="on" \
go test -v ./test/integration $(ARGS)
$(GOPATH)/bin/dep:
@go get -u github.com/golang/dep/cmd/dep
build: vet lint
go build ./...
vendor: $(GOPATH)/bin/dep
@dep ensure
vet:
go vet ./...
example:
@go run example/main.go
lint:
ifeq ($(SKIP_LINT), 1)
@echo Skipping lint stage
else
docker run --rm -v $(shell pwd):/app -w /app $(GOLANGCILINT_IMG) $(GOLANGCILINT) run
endif
clean-fixtures:
@-rm fixtures/*.yaml
refresh-fixtures: clean-fixtures fixtures
.PHONY: fixtures
fixtures:
run_fixtures:
@echo "* Running fixtures"
@LINODE_TOKEN=$(LINODE_TOKEN) \
LINODE_FIXTURE_MODE="record" go test $(ARGS)
@echo "* Santizing fixtures"
@for yaml in fixtures/*yaml; do \
sed -E -i "" -e "s/$(LINODE_TOKEN)/awesometokenawesometokenawesometoken/g" \
@LINODE_FIXTURE_MODE="record" \
LINODE_TOKEN=$(LINODE_TOKEN) \
LINODE_API_VERSION="v4beta" \
GO111MODULE="on" \
go test -timeout=60m -v ./test/integration $(ARGS)
sanitize:
@echo "* Sanitizing fixtures"
@for yaml in test/integration/fixtures/*yaml; do \
sed -E -i.bak \
-e 's_stats/20[0-9]{2}/[1-9][0-2]?_stats/2018/1_g' \
-e 's/20[0-9]{2}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-9]{2}:[0-9]{2}/2018-01-02T03:04:05/g' \
-e 's/nb-[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}\./nb-10-20-30-40./g' \
-e 's/192\.168\.((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.)(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])/192.168.030.040/g' \
@ -37,7 +63,9 @@ fixtures:
-e 's/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/1234::5678/g' \
$$yaml; \
done
@find test/integration/fixtures -name *yaml.bak -exec rm {} \;
fixtures: run_fixtures sanitize
.PHONY: godoc
godoc:
@godoc -http=:6060

View File

@ -1,6 +1,7 @@
# linodego
[![Build Status](https://travis-ci.org/linode/linodego.svg?branch=master)](https://travis-ci.org/linode/linodego)
[![Release](https://img.shields.io/github/v/release/linode/linodego)](https://github.com/linode/linodego/releases/latest)
[![GoDoc](https://godoc.org/github.com/linode/linodego?status.svg)](https://godoc.org/github.com/linode/linodego)
[![Go Report Card](https://goreportcard.com/badge/github.com/linode/linodego)](https://goreportcard.com/report/github.com/linode/linodego)
[![codecov](https://codecov.io/gh/linode/linodego/branch/master/graph/badge.svg)](https://codecov.io/gh/linode/linodego)
@ -84,7 +85,7 @@ kernels, err := linodego.ListKernels(context.Background(), nil)
Or, use a page value of "0":
```go
opts := NewListOptions(0,"")
opts := linodego.NewListOptions(0,"")
kernels, err := linodego.ListKernels(context.Background(), opts)
// len(kernels) == 218
```
@ -92,8 +93,8 @@ kernels, err := linodego.ListKernels(context.Background(), opts)
#### Single Page
```go
opts := NewListOptions(2,"")
// or opts := ListOptions{PageOptions: &PageOptions: {Page: 2 }}
opts := linodego.NewListOptions(2,"")
// or opts := linodego.ListOptions{PageOptions: &PageOptions: {Page: 2 }}
kernels, err := linodego.ListKernels(context.Background(), opts)
// len(kernels) == 100
```
@ -108,8 +109,8 @@ values are set in the supplied ListOptions.
#### Filtering
```go
opts := ListOptions{Filter: "{\"mine\":true}"}
// or opts := NewListOptions(0, "{\"mine\":true}")
opts := linodego.ListOptions{Filter: "{\"mine\":true}"}
// or opts := linodego.NewListOptions(0, "{\"mine\":true}")
stackscripts, err := linodego.ListStackscripts(context.Background(), opts)
```
@ -118,7 +119,7 @@ stackscripts, err := linodego.ListStackscripts(context.Background(), opts)
#### Getting Single Entities
```go
linode, err := linodego.GetLinode(context.Background(), 555) // any Linode ID that does not exist or is not yours
linode, err := linodego.GetInstance(context.Background(), 555) // any Linode ID that does not exist or is not yours
// linode == nil: true
// err.Error() == "[404] Not Found"
// err.Code == "404"
@ -130,7 +131,7 @@ linode, err := linodego.GetLinode(context.Background(), 555) // any Linode ID th
For lists, the list is still returned as `[]`, but `err` works the same way as on the `Get` request.
```go
linodes, err := linodego.ListLinodes(context.Background(), NewListOptions(0, "{\"foo\":bar}"))
linodes, err := linodego.ListInstances(context.Background(), linodego.NewListOptions(0, "{\"foo\":bar}"))
// linodes == []
// err.Error() == "[400] [X-Filter] Cannot filter on foo"
```
@ -138,7 +139,7 @@ linodes, err := linodego.ListLinodes(context.Background(), NewListOptions(0, "{\
Otherwise sane requests beyond the last page do not trigger an error, just an empty result:
```go
linodes, err := linodego.ListLinodes(context.Background(), NewListOptions(9999, ""))
linodes, err := linodego.ListInstances(context.Background(), linodego.NewListOptions(9999, ""))
// linodes == []
// err = nil
```
@ -153,15 +154,9 @@ When performing a `POST` or `PUT` request, multiple field related errors will be
## Tests
Run `make test` to run the unit tests. This is the same as running `go test` except that `make test` will
execute the tests while playing back API response fixtures that were recorded during a previous development build.
Run `make testunit` to run the unit tests.
`go test` can be used without the fixtures. Copy `env.sample` to `.env` and configure your persistent test
settings, including an API token.
`go test -short` can be used to run live API tests that do not require an account token.
This will be simplified in future versions.
Run `make testint` to run the integration tests. The integration tests use fixtures.
To update the test fixtures, run `make fixtures`. This will record the API responses into the `fixtures/` directory.
Be careful about committing any sensitive account details. An attempt has been made to sanitize IP addresses and

View File

@ -11,6 +11,7 @@ type Account struct {
Address1 string `json:"address_1"`
Address2 string `json:"address_2"`
Balance float32 `json:"balance"`
BalanceUninvoiced float32 `json:"balance_uninvoiced"`
City string `json:"city"`
State string `json:"state"`
Zip string `json:"zip"`
@ -26,20 +27,18 @@ type CreditCard struct {
Expiry string `json:"expiry"`
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *Account) fixDates() *Account {
return v
}
// GetAccount gets the contact and billing information related to the Account
func (c *Client) GetAccount(ctx context.Context) (*Account, error) {
e, err := c.Account.Endpoint()
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Account{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Account).fixDates(), nil
return r.Result().(*Account), nil
}

View File

@ -4,16 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/linode/linodego/internal/duration"
"github.com/linode/linodego/internal/parseabletime"
)
// Event represents an action taken on the Account.
type Event struct {
CreatedStr string `json:"created"`
// The unique ID of this Event.
ID int `json:"id"`
@ -36,7 +34,6 @@ type Event struct {
Seen bool `json:"seen"`
// The estimated time remaining until the completion of this Event. This value is only returned for in-progress events.
TimeRemainingMsg json.RawMessage `json:"time_remaining"`
TimeRemaining *int `json:"-"`
// The username of the User who caused the Event.
@ -45,6 +42,9 @@ type Event struct {
// Detailed information about the Event's entity, including ID, type, label, and URL used to access it.
Entity *EventEntity `json:"entity"`
// Detailed information about the Event's secondary or related entity, including ID, type, label, and URL used to access it.
SecondaryEntity *EventEntity `json:"secondary_entity"`
// When this Event was created.
Created *time.Time `json:"-"`
}
@ -54,59 +54,80 @@ type EventAction string
// EventAction constants represent the actions that cause an Event. New actions may be added in the future.
const (
ActionAccountUpdate EventAction = "account_update"
ActionAccountSettingsUpdate EventAction = "account_settings_update"
ActionBackupsEnable EventAction = "backups_enable"
ActionBackupsCancel EventAction = "backups_cancel"
ActionBackupsRestore EventAction = "backups_restore"
ActionCommunityQuestionReply EventAction = "community_question_reply"
ActionCommunityLike EventAction = "community_like"
ActionCreateCardUpdated EventAction = "credit_card_updated"
ActionDiskCreate EventAction = "disk_create"
ActionDiskDelete EventAction = "disk_delete"
ActionDiskUpdate EventAction = "disk_update"
ActionDiskDuplicate EventAction = "disk_duplicate"
ActionDiskImagize EventAction = "disk_imagize"
ActionDiskResize EventAction = "disk_resize"
ActionDNSRecordCreate EventAction = "dns_record_create"
ActionDNSRecordDelete EventAction = "dns_record_delete"
ActionDNSRecordUpdate EventAction = "dns_record_update"
ActionDNSZoneCreate EventAction = "dns_zone_create"
ActionDNSZoneDelete EventAction = "dns_zone_delete"
ActionDNSZoneUpdate EventAction = "dns_zone_update"
ActionHostReboot EventAction = "host_reboot"
ActionImageDelete EventAction = "image_delete"
ActionImageUpdate EventAction = "image_update"
ActionLassieReboot EventAction = "lassie_reboot"
ActionLinodeAddIP EventAction = "linode_addip"
ActionLinodeBoot EventAction = "linode_boot"
ActionLinodeClone EventAction = "linode_clone"
ActionLinodeCreate EventAction = "linode_create"
ActionLinodeDelete EventAction = "linode_delete"
ActionLinodeUpdate EventAction = "linode_update"
ActionLinodeDeleteIP EventAction = "linode_deleteip"
ActionLinodeMigrate EventAction = "linode_migrate"
ActionLinodeMutate EventAction = "linode_mutate"
ActionLinodeMutateCreate EventAction = "linode_mutate_create"
ActionLinodeReboot EventAction = "linode_reboot"
ActionLinodeRebuild EventAction = "linode_rebuild"
ActionLinodeResize EventAction = "linode_resize"
ActionLinodeResizeCreate EventAction = "linode_resize_create"
ActionLinodeShutdown EventAction = "linode_shutdown"
ActionLinodeSnapshot EventAction = "linode_snapshot"
ActionLinodeConfigCreate EventAction = "linode_config_create"
ActionLinodeConfigDelete EventAction = "linode_config_delete"
ActionLinodeConfigUpdate EventAction = "linode_config_update"
ActionLishBoot EventAction = "lish_boot"
ActionLongviewClientCreate EventAction = "longviewclient_create"
ActionLongviewClientDelete EventAction = "longviewclient_delete"
ActionLongviewClientUpdate EventAction = "longviewclient_update"
ActionManagedDisabled EventAction = "managed_disabled"
ActionManagedEnabled EventAction = "managed_enabled"
ActionManagedServiceCreate EventAction = "managed_service_create"
ActionManagedServiceDelete EventAction = "managed_service_delete"
ActionNodebalancerCreate EventAction = "nodebalancer_create"
ActionNodebalancerDelete EventAction = "nodebalancer_delete"
ActionNodebalancerUpdate EventAction = "nodebalancer_update"
ActionNodebalancerConfigCreate EventAction = "nodebalancer_config_create"
ActionNodebalancerConfigDelete EventAction = "nodebalancer_config_delete"
ActionNodebalancerConfigUpdate EventAction = "nodebalancer_config_update"
ActionPasswordReset EventAction = "password_reset"
ActionPaymentSubmitted EventAction = "payment_submitted"
ActionStackScriptCreate EventAction = "stackscript_create"
ActionStackScriptDelete EventAction = "stackscript_delete"
ActionStackScriptUpdate EventAction = "stackscript_update"
ActionStackScriptPublicize EventAction = "stackscript_publicize"
ActionStackScriptRevise EventAction = "stackscript_revise"
ActionTFADisabled EventAction = "tfa_disabled"
ActionTFAEnabled EventAction = "tfa_enabled"
ActionTicketAttachmentUpload EventAction = "ticket_attachment_upload"
ActionTicketCreate EventAction = "ticket_create"
ActionTicketReply EventAction = "ticket_reply"
ActionTicketUpdate EventAction = "ticket_update"
ActionVolumeAttach EventAction = "volume_attach"
ActionVolumeClone EventAction = "volume_clone"
ActionVolumeCreate EventAction = "volume_create"
ActionVolumeDelte EventAction = "volume_delete"
ActionVolumeUpdate EventAction = "volume_update"
ActionVolumeDetach EventAction = "volume_detach"
ActionVolumeResize EventAction = "volume_resize"
)
@ -114,10 +135,12 @@ const (
// EntityType constants start with Entity and include Linode API Event Entity Types
type EntityType string
// EntityType contants are the entities an Event can be related to
// EntityType contants are the entities an Event can be related to.
const (
EntityLinode EntityType = "linode"
EntityDisk EntityType = "disk"
EntityDomain EntityType = "domain"
EntityNodebalancer EntityType = "nodebalancer"
)
// EventStatus constants start with Event and include Linode API Event Status values
@ -155,16 +178,41 @@ func (EventsPagedResponse) endpoint(c *Client) string {
if err != nil {
panic(err)
}
return endpoint
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Event) UnmarshalJSON(b []byte) error {
type Mask Event
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
TimeRemaining json.RawMessage `json:"time_remaining"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.TimeRemaining = duration.UnmarshalTimeRemaining(p.TimeRemaining)
return nil
}
// endpointWithID gets the endpoint URL for a specific Event
func (e Event) endpointWithID(c *Client) string {
func (i Event) endpointWithID(c *Client) string {
endpoint, err := c.Events.Endpoint()
if err != nil {
panic(err)
}
endpoint = fmt.Sprintf("%s/%d", endpoint, e.ID)
endpoint = fmt.Sprintf("%s/%d", endpoint, i.ID)
return endpoint
}
@ -179,12 +227,11 @@ func (resp *EventsPagedResponse) appendData(r *EventsPagedResponse) {
func (c *Client) ListEvents(ctx context.Context, opts *ListOptions) ([]Event, error) {
response := EventsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
@ -194,19 +241,15 @@ func (c *Client) GetEvent(ctx context.Context, id int) (*Event, error) {
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := c.R(ctx).SetResult(&Event{}).Get(e)
if err != nil {
return nil, err
}
return r.Result().(*Event).fixDates(), nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (e *Event) fixDates() *Event {
e.Created, _ = parseDates(e.CreatedStr)
e.TimeRemaining = unmarshalTimeRemaining(e.TimeRemainingMsg)
return e
return r.Result().(*Event), nil
}
// MarkEventRead marks a single Event as read.
@ -228,50 +271,3 @@ func (c *Client) MarkEventsSeen(ctx context.Context, event *Event) error {
return err
}
func unmarshalTimeRemaining(m json.RawMessage) *int {
jsonBytes, err := m.MarshalJSON()
if err != nil {
panic(jsonBytes)
}
if len(jsonBytes) == 4 && string(jsonBytes) == "null" {
return nil
}
var timeStr string
if err := json.Unmarshal(jsonBytes, &timeStr); err == nil && len(timeStr) > 0 {
if dur, err := durationToSeconds(timeStr); err != nil {
panic(err)
} else {
return &dur
}
} else {
var intPtr int
if err := json.Unmarshal(jsonBytes, &intPtr); err == nil {
return &intPtr
}
}
log.Println("[WARN] Unexpected unmarshalTimeRemaining value: ", jsonBytes)
return nil
}
// durationToSeconds takes a hh:mm:ss string and returns the number of seconds
func durationToSeconds(s string) (int, error) {
multipliers := [3]int{60 * 60, 60, 1}
segs := strings.Split(s, ":")
if len(segs) > len(multipliers) {
return 0, fmt.Errorf("too many ':' separators in time duration: %s", s)
}
var d int
l := len(segs)
for i := 0; i < l; i++ {
m, err := strconv.Atoi(segs[i])
if err != nil {
return 0, err
}
d += m * multipliers[i+len(multipliers)-l]
}
return d, nil
}

View File

@ -2,14 +2,15 @@ package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Invoice structs reflect an invoice for billable activity on the account.
type Invoice struct {
DateStr string `json:"date"`
ID int `json:"id"`
Label string `json:"label"`
Total float32 `json:"total"`
@ -18,9 +19,6 @@ type Invoice struct {
// InvoiceItem structs reflect an single billable activity associate with an Invoice
type InvoiceItem struct {
FromStr string `json:"from"`
ToStr string `json:"to"`
Label string `json:"label"`
Type string `json:"type"`
UnitPrice int `json:"unitprice"`
@ -42,6 +40,7 @@ func (InvoicesPagedResponse) endpoint(c *Client) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -54,26 +53,54 @@ func (resp *InvoicesPagedResponse) appendData(r *InvoicesPagedResponse) {
func (c *Client) ListInvoices(ctx context.Context, opts *ListOptions) ([]Invoice, error) {
response := InvoicesPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *Invoice) fixDates() *Invoice {
v.Date, _ = parseDates(v.DateStr)
return v
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Invoice) UnmarshalJSON(b []byte) error {
type Mask Invoice
p := struct {
*Mask
Date *parseabletime.ParseableTime `json:"date"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Date = (*time.Time)(p.Date)
return nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *InvoiceItem) fixDates() *InvoiceItem {
v.From, _ = parseDates(v.FromStr)
v.To, _ = parseDates(v.ToStr)
return v
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *InvoiceItem) UnmarshalJSON(b []byte) error {
type Mask InvoiceItem
p := struct {
*Mask
From *parseabletime.ParseableTime `json:"from"`
To *parseabletime.ParseableTime `json:"to"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.From = (*time.Time)(p.From)
i.To = (*time.Time)(p.To)
return nil
}
// GetInvoice gets the a single Invoice matching the provided ID
@ -85,10 +112,12 @@ func (c *Client) GetInvoice(ctx context.Context, id int) (*Invoice, error) {
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Invoice{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Invoice).fixDates(), nil
return r.Result().(*Invoice), nil
}
// InvoiceItemsPagedResponse represents a paginated Invoice Item API response
@ -103,6 +132,7 @@ func (InvoiceItemsPagedResponse) endpointWithID(c *Client, id int) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -115,11 +145,10 @@ func (resp *InvoiceItemsPagedResponse) appendData(r *InvoiceItemsPagedResponse)
func (c *Client) ListInvoiceItems(ctx context.Context, id int, opts *ListOptions) ([]InvoiceItem, error) {
response := InvoiceItemsPagedResponse{}
err := c.listHelperWithID(ctx, &response, id, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}

View File

@ -2,14 +2,14 @@ package linodego
import (
"context"
"encoding/json"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Notification represents a notification on an Account
type Notification struct {
UntilStr string `json:"until"`
WhenStr string `json:"when"`
Label string `json:"label"`
Body *string `json:"body"`
Message string `json:"message"`
@ -68,6 +68,7 @@ func (NotificationsPagedResponse) endpoint(c *Client) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -84,18 +85,32 @@ func (resp *NotificationsPagedResponse) appendData(r *NotificationsPagedResponse
func (c *Client) ListNotifications(ctx context.Context, opts *ListOptions) ([]Notification, error) {
response := NotificationsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *Notification) fixDates() *Notification {
v.Until, _ = parseDates(v.UntilStr)
v.When, _ = parseDates(v.WhenStr)
return v
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Notification) UnmarshalJSON(b []byte) error {
type Mask Notification
p := struct {
*Mask
Until *parseabletime.ParseableTime `json:"until"`
When *parseabletime.ParseableTime `json:"when"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Until = (*time.Time)(p.Until)
i.When = (*time.Time)(p.When)
return nil
}

View File

@ -0,0 +1,207 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
)
// OAuthClientStatus constants start with OAuthClient and include Linode API Instance Status values
type OAuthClientStatus string
// OAuthClientStatus constants reflect the current status of an OAuth Client
const (
OAuthClientActive OAuthClientStatus = "active"
OAuthClientDisabled OAuthClientStatus = "disabled"
OAuthClientSuspended OAuthClientStatus = "suspended"
)
// OAuthClient represents a OAuthClient object
type OAuthClient struct {
// The unique ID of this OAuth Client.
ID string `json:"id"`
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// Current status of the OAuth Client, Enum: "active" "disabled" "suspended"
Status OAuthClientStatus `json:"status"`
// The OAuth Client secret, used in the OAuth exchange. This is returned as <REDACTED> except when an OAuth Client is created or its secret is reset. This is a secret, and should not be shared or disclosed publicly.
Secret string `json:"secret"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
// The URL where this client's thumbnail may be viewed, or nil if this client does not have a thumbnail set.
ThumbnailURL *string `json:"thumbnail_url"`
}
// OAuthClientCreateOptions fields are those accepted by CreateOAuthClient
type OAuthClientCreateOptions struct {
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
}
// OAuthClientUpdateOptions fields are those accepted by UpdateOAuthClient
type OAuthClientUpdateOptions struct {
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
}
// GetCreateOptions converts a OAuthClient to OAuthClientCreateOptions for use in CreateOAuthClient
func (i OAuthClient) GetCreateOptions() (o OAuthClientCreateOptions) {
o.RedirectURI = i.RedirectURI
o.Label = i.Label
o.Public = i.Public
return
}
// GetUpdateOptions converts a OAuthClient to OAuthClientUpdateOptions for use in UpdateOAuthClient
func (i OAuthClient) GetUpdateOptions() (o OAuthClientUpdateOptions) {
o.RedirectURI = i.RedirectURI
o.Label = i.Label
o.Public = i.Public
return
}
// OAuthClientsPagedResponse represents a paginated OAuthClient API response
type OAuthClientsPagedResponse struct {
*PageOptions
Data []OAuthClient `json:"data"`
}
// endpoint gets the endpoint URL for OAuthClient
func (OAuthClientsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.OAuthClients.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends OAuthClients when processing paginated OAuthClient responses
func (resp *OAuthClientsPagedResponse) appendData(r *OAuthClientsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListOAuthClients lists OAuthClients
func (c *Client) ListOAuthClients(ctx context.Context, opts *ListOptions) ([]OAuthClient, error) {
response := OAuthClientsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetOAuthClient gets the OAuthClient with the provided ID
func (c *Client) GetOAuthClient(ctx context.Context, id string) (*OAuthClient, error) {
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&OAuthClient{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// CreateOAuthClient creates an OAuthClient
func (c *Client) CreateOAuthClient(ctx context.Context, createOpts OAuthClientCreateOptions) (*OAuthClient, error) {
var body string
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&OAuthClient{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// UpdateOAuthClient updates the OAuthClient with the specified id
func (c *Client) UpdateOAuthClient(ctx context.Context, id string, updateOpts OAuthClientUpdateOptions) (*OAuthClient, error) {
var body string
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&OAuthClient{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// DeleteOAuthClient deletes the OAuthClient with the specified id
func (c *Client) DeleteOAuthClient(ctx context.Context, id string) error {
e, err := c.OAuthClients.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

136
vendor/github.com/linode/linodego/account_payments.go generated vendored Normal file
View File

@ -0,0 +1,136 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Payment represents a Payment object
type Payment struct {
// The unique ID of the Payment
ID int `json:"id"`
// The amount, in US dollars, of the Payment.
USD json.Number `json:"usd,Number"`
// When the Payment was made.
Date *time.Time `json:"-"`
}
// PaymentCreateOptions fields are those accepted by CreatePayment
type PaymentCreateOptions struct {
// CVV (Card Verification Value) of the credit card to be used for the Payment
CVV string `json:"cvv,omitempty"`
// The amount, in US dollars, of the Payment
USD json.Number `json:"usd,Number"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Payment) UnmarshalJSON(b []byte) error {
type Mask Payment
p := struct {
*Mask
Date *parseabletime.ParseableTime `json:"date"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Date = (*time.Time)(p.Date)
return nil
}
// GetCreateOptions converts a Payment to PaymentCreateOptions for use in CreatePayment
func (i Payment) GetCreateOptions() (o PaymentCreateOptions) {
o.USD = i.USD
return
}
// PaymentsPagedResponse represents a paginated Payment API response
type PaymentsPagedResponse struct {
*PageOptions
Data []Payment `json:"data"`
}
// endpoint gets the endpoint URL for Payment
func (PaymentsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Payments.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends Payments when processing paginated Payment responses
func (resp *PaymentsPagedResponse) appendData(r *PaymentsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListPayments lists Payments
func (c *Client) ListPayments(ctx context.Context, opts *ListOptions) ([]Payment, error) {
response := PaymentsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetPayment gets the payment with the provided ID
func (c *Client) GetPayment(ctx context.Context, id int) (*Payment, error) {
e, err := c.Payments.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Payment{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Payment), nil
}
// CreatePayment creates a Payment
func (c *Client) CreatePayment(ctx context.Context, createOpts PaymentCreateOptions) (*Payment, error) {
var body string
e, err := c.Payments.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Payment{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*Payment), nil
}

78
vendor/github.com/linode/linodego/account_settings.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
package linodego
import (
"context"
"encoding/json"
)
// AccountSettings are the account wide flags or plans that effect new resources
type AccountSettings struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
BackupsEnabled bool `json:"backups_enabled"`
// Wether or not Linode Managed service is enabled for the account.
Managed bool `json:"managed"`
// Wether or not the Network Helper is enabled for all new Linode Instance Configs on the account.
NetworkHelper bool `json:"network_helper"`
// A plan name like "longview-3"..."longview-100", or a nil value for to cancel any existing subscription plan.
LongviewSubscription *string `json:"longview_subscription"`
}
// AccountSettingsUpdateOptions are the updateable account wide flags or plans that effect new resources.
type AccountSettingsUpdateOptions struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
BackupsEnabled *bool `json:"backups_enabled,omitempty"`
// A plan name like "longview-3"..."longview-100", or a nil value for to cancel any existing subscription plan.
LongviewSubscription *string `json:"longview_subscription,omitempty"`
// The default network helper setting for all new Linodes and Linode Configs for all users on the account.
NetworkHelper *bool `json:"network_helper,omitempty"`
}
// GetAccountSettings gets the account wide flags or plans that effect new resources
func (c *Client) GetAccountSettings(ctx context.Context) (*AccountSettings, error) {
e, err := c.AccountSettings.Endpoint()
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&AccountSettings{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*AccountSettings), nil
}
// UpdateAccountSettings updates the settings associated with the account
func (c *Client) UpdateAccountSettings(ctx context.Context, settings AccountSettingsUpdateOptions) (*AccountSettings, error) {
var body string
e, err := c.AccountSettings.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&AccountSettings{})
if bodyData, err := json.Marshal(settings); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*AccountSettings), nil
}

View File

@ -34,6 +34,7 @@ func (i User) GetCreateOptions() (o UserCreateOptions) {
o.Username = i.Username
o.Email = i.Email
o.Restricted = i.Restricted
return
}
@ -42,6 +43,7 @@ func (i User) GetUpdateOptions() (o UserUpdateOptions) {
o.Username = i.Username
o.Email = i.Email
o.Restricted = copyBool(&i.Restricted)
return
}
@ -57,6 +59,7 @@ func (UsersPagedResponse) endpoint(c *Client) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -69,18 +72,12 @@ func (resp *UsersPagedResponse) appendData(r *UsersPagedResponse) {
func (c *Client) ListUsers(ctx context.Context, opts *ListOptions) ([]User, error) {
response := UsersPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *User) fixDates() *User {
return i
return response.Data, nil
}
// GetUser gets the user with the provided ID
@ -89,19 +86,24 @@ func (c *Client) GetUser(ctx context.Context, id string) (*User, error) {
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&User{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
return r.Result().(*User), nil
}
// CreateUser creates a User. The email address must be confirmed before the
// User account can be accessed.
func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (*User, error) {
var body string
e, err := c.Users.Endpoint()
if err != nil {
return nil, err
}
@ -121,16 +123,20 @@ func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
return r.Result().(*User), nil
}
// UpdateUser updates the User with the specified id
func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdateOptions) (*User, error) {
var body string
e, err := c.Users.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&User{})
@ -148,7 +154,8 @@ func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdat
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
return r.Result().(*User), nil
}
// DeleteUser deletes the User with the specified id
@ -157,8 +164,10 @@ func (c *Client) DeleteUser(ctx context.Context, id string) error {
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -3,28 +3,37 @@ package linodego
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"time"
"gopkg.in/resty.v1"
"github.com/go-resty/resty/v2"
)
const (
// APIHost Linode API hostname
APIHost = "api.linode.com"
// APIHostVar environment var to check for alternate API URL
APIHostVar = "LINODE_URL"
// APIHostCert environment var containing path to CA cert to validate against
APIHostCert = "LINODE_CA"
// APIVersion Linode API version
APIVersion = "v4"
// APIVersionVar environment var to check for alternate API Version
APIVersionVar = "LINODE_API_VERSION"
// APIProto connect to API with http(s)
APIProto = "https"
// Version of linodego
Version = "0.7.0"
Version = "0.12.0"
// APIEnvVar environment var to check for API token
APIEnvVar = "LINODE_TOKEN"
// APISecondsPerPoll how frequently to poll for new Events or Status in WaitFor functions
APISecondsPerPoll = 3
// Maximum wait time for retries
APIRetryMaxWaitTime = time.Duration(30) * time.Second
// DefaultUserAgent is the default User-Agent sent in HTTP request headers
DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
)
@ -39,45 +48,58 @@ type Client struct {
userAgent string
resources map[string]*Resource
debug bool
retryConditionals []RetryConditional
millisecondsPerPoll time.Duration
Images *Resource
InstanceDisks *Resource
InstanceConfigs *Resource
InstanceSnapshots *Resource
InstanceIPs *Resource
InstanceVolumes *Resource
Instances *Resource
Account *Resource
AccountSettings *Resource
DomainRecords *Resource
Domains *Resource
Events *Resource
Firewalls *Resource
IPAddresses *Resource
IPv6Pools *Resource
IPv6Ranges *Resource
Regions *Resource
StackScripts *Resource
Volumes *Resource
Images *Resource
InstanceConfigs *Resource
InstanceDisks *Resource
InstanceIPs *Resource
InstanceSnapshots *Resource
InstanceStats *Resource
InstanceVolumes *Resource
Instances *Resource
InvoiceItems *Resource
Invoices *Resource
Kernels *Resource
Types *Resource
Domains *Resource
DomainRecords *Resource
LKEClusters *Resource
LKEClusterPools *Resource
LKEVersions *Resource
Longview *Resource
LongviewClients *Resource
LongviewSubscriptions *Resource
NodeBalancers *Resource
Managed *Resource
NodeBalancerConfigs *Resource
NodeBalancerNodes *Resource
SSHKeys *Resource
Tickets *Resource
Tokens *Resource
Token *Resource
Account *Resource
Invoices *Resource
InvoiceItems *Resource
Events *Resource
NodeBalancerStats *Resource
NodeBalancers *Resource
Notifications *Resource
OAuthClients *Resource
ObjectStorageBuckets *Resource
ObjectStorageClusters *Resource
ObjectStorageKeys *Resource
Payments *Resource
Profile *Resource
Managed *Resource
Regions *Resource
SSHKeys *Resource
StackScripts *Resource
Tags *Resource
Tickets *Resource
Token *Resource
Tokens *Resource
Types *Resource
Users *Resource
Volumes *Resource
}
func init() {
@ -90,7 +112,6 @@ func init() {
log.Println("[WARN] LINODE_DEBUG should be an integer, 0 or 1")
}
}
}
// SetUserAgent sets a custom user-agent for HTTP requests
@ -114,6 +135,7 @@ func (c *Client) R(ctx context.Context) *resty.Request {
func (c *Client) SetDebug(debug bool) *Client {
c.debug = debug
c.resty.SetDebug(debug)
return c
}
@ -123,10 +145,50 @@ func (c *Client) SetBaseURL(url string) *Client {
return c
}
// SetAPIVersion sets the version of the API to interface with
func (c *Client) SetAPIVersion(apiVersion string) *Client {
c.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, apiVersion))
return c
}
// SetRootCertificate adds a root certificate to the underlying TLS client config
func (c *Client) SetRootCertificate(path string) *Client {
c.resty.SetRootCertificate(path)
return c
}
// SetToken sets the API token for all requests from this client
// Only necessary if you haven't already provided an http client to NewClient() configured with the token.
func (c *Client) SetToken(token string) *Client {
c.resty.SetHeader("Authorization", fmt.Sprintf("Bearer %s", token))
return c
}
// SetRetries adds retry conditions for "Linode Busy." errors and 429s.
func (c *Client) SetRetries() *Client {
c.
addRetryConditional(linodeBusyRetryCondition).
addRetryConditional(tooManyRequestsRetryCondition).
SetRetryMaxWaitTime(APIRetryMaxWaitTime)
configureRetries(c)
return c
}
func (c *Client) addRetryConditional(retryConditional RetryConditional) *Client {
c.retryConditionals = append(c.retryConditionals, retryConditional)
return c
}
func (c *Client) SetRetryMaxWaitTime(max time.Duration) *Client {
c.resty.SetRetryMaxWaitTime(max)
return c
}
// SetPollDelay sets the number of milliseconds to wait between events or status polls.
// Affects all WaitFor* functions.
// Affects all WaitFor* functions and retries.
func (c *Client) SetPollDelay(delay time.Duration) *Client {
c.millisecondsPerPoll = delay
c.resty.SetRetryWaitTime(delay * time.Millisecond)
return c
}
@ -136,99 +198,165 @@ func (c Client) Resource(resourceName string) *Resource {
if !ok {
log.Fatalf("Could not find resource named '%s', exiting.", resourceName)
}
return selectedResource
}
// NewClient factory to create new Client struct
func NewClient(hc *http.Client) (client Client) {
restyClient := resty.NewWithClient(hc)
client.resty = restyClient
client.SetUserAgent(DefaultUserAgent)
client.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, APIVersion))
client.SetPollDelay(1000 * APISecondsPerPoll)
if hc != nil {
client.resty = resty.NewWithClient(hc)
} else {
client.resty = resty.New()
}
client.SetUserAgent(DefaultUserAgent)
baseURL, baseURLExists := os.LookupEnv(APIHostVar)
if baseURLExists {
client.SetBaseURL(baseURL)
} else {
apiVersion, apiVersionExists := os.LookupEnv(APIVersionVar)
if apiVersionExists {
client.SetAPIVersion(apiVersion)
} else {
client.SetAPIVersion(APIVersion)
}
}
certPath, certPathExists := os.LookupEnv(APIHostCert)
if certPathExists {
cert, err := ioutil.ReadFile(certPath)
if err != nil {
log.Fatalf("[ERROR] Error when reading cert at %s: %s\n", certPath, err.Error())
}
client.SetRootCertificate(certPath)
if envDebug {
log.Printf("[DEBUG] Set API root certificate to %s with contents %s\n", certPath, cert)
}
}
client.
SetPollDelay(1000 * APISecondsPerPoll).
SetRetries().
SetDebug(envDebug)
addResources(&client)
return
}
// nolint
func addResources(client *Client) {
resources := map[string]*Resource{
stackscriptsName: NewResource(&client, stackscriptsName, stackscriptsEndpoint, false, Stackscript{}, StackscriptsPagedResponse{}),
imagesName: NewResource(&client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
instancesName: NewResource(&client, instancesName, instancesEndpoint, false, Instance{}, InstancesPagedResponse{}),
instanceDisksName: NewResource(&client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
instanceConfigsName: NewResource(&client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
instanceSnapshotsName: NewResource(&client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
instanceIPsName: NewResource(&client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
instanceVolumesName: NewResource(&client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
ipaddressesName: NewResource(&client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
ipv6poolsName: NewResource(&client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
ipv6rangesName: NewResource(&client, ipv6rangesName, ipv6rangesEndpoint, false, IPv6Range{}, IPv6RangesPagedResponse{}),
regionsName: NewResource(&client, regionsName, regionsEndpoint, false, Region{}, RegionsPagedResponse{}),
volumesName: NewResource(&client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}),
kernelsName: NewResource(&client, kernelsName, kernelsEndpoint, false, LinodeKernel{}, LinodeKernelsPagedResponse{}),
typesName: NewResource(&client, typesName, typesEndpoint, false, LinodeType{}, LinodeTypesPagedResponse{}),
domainsName: NewResource(&client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
domainRecordsName: NewResource(&client, domainRecordsName, domainRecordsEndpoint, true, DomainRecord{}, DomainRecordsPagedResponse{}),
longviewName: NewResource(&client, longviewName, longviewEndpoint, false, nil, nil), // really?
longviewclientsName: NewResource(&client, longviewclientsName, longviewclientsEndpoint, false, LongviewClient{}, LongviewClientsPagedResponse{}),
longviewsubscriptionsName: NewResource(&client, longviewsubscriptionsName, longviewsubscriptionsEndpoint, false, LongviewSubscription{}, LongviewSubscriptionsPagedResponse{}),
nodebalancersName: NewResource(&client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
nodebalancerconfigsName: NewResource(&client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
nodebalancernodesName: NewResource(&client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
notificationsName: NewResource(&client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
sshkeysName: NewResource(&client, sshkeysName, sshkeysEndpoint, false, SSHKey{}, SSHKeysPagedResponse{}),
ticketsName: NewResource(&client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}),
tokensName: NewResource(&client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}),
accountName: NewResource(&client, accountName, accountEndpoint, false, Account{}, nil), // really?
eventsName: NewResource(&client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
invoicesName: NewResource(&client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
invoiceItemsName: NewResource(&client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
profileName: NewResource(&client, profileName, profileEndpoint, false, nil, nil), // really?
managedName: NewResource(&client, managedName, managedEndpoint, false, nil, nil), // really?
tagsName: NewResource(&client, tagsName, tagsEndpoint, false, Tag{}, TagsPagedResponse{}),
usersName: NewResource(&client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}),
accountName: NewResource(client, accountName, accountEndpoint, false, Account{}, nil), // really?
accountSettingsName: NewResource(client, accountSettingsName, accountSettingsEndpoint, false, AccountSettings{}, nil), // really?
domainRecordsName: NewResource(client, domainRecordsName, domainRecordsEndpoint, true, DomainRecord{}, DomainRecordsPagedResponse{}),
domainsName: NewResource(client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
eventsName: NewResource(client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
firewallsName: NewResource(client, firewallsName, firewallsEndpoint, false, Firewall{}, FirewallsPagedResponse{}),
imagesName: NewResource(client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
instanceConfigsName: NewResource(client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
instanceDisksName: NewResource(client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
instanceIPsName: NewResource(client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
instanceSnapshotsName: NewResource(client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
instanceStatsName: NewResource(client, instanceStatsName, instanceStatsEndpoint, true, InstanceStats{}, nil),
instanceVolumesName: NewResource(client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
instancesName: NewResource(client, instancesName, instancesEndpoint, false, Instance{}, InstancesPagedResponse{}),
invoiceItemsName: NewResource(client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
invoicesName: NewResource(client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
ipaddressesName: NewResource(client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
ipv6poolsName: NewResource(client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
ipv6rangesName: NewResource(client, ipv6rangesName, ipv6rangesEndpoint, false, IPv6Range{}, IPv6RangesPagedResponse{}),
kernelsName: NewResource(client, kernelsName, kernelsEndpoint, false, LinodeKernel{}, LinodeKernelsPagedResponse{}),
lkeClustersName: NewResource(client, lkeClustersName, lkeClustersEndpoint, false, LKECluster{}, LKEClustersPagedResponse{}),
lkeClusterPoolsName: NewResource(client, lkeClusterPoolsName, lkeClusterPoolsEndpoint, true, LKEClusterPool{}, LKEClusterPoolsPagedResponse{}),
lkeVersionsName: NewResource(client, lkeVersionsName, lkeVersionsEndpoint, false, LKEVersion{}, LKEVersionsPagedResponse{}),
longviewName: NewResource(client, longviewName, longviewEndpoint, false, nil, nil), // really?
longviewclientsName: NewResource(client, longviewclientsName, longviewclientsEndpoint, false, LongviewClient{}, LongviewClientsPagedResponse{}),
longviewsubscriptionsName: NewResource(client, longviewsubscriptionsName, longviewsubscriptionsEndpoint, false, LongviewSubscription{}, LongviewSubscriptionsPagedResponse{}),
managedName: NewResource(client, managedName, managedEndpoint, false, nil, nil), // really?
nodebalancerconfigsName: NewResource(client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
nodebalancernodesName: NewResource(client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
nodebalancerStatsName: NewResource(client, nodebalancerStatsName, nodebalancerStatsEndpoint, true, NodeBalancerStats{}, nil),
nodebalancersName: NewResource(client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
notificationsName: NewResource(client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
oauthClientsName: NewResource(client, oauthClientsName, oauthClientsEndpoint, false, OAuthClient{}, OAuthClientsPagedResponse{}),
objectStorageBucketsName: NewResource(client, objectStorageBucketsName, objectStorageBucketsEndpoint, false, ObjectStorageBucket{}, ObjectStorageBucketsPagedResponse{}),
objectStorageClustersName: NewResource(client, objectStorageClustersName, objectStorageClustersEndpoint, false, ObjectStorageCluster{}, ObjectStorageClustersPagedResponse{}),
objectStorageKeysName: NewResource(client, objectStorageKeysName, objectStorageKeysEndpoint, false, ObjectStorageKey{}, ObjectStorageKeysPagedResponse{}),
paymentsName: NewResource(client, paymentsName, paymentsEndpoint, false, Payment{}, PaymentsPagedResponse{}),
profileName: NewResource(client, profileName, profileEndpoint, false, nil, nil), // really?
regionsName: NewResource(client, regionsName, regionsEndpoint, false, Region{}, RegionsPagedResponse{}),
sshkeysName: NewResource(client, sshkeysName, sshkeysEndpoint, false, SSHKey{}, SSHKeysPagedResponse{}),
stackscriptsName: NewResource(client, stackscriptsName, stackscriptsEndpoint, false, Stackscript{}, StackscriptsPagedResponse{}),
tagsName: NewResource(client, tagsName, tagsEndpoint, false, Tag{}, TagsPagedResponse{}),
ticketsName: NewResource(client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}),
tokensName: NewResource(client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}),
typesName: NewResource(client, typesName, typesEndpoint, false, LinodeType{}, LinodeTypesPagedResponse{}),
usersName: NewResource(client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}),
volumesName: NewResource(client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}),
}
client.resources = resources
client.SetDebug(envDebug)
client.Images = resources[imagesName]
client.StackScripts = resources[stackscriptsName]
client.Instances = resources[instancesName]
client.Regions = resources[regionsName]
client.InstanceDisks = resources[instanceDisksName]
client.InstanceConfigs = resources[instanceConfigsName]
client.InstanceSnapshots = resources[instanceSnapshotsName]
client.InstanceIPs = resources[instanceIPsName]
client.InstanceVolumes = resources[instanceVolumesName]
client.Account = resources[accountName]
client.DomainRecords = resources[domainRecordsName]
client.Domains = resources[domainsName]
client.Events = resources[eventsName]
client.Firewalls = resources[firewallsName]
client.IPAddresses = resources[ipaddressesName]
client.IPv6Pools = resources[ipv6poolsName]
client.IPv6Ranges = resources[ipv6rangesName]
client.Volumes = resources[volumesName]
client.Images = resources[imagesName]
client.InstanceConfigs = resources[instanceConfigsName]
client.InstanceDisks = resources[instanceDisksName]
client.InstanceIPs = resources[instanceIPsName]
client.InstanceSnapshots = resources[instanceSnapshotsName]
client.InstanceStats = resources[instanceStatsName]
client.InstanceVolumes = resources[instanceVolumesName]
client.Instances = resources[instancesName]
client.Invoices = resources[invoicesName]
client.Kernels = resources[kernelsName]
client.Types = resources[typesName]
client.Domains = resources[domainsName]
client.DomainRecords = resources[domainRecordsName]
client.LKEClusters = resources[lkeClustersName]
client.LKEClusterPools = resources[lkeClusterPoolsName]
client.LKEVersions = resources[lkeVersionsName]
client.Longview = resources[longviewName]
client.LongviewSubscriptions = resources[longviewsubscriptionsName]
client.NodeBalancers = resources[nodebalancersName]
client.Managed = resources[managedName]
client.NodeBalancerConfigs = resources[nodebalancerconfigsName]
client.NodeBalancerNodes = resources[nodebalancernodesName]
client.NodeBalancerStats = resources[nodebalancerStatsName]
client.NodeBalancers = resources[nodebalancersName]
client.Notifications = resources[notificationsName]
client.OAuthClients = resources[oauthClientsName]
client.ObjectStorageBuckets = resources[objectStorageBucketsName]
client.ObjectStorageClusters = resources[objectStorageClustersName]
client.ObjectStorageKeys = resources[objectStorageKeysName]
client.Payments = resources[paymentsName]
client.Profile = resources[profileName]
client.Regions = resources[regionsName]
client.SSHKeys = resources[sshkeysName]
client.StackScripts = resources[stackscriptsName]
client.Tags = resources[tagsName]
client.Tickets = resources[ticketsName]
client.Tokens = resources[tokensName]
client.Account = resources[accountName]
client.Events = resources[eventsName]
client.Invoices = resources[invoicesName]
client.Profile = resources[profileName]
client.Managed = resources[managedName]
client.Tags = resources[tagsName]
client.Types = resources[typesName]
client.Users = resources[usersName]
return
client.Volumes = resources[volumesName]
}
func copyBool(bPtr *bool) *bool {
if bPtr == nil {
return nil
}
var t = *bPtr
return &t
}
@ -236,7 +364,9 @@ func copyInt(iPtr *int) *int {
if iPtr == nil {
return nil
}
var t = *iPtr
return &t
}
@ -244,7 +374,9 @@ func copyString(sPtr *string) *string {
if sPtr == nil {
return nil
}
var t = *sPtr
return &t
}
@ -252,6 +384,8 @@ func copyTime(tPtr *time.Time) *time.Time {
if tPtr == nil {
return nil
}
var t = *tPtr
return &t
}

View File

@ -77,6 +77,7 @@ func (d DomainRecord) GetUpdateOptions() (du DomainRecordUpdateOptions) {
du.Protocol = copyString(d.Protocol)
du.TTLSec = d.TTLSec
du.Tag = copyString(d.Tag)
return
}
@ -92,6 +93,7 @@ func (DomainRecordsPagedResponse) endpointWithID(c *Client, id int) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -104,15 +106,12 @@ func (resp *DomainRecordsPagedResponse) appendData(r *DomainRecordsPagedResponse
func (c *Client) ListDomainRecords(ctx context.Context, domainID int, opts *ListOptions) ([]DomainRecord, error) {
response := DomainRecordsPagedResponse{}
err := c.listHelperWithID(ctx, &response, domainID, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (d *DomainRecord) fixDates() *DomainRecord {
return d
return response.Data, nil
}
// GetDomainRecord gets the domainrecord with the provided ID
@ -121,18 +120,23 @@ func (c *Client) GetDomainRecord(ctx context.Context, domainID int, id int) (*Do
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&DomainRecord{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*DomainRecord), nil
}
// CreateDomainRecord creates a DomainRecord
func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrecord DomainRecordCreateOptions) (*DomainRecord, error) {
var body string
e, err := c.DomainRecords.endpointWithID(domainID)
if err != nil {
return nil, err
}
@ -143,6 +147,7 @@ func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrec
if err != nil {
return nil, NewError(err)
}
body = string(bodyData)
r, err := coupleAPIErrors(req.
@ -152,16 +157,20 @@ func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrec
if err != nil {
return nil, err
}
return r.Result().(*DomainRecord).fixDates(), nil
return r.Result().(*DomainRecord), nil
}
// UpdateDomainRecord updates the DomainRecord with the specified id
func (c *Client) UpdateDomainRecord(ctx context.Context, domainID int, id int, domainrecord DomainRecordUpdateOptions) (*DomainRecord, error) {
var body string
e, err := c.DomainRecords.endpointWithID(domainID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&DomainRecord{})
@ -179,7 +188,8 @@ func (c *Client) UpdateDomainRecord(ctx context.Context, domainID int, id int, d
if err != nil {
return nil, err
}
return r.Result().(*DomainRecord).fixDates(), nil
return r.Result().(*DomainRecord), nil
}
// DeleteDomainRecord deletes the DomainRecord with the specified id
@ -188,8 +198,10 @@ func (c *Client) DeleteDomainRecord(ctx context.Context, domainID int, id int) e
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -177,6 +177,7 @@ func (d Domain) GetUpdateOptions() (du DomainUpdateOptions) {
du.ExpireSec = d.ExpireSec
du.RefreshSec = d.RefreshSec
du.TTLSec = d.TTLSec
return
}
@ -192,6 +193,7 @@ func (DomainsPagedResponse) endpoint(c *Client) string {
if err != nil {
panic(err)
}
return endpoint
}
@ -204,15 +206,12 @@ func (resp *DomainsPagedResponse) appendData(r *DomainsPagedResponse) {
func (c *Client) ListDomains(ctx context.Context, opts *ListOptions) ([]Domain, error) {
response := DomainsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (d *Domain) fixDates() *Domain {
return d
return response.Data, nil
}
// GetDomain gets the domain with the provided ID
@ -221,18 +220,23 @@ func (c *Client) GetDomain(ctx context.Context, id int) (*Domain, error) {
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Domain{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Domain).fixDates(), nil
return r.Result().(*Domain), nil
}
// CreateDomain creates a Domain
func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (*Domain, error) {
var body string
e, err := c.Domains.Endpoint()
if err != nil {
return nil, err
}
@ -243,6 +247,7 @@ func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (
if err != nil {
return nil, NewError(err)
}
body = string(bodyData)
r, err := coupleAPIErrors(req.
@ -252,16 +257,20 @@ func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (
if err != nil {
return nil, err
}
return r.Result().(*Domain).fixDates(), nil
return r.Result().(*Domain), nil
}
// UpdateDomain updates the Domain with the specified id
func (c *Client) UpdateDomain(ctx context.Context, id int, domain DomainUpdateOptions) (*Domain, error) {
var body string
e, err := c.Domains.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&Domain{})
@ -279,7 +288,8 @@ func (c *Client) UpdateDomain(ctx context.Context, id int, domain DomainUpdateOp
if err != nil {
return nil, err
}
return r.Result().(*Domain).fixDates(), nil
return r.Result().(*Domain), nil
}
// DeleteDomain deletes the Domain with the specified id
@ -288,8 +298,10 @@ func (c *Client) DeleteDomain(ctx context.Context, id int) error {
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -6,7 +6,7 @@ import (
"net/http"
"strings"
"gopkg.in/resty.v1"
"github.com/go-resty/resty/v2"
)
const (
@ -35,6 +35,7 @@ func (r APIErrorReason) Error() string {
if len(r.Field) == 0 {
return r.Reason
}
return fmt.Sprintf("[%s] %s", r.Field, r.Reason)
}
@ -49,10 +50,25 @@ func coupleAPIErrors(r *resty.Response, err error) (*resty.Response, error) {
}
if r.Error() != nil {
// Check that response is of the correct content-type before unmarshalling
expectedContentType := r.Request.Header.Get("Accept")
responseContentType := r.Header().Get("Content-Type")
if responseContentType != expectedContentType {
msg := fmt.Sprintf(
"Unexpected Content-Type: Expected: %v, Received: %v",
expectedContentType,
responseContentType,
)
return nil, NewError(msg)
}
apiError, ok := r.Error().(*APIError)
if !ok || (ok && len(apiError.Errors) == 0) {
return r, nil
}
return nil, NewError(r)
}
@ -60,10 +76,11 @@ func coupleAPIErrors(r *resty.Response, err error) (*resty.Response, error) {
}
func (e APIError) Error() string {
var x []string
x := []string{}
for _, msg := range e.Errors {
x = append(x, msg.Error())
}
return strings.Join(x, "; ")
}

30
vendor/github.com/linode/linodego/firewall_rules.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package linodego
// NetworkProtocol enum type
type NetworkProtocol string
// NetworkProtocol enum values
const (
TCP NetworkProtocol = "TCP"
UDP NetworkProtocol = "UDP"
ICMP NetworkProtocol = "ALL"
)
// NetworkAddresses are arrays of ipv4 and v6 addresses
type NetworkAddresses struct {
IPv4 []string `json:"ipv4"`
IPv6 []string `json:"ipv6"`
}
// A FirewallRule is a whitelist of ports, protocols, and addresses for which traffic should be allowed.
type FirewallRule struct {
Ports string `json:"ports"`
Protocol NetworkProtocol `json:"protocol"`
Addresses NetworkAddresses `json:"addresses"`
}
// FirewallRuleSet is a pair of inbound and outbound rules that specify what network traffic should be allowed.
type FirewallRuleSet struct {
Inbound []FirewallRule `json:"inbound,omitempty"`
Outbound []FirewallRule `json:"outbound,omitempty"`
}

138
vendor/github.com/linode/linodego/firewalls.go generated vendored Normal file
View File

@ -0,0 +1,138 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// FirewallStatus enum type
type FirewallStatus string
// FirewallStatus enums start with Firewall
const (
FirewallEnabled FirewallStatus = "enabled"
FirewallDisabled FirewallStatus = "disabled"
FirewallDeleted FirewallStatus = "deleted"
)
// A Firewall is a set of networking rules (iptables) applied to Devices with which it is associated
type Firewall struct {
ID int `json:"id"`
Label string `json:"label"`
Status FirewallStatus `json:"status"`
Tags []string `json:"tags,omitempty"`
Rules FirewallRuleSet `json:"rules"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
}
// DevicesCreationOptions fields are used when adding devices during the Firewall creation process.
type DevicesCreationOptions struct {
Linodes []string `json:"linodes,omitempty"`
NodeBalancers []string `json:"nodebalancers,omitempty"`
}
// FirewallCreateOptions fields are those accepted by CreateFirewall
type FirewallCreateOptions struct {
Label string `json:"label,omitempty"`
Rules FirewallRuleSet `json:"rules"`
Tags []string `json:"tags,omitempty"`
Devices DevicesCreationOptions `json:"devices,omitempty"`
}
// UnmarshalJSON for Firewall responses
func (i *Firewall) UnmarshalJSON(b []byte) error {
type Mask Firewall
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// FirewallsPagedResponse represents a Linode API response for listing of Cloud Firewalls
type FirewallsPagedResponse struct {
*PageOptions
Data []Firewall `json:"data"`
}
func (FirewallsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Firewalls.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
func (resp *FirewallsPagedResponse) appendData(r *FirewallsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListFirewalls returns a paginated list of Cloud Firewalls
func (c *Client) ListFirewalls(ctx context.Context, opts *ListOptions) ([]Firewall, error) {
response := FirewallsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// CreateFirewall creates a single Firewall with at least one set of inbound or outbound rules
func (c *Client) CreateFirewall(ctx context.Context, createOpts FirewallCreateOptions) (*Firewall, error) {
var body string
e, err := c.Firewalls.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Firewall{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*Firewall), nil
}
// DeleteFirewall deletes a single Firewall with the provided ID
func (c *Client) DeleteFirewall(ctx context.Context, id int) error {
e, err := c.Firewalls.Endpoint()
if err != nil {
return err
}
req := c.R(ctx)
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(req.Delete(e))
return err
}

15
vendor/github.com/linode/linodego/go.mod generated vendored Normal file
View File

@ -0,0 +1,15 @@
module github.com/linode/linodego
require (
github.com/dnaeon/go-vcr v1.0.1
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48
github.com/golang/protobuf v1.2.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
google.golang.org/appengine v1.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.1 // indirect
)
go 1.13

29
vendor/github.com/linode/linodego/go.sum generated vendored Normal file
View File

@ -0,0 +1,29 @@
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -5,12 +5,12 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Image represents a deployable Image object for use with Linode Instances
type Image struct {
CreatedStr string `json:"created"`
ExpiryStr string `json:"expiry"`
ID string `json:"id"`
CreatedBy string `json:"created_by"`
Label string `json:"label"`
@ -20,7 +20,6 @@ type Image struct {
Size int `json:"size"`
IsPublic bool `json:"is_public"`
Deprecated bool `json:"deprecated"`
Created *time.Time `json:"-"`
Expiry *time.Time `json:"-"`
}
@ -38,15 +37,26 @@ type ImageUpdateOptions struct {
Description *string `json:"description,omitempty"`
}
func (i *Image) fixDates() *Image {
i.Created, _ = parseDates(i.CreatedStr)
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Image) UnmarshalJSON(b []byte) error {
type Mask Image
if len(i.ExpiryStr) > 0 {
i.Expiry, _ = parseDates(i.ExpiryStr)
} else {
i.Expiry = nil
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Expiry *parseabletime.ParseableTime `json:"expiry"`
}{
Mask: (*Mask)(i),
}
return i
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Expiry = (*time.Time)(p.Expiry)
return nil
}
// GetUpdateOptions converts an Image to ImageUpdateOptions for use in UpdateImage
@ -78,14 +88,11 @@ func (resp *ImagesPagedResponse) appendData(r *ImagesPagedResponse) {
func (c *Client) ListImages(ctx context.Context, opts *ListOptions) ([]Image, error) {
response := ImagesPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetImage gets the Image with the provided ID
@ -94,18 +101,22 @@ func (c *Client) GetImage(ctx context.Context, id string) (*Image, error) {
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.Images.R(ctx).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Image).fixDates(), nil
return r.Result().(*Image), nil
}
// CreateImage creates a Image
func (c *Client) CreateImage(ctx context.Context, createOpts ImageCreateOptions) (*Image, error) {
var body string
e, err := c.Images.Endpoint()
if err != nil {
return nil, err
}
@ -125,16 +136,18 @@ func (c *Client) CreateImage(ctx context.Context, createOpts ImageCreateOptions)
if err != nil {
return nil, err
}
return r.Result().(*Image).fixDates(), nil
return r.Result().(*Image), nil
}
// UpdateImage updates the Image with the specified id
func (c *Client) UpdateImage(ctx context.Context, id string, updateOpts ImageUpdateOptions) (*Image, error) {
var body string
e, err := c.Images.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&Image{})
@ -152,7 +165,7 @@ func (c *Client) UpdateImage(ctx context.Context, id string, updateOpts ImageUpd
if err != nil {
return nil, err
}
return r.Result().(*Image).fixDates(), nil
return r.Result().(*Image), nil
}
// DeleteImage deletes the Image with the specified id
@ -161,6 +174,7 @@ func (c *Client) DeleteImage(ctx context.Context, id string) error {
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))

View File

@ -5,13 +5,12 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// InstanceConfig represents all of the settings that control the boot and run configuration of a Linode Instance
type InstanceConfig struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Label string `json:"label"`
Comments string `json:"comments"`
@ -90,6 +89,28 @@ type InstanceConfigUpdateOptions struct {
VirtMode string `json:"virt_mode,omitempty"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *InstanceConfig) UnmarshalJSON(b []byte) error {
type Mask InstanceConfig
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// GetCreateOptions converts a InstanceConfig to InstanceConfigCreateOptions for use in CreateInstanceConfig
func (i InstanceConfig) GetCreateOptions() InstanceConfigCreateOptions {
initrd := 0
@ -144,22 +165,13 @@ func (resp *InstanceConfigsPagedResponse) appendData(r *InstanceConfigsPagedResp
func (c *Client) ListInstanceConfigs(ctx context.Context, linodeID int, opts *ListOptions) ([]InstanceConfig, error) {
response := InstanceConfigsPagedResponse{}
err := c.listHelperWithID(ctx, &response, linodeID, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *InstanceConfig) fixDates() *InstanceConfig {
i.Created, _ = parseDates(i.CreatedStr)
i.Updated, _ = parseDates(i.UpdatedStr)
return i
}
// GetInstanceConfig gets the template with the provided ID
func (c *Client) GetInstanceConfig(ctx context.Context, linodeID int, configID int) (*InstanceConfig, error) {
e, err := c.InstanceConfigs.endpointWithID(linodeID)
@ -168,16 +180,18 @@ func (c *Client) GetInstanceConfig(ctx context.Context, linodeID int, configID i
}
e = fmt.Sprintf("%s/%d", e, configID)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceConfig{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceConfig).fixDates(), nil
return r.Result().(*InstanceConfig), nil
}
// CreateInstanceConfig creates a new InstanceConfig for the given Instance
func (c *Client) CreateInstanceConfig(ctx context.Context, linodeID int, createOpts InstanceConfigCreateOptions) (*InstanceConfig, error) {
var body string
e, err := c.InstanceConfigs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
@ -198,13 +212,14 @@ func (c *Client) CreateInstanceConfig(ctx context.Context, linodeID int, createO
return nil, err
}
return r.Result().(*InstanceConfig).fixDates(), nil
return r.Result().(*InstanceConfig), nil
}
// UpdateInstanceConfig update an InstanceConfig for the given Instance
func (c *Client) UpdateInstanceConfig(ctx context.Context, linodeID int, configID int, updateOpts InstanceConfigUpdateOptions) (*InstanceConfig, error) {
var body string
e, err := c.InstanceConfigs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
@ -225,7 +240,7 @@ func (c *Client) UpdateInstanceConfig(ctx context.Context, linodeID int, configI
return nil, err
}
return r.Result().(*InstanceConfig).fixDates(), nil
return r.Result().(*InstanceConfig), nil
}
// RenameInstanceConfig renames an InstanceConfig

View File

@ -5,13 +5,12 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// InstanceDisk represents an Instance Disk object
type InstanceDisk struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Label string `json:"label"`
Status DiskStatus `json:"status"`
@ -90,24 +89,37 @@ func (resp *InstanceDisksPagedResponse) appendData(r *InstanceDisksPagedResponse
func (c *Client) ListInstanceDisks(ctx context.Context, linodeID int, opts *ListOptions) ([]InstanceDisk, error) {
response := InstanceDisksPagedResponse{}
err := c.listHelperWithID(ctx, &response, linodeID, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *InstanceDisk) fixDates() *InstanceDisk {
if created, err := parseDates(v.CreatedStr); err == nil {
v.Created = *created
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *InstanceDisk) UnmarshalJSON(b []byte) error {
type Mask InstanceDisk
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if updated, err := parseDates(v.UpdatedStr); err == nil {
v.Updated = *updated
if err := json.Unmarshal(b, &p); err != nil {
return err
}
return v
if p.Created != nil {
i.Created = time.Time(*p.Created)
}
if p.Updated != nil {
i.Updated = time.Time(*p.Updated)
}
return nil
}
// GetInstanceDisk gets the template with the provided ID
@ -116,18 +128,21 @@ func (c *Client) GetInstanceDisk(ctx context.Context, linodeID int, configID int
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, configID)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceDisk{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceDisk).fixDates(), nil
return r.Result().(*InstanceDisk), nil
}
// CreateInstanceDisk creates a new InstanceDisk for the given Instance
func (c *Client) CreateInstanceDisk(ctx context.Context, linodeID int, createOpts InstanceDiskCreateOptions) (*InstanceDisk, error) {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return nil, err
}
@ -148,18 +163,19 @@ func (c *Client) CreateInstanceDisk(ctx context.Context, linodeID int, createOpt
return nil, err
}
return r.Result().(*InstanceDisk).fixDates(), nil
return r.Result().(*InstanceDisk), nil
}
// UpdateInstanceDisk creates a new InstanceDisk for the given Instance
func (c *Client) UpdateInstanceDisk(ctx context.Context, linodeID int, diskID int, updateOpts InstanceDiskUpdateOptions) (*InstanceDisk, error) {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, diskID)
e = fmt.Sprintf("%s/%d", e, diskID)
req := c.R(ctx).SetResult(&InstanceDisk{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
@ -176,7 +192,7 @@ func (c *Client) UpdateInstanceDisk(ctx context.Context, linodeID int, diskID in
return nil, err
}
return r.Result().(*InstanceDisk).fixDates(), nil
return r.Result().(*InstanceDisk), nil
}
// RenameInstanceDisk renames an InstanceDisk
@ -188,6 +204,7 @@ func (c *Client) RenameInstanceDisk(ctx context.Context, linodeID int, diskID in
func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID int, size int) error {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return err
}
@ -215,6 +232,7 @@ func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID in
func (c *Client) PasswordResetInstanceDisk(ctx context.Context, linodeID int, diskID int, password string) error {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return err
}

View File

@ -17,6 +17,7 @@ type InstanceIPv4Response struct {
Public []*InstanceIP `json:"public"`
Private []*InstanceIP `json:"private"`
Shared []*InstanceIP `json:"shared"`
Reserved []*InstanceIP `json:"reserved"`
}
// InstanceIP represents an Instance IP with additional DNS and networking details
@ -25,7 +26,7 @@ type InstanceIP struct {
Gateway string `json:"gateway"`
SubnetMask string `json:"subnet_mask"`
Prefix int `json:"prefix"`
Type string `json:"type"`
Type InstanceIPType `json:"type"`
Public bool `json:"public"`
RDNS string `json:"rdns"`
LinodeID int `json:"linode_id"`
@ -43,14 +44,27 @@ type InstanceIPv6Response struct {
type IPv6Range struct {
Range string `json:"range"`
Region string `json:"region"`
Prefix int `json:"prefix"`
}
// InstanceIPType constants start with IPType and include Linode Instance IP Types
type InstanceIPType string
// InstanceIPType constants represent the IP types an Instance IP may be
const (
IPTypeIPv4 InstanceIPType = "ipv4"
IPTypeIPv6 InstanceIPType = "ipv6"
IPTypeIPv6Pool InstanceIPType = "ipv6/pool"
IPTypeIPv6Range InstanceIPType = "ipv6/range"
)
// GetInstanceIPAddresses gets the IPAddresses for a Linode instance
func (c *Client) GetInstanceIPAddresses(ctx context.Context, linodeID int) (*InstanceIPAddressResponse, error) {
e, err := c.InstanceIPs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIPAddressResponse{}).Get(e))
if err != nil {
return nil, err
@ -66,6 +80,7 @@ func (c *Client) GetInstanceIPAddress(ctx context.Context, linodeID int, ipaddre
}
e = fmt.Sprintf("%s/%s", e, ipaddress)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIP{}).Get(e))
if err != nil {
return nil, err
}
@ -76,6 +91,7 @@ func (c *Client) GetInstanceIPAddress(ctx context.Context, linodeID int, ipaddre
func (c *Client) AddInstanceIPAddress(ctx context.Context, linodeID int, public bool) (*InstanceIP, error) {
var body string
e, err := c.InstanceIPs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
@ -104,3 +120,31 @@ func (c *Client) AddInstanceIPAddress(ctx context.Context, linodeID int, public
return r.Result().(*InstanceIP), nil
}
// UpdateInstanceIPAddress updates the IPAddress with the specified instance id and IP address
func (c *Client) UpdateInstanceIPAddress(ctx context.Context, linodeID int, ipAddress string, updateOpts IPAddressUpdateOptions) (*InstanceIP, error) {
var body string
e, err := c.InstanceIPs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, ipAddress)
req := c.R(ctx).SetResult(&InstanceIP{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceIP), nil
}

View File

@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// InstanceBackupsResponse response struct for backup snapshot
@ -27,10 +29,6 @@ type RestoreInstanceOptions struct {
// InstanceSnapshot represents a linode backup snapshot
type InstanceSnapshot struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
FinishedStr string `json:"finished"`
ID int `json:"id"`
Label string `json:"label"`
Status InstanceSnapshotStatus `json:"status"`
@ -63,11 +61,28 @@ var (
SnapshotUserAborted InstanceSnapshotStatus = "userAborted"
)
func (l *InstanceSnapshot) fixDates() *InstanceSnapshot {
l.Created, _ = parseDates(l.CreatedStr)
l.Updated, _ = parseDates(l.UpdatedStr)
l.Finished, _ = parseDates(l.FinishedStr)
return l
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *InstanceSnapshot) UnmarshalJSON(b []byte) error {
type Mask InstanceSnapshot
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
Finished *parseabletime.ParseableTime `json:"finished"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
i.Finished = (*time.Time)(p.Finished)
return nil
}
// GetInstanceSnapshot gets the snapshot with the provided ID
@ -78,10 +93,11 @@ func (c *Client) GetInstanceSnapshot(ctx context.Context, linodeID int, snapshot
}
e = fmt.Sprintf("%s/%d", e, snapshotID)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceSnapshot{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceSnapshot).fixDates(), nil
return r.Result().(*InstanceSnapshot), nil
}
// CreateInstanceSnapshot Creates or Replaces the snapshot Backup of a Linode. If a previous snapshot exists for this Linode, it will be deleted.
@ -92,6 +108,7 @@ func (c *Client) CreateInstanceSnapshot(ctx context.Context, linodeID int, label
}
body := string(o)
e, err := c.InstanceSnapshots.endpointWithID(linodeID)
if err != nil {
return nil, err
}
@ -105,7 +122,7 @@ func (c *Client) CreateInstanceSnapshot(ctx context.Context, linodeID int, label
return nil, err
}
return r.Result().(*InstanceSnapshot).fixDates(), nil
return r.Result().(*InstanceSnapshot), nil
}
// GetInstanceBackups gets the Instance's available Backups.
@ -118,10 +135,11 @@ func (c *Client) GetInstanceBackups(ctx context.Context, linodeID int) (*Instanc
r, err := coupleAPIErrors(c.R(ctx).
SetResult(&InstanceBackupsResponse{}).
Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceBackupsResponse).fixDates(), nil
return r.Result().(*InstanceBackupsResponse), nil
}
// EnableInstanceBackups Enables backups for the specified Linode.
@ -164,25 +182,4 @@ func (c *Client) RestoreInstanceBackup(ctx context.Context, linodeID int, backup
_, err = coupleAPIErrors(c.R(ctx).SetBody(body).Post(e))
return err
}
func (l *InstanceBackupSnapshotResponse) fixDates() *InstanceBackupSnapshotResponse {
if l.Current != nil {
l.Current.fixDates()
}
if l.InProgress != nil {
l.InProgress.fixDates()
}
return l
}
func (l *InstanceBackupsResponse) fixDates() *InstanceBackupsResponse {
for i := range l.Automatic {
l.Automatic[i].fixDates()
}
if l.Snapshot != nil {
l.Snapshot.fixDates()
}
return l
}

68
vendor/github.com/linode/linodego/instance_stats.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
package linodego
import (
"context"
"fmt"
)
// StatsNet represents a network stats object
type StatsNet struct {
In [][]float64 `json:"in"`
Out [][]float64 `json:"out"`
PrivateIn [][]float64 `json:"private_in"`
PrivateOut [][]float64 `json:"private_out"`
}
// StatsIO represents an IO stats object
type StatsIO struct {
IO [][]float64 `json:"io"`
Swap [][]float64 `json:"swap"`
}
// InstanceStatsData represents an instance stats data object
type InstanceStatsData struct {
CPU [][]float64 `json:"cpu"`
IO StatsIO `json:"io"`
NetV4 StatsNet `json:"netv4"`
NetV6 StatsNet `json:"netv6"`
}
// InstanceStats represents an instance stats object
type InstanceStats struct {
Title string `json:"title"`
Data InstanceStatsData `json:"data"`
}
// endpointWithIDAndDate gets the endpoint URL for InstanceStats of a given Instance and Year/Month
func endpointWithIDAndDate(c *Client, id int, year int, month int) string {
endpoint, err := c.InstanceStats.endpointWithID(id)
if err != nil {
panic(err)
}
endpoint = fmt.Sprintf("%s/%d/%d", endpoint, year, month)
return endpoint
}
// GetInstanceStats gets the template with the provided ID
func (c *Client) GetInstanceStats(ctx context.Context, linodeID int) (*InstanceStats, error) {
e, err := c.InstanceStats.endpointWithID(linodeID)
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceStats{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceStats), nil
}
// GetInstanceStatsByDate gets the template with the provided ID, year, and month
func (c *Client) GetInstanceStatsByDate(ctx context.Context, linodeID int, year int, month int) (*InstanceStats, error) {
e := endpointWithIDAndDate(c, linodeID, year, month)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceStats{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceStats), nil
}

View File

@ -28,9 +28,7 @@ func (resp *InstanceVolumesPagedResponse) appendData(r *InstanceVolumesPagedResp
func (c *Client) ListInstanceVolumes(ctx context.Context, linodeID int, opts *ListOptions) ([]Volume, error) {
response := InstanceVolumesPagedResponse{}
err := c.listHelperWithID(ctx, &response, linodeID, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}

View File

@ -6,6 +6,8 @@ import (
"fmt"
"net"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
/*
@ -33,9 +35,6 @@ const (
// Instance represents a linode object
type Instance struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
@ -81,6 +80,18 @@ type InstanceBackup struct {
}
}
// InstanceTransfer pool stats for a Linode Instance during the current billing month
type InstanceTransfer struct {
// Bytes of transfer this instance has consumed
Used int `json:"used"`
// GB of billable transfer this instance has consumed
Billable int `json:"billable"`
// GB of transfer this instance adds to the Transfer pool
Quota int `json:"quota"`
}
// InstanceCreateOptions require only Region and Type
type InstanceCreateOptions struct {
Region string `json:"region"`
@ -113,15 +124,37 @@ type InstanceUpdateOptions struct {
Tags *[]string `json:"tags,omitempty"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Instance) UnmarshalJSON(b []byte) error {
type Mask Instance
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// GetUpdateOptions converts an Instance to InstanceUpdateOptions for use in UpdateInstance
func (l *Instance) GetUpdateOptions() InstanceUpdateOptions {
func (i *Instance) GetUpdateOptions() InstanceUpdateOptions {
return InstanceUpdateOptions{
Label: l.Label,
Group: l.Group,
Backups: l.Backups,
Alerts: l.Alerts,
WatchdogEnabled: &l.WatchdogEnabled,
Tags: &l.Tags,
Label: i.Label,
Group: i.Group,
Backups: i.Backups,
Alerts: i.Alerts,
WatchdogEnabled: &i.WatchdogEnabled,
Tags: &i.Tags,
}
}
@ -139,10 +172,12 @@ type InstanceCloneOptions struct {
Configs []int `json:"configs,omitempty"`
}
func (l *Instance) fixDates() *Instance {
l.Created, _ = parseDates(l.CreatedStr)
l.Updated, _ = parseDates(l.UpdatedStr)
return l
// InstanceResizeOptions is an options struct used when resizing an instance
type InstanceResizeOptions struct {
Type string `json:"type"`
// When enabled, an instance resize will also resize a data disk if the instance has no more than one data disk and one swap disk
AllowAutoDiskResize *bool `json:"allow_auto_disk_resize,omitempty"`
}
// InstancesPagedResponse represents a linode API response for listing
@ -169,9 +204,7 @@ func (resp *InstancesPagedResponse) appendData(r *InstancesPagedResponse) {
func (c *Client) ListInstances(ctx context.Context, opts *ListOptions) ([]Instance, error) {
response := InstancesPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
@ -191,7 +224,23 @@ func (c *Client) GetInstance(ctx context.Context, linodeID int) (*Instance, erro
if err != nil {
return nil, err
}
return r.Result().(*Instance).fixDates(), nil
return r.Result().(*Instance), nil
}
// GetInstanceTransfer gets the instance with the provided ID
func (c *Client) GetInstanceTransfer(ctx context.Context, linodeID int) (*InstanceTransfer, error) {
e, err := c.Instances.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d/transfer", e, linodeID)
r, err := coupleAPIErrors(c.R(ctx).
SetResult(InstanceTransfer{}).
Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceTransfer), nil
}
// CreateInstance creates a Linode instance
@ -217,7 +266,7 @@ func (c *Client) CreateInstance(ctx context.Context, instance InstanceCreateOpti
if err != nil {
return nil, err
}
return r.Result().(*Instance).fixDates(), nil
return r.Result().(*Instance), nil
}
// UpdateInstance creates a Linode instance
@ -244,7 +293,7 @@ func (c *Client) UpdateInstance(ctx context.Context, id int, instance InstanceUp
if err != nil {
return nil, err
}
return r.Result().(*Instance).fixDates(), nil
return r.Result().(*Instance), nil
}
// RenameInstance renames an Instance
@ -316,7 +365,7 @@ func (c *Client) CloneInstance(ctx context.Context, id int, options InstanceClon
return nil, err
}
return r.Result().(*Instance).fixDates(), nil
return r.Result().(*Instance), nil
}
// RebootInstance reboots a Linode instance
@ -347,20 +396,20 @@ func (c *Client) RebootInstance(ctx context.Context, id int, configID int) error
return err
}
// RebuildInstanceOptions is a struct representing the options to send to the rebuild linode endpoint
type RebuildInstanceOptions struct {
Image string `json:"image"`
RootPass string `json:"root_pass"`
AuthorizedKeys []string `json:"authorized_keys"`
AuthorizedUsers []string `json:"authorized_users"`
StackscriptID int `json:"stackscript_id"`
StackscriptData map[string]string `json:"stackscript_data"`
Booted bool `json:"booted"`
// InstanceRebuildOptions is a struct representing the options to send to the rebuild linode endpoint
type InstanceRebuildOptions struct {
Image string `json:"image,omitempty"`
RootPass string `json:"root_pass,omitempty"`
AuthorizedKeys []string `json:"authorized_keys,omitempty"`
AuthorizedUsers []string `json:"authorized_users,omitempty"`
StackScriptID int `json:"stackscript_id,omitempty"`
StackScriptData map[string]string `json:"stackscript_data,omitempty"`
Booted *bool `json:"booted,omitempty"`
}
// RebuildInstance Deletes all Disks and Configs on this Linode,
// then deploys a new Image to this Linode with the given attributes.
func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstanceOptions) (*Instance, error) {
func (c *Client) RebuildInstance(ctx context.Context, id int, opts InstanceRebuildOptions) (*Instance, error) {
o, err := json.Marshal(opts)
if err != nil {
return nil, NewError(err)
@ -378,11 +427,11 @@ func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstan
if err != nil {
return nil, err
}
return r.Result().(*Instance).fixDates(), nil
return r.Result().(*Instance), nil
}
// RescueInstanceOptions fields are those accepted by RescueInstance
type RescueInstanceOptions struct {
// InstanceRescueOptions fields are those accepted by RescueInstance
type InstanceRescueOptions struct {
Devices InstanceConfigDeviceMap `json:"devices"`
}
@ -390,7 +439,7 @@ type RescueInstanceOptions struct {
// Rescue Mode is based on the Finnix recovery distribution, a self-contained and bootable Linux distribution.
// You can also use Rescue Mode for tasks other than disaster recovery, such as formatting disks to use different filesystems,
// copying data between disks, and downloading files from a disk via SSH and SFTP.
func (c *Client) RescueInstance(ctx context.Context, id int, opts RescueInstanceOptions) error {
func (c *Client) RescueInstance(ctx context.Context, id int, opts InstanceRescueOptions) error {
o, err := json.Marshal(opts)
if err != nil {
return NewError(err)
@ -410,9 +459,12 @@ func (c *Client) RescueInstance(ctx context.Context, id int, opts RescueInstance
}
// ResizeInstance resizes an instance to new Linode type
func (c *Client) ResizeInstance(ctx context.Context, id int, linodeType string) error {
body := fmt.Sprintf("{\"type\":\"%s\"}", linodeType)
func (c *Client) ResizeInstance(ctx context.Context, id int, opts InstanceResizeOptions) error {
o, err := json.Marshal(opts)
if err != nil {
return NewError(err)
}
body := string(o)
e, err := c.Instances.Endpoint()
if err != nil {
return err

View File

@ -0,0 +1,63 @@
package duration
import (
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
)
func UnmarshalTimeRemaining(m json.RawMessage) *int {
jsonBytes, err := m.MarshalJSON()
if err != nil {
panic(jsonBytes)
}
if len(jsonBytes) == 4 && string(jsonBytes) == "null" {
return nil
}
var timeStr string
if err := json.Unmarshal(jsonBytes, &timeStr); err == nil && len(timeStr) > 0 {
if dur, err := durationToSeconds(timeStr); err != nil {
panic(err)
} else {
return &dur
}
} else {
var intPtr int
if err := json.Unmarshal(jsonBytes, &intPtr); err == nil {
return &intPtr
}
}
log.Println("[WARN] Unexpected unmarshalTimeRemaining value: ", jsonBytes)
return nil
}
// durationToSeconds takes a hh:mm:ss string and returns the number of seconds
func durationToSeconds(s string) (int, error) {
multipliers := [3]int{60 * 60, 60, 1}
segs := strings.Split(s, ":")
if len(segs) > len(multipliers) {
return 0, fmt.Errorf("too many ':' separators in time duration: %s", s)
}
var d int
l := len(segs)
for i := 0; i < l; i++ {
m, err := strconv.Atoi(segs[i])
if err != nil {
return 0, err
}
d += m * multipliers[i+len(multipliers)-l]
}
return d, nil
}

View File

@ -0,0 +1,22 @@
package parseabletime
import (
"time"
)
const (
dateLayout = "2006-01-02T15:04:05"
)
type ParseableTime time.Time
func (p *ParseableTime) UnmarshalJSON(b []byte) error {
t, err := time.Parse(`"`+dateLayout+`"`, string(b))
if err != nil {
return err
}
*p = ParseableTime(t)
return nil
}

166
vendor/github.com/linode/linodego/lke_cluster_pools.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
)
// LKELinodeStatus constants start with LKELinode and include
// Linode API LKEClusterPool Linode Status values
type LKELinodeStatus string
// LKEClusterPoolStatus constants reflect the current status of an LKEClusterPool
const (
LKELinodeReady LKELinodeStatus = "ready"
LKELinodeNotReady LKELinodeStatus = "not_ready"
)
// LKEClusterPoolLinode represents a LKEClusterPoolLinode object
type LKEClusterPoolLinode struct {
ID string `json:"id"`
Status LKELinodeStatus `json:"status"`
}
// LKEClusterPool represents a LKEClusterPool object
type LKEClusterPool struct {
ID int `json:"id"`
Count int `json:"count"`
Type string `json:"type"`
Linodes []LKEClusterPoolLinode `json:"nodes"`
}
// LKEClusterPoolCreateOptions fields are those accepted by CreateLKEClusterPool
type LKEClusterPoolCreateOptions struct {
Count int `json:"count"`
Type string `json:"type"`
}
// LKEClusterPoolUpdateOptions fields are those accepted by UpdateLKEClusterPool
type LKEClusterPoolUpdateOptions struct {
Count int `json:"count"`
}
// GetCreateOptions converts a LKEClusterPool to LKEClusterPoolCreateOptions for
// use in CreateLKEClusterPool
func (l LKEClusterPool) GetCreateOptions() (o LKEClusterPoolCreateOptions) {
o.Count = l.Count
return
}
// GetUpdateOptions converts a LKEClusterPool to LKEClusterPoolUpdateOptions for use in UpdateLKEClusterPool
func (l LKEClusterPool) GetUpdateOptions() (o LKEClusterPoolUpdateOptions) {
o.Count = l.Count
return
}
// LKEClusterPoolsPagedResponse represents a paginated LKEClusterPool API response
type LKEClusterPoolsPagedResponse struct {
*PageOptions
Data []LKEClusterPool `json:"data"`
}
// endpointWithID gets the endpoint URL for InstanceConfigs of a given Instance
func (LKEClusterPoolsPagedResponse) endpointWithID(c *Client, id int) string {
endpoint, err := c.LKEClusterPools.endpointWithID(id)
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends LKEClusterPools when processing paginated LKEClusterPool responses
func (resp *LKEClusterPoolsPagedResponse) appendData(r *LKEClusterPoolsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListLKEClusterPools lists LKEClusterPools
func (c *Client) ListLKEClusterPools(ctx context.Context, clusterID int, opts *ListOptions) ([]LKEClusterPool, error) {
response := LKEClusterPoolsPagedResponse{}
err := c.listHelperWithID(ctx, &response, clusterID, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetLKEClusterPool gets the lkeClusterPool with the provided ID
func (c *Client) GetLKEClusterPool(ctx context.Context, clusterID, id int) (*LKEClusterPool, error) {
e, err := c.LKEClusterPools.endpointWithID(clusterID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&LKEClusterPool{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEClusterPool), nil
}
// CreateLKEClusterPool creates a LKEClusterPool
func (c *Client) CreateLKEClusterPool(ctx context.Context, clusterID int, createOpts LKEClusterPoolCreateOptions) (*LKEClusterPool, error) {
var body string
e, err := c.LKEClusterPools.endpointWithID(clusterID)
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&LKEClusterPool{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEClusterPool), nil
}
// UpdateLKEClusterPool updates the LKEClusterPool with the specified id
func (c *Client) UpdateLKEClusterPool(ctx context.Context, clusterID, id int, updateOpts LKEClusterPoolUpdateOptions) (*LKEClusterPool, error) {
var body string
e, err := c.LKEClusterPools.endpointWithID(clusterID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&LKEClusterPool{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEClusterPool), nil
}
// DeleteLKEClusterPool deletes the LKEClusterPool with the specified id
func (c *Client) DeleteLKEClusterPool(ctx context.Context,
clusterID, id int) error {
e, err := c.LKEClusterPools.endpointWithID(clusterID)
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

279
vendor/github.com/linode/linodego/lke_clusters.go generated vendored Normal file
View File

@ -0,0 +1,279 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// LKEClusterStatus represents the status of an LKECluster
type LKEClusterStatus string
// LKEClusterStatus enums start with LKECluster
const (
LKEClusterReady LKEClusterStatus = "ready"
LKEClusterNotReady LKEClusterStatus = "not_ready"
)
// LKECluster represents a LKECluster object
type LKECluster struct {
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Label string `json:"label"`
Region string `json:"region"`
Status LKEClusterStatus `json:"status"`
Version string `json:"version"`
Tags []string `json:"tags"`
}
// LKEClusterCreateOptions fields are those accepted by CreateLKECluster
type LKEClusterCreateOptions struct {
NodePools []LKEClusterPoolCreateOptions `json:"node_pools"`
Label string `json:"label"`
Region string `json:"region"`
Version string `json:"version"`
Tags []string `json:"tags,omitempty"`
}
// LKEClusterUpdateOptions fields are those accepted by UpdateLKECluster
type LKEClusterUpdateOptions struct {
Label string `json:"label,omitempty"`
}
// LKEClusterAPIEndpoint fields are those returned by GetLKEClusterAPIEndpoint
type LKEClusterAPIEndpoint struct {
Endpoints []string `json:"endpoints"`
}
// LKEClusterKubeconfig fields are those returned by GetLKEClusterKubeconfig
type LKEClusterKubeconfig struct {
KubeConfig string `json:"kubeconfig"`
}
// LKEVersion fields are those returned by GetLKEVersion
type LKEVersion struct {
ID string `json:"id"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *LKECluster) UnmarshalJSON(b []byte) error {
type Mask LKECluster
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// GetCreateOptions converts a LKECluster to LKEClusterCreateOptions for use in CreateLKECluster
func (i LKECluster) GetCreateOptions() (o LKEClusterCreateOptions) {
o.Label = i.Label
o.Region = i.Region
o.Version = i.Version
o.Tags = i.Tags
// @TODO copy NodePools?
return
}
// GetUpdateOptions converts a LKECluster to LKEClusterUpdateOptions for use in UpdateLKECluster
func (i LKECluster) GetUpdateOptions() (o LKEClusterUpdateOptions) {
o.Label = i.Label
return
}
// LKEClustersPagedResponse represents a paginated LKECluster API response
type LKEClustersPagedResponse struct {
*PageOptions
Data []LKECluster `json:"data"`
}
// LKEVersionsPagedResponse represents a paginated LKEVersion API response
type LKEVersionsPagedResponse struct {
*PageOptions
Data []LKEVersion `json:"data"`
}
// endpoint gets the endpoint URL for LKECluster
func (LKEClustersPagedResponse) endpoint(c *Client) string {
endpoint, err := c.LKEClusters.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends LKEClusters when processing paginated LKECluster responses
func (resp *LKEClustersPagedResponse) appendData(r *LKEClustersPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// endpoint gets the endpoint URL for LKEVersion
func (LKEVersionsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.LKEVersions.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends LKEVersions when processing paginated LKEVersion responses
func (resp *LKEVersionsPagedResponse) appendData(r *LKEVersionsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListLKEClusters lists LKEClusters
func (c *Client) ListLKEClusters(ctx context.Context, opts *ListOptions) ([]LKECluster, error) {
response := LKEClustersPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetLKECluster gets the lkeCluster with the provided ID
func (c *Client) GetLKECluster(ctx context.Context, id int) (*LKECluster, error) {
e, err := c.LKEClusters.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&LKECluster{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*LKECluster), nil
}
// CreateLKECluster creates a LKECluster
func (c *Client) CreateLKECluster(ctx context.Context, createOpts LKEClusterCreateOptions) (*LKECluster, error) {
var body string
e, err := c.LKEClusters.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&LKECluster{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*LKECluster), nil
}
// UpdateLKECluster updates the LKECluster with the specified id
func (c *Client) UpdateLKECluster(ctx context.Context, id int, updateOpts LKEClusterUpdateOptions) (*LKECluster, error) {
var body string
e, err := c.LKEClusters.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&LKECluster{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*LKECluster), nil
}
// DeleteLKECluster deletes the LKECluster with the specified id
func (c *Client) DeleteLKECluster(ctx context.Context, id int) error {
e, err := c.LKEClusters.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}
// GetLKEClusterAPIEndpoint gets the API Endpoint for the LKE Cluster specified
func (c *Client) GetLKEClusterAPIEndpoint(ctx context.Context, id int) (*LKEClusterAPIEndpoint, error) {
e, err := c.LKEClusters.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d/api-endpoint", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&LKEClusterAPIEndpoint{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEClusterAPIEndpoint), nil
}
// GetLKEClusterKubeconfig gets the Kubeconfig for the LKE Cluster specified
func (c *Client) GetLKEClusterKubeconfig(ctx context.Context, id int) (*LKEClusterKubeconfig, error) {
e, err := c.LKEClusters.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d/kubeconfig", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&LKEClusterKubeconfig{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEClusterKubeconfig), nil
}
// GetLKEVersion gets details about a specific LKE Version
func (c *Client) GetLKEVersion(ctx context.Context, version string) (*LKEVersion, error) {
e, err := c.LKEVersions.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, version)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&LKEVersion{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*LKEVersion), nil
}
// ListLKEVersions lists the Kubernetes versions available through LKE
func (c *Client) ListLKEVersions(ctx context.Context, opts *ListOptions) ([]LKEVersion, error) {
response := LKEVersionsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}

View File

@ -36,22 +36,13 @@ func (resp *LongviewClientsPagedResponse) appendData(r *LongviewClientsPagedResp
func (c *Client) ListLongviewClients(ctx context.Context, opts *ListOptions) ([]LongviewClient, error) {
response := LongviewClientsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *LongviewClient) fixDates() *LongviewClient {
// v.Created, _ = parseDates(v.CreatedStr)
// v.Updated, _ = parseDates(v.UpdatedStr)
return v
}
// GetLongviewClient gets the template with the provided ID
func (c *Client) GetLongviewClient(ctx context.Context, id string) (*LongviewClient, error) {
e, err := c.LongviewClients.Endpoint()
@ -63,5 +54,5 @@ func (c *Client) GetLongviewClient(ctx context.Context, id string) (*LongviewCli
if err != nil {
return nil, err
}
return r.Result().(*LongviewClient).fixDates(), nil
return r.Result().(*LongviewClient), nil
}

View File

@ -39,22 +39,13 @@ func (resp *LongviewSubscriptionsPagedResponse) appendData(r *LongviewSubscripti
func (c *Client) ListLongviewSubscriptions(ctx context.Context, opts *ListOptions) ([]LongviewSubscription, error) {
response := LongviewSubscriptionsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *LongviewSubscription) fixDates() *LongviewSubscription {
// v.Created, _ = parseDates(v.CreatedStr)
// v.Updated, _ = parseDates(v.UpdatedStr)
return v
}
// GetLongviewSubscription gets the template with the provided ID
func (c *Client) GetLongviewSubscription(ctx context.Context, id string) (*LongviewSubscription, error) {
e, err := c.LongviewSubscriptions.Endpoint()
@ -66,5 +57,5 @@ func (c *Client) GetLongviewSubscription(ctx context.Context, id string) (*Longv
if err != nil {
return nil, err
}
return r.Result().(*LongviewSubscription).fixDates(), nil
return r.Result().(*LongviewSubscription), nil
}

View File

@ -5,12 +5,12 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// NodeBalancer represents a NodeBalancer object
type NodeBalancer struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
// This NodeBalancer's unique ID.
ID int `json:"id"`
// This NodeBalancer's label. These must be unique on your Account.
@ -61,6 +61,28 @@ type NodeBalancerUpdateOptions struct {
Tags *[]string `json:"tags,omitempty"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *NodeBalancer) UnmarshalJSON(b []byte) error {
type Mask NodeBalancer
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// GetCreateOptions converts a NodeBalancer to NodeBalancerCreateOptions for use in CreateNodeBalancer
func (i NodeBalancer) GetCreateOptions() NodeBalancerCreateOptions {
return NodeBalancerCreateOptions{
@ -108,13 +130,6 @@ func (c *Client) ListNodeBalancers(ctx context.Context, opts *ListOptions) ([]No
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *NodeBalancer) fixDates() *NodeBalancer {
i.Created, _ = parseDates(i.CreatedStr)
i.Updated, _ = parseDates(i.UpdatedStr)
return i
}
// GetNodeBalancer gets the NodeBalancer with the provided ID
func (c *Client) GetNodeBalancer(ctx context.Context, id int) (*NodeBalancer, error) {
e, err := c.NodeBalancers.Endpoint()
@ -128,7 +143,7 @@ func (c *Client) GetNodeBalancer(ctx context.Context, id int) (*NodeBalancer, er
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancer).fixDates(), nil
return r.Result().(*NodeBalancer), nil
}
// CreateNodeBalancer creates a NodeBalancer
@ -155,7 +170,7 @@ func (c *Client) CreateNodeBalancer(ctx context.Context, nodebalancer NodeBalanc
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancer).fixDates(), nil
return r.Result().(*NodeBalancer), nil
}
// UpdateNodeBalancer updates the NodeBalancer with the specified id
@ -182,7 +197,7 @@ func (c *Client) UpdateNodeBalancer(ctx context.Context, id int, updateOpts Node
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancer).fixDates(), nil
return r.Result().(*NodeBalancer), nil
}
// DeleteNodeBalancer deletes the NodeBalancer with the specified id

View File

@ -30,6 +30,9 @@ var (
// ModeDrain is the NodeMode indicating a NodeBalancer Node is not receiving new traffic, but may continue receiving traffic from pinned connections
ModeDrain NodeMode = "drain"
// ModeBackup is the NodeMode indicating a NodeBalancer Node will only receive traffic if all "accept" Nodes are down
ModeBackup NodeMode = "backup"
)
// NodeBalancerNodeCreateOptions fields are those accepted by CreateNodeBalancerNode
@ -92,20 +95,13 @@ func (resp *NodeBalancerNodesPagedResponse) appendData(r *NodeBalancerNodesPaged
func (c *Client) ListNodeBalancerNodes(ctx context.Context, nodebalancerID int, configID int, opts *ListOptions) ([]NodeBalancerNode, error) {
response := NodeBalancerNodesPagedResponse{}
err := c.listHelperWithTwoIDs(ctx, &response, nodebalancerID, configID, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *NodeBalancerNode) fixDates() *NodeBalancerNode {
return i
}
// GetNodeBalancerNode gets the template with the provided ID
func (c *Client) GetNodeBalancerNode(ctx context.Context, nodebalancerID int, configID int, nodeID int) (*NodeBalancerNode, error) {
e, err := c.NodeBalancerNodes.endpointWithID(nodebalancerID, configID)
@ -117,7 +113,7 @@ func (c *Client) GetNodeBalancerNode(ctx context.Context, nodebalancerID int, co
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerNode).fixDates(), nil
return r.Result().(*NodeBalancerNode), nil
}
// CreateNodeBalancerNode creates a NodeBalancerNode
@ -143,7 +139,7 @@ func (c *Client) CreateNodeBalancerNode(ctx context.Context, nodebalancerID int,
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerNode).fixDates(), nil
return r.Result().(*NodeBalancerNode), nil
}
// UpdateNodeBalancerNode updates the NodeBalancerNode with the specified id
@ -170,7 +166,7 @@ func (c *Client) UpdateNodeBalancerNode(ctx context.Context, nodebalancerID int,
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerNode).fixDates(), nil
return r.Result().(*NodeBalancerNode), nil
}
// DeleteNodeBalancerNode deletes the NodeBalancerNode with the specified id

View File

@ -211,20 +211,13 @@ func (resp *NodeBalancerConfigsPagedResponse) appendData(r *NodeBalancerConfigsP
func (c *Client) ListNodeBalancerConfigs(ctx context.Context, nodebalancerID int, opts *ListOptions) ([]NodeBalancerConfig, error) {
response := NodeBalancerConfigsPagedResponse{}
err := c.listHelperWithID(ctx, &response, nodebalancerID, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *NodeBalancerConfig) fixDates() *NodeBalancerConfig {
return i
}
// GetNodeBalancerConfig gets the template with the provided ID
func (c *Client) GetNodeBalancerConfig(ctx context.Context, nodebalancerID int, configID int) (*NodeBalancerConfig, error) {
e, err := c.NodeBalancerConfigs.endpointWithID(nodebalancerID)
@ -236,7 +229,7 @@ func (c *Client) GetNodeBalancerConfig(ctx context.Context, nodebalancerID int,
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerConfig).fixDates(), nil
return r.Result().(*NodeBalancerConfig), nil
}
// CreateNodeBalancerConfig creates a NodeBalancerConfig
@ -264,7 +257,7 @@ func (c *Client) CreateNodeBalancerConfig(ctx context.Context, nodebalancerID in
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerConfig).fixDates(), nil
return r.Result().(*NodeBalancerConfig), nil
}
// UpdateNodeBalancerConfig updates the NodeBalancerConfig with the specified id
@ -291,7 +284,7 @@ func (c *Client) UpdateNodeBalancerConfig(ctx context.Context, nodebalancerID in
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerConfig).fixDates(), nil
return r.Result().(*NodeBalancerConfig), nil
}
// DeleteNodeBalancerConfig deletes the NodeBalancerConfig with the specified id
@ -330,5 +323,5 @@ func (c *Client) RebuildNodeBalancerConfig(ctx context.Context, nodeBalancerID i
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerConfig).fixDates(), nil
return r.Result().(*NodeBalancerConfig), nil
}

View File

@ -0,0 +1,36 @@
package linodego
import (
"context"
)
// NodeBalancerStats represents a nodebalancer stats object
type NodeBalancerStats struct {
Title string `json:"title"`
Data NodeBalancerStatsData `json:"data"`
}
// NodeBalancerStatsData represents a nodebalancer stats data object
type NodeBalancerStatsData struct {
Connections [][]float64 `json:"connections"`
Traffic StatsTraffic `json:"traffic"`
}
// StatsTraffic represents a Traffic stats object
type StatsTraffic struct {
In [][]float64 `json:"in"`
Out [][]float64 `json:"out"`
}
// GetNodeBalancerStats gets the template with the provided ID
func (c *Client) GetNodeBalancerStats(ctx context.Context, linodeID int) (*NodeBalancerStats, error) {
e, err := c.NodeBalancerStats.endpointWithID(linodeID)
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&NodeBalancerStats{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*NodeBalancerStats), nil
}

View File

@ -0,0 +1,128 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// ObjectStorageBucket represents a ObjectStorage object
type ObjectStorageBucket struct {
Label string `json:"label"`
Cluster string `json:"cluster"`
Created *time.Time `json:"-"`
Hostname string `json:"hostname"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *ObjectStorageBucket) UnmarshalJSON(b []byte) error {
type Mask ObjectStorageBucket
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
return nil
}
// ObjectStorageBucketCreateOptions fields are those accepted by CreateObjectStorageBucket
type ObjectStorageBucketCreateOptions struct {
Cluster string `json:"cluster"`
Label string `json:"label"`
}
// ObjectStorageBucketsPagedResponse represents a paginated ObjectStorageBucket API response
type ObjectStorageBucketsPagedResponse struct {
*PageOptions
Data []ObjectStorageBucket `json:"data"`
}
// endpoint gets the endpoint URL for ObjectStorageBucket
func (ObjectStorageBucketsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.ObjectStorageBuckets.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends ObjectStorageBuckets when processing paginated ObjectStorageBucket responses
func (resp *ObjectStorageBucketsPagedResponse) appendData(r *ObjectStorageBucketsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListObjectStorageBuckets lists ObjectStorageBuckets
func (c *Client) ListObjectStorageBuckets(ctx context.Context, opts *ListOptions) ([]ObjectStorageBucket, error) {
response := ObjectStorageBucketsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetObjectStorageBucket gets the ObjectStorageBucket with the provided label
func (c *Client) GetObjectStorageBucket(ctx context.Context, clusterID, label string) (*ObjectStorageBucket, error) {
e, err := c.ObjectStorageBuckets.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s/%s", e, clusterID, label)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&ObjectStorageBucket{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageBucket), nil
}
// CreateObjectStorageBucket creates an ObjectStorageBucket
func (c *Client) CreateObjectStorageBucket(ctx context.Context, createOpts ObjectStorageBucketCreateOptions) (*ObjectStorageBucket, error) {
var body string
e, err := c.ObjectStorageBuckets.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&ObjectStorageBucket{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageBucket), nil
}
// DeleteObjectStorageBucket deletes the ObjectStorageBucket with the specified label
func (c *Client) DeleteObjectStorageBucket(ctx context.Context, clusterID, label string) error {
e, err := c.ObjectStorageBuckets.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s/%s", e, clusterID, label)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -0,0 +1,59 @@
package linodego
import (
"context"
"fmt"
)
// ObjectStorageCluster represents a linode object storage cluster object
type ObjectStorageCluster struct {
ID string `json:"id"`
Domain string `json:"domain"`
Status string `json:"status"`
Region string `json:"region"`
StaticSiteDomain string `json:"static_site_domain"`
}
// ObjectStorageClustersPagedResponse represents a linode API response for listing
type ObjectStorageClustersPagedResponse struct {
*PageOptions
Data []ObjectStorageCluster `json:"data"`
}
// endpoint gets the endpoint URL for ObjectStorageCluster
func (ObjectStorageClustersPagedResponse) endpoint(c *Client) string {
endpoint, err := c.ObjectStorageClusters.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends ObjectStorageClusters when processing paginated ObjectStorageCluster responses
func (resp *ObjectStorageClustersPagedResponse) appendData(r *ObjectStorageClustersPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListObjectStorageClusters lists ObjectStorageClusters
func (c *Client) ListObjectStorageClusters(ctx context.Context, opts *ListOptions) ([]ObjectStorageCluster, error) {
response := ObjectStorageClustersPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetObjectStorageCluster gets the template with the provided ID
func (c *Client) GetObjectStorageCluster(ctx context.Context, id string) (*ObjectStorageCluster, error) {
e, err := c.ObjectStorageClusters.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&ObjectStorageCluster{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageCluster), nil
}

View File

@ -0,0 +1,134 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
)
// ObjectStorageKey represents a linode object storage key object
type ObjectStorageKey struct {
ID int `json:"id"`
Label string `json:"label"`
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
}
// ObjectStorageKeyCreateOptions fields are those accepted by CreateObjectStorageKey
type ObjectStorageKeyCreateOptions struct {
Label string `json:"label"`
}
// ObjectStorageKeyUpdateOptions fields are those accepted by UpdateObjectStorageKey
type ObjectStorageKeyUpdateOptions struct {
Label string `json:"label"`
}
// ObjectStorageKeysPagedResponse represents a linode API response for listing
type ObjectStorageKeysPagedResponse struct {
*PageOptions
Data []ObjectStorageKey `json:"data"`
}
// endpoint gets the endpoint URL for Object Storage keys
func (ObjectStorageKeysPagedResponse) endpoint(c *Client) string {
endpoint, err := c.ObjectStorageKeys.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends ObjectStorageKeys when processing paginated Objkey responses
func (resp *ObjectStorageKeysPagedResponse) appendData(r *ObjectStorageKeysPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListObjectStorageKeys lists ObjectStorageKeys
func (c *Client) ListObjectStorageKeys(ctx context.Context, opts *ListOptions) ([]ObjectStorageKey, error) {
response := ObjectStorageKeysPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// CreateObjectStorageKey creates a ObjectStorageKey
func (c *Client) CreateObjectStorageKey(ctx context.Context, createOpts ObjectStorageKeyCreateOptions) (*ObjectStorageKey, error) {
var body string
e, err := c.ObjectStorageKeys.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&ObjectStorageKey{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageKey), nil
}
// GetObjectStorageKey gets the object storage key with the provided ID
func (c *Client) GetObjectStorageKey(ctx context.Context, id int) (*ObjectStorageKey, error) {
e, err := c.ObjectStorageKeys.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&ObjectStorageKey{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageKey), nil
}
// UpdateObjectStorageKey updates the object storage key with the specified id
func (c *Client) UpdateObjectStorageKey(ctx context.Context, id int, updateOpts ObjectStorageKeyUpdateOptions) (*ObjectStorageKey, error) {
var body string
e, err := c.ObjectStorageKeys.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&ObjectStorageKey{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*ObjectStorageKey), nil
}
// DeleteObjectStorageKey deletes the ObjectStorageKey with the specified id
func (c *Client) DeleteObjectStorageKey(ctx context.Context, id int) error {
e, err := c.ObjectStorageKeys.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -10,7 +10,7 @@ import (
"log"
"strconv"
"gopkg.in/resty.v1"
"github.com/go-resty/resty/v2"
)
// PageOptions are the pagination parameters for List endpoints
@ -28,9 +28,8 @@ type ListOptions struct {
// NewListOptions simplified construction of ListOptions using only
// the two writable properties, Page and Filter
func NewListOptions(Page int, Filter string) *ListOptions {
return &ListOptions{PageOptions: &PageOptions{Page: Page}, Filter: Filter}
func NewListOptions(page int, filter string) *ListOptions {
return &ListOptions{PageOptions: &PageOptions{Page: page}, Filter: filter}
}
// listHelper abstracts fetching and pagination for GET endpoints that
@ -38,6 +37,7 @@ func NewListOptions(Page int, Filter string) *ListOptions {
// When opts (or opts.Page) is nil, all pages will be fetched and
// returned in a single (endpoint-specific)PagedResponse
// opts.results and opts.pages will be updated from the API response
// nolint
func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOptions) error {
req := c.R(ctx)
if opts != nil && opts.PageOptions != nil && opts.Page > 0 {
@ -102,7 +102,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
if r, err = coupleAPIErrors(req.SetResult(DomainsPagedResponse{}).Get(v.endpoint(c))); err == nil {
response, ok := r.Result().(*DomainsPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *DomainsPagedResponse")
return fmt.Errorf("response is not a *DomainsPagedResponse")
}
pages = response.Pages
results = response.Results
@ -114,6 +114,24 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
results = r.Result().(*EventsPagedResponse).Results
v.appendData(r.Result().(*EventsPagedResponse))
}
case *FirewallsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(FirewallsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*FirewallsPagedResponse).Pages
results = r.Result().(*FirewallsPagedResponse).Results
v.appendData(r.Result().(*FirewallsPagedResponse))
}
case *LKEClustersPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(LKEClustersPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*LKEClustersPagedResponse).Pages
results = r.Result().(*LKEClustersPagedResponse).Results
v.appendData(r.Result().(*LKEClustersPagedResponse))
}
case *LKEVersionsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(LKEVersionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*LKEVersionsPagedResponse).Pages
results = r.Result().(*LKEVersionsPagedResponse).Results
v.appendData(r.Result().(*LKEVersionsPagedResponse))
}
case *LongviewSubscriptionsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(LongviewSubscriptionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*LongviewSubscriptionsPagedResponse).Pages
@ -149,7 +167,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
if r, err = coupleAPIErrors(req.SetResult(SSHKeysPagedResponse{}).Get(v.endpoint(c))); err == nil {
response, ok := r.Result().(*SSHKeysPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *SSHKeysPagedResponse")
return fmt.Errorf("response is not a *SSHKeysPagedResponse")
}
pages = response.Pages
results = response.Results
@ -173,6 +191,18 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
results = r.Result().(*NotificationsPagedResponse).Results
v.appendData(r.Result().(*NotificationsPagedResponse))
}
case *OAuthClientsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(OAuthClientsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*OAuthClientsPagedResponse).Pages
results = r.Result().(*OAuthClientsPagedResponse).Results
v.appendData(r.Result().(*OAuthClientsPagedResponse))
}
case *PaymentsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(PaymentsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*PaymentsPagedResponse).Pages
results = r.Result().(*PaymentsPagedResponse).Results
v.appendData(r.Result().(*PaymentsPagedResponse))
}
case *NodeBalancersPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(NodeBalancersPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*NodeBalancersPagedResponse).Pages
@ -197,9 +227,25 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
results = r.Result().(*UsersPagedResponse).Results
v.appendData(r.Result().(*UsersPagedResponse))
}
case *ObjectStorageBucketsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(ObjectStorageBucketsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*ObjectStorageBucketsPagedResponse).Pages
results = r.Result().(*ObjectStorageBucketsPagedResponse).Results
v.appendData(r.Result().(*ObjectStorageBucketsPagedResponse))
}
case *ObjectStorageClustersPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(ObjectStorageClustersPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*ObjectStorageClustersPagedResponse).Pages
results = r.Result().(*ObjectStorageClustersPagedResponse).Results
v.appendData(r.Result().(*ObjectStorageClustersPagedResponse))
}
case *ObjectStorageKeysPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(ObjectStorageKeysPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*ObjectStorageKeysPagedResponse).Pages
results = r.Result().(*ObjectStorageKeysPagedResponse).Results
v.appendData(r.Result().(*ObjectStorageKeysPagedResponse))
}
/**
case AccountOauthClientsPagedResponse:
case AccountPaymentsPagedResponse:
case ProfileAppsPagedResponse:
case ProfileWhitelistPagedResponse:
case ManagedContactsPagedResponse:
@ -217,7 +263,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@ -228,7 +274,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelper(ctx, i, opts); err != nil {
return err
@ -247,6 +293,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
// When opts (or opts.Page) is nil, all pages will be fetched and
// returned in a single (endpoint-specific)PagedResponse
// opts.results and opts.pages will be updated from the API response
// nolint
func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw interface{}, opts *ListOptions) error {
req := c.R(ctx)
if opts != nil && opts.Page > 0 {
@ -267,17 +314,11 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
}
switch v := i.(type) {
case *InvoiceItemsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(InvoiceItemsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*InvoiceItemsPagedResponse).Pages
results = r.Result().(*InvoiceItemsPagedResponse).Results
v.appendData(r.Result().(*InvoiceItemsPagedResponse))
}
case *DomainRecordsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(DomainRecordsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
response, ok := r.Result().(*DomainRecordsPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *DomainRecordsPagedResponse")
return fmt.Errorf("response is not a *DomainRecordsPagedResponse")
}
pages = response.Pages
results = response.Results
@ -295,18 +336,30 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
results = r.Result().(*InstanceDisksPagedResponse).Results
v.appendData(r.Result().(*InstanceDisksPagedResponse))
}
case *NodeBalancerConfigsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(NodeBalancerConfigsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*NodeBalancerConfigsPagedResponse).Pages
results = r.Result().(*NodeBalancerConfigsPagedResponse).Results
v.appendData(r.Result().(*NodeBalancerConfigsPagedResponse))
}
case *InstanceVolumesPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(InstanceVolumesPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*InstanceVolumesPagedResponse).Pages
results = r.Result().(*InstanceVolumesPagedResponse).Results
v.appendData(r.Result().(*InstanceVolumesPagedResponse))
}
case *InvoiceItemsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(InvoiceItemsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*InvoiceItemsPagedResponse).Pages
results = r.Result().(*InvoiceItemsPagedResponse).Results
v.appendData(r.Result().(*InvoiceItemsPagedResponse))
}
case *LKEClusterPoolsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(LKEClusterPoolsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*LKEClusterPoolsPagedResponse).Pages
results = r.Result().(*LKEClusterPoolsPagedResponse).Results
v.appendData(r.Result().(*LKEClusterPoolsPagedResponse))
}
case *NodeBalancerConfigsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(NodeBalancerConfigsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*NodeBalancerConfigsPagedResponse).Pages
results = r.Result().(*NodeBalancerConfigsPagedResponse).Results
v.appendData(r.Result().(*NodeBalancerConfigsPagedResponse))
}
case *TaggedObjectsPagedResponse:
idStr := idRaw.(string)
@ -342,7 +395,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelperWithID(ctx, i, id, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@ -352,7 +405,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
opts.PageOptions = &PageOptions{}
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelperWithID(ctx, i, id, opts); err != nil {
return err
@ -373,6 +426,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
// opts.results and opts.pages will be updated from the API response
func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstID, secondID int, opts *ListOptions) error {
req := c.R(ctx)
if opts != nil && opts.Page > 0 {
req.SetQueryParam("page", strconv.Itoa(opts.Page))
}
@ -395,7 +449,6 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
results = r.Result().(*NodeBalancerNodesPagedResponse).Results
v.appendData(r.Result().(*NodeBalancerNodesPagedResponse))
}
default:
log.Fatalf("Unknown listHelperWithTwoIDs interface{} %T used", i)
}
@ -405,7 +458,7 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@ -415,7 +468,7 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
opts.PageOptions = &PageOptions{}
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelperWithTwoIDs(ctx, i, firstID, secondID, opts); err != nil {
return err

View File

@ -76,7 +76,7 @@ func (i Profile) GetUpdateOptions() (o ProfileUpdateOptions) {
return
}
// GetProfile gets the profile with the provided ID
// GetProfile returns the Profile of the authenticated user
func (c *Client) GetProfile(ctx context.Context) (*Profile, error) {
e, err := c.Profile.Endpoint()
if err != nil {

View File

@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// SSHKey represents a SSHKey object
@ -12,7 +14,6 @@ type SSHKey struct {
ID int `json:"id"`
Label string `json:"label"`
SSHKey string `json:"ssh_key"`
CreatedStr string `json:"created"`
Created *time.Time `json:"-"`
}
@ -27,6 +28,26 @@ type SSHKeyUpdateOptions struct {
Label string `json:"label"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *SSHKey) UnmarshalJSON(b []byte) error {
type Mask SSHKey
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
return nil
}
// GetCreateOptions converts a SSHKey to SSHKeyCreateOptions for use in CreateSSHKey
func (i SSHKey) GetCreateOptions() (o SSHKeyCreateOptions) {
o.Label = i.Label
@ -64,21 +85,13 @@ func (resp *SSHKeysPagedResponse) appendData(r *SSHKeysPagedResponse) {
func (c *Client) ListSSHKeys(ctx context.Context, opts *ListOptions) ([]SSHKey, error) {
response := SSHKeysPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *SSHKey) fixDates() *SSHKey {
i.Created, _ = parseDates(i.CreatedStr)
return i
}
// GetSSHKey gets the sshkey with the provided ID
func (c *Client) GetSSHKey(ctx context.Context, id int) (*SSHKey, error) {
e, err := c.SSHKeys.Endpoint()
@ -90,7 +103,7 @@ func (c *Client) GetSSHKey(ctx context.Context, id int) (*SSHKey, error) {
if err != nil {
return nil, err
}
return r.Result().(*SSHKey).fixDates(), nil
return r.Result().(*SSHKey), nil
}
// CreateSSHKey creates a SSHKey
@ -116,7 +129,7 @@ func (c *Client) CreateSSHKey(ctx context.Context, createOpts SSHKeyCreateOption
if err != nil {
return nil, err
}
return r.Result().(*SSHKey).fixDates(), nil
return r.Result().(*SSHKey), nil
}
// UpdateSSHKey updates the SSHKey with the specified id
@ -143,7 +156,7 @@ func (c *Client) UpdateSSHKey(ctx context.Context, id int, updateOpts SSHKeyUpda
if err != nil {
return nil, err
}
return r.Result().(*SSHKey).fixDates(), nil
return r.Result().(*SSHKey), nil
}
// DeleteSSHKey deletes the SSHKey with the specified id
@ -156,5 +169,4 @@ func (c *Client) DeleteSSHKey(ctx context.Context, id int) error {
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Token represents a Token object
@ -13,6 +15,7 @@ type Token struct {
ID int `json:"id"`
// The scopes this token was created with. These define what parts of the Account the token can be used to access. Many command-line tools, such as the Linode CLI, require tokens with access to *. Tokens with more restrictive scopes are generally more secure.
// Valid values are "*" or a comma separated list of scopes https://developers.linode.com/api/v4/#o-auth
Scopes string `json:"scopes"`
// This token's label. This is for display purposes only, but can be used to more easily track what you're using each token for. (1-100 Characters)
@ -23,11 +26,9 @@ type Token struct {
// The date and time this token was created.
Created *time.Time `json:"-"`
CreatedStr string `json:"created"`
// When this token will expire. Personal Access Tokens cannot be renewed, so after this time the token will be completely unusable and a new token will need to be generated. Tokens may be created with "null" as their expiry and will never expire unless revoked.
Expiry *time.Time `json:"-"`
ExpiryStr string `json:"expiry"`
}
// TokenCreateOptions fields are those accepted by CreateToken
@ -48,6 +49,28 @@ type TokenUpdateOptions struct {
Label string `json:"label"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Token) UnmarshalJSON(b []byte) error {
type Mask Token
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Expiry *parseabletime.ParseableTime `json:"expiry"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Expiry = (*time.Time)(p.Expiry)
return nil
}
// GetCreateOptions converts a Token to TokenCreateOptions for use in CreateToken
func (i Token) GetCreateOptions() (o TokenCreateOptions) {
o.Label = i.Label
@ -86,22 +109,13 @@ func (resp *TokensPagedResponse) appendData(r *TokensPagedResponse) {
func (c *Client) ListTokens(ctx context.Context, opts *ListOptions) ([]Token, error) {
response := TokensPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *Token) fixDates() *Token {
i.Created, _ = parseDates(i.CreatedStr)
i.Expiry, _ = parseDates(i.ExpiryStr)
return i
}
// GetToken gets the token with the provided ID
func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
e, err := c.Tokens.Endpoint()
@ -113,7 +127,7 @@ func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
return r.Result().(*Token), nil
}
// CreateToken creates a Token
@ -152,7 +166,7 @@ func (c *Client) CreateToken(ctx context.Context, createOpts TokenCreateOptions)
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
return r.Result().(*Token), nil
}
// UpdateToken updates the Token with the specified id
@ -179,7 +193,7 @@ func (c *Client) UpdateToken(ctx context.Context, id int, updateOpts TokenUpdate
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
return r.Result().(*Token), nil
}
// DeleteToken deletes the Token with the specified id

View File

@ -35,20 +35,13 @@ func (resp *RegionsPagedResponse) appendData(r *RegionsPagedResponse) {
func (c *Client) ListRegions(ctx context.Context, opts *ListOptions) ([]Region, error) {
response := RegionsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *Region) fixDates() *Region {
return v
}
// GetRegion gets the template with the provided ID
func (c *Client) GetRegion(ctx context.Context, id string) (*Region, error) {
e, err := c.Regions.Endpoint()
@ -60,5 +53,5 @@ func (c *Client) GetRegion(ctx context.Context, id string) (*Region, error) {
if err != nil {
return nil, err
}
return r.Result().(*Region).fixDates(), nil
return r.Result().(*Region), nil
}

View File

@ -6,86 +6,109 @@ import (
"fmt"
"text/template"
"gopkg.in/resty.v1"
"github.com/go-resty/resty/v2"
)
const (
stackscriptsName = "stackscripts"
accountName = "account"
accountSettingsName = "accountsettings"
domainRecordsName = "records"
domainsName = "domains"
eventsName = "events"
firewallsName = "firewalls"
imagesName = "images"
instancesName = "instances"
instanceDisksName = "disks"
instanceConfigsName = "configs"
instanceDisksName = "disks"
instanceIPsName = "ips"
instanceSnapshotsName = "snapshots"
instanceStatsName = "instancestats"
instanceVolumesName = "instancevolumes"
instancesName = "instances"
invoiceItemsName = "invoiceitems"
invoicesName = "invoices"
ipaddressesName = "ipaddresses"
ipv6poolsName = "ipv6pools"
ipv6rangesName = "ipv6ranges"
regionsName = "regions"
volumesName = "volumes"
kernelsName = "kernels"
typesName = "types"
domainsName = "domains"
domainRecordsName = "records"
lkeClustersName = "lkeclusters"
lkeClusterPoolsName = "lkeclusterpools"
lkeVersionsName = "lkeversions"
longviewName = "longview"
longviewclientsName = "longviewclients"
longviewsubscriptionsName = "longviewsubscriptions"
nodebalancersName = "nodebalancers"
managedName = "managed"
nodebalancerconfigsName = "nodebalancerconfigs"
nodebalancernodesName = "nodebalancernodes"
nodebalancerStatsName = "nodebalancerstats"
nodebalancersName = "nodebalancers"
notificationsName = "notifications"
oauthClientsName = "oauthClients"
objectStorageBucketsName = "objectstoragebuckets"
objectStorageClustersName = "objectstorageclusters"
objectStorageKeysName = "objectstoragekeys"
paymentsName = "payments"
profileName = "profile"
regionsName = "regions"
sshkeysName = "sshkeys"
stackscriptsName = "stackscripts"
tagsName = "tags"
ticketsName = "tickets"
tokensName = "tokens"
accountName = "account"
eventsName = "events"
invoicesName = "invoices"
invoiceItemsName = "invoiceitems"
profileName = "profile"
managedName = "managed"
tagsName = "tags"
typesName = "types"
usersName = "users"
// notificationsName = "notifications"
volumesName = "volumes"
stackscriptsEndpoint = "linode/stackscripts"
accountEndpoint = "account"
accountSettingsEndpoint = "account/settings"
domainRecordsEndpoint = "domains/{{ .ID }}/records"
domainsEndpoint = "domains"
eventsEndpoint = "account/events"
firewallsEndpoint = "networking/firewalls"
imagesEndpoint = "images"
instancesEndpoint = "linode/instances"
instanceConfigsEndpoint = "linode/instances/{{ .ID }}/configs"
instanceDisksEndpoint = "linode/instances/{{ .ID }}/disks"
instanceSnapshotsEndpoint = "linode/instances/{{ .ID }}/backups"
instanceIPsEndpoint = "linode/instances/{{ .ID }}/ips"
instanceSnapshotsEndpoint = "linode/instances/{{ .ID }}/backups"
instanceStatsEndpoint = "linode/instances/{{ .ID }}/stats"
instanceVolumesEndpoint = "linode/instances/{{ .ID }}/volumes"
instancesEndpoint = "linode/instances"
invoiceItemsEndpoint = "account/invoices/{{ .ID }}/items"
invoicesEndpoint = "account/invoices"
ipaddressesEndpoint = "networking/ips"
ipv6poolsEndpoint = "networking/ipv6/pools"
ipv6rangesEndpoint = "networking/ipv6/ranges"
regionsEndpoint = "regions"
volumesEndpoint = "volumes"
kernelsEndpoint = "linode/kernels"
typesEndpoint = "linode/types"
domainsEndpoint = "domains"
domainRecordsEndpoint = "domains/{{ .ID }}/records"
lkeClustersEndpoint = "lke/clusters"
lkeClusterPoolsEndpoint = "lke/clusters/{{ .ID }}/pools"
lkeVersionsEndpoint = "lke/versions"
longviewEndpoint = "longview"
longviewclientsEndpoint = "longview/clients"
longviewsubscriptionsEndpoint = "longview/subscriptions"
nodebalancersEndpoint = "nodebalancers"
managedEndpoint = "managed"
// @TODO we can't use these nodebalancer endpoints unless we include these templated fields
// The API seems inconsistent about including parent IDs in objects, (compare instance configs to nb configs)
// Parent IDs would be immutable for updates and are ignored in create requests ..
// Should we include these fields in CreateOpts and UpdateOpts?
nodebalancerconfigsEndpoint = "nodebalancers/{{ .ID }}/configs"
nodebalancernodesEndpoint = "nodebalancers/{{ .ID }}/configs/{{ .SecondID }}/nodes"
nodebalancerStatsEndpoint = "nodebalancers/{{ .ID }}/stats"
nodebalancersEndpoint = "nodebalancers"
notificationsEndpoint = "account/notifications"
oauthClientsEndpoint = "account/oauth-clients"
objectStorageBucketsEndpoint = "object-storage/buckets"
objectStorageClustersEndpoint = "object-storage/clusters"
objectStorageKeysEndpoint = "object-storage/keys"
paymentsEndpoint = "account/payments"
profileEndpoint = "profile"
regionsEndpoint = "regions"
sshkeysEndpoint = "profile/sshkeys"
stackscriptsEndpoint = "linode/stackscripts"
tagsEndpoint = "tags"
ticketsEndpoint = "support/tickets"
tokensEndpoint = "profile/tokens"
accountEndpoint = "account"
eventsEndpoint = "account/events"
invoicesEndpoint = "account/invoices"
invoiceItemsEndpoint = "account/invoices/{{ .ID }}/items"
profileEndpoint = "profile"
managedEndpoint = "managed"
tagsEndpoint = "tags"
typesEndpoint = "linode/types"
usersEndpoint = "account/users"
notificationsEndpoint = "account/notifications"
volumesEndpoint = "volumes"
)
// Resource represents a linode API resource
@ -125,16 +148,19 @@ func (r Resource) render(data ...interface{}) (string, error) {
buf := bytes.NewBufferString(out)
var substitutions interface{}
if len(data) == 1 {
switch len(data) {
case 1:
substitutions = struct{ ID interface{} }{data[0]}
} else if len(data) == 2 {
case 2:
substitutions = struct {
ID interface{}
SecondID interface{}
}{data[0], data[1]}
} else {
default:
return "", NewError("Too many arguments to render template (expected 1 or 2)")
}
if err := r.endpointTemplate.Execute(buf, substitutions); err != nil {
return "", NewError(err)
}
@ -147,6 +173,7 @@ func (r Resource) endpointWithID(id ...int) (string, error) {
return r.endpoint, nil
}
data := make([]interface{}, len(id))
for i, v := range id {
data[i] = v
}

65
vendor/github.com/linode/linodego/retries.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package linodego
import (
"log"
"net/http"
"strconv"
"time"
"github.com/go-resty/resty/v2"
)
// type RetryConditional func(r *resty.Response) (shouldRetry bool)
type RetryConditional resty.RetryConditionFunc
// Configures resty to
// lock until enough time has passed to retry the request as determined by the Retry-After response header.
// If the Retry-After header is not set, we fall back to value of SetPollDelay.
func configureRetries(c *Client) {
c.resty.
SetRetryCount(1000).
AddRetryCondition(checkRetryConditionals(c)).
SetRetryAfter(respectRetryAfter)
}
func checkRetryConditionals(c *Client) func(*resty.Response, error) bool {
return func(r *resty.Response, err error) bool {
for _, retryConditional := range c.retryConditionals {
retry := retryConditional(r, err)
if retry {
log.Printf("[INFO] Received error %s - Retrying", r.Error())
return true
}
}
return false
}
}
// SetLinodeBusyRetry configures resty to retry specifically on "Linode busy." errors
// The retry wait time is configured in SetPollDelay
func linodeBusyRetryCondition(r *resty.Response, _ error) bool {
apiError, ok := r.Error().(*APIError)
linodeBusy := ok && apiError.Error() == "Linode busy."
retry := r.StatusCode() == http.StatusBadRequest && linodeBusy
return retry
}
func tooManyRequestsRetryCondition(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusTooManyRequests
}
func respectRetryAfter(client *resty.Client, resp *resty.Response) (time.Duration, error) {
retryAfterStr := resp.Header().Get("Retry-After")
if retryAfterStr == "" {
return 0, nil
}
retryAfter, err := strconv.Atoi(retryAfterStr)
if err != nil {
return 0, err
}
duration := time.Duration(retryAfter) * time.Second
log.Printf("[INFO] Respecting Retry-After Header of %d (%s) (max %s)", retryAfter, duration, client.RetryMaxWaitTime)
return duration, nil
}

View File

@ -5,17 +5,18 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// Stackscript represents a Linode StackScript
type Stackscript struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Username string `json:"username"`
Label string `json:"label"`
Description string `json:"description"`
Ordinal int `json:"ordinal"`
LogoURL string `json:"logo_url"`
Images []string `json:"images"`
DeploymentsTotal int `json:"deployments_total"`
DeploymentsActive int `json:"deployments_active"`
@ -62,6 +63,28 @@ type StackscriptCreateOptions struct {
// StackscriptUpdateOptions fields are those accepted by UpdateStackscript
type StackscriptUpdateOptions StackscriptCreateOptions
// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Stackscript) UnmarshalJSON(b []byte) error {
type Mask Stackscript
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)
return nil
}
// GetCreateOptions converts a Stackscript to StackscriptCreateOptions for use in CreateStackscript
func (i Stackscript) GetCreateOptions() StackscriptCreateOptions {
return StackscriptCreateOptions{
@ -110,22 +133,13 @@ func (resp *StackscriptsPagedResponse) appendData(r *StackscriptsPagedResponse)
func (c *Client) ListStackscripts(ctx context.Context, opts *ListOptions) ([]Stackscript, error) {
response := StackscriptsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *Stackscript) fixDates() *Stackscript {
i.Created, _ = parseDates(i.CreatedStr)
i.Updated, _ = parseDates(i.UpdatedStr)
return i
}
// GetStackscript gets the Stackscript with the provided ID
func (c *Client) GetStackscript(ctx context.Context, id int) (*Stackscript, error) {
e, err := c.StackScripts.Endpoint()
@ -139,7 +153,7 @@ func (c *Client) GetStackscript(ctx context.Context, id int) (*Stackscript, erro
if err != nil {
return nil, err
}
return r.Result().(*Stackscript).fixDates(), nil
return r.Result().(*Stackscript), nil
}
// CreateStackscript creates a StackScript
@ -165,7 +179,7 @@ func (c *Client) CreateStackscript(ctx context.Context, createOpts StackscriptCr
if err != nil {
return nil, err
}
return r.Result().(*Stackscript).fixDates(), nil
return r.Result().(*Stackscript), nil
}
// UpdateStackscript updates the StackScript with the specified id
@ -192,7 +206,7 @@ func (c *Client) UpdateStackscript(ctx context.Context, id int, updateOpts Stack
if err != nil {
return nil, err
}
return r.Result().(*Stackscript).fixDates(), nil
return r.Result().(*Stackscript), nil
}
// DeleteStackscript deletes the StackScript with the specified id

View File

@ -22,6 +22,7 @@ type TaggedObject struct {
// SortedObjects currently only includes Instances
type SortedObjects struct {
Instances []Instance
LKEClusters []LKECluster
Domains []Domain
Volumes []Volume
NodeBalancers []NodeBalancer
@ -37,6 +38,8 @@ type TaggedObjectList []TaggedObject
type TagCreateOptions struct {
Label string `json:"label"`
Linodes []int `json:"linodes,omitempty"`
// @TODO is this implemented?
LKEClusters []int `json:"lke_clusters,omitempty"`
Domains []int `json:"domains,omitempty"`
Volumes []int `json:"volumes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
@ -108,6 +111,12 @@ func (i *TaggedObject) fixData() (*TaggedObject, error) {
return nil, err
}
i.Data = obj
case "lke_cluster":
obj := LKECluster{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
return nil, err
}
i.Data = obj
case "nodebalancer":
obj := NodeBalancer{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
@ -135,9 +144,11 @@ func (i *TaggedObject) fixData() (*TaggedObject, error) {
func (c *Client) ListTaggedObjects(ctx context.Context, label string, opts *ListOptions) (TaggedObjectList, error) {
response := TaggedObjectsPagedResponse{}
err := c.listHelperWithID(ctx, &response, label, opts)
if err != nil {
return nil, err
}
for i := range response.Data {
if _, err := response.Data[i].fixData(); err != nil {
return nil, err
@ -149,31 +160,38 @@ func (c *Client) ListTaggedObjects(ctx context.Context, label string, opts *List
// SortedObjects converts a list of TaggedObjects into a Sorted Objects struct, for easier access
func (t TaggedObjectList) SortedObjects() (SortedObjects, error) {
so := SortedObjects{}
for _, o := range t {
switch o.Type {
case "linode":
if instance, ok := o.Data.(Instance); ok {
so.Instances = append(so.Instances, instance)
} else {
return so, errors.New("Expected an Instance when Type was \"linode\"")
return so, errors.New("expected an Instance when Type was \"linode\"")
}
case "lke_cluster":
if lkeCluster, ok := o.Data.(LKECluster); ok {
so.LKEClusters = append(so.LKEClusters, lkeCluster)
} else {
return so, errors.New("expected an LKECluster when Type was \"lke_cluster\"")
}
case "domain":
if domain, ok := o.Data.(Domain); ok {
so.Domains = append(so.Domains, domain)
} else {
return so, errors.New("Expected a Domain when Type was \"domain\"")
return so, errors.New("expected a Domain when Type was \"domain\"")
}
case "volume":
if volume, ok := o.Data.(Volume); ok {
so.Volumes = append(so.Volumes, volume)
} else {
return so, errors.New("Expected an Volume when Type was \"volume\"")
return so, errors.New("expected an Volume when Type was \"volume\"")
}
case "nodebalancer":
if nodebalancer, ok := o.Data.(NodeBalancer); ok {
so.NodeBalancers = append(so.NodeBalancers, nodebalancer)
} else {
return so, errors.New("Expected an NodeBalancer when Type was \"nodebalancer\"")
return so, errors.New("expected an NodeBalancer when Type was \"nodebalancer\"")
}
}
}

View File

@ -1,15 +0,0 @@
package linodego
import "time"
const (
dateLayout = "2006-01-02T15:04:05"
)
func parseDates(dateStr string) (*time.Time, error) {
d, err := time.Parse(dateLayout, dateStr)
if err != nil {
return nil, err
}
return &d, nil
}

View File

@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/linode/linodego/internal/parseabletime"
)
// VolumeStatus indicates the status of the Volume
@ -26,9 +28,6 @@ const (
// Volume represents a linode volume object
type Volume struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Label string `json:"label"`
Status VolumeStatus `json:"status"`
@ -51,6 +50,7 @@ type VolumeCreateOptions struct {
Size int `json:"size,omitempty"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
}
// VolumeUpdateOptions fields are those accepted by UpdateVolume
@ -63,6 +63,7 @@ type VolumeUpdateOptions struct {
type VolumeAttachOptions struct {
LinodeID int `json:"linode_id"`
ConfigID int `json:"config_id,omitempty"`
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
}
// VolumesPagedResponse represents a linode API response for listing of volumes
@ -71,6 +72,28 @@ type VolumesPagedResponse struct {
Data []Volume `json:"data"`
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (v *Volume) UnmarshalJSON(b []byte) error {
type Mask Volume
p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(v),
}
if err := json.Unmarshal(b, &p); err != nil {
return err
}
v.Created = time.Time(*p.Created)
v.Updated = time.Time(*p.Updated)
return nil
}
// GetUpdateOptions converts a Volume to VolumeUpdateOptions for use in UpdateVolume
func (v Volume) GetUpdateOptions() (updateOpts VolumeUpdateOptions) {
updateOpts.Label = v.Label
@ -108,26 +131,13 @@ func (resp *VolumesPagedResponse) appendData(r *VolumesPagedResponse) {
func (c *Client) ListVolumes(ctx context.Context, opts *ListOptions) ([]Volume, error) {
response := VolumesPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (v *Volume) fixDates() *Volume {
if parsed, err := parseDates(v.CreatedStr); err != nil {
v.Created = *parsed
}
if parsed, err := parseDates(v.UpdatedStr); err != nil {
v.Updated = *parsed
}
return v
}
// GetVolume gets the template with the provided ID
func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) {
e, err := c.Volumes.Endpoint()
@ -139,7 +149,7 @@ func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) {
if err != nil {
return nil, err
}
return r.Result().(*Volume).fixDates(), nil
return r.Result().(*Volume), nil
}
// AttachVolume attaches a volume to a Linode instance
@ -166,7 +176,7 @@ func (c *Client) AttachVolume(ctx context.Context, id int, options *VolumeAttach
return nil, err
}
return resp.Result().(*Volume).fixDates(), nil
return resp.Result().(*Volume), nil
}
// CreateVolume creates a Linode Volume
@ -192,7 +202,7 @@ func (c *Client) CreateVolume(ctx context.Context, createOpts VolumeCreateOption
return nil, err
}
return resp.Result().(*Volume).fixDates(), nil
return resp.Result().(*Volume), nil
}
// RenameVolume renames the label of a Linode volume
@ -226,7 +236,7 @@ func (c *Client) UpdateVolume(ctx context.Context, id int, volume VolumeUpdateOp
if err != nil {
return nil, err
}
return r.Result().(*Volume).fixDates(), nil
return r.Result().(*Volume), nil
}
// CloneVolume clones a Linode volume
@ -248,7 +258,7 @@ func (c *Client) CloneVolume(ctx context.Context, id int, label string) (*Volume
return nil, err
}
return resp.Result().(*Volume).fixDates(), nil
return resp.Result().(*Volume), nil
}
// DetachVolume detaches a Linode volume

View File

@ -18,6 +18,7 @@ func (client Client) WaitForInstanceStatus(ctx context.Context, instanceID int,
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -44,6 +45,7 @@ func (client Client) WaitForInstanceDiskStatus(ctx context.Context, instanceID i
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -53,16 +55,18 @@ func (client Client) WaitForInstanceDiskStatus(ctx context.Context, instanceID i
if err != nil {
return nil, err
}
for _, disk := range disks {
disk := disk
if disk.ID == diskID {
complete := (disk.Status == status)
if complete {
return &disk, nil
}
break
}
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Instance %d Disk %d status %s: %s", instanceID, diskID, status, ctx.Err())
}
@ -77,6 +81,7 @@ func (client Client) WaitForVolumeStatus(ctx context.Context, volumeID int, stat
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -103,6 +108,7 @@ func (client Client) WaitForSnapshotStatus(ctx context.Context, instanceID int,
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -131,6 +137,7 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -139,34 +146,54 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
return volume, err
}
if linodeID == nil && volume.LinodeID == nil {
switch {
case linodeID == nil && volume.LinodeID == nil:
return volume, nil
} else if linodeID == nil || volume.LinodeID == nil {
case linodeID == nil || volume.LinodeID == nil:
// continue waiting
} else if *volume.LinodeID == *linodeID {
case *volume.LinodeID == *linodeID:
return volume, nil
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Volume %d to have Instance %v: %s", volumeID, linodeID, ctx.Err())
}
}
}
// WaitForLKEClusterStatus waits for the LKECluster to reach the desired state
// before returning. It will timeout with an error after timeoutSeconds.
func (client Client) WaitForLKEClusterStatus(ctx context.Context, clusterID int, status LKEClusterStatus, timeoutSeconds int) (*LKECluster, error) {
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
cluster, err := client.GetLKECluster(ctx, clusterID)
if err != nil {
return cluster, err
}
complete := (cluster.Status == status)
if complete {
return cluster, nil
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Cluster %d status %s: %s", clusterID, status, ctx.Err())
}
}
}
// WaitForEventFinished waits for an entity action to reach the 'finished' state
// before returning. It will timeout with an error after timeoutSeconds.
// If the event indicates a failure both the failed event and the error will be returned.
// nolint
func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, entityType EntityType, action EventAction, minStart time.Time, timeoutSeconds int) (*Event, error) {
titledEntityType := strings.Title(string(entityType))
filter, _ := json.Marshal(map[string]interface{}{
// Entity is not filtered by the API
// Perhaps one day they will permit Entity ID/Type filtering.
// We'll have to verify these values manually, for now.
//"entity": map[string]interface{}{
// "id": fmt.Sprintf("%v", id),
// "type": entityType,
//},
filterStruct := map[string]interface{}{
// Nor is action
//"action": action,
@ -177,16 +204,33 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
//},
// With potentially 1000+ events coming back, we should filter on something
// Warning: This optimization has the potential to break if users are clearing
// events before we see them.
"seen": false,
// Float the latest events to page 1
"+order_by": "created",
"+order": "desc",
})
}
// Optimistically restrict results to page 1. We should remove this when more
// precise filtering options exist.
listOptions := NewListOptions(1, string(filter))
pages := 1
// The API has limitted filtering support for Event ID and Event Type
// Optimize the list, if possible
switch entityType {
case EntityDisk, EntityLinode, EntityDomain, EntityNodebalancer:
// All of the filter supported types have int ids
filterableEntityID, err := strconv.Atoi(fmt.Sprintf("%v", id))
if err != nil {
return nil, fmt.Errorf("Error parsing Entity ID %q for optimized WaitForEventFinished EventType %q: %s", id, entityType, err)
}
filterStruct["entity.id"] = filterableEntityID
filterStruct["entity.type"] = entityType
// TODO: are we conformatable with pages = 0 with the event type and id filter?
}
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
@ -197,10 +241,27 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
}
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
// avoid repeating log messages
nextLog := ""
lastLog := ""
lastEventID := 0
defer ticker.Stop()
for {
select {
case <-ticker.C:
if lastEventID > 0 {
filterStruct["id"] = map[string]interface{}{
"+gte": lastEventID,
}
}
filter, err := json.Marshal(filterStruct)
if err != nil {
return nil, err
}
listOptions := NewListOptions(pages, string(filter))
events, err := client.ListEvents(ctx, listOptions)
if err != nil {
@ -209,6 +270,8 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
// If there are events for this instance + action, inspect them
for _, event := range events {
event := event
if event.Action != action {
// log.Println("action mismatch", event.Action, action)
continue
@ -220,21 +283,21 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
var entID string
switch event.Entity.ID.(type) {
switch id := event.Entity.ID.(type) {
case float64, float32:
entID = fmt.Sprintf("%.f", event.Entity.ID)
entID = fmt.Sprintf("%.f", id)
case int:
entID = strconv.Itoa(event.Entity.ID.(int))
entID = strconv.Itoa(id)
default:
entID = fmt.Sprintf("%v", event.Entity.ID)
entID = fmt.Sprintf("%v", id)
}
var findID string
switch id.(type) {
switch id := id.(type) {
case float64, float32:
findID = fmt.Sprintf("%.f", id)
case int:
findID = strconv.Itoa(id.(int))
findID = strconv.Itoa(id)
default:
findID = fmt.Sprintf("%v", id)
}
@ -247,24 +310,33 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
// @TODO(displague) This event.Created check shouldn't be needed, but it appears
// that the ListEvents method is not populating it correctly
if event.Created == nil {
log.Printf("[WARN] event.Created is nil when API returned: %#+v", event.CreatedStr)
log.Printf("[WARN] event.Created is nil when API returned: %#+v", event.Created)
} else if *event.Created != minStart && !event.Created.After(minStart) {
// Not the event we were looking for
// log.Println(event.Created, "is not >=", minStart)
continue
}
if event.Status == EventFailed {
// This is the event we are looking for. Save our place.
if lastEventID == 0 {
lastEventID = event.ID
}
switch event.Status {
case EventFailed:
return &event, fmt.Errorf("%s %v action %s failed", titledEntityType, id, action)
} else if event.Status == EventScheduled {
log.Printf("[INFO] %s %v action %s is scheduled", titledEntityType, id, action)
} else if event.Status == EventFinished {
case EventFinished:
log.Printf("[INFO] %s %v action %s is finished", titledEntityType, id, action)
return &event, nil
}
// TODO(displague) can we bump the ticker to TimeRemaining/2 (>=1) when non-nil?
log.Printf("[INFO] %s %v action %s is %s", titledEntityType, id, action, event.Status)
nextLog = fmt.Sprintf("[INFO] %s %v action %s is %s", titledEntityType, id, action, event.Status)
}
// de-dupe logging statements
if nextLog != lastLog {
log.Print(nextLog)
lastLog = nextLog
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Event Status '%s' of %s %v action '%s': %s", EventFinished, titledEntityType, id, action, ctx.Err())

View File

@ -107,6 +107,7 @@ func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMis
// dialCall is an in-flight Transport dial call to a host.
type dialCall struct {
_ incomparable
p *clientConnPool
done chan struct{} // closed when done
res *ClientConn // valid after done is closed
@ -180,6 +181,7 @@ func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn)
}
type addConnCall struct {
_ incomparable
p *clientConnPool
done chan struct{} // closed when done
err error
@ -200,12 +202,6 @@ func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
close(c.done)
}
func (p *clientConnPool) addConn(key string, cc *ClientConn) {
p.mu.Lock()
p.addConnLocked(key, cc)
p.mu.Unlock()
}
// p.mu must be held
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
for _, v := range p.conns[key] {

View File

@ -8,6 +8,8 @@ package http2
// flow is the flow control window's size.
type flow struct {
_ incomparable
// n is the number of DATA bytes we're allowed to send.
// A flow is kept both on a conn and a per-stream.
n int32

View File

@ -105,7 +105,14 @@ func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error {
return nil
}
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()
type node struct {
_ incomparable
// children is non-nil for internal nodes
children *[256]*node

View File

@ -241,6 +241,7 @@ func (cw closeWaiter) Wait() {
// Its buffered writer is lazily allocated as needed, to minimize
// idle memory usage with many connections.
type bufferedWriter struct {
_ incomparable
w io.Writer // immutable
bw *bufio.Writer // non-nil when data is buffered
}
@ -313,6 +314,7 @@ func bodyAllowedForStatus(status int) bool {
}
type httpError struct {
_ incomparable
msg string
timeout bool
}
@ -376,3 +378,8 @@ func (s *sorter) SortStrings(ss []string) {
func validPseudoPath(v string) bool {
return (len(v) > 0 && v[0] == '/') || v == "*"
}
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()

View File

@ -761,6 +761,7 @@ func (sc *serverConn) readFrames() {
// frameWriteResult is the message passed from writeFrameAsync to the serve goroutine.
type frameWriteResult struct {
_ incomparable
wr FrameWriteRequest // what was written (or attempted)
err error // result of the writeFrame call
}
@ -771,7 +772,7 @@ type frameWriteResult struct {
// serverConn.
func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest) {
err := wr.write.writeFrame(sc)
sc.wroteFrameCh <- frameWriteResult{wr, err}
sc.wroteFrameCh <- frameWriteResult{wr: wr, err: err}
}
func (sc *serverConn) closeAllStreamsOnConnClose() {
@ -1161,7 +1162,7 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) {
if wr.write.staysWithinBuffer(sc.bw.Available()) {
sc.writingFrameAsync = false
err := wr.write.writeFrame(sc)
sc.wroteFrame(frameWriteResult{wr, err})
sc.wroteFrame(frameWriteResult{wr: wr, err: err})
} else {
sc.writingFrameAsync = true
go sc.writeFrameAsync(wr)
@ -2057,7 +2058,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
var trailer http.Header
for _, v := range rp.header["Trailer"] {
for _, key := range strings.Split(v, ",") {
key = http.CanonicalHeaderKey(strings.TrimSpace(key))
key = http.CanonicalHeaderKey(textproto.TrimString(key))
switch key {
case "Transfer-Encoding", "Trailer", "Content-Length":
// Bogus. (copy of http1 rules)
@ -2275,6 +2276,7 @@ func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
// requestBody is the Handler's Request.Body type.
// Read and Close may be called concurrently.
type requestBody struct {
_ incomparable
stream *stream
conn *serverConn
closed bool // for use by Close only

View File

@ -108,6 +108,19 @@ type Transport struct {
// waiting for their turn.
StrictMaxConcurrentStreams bool
// ReadIdleTimeout is the timeout after which a health check using ping
// frame will be carried out if no frame is received on the connection.
// Note that a ping response will is considered a received frame, so if
// there is no other traffic on the connection, the health check will
// be performed every ReadIdleTimeout interval.
// If zero, no health check is performed.
ReadIdleTimeout time.Duration
// PingTimeout is the timeout after which the connection will be closed
// if a response to Ping is not received.
// Defaults to 15s.
PingTimeout time.Duration
// t1, if non-nil, is the standard library Transport using
// this transport. Its settings are used (but not its
// RoundTrip method, etc).
@ -131,6 +144,14 @@ func (t *Transport) disableCompression() bool {
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
}
func (t *Transport) pingTimeout() time.Duration {
if t.PingTimeout == 0 {
return 15 * time.Second
}
return t.PingTimeout
}
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
// It returns an error if t1 has already been HTTP/2-enabled.
func ConfigureTransport(t1 *http.Transport) error {
@ -675,6 +696,20 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
return cc, nil
}
func (cc *ClientConn) healthCheck() {
pingTimeout := cc.t.pingTimeout()
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
// trigger the healthCheck again if there is no frame received.
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
defer cancel()
err := cc.Ping(ctx)
if err != nil {
cc.closeForLostPing()
cc.t.connPool().MarkDead(cc)
return
}
}
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
cc.mu.Lock()
defer cc.mu.Unlock()
@ -846,14 +881,12 @@ func (cc *ClientConn) sendGoAway() error {
return nil
}
// Close closes the client connection immediately.
//
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
func (cc *ClientConn) Close() error {
// closes the client connection immediately. In-flight requests are interrupted.
// err is sent to streams.
func (cc *ClientConn) closeForError(err error) error {
cc.mu.Lock()
defer cc.cond.Broadcast()
defer cc.mu.Unlock()
err := errors.New("http2: client connection force closed via ClientConn.Close")
for id, cs := range cc.streams {
select {
case cs.resc <- resAndError{err: err}:
@ -866,6 +899,20 @@ func (cc *ClientConn) Close() error {
return cc.tconn.Close()
}
// Close closes the client connection immediately.
//
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
func (cc *ClientConn) Close() error {
err := errors.New("http2: client connection force closed via ClientConn.Close")
return cc.closeForError(err)
}
// closes the client connection immediately. In-flight requests are interrupted.
func (cc *ClientConn) closeForLostPing() error {
err := errors.New("http2: client connection lost")
return cc.closeForError(err)
}
const maxAllocFrameSize = 512 << 10
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
@ -916,7 +963,7 @@ func commaSeparatedTrailers(req *http.Request) (string, error) {
k = http.CanonicalHeaderKey(k)
switch k {
case "Transfer-Encoding", "Trailer", "Content-Length":
return "", &badStringError{"invalid Trailer key", k}
return "", fmt.Errorf("invalid Trailer key %q", k)
}
keys = append(keys, k)
}
@ -1394,13 +1441,6 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
}
}
type badStringError struct {
what string
str string
}
func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
// requires cc.mu be held.
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
cc.hbuf.Reset()
@ -1616,6 +1656,7 @@ func (cc *ClientConn) writeHeader(name, value string) {
}
type resAndError struct {
_ incomparable
res *http.Response
err error
}
@ -1663,6 +1704,7 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
type clientConnReadLoop struct {
_ incomparable
cc *ClientConn
closeWhenIdle bool
}
@ -1742,8 +1784,17 @@ func (rl *clientConnReadLoop) run() error {
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
gotReply := false // ever saw a HEADERS reply
gotSettings := false
readIdleTimeout := cc.t.ReadIdleTimeout
var t *time.Timer
if readIdleTimeout != 0 {
t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
defer t.Stop()
}
for {
f, err := cc.fr.ReadFrame()
if t != nil {
t.Reset(readIdleTimeout)
}
if err != nil {
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
}
@ -2479,6 +2530,7 @@ func (rt erringRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
// gzipReader wraps a response body so it can lazily
// call gzip.NewReader on the first call to Read
type gzipReader struct {
_ incomparable
body io.ReadCloser // underlying Response.Body
zr *gzip.Reader // lazily-initialized gzip reader
zerr error // sticky error

File diff suppressed because it is too large Load Diff

327
vendor/gopkg.in/resty.v1/default.go generated vendored
View File

@ -1,327 +0,0 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"crypto/tls"
"encoding/json"
"io"
"math"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"time"
"golang.org/x/net/publicsuffix"
)
// DefaultClient of resty
var DefaultClient *Client
// New method creates a new go-resty client.
func New() *Client {
cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
return createClient(&http.Client{Jar: cookieJar})
}
// NewWithClient method create a new go-resty client with given `http.Client`.
func NewWithClient(hc *http.Client) *Client {
return createClient(hc)
}
// R creates a new resty request object, it is used form a HTTP/RESTful request
// such as GET, POST, PUT, DELETE, HEAD, PATCH and OPTIONS.
func R() *Request {
return DefaultClient.R()
}
// NewRequest is an alias for R(). Creates a new resty request object, it is used form a HTTP/RESTful request
// such as GET, POST, PUT, DELETE, HEAD, PATCH and OPTIONS.
func NewRequest() *Request {
return R()
}
// SetHostURL sets Host URL. See `Client.SetHostURL for more information.
func SetHostURL(url string) *Client {
return DefaultClient.SetHostURL(url)
}
// SetHeader sets single header. See `Client.SetHeader` for more information.
func SetHeader(header, value string) *Client {
return DefaultClient.SetHeader(header, value)
}
// SetHeaders sets multiple headers. See `Client.SetHeaders` for more information.
func SetHeaders(headers map[string]string) *Client {
return DefaultClient.SetHeaders(headers)
}
// SetCookieJar sets custom http.CookieJar. See `Client.SetCookieJar` for more information.
func SetCookieJar(jar http.CookieJar) *Client {
return DefaultClient.SetCookieJar(jar)
}
// SetCookie sets single cookie object. See `Client.SetCookie` for more information.
func SetCookie(hc *http.Cookie) *Client {
return DefaultClient.SetCookie(hc)
}
// SetCookies sets multiple cookie object. See `Client.SetCookies` for more information.
func SetCookies(cs []*http.Cookie) *Client {
return DefaultClient.SetCookies(cs)
}
// SetQueryParam method sets single parameter and its value. See `Client.SetQueryParam` for more information.
func SetQueryParam(param, value string) *Client {
return DefaultClient.SetQueryParam(param, value)
}
// SetQueryParams method sets multiple parameters and its value. See `Client.SetQueryParams` for more information.
func SetQueryParams(params map[string]string) *Client {
return DefaultClient.SetQueryParams(params)
}
// SetFormData method sets Form parameters and its values. See `Client.SetFormData` for more information.
func SetFormData(data map[string]string) *Client {
return DefaultClient.SetFormData(data)
}
// SetBasicAuth method sets the basic authentication header. See `Client.SetBasicAuth` for more information.
func SetBasicAuth(username, password string) *Client {
return DefaultClient.SetBasicAuth(username, password)
}
// SetAuthToken method sets bearer auth token header. See `Client.SetAuthToken` for more information.
func SetAuthToken(token string) *Client {
return DefaultClient.SetAuthToken(token)
}
// OnBeforeRequest method sets request middleware. See `Client.OnBeforeRequest` for more information.
func OnBeforeRequest(m func(*Client, *Request) error) *Client {
return DefaultClient.OnBeforeRequest(m)
}
// OnAfterResponse method sets response middleware. See `Client.OnAfterResponse` for more information.
func OnAfterResponse(m func(*Client, *Response) error) *Client {
return DefaultClient.OnAfterResponse(m)
}
// SetPreRequestHook method sets the pre-request hook. See `Client.SetPreRequestHook` for more information.
func SetPreRequestHook(h func(*Client, *Request) error) *Client {
return DefaultClient.SetPreRequestHook(h)
}
// SetDebug method enables the debug mode. See `Client.SetDebug` for more information.
func SetDebug(d bool) *Client {
return DefaultClient.SetDebug(d)
}
// SetDebugBodyLimit method sets the response body limit for debug mode. See `Client.SetDebugBodyLimit` for more information.
func SetDebugBodyLimit(sl int64) *Client {
return DefaultClient.SetDebugBodyLimit(sl)
}
// SetAllowGetMethodPayload method allows the GET method with payload. See `Client.SetAllowGetMethodPayload` for more information.
func SetAllowGetMethodPayload(a bool) *Client {
return DefaultClient.SetAllowGetMethodPayload(a)
}
// SetRetryCount method sets the retry count. See `Client.SetRetryCount` for more information.
func SetRetryCount(count int) *Client {
return DefaultClient.SetRetryCount(count)
}
// SetRetryWaitTime method sets the retry wait time. See `Client.SetRetryWaitTime` for more information.
func SetRetryWaitTime(waitTime time.Duration) *Client {
return DefaultClient.SetRetryWaitTime(waitTime)
}
// SetRetryMaxWaitTime method sets the retry max wait time. See `Client.SetRetryMaxWaitTime` for more information.
func SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
return DefaultClient.SetRetryMaxWaitTime(maxWaitTime)
}
// AddRetryCondition method appends check function for retry. See `Client.AddRetryCondition` for more information.
func AddRetryCondition(condition RetryConditionFunc) *Client {
return DefaultClient.AddRetryCondition(condition)
}
// SetDisableWarn method disables warning comes from `go-resty` client. See `Client.SetDisableWarn` for more information.
func SetDisableWarn(d bool) *Client {
return DefaultClient.SetDisableWarn(d)
}
// SetLogger method sets given writer for logging. See `Client.SetLogger` for more information.
func SetLogger(w io.Writer) *Client {
return DefaultClient.SetLogger(w)
}
// SetContentLength method enables `Content-Length` value. See `Client.SetContentLength` for more information.
func SetContentLength(l bool) *Client {
return DefaultClient.SetContentLength(l)
}
// SetError method is to register the global or client common `Error` object. See `Client.SetError` for more information.
func SetError(err interface{}) *Client {
return DefaultClient.SetError(err)
}
// SetRedirectPolicy method sets the client redirect poilicy. See `Client.SetRedirectPolicy` for more information.
func SetRedirectPolicy(policies ...interface{}) *Client {
return DefaultClient.SetRedirectPolicy(policies...)
}
// SetHTTPMode method sets go-resty mode into HTTP. See `Client.SetMode` for more information.
func SetHTTPMode() *Client {
return DefaultClient.SetHTTPMode()
}
// SetRESTMode method sets go-resty mode into RESTful. See `Client.SetMode` for more information.
func SetRESTMode() *Client {
return DefaultClient.SetRESTMode()
}
// Mode method returns the current client mode. See `Client.Mode` for more information.
func Mode() string {
return DefaultClient.Mode()
}
// SetTLSClientConfig method sets TLSClientConfig for underling client Transport. See `Client.SetTLSClientConfig` for more information.
func SetTLSClientConfig(config *tls.Config) *Client {
return DefaultClient.SetTLSClientConfig(config)
}
// SetTimeout method sets timeout for request. See `Client.SetTimeout` for more information.
func SetTimeout(timeout time.Duration) *Client {
return DefaultClient.SetTimeout(timeout)
}
// SetProxy method sets Proxy for request. See `Client.SetProxy` for more information.
func SetProxy(proxyURL string) *Client {
return DefaultClient.SetProxy(proxyURL)
}
// RemoveProxy method removes the proxy configuration. See `Client.RemoveProxy` for more information.
func RemoveProxy() *Client {
return DefaultClient.RemoveProxy()
}
// SetCertificates method helps to set client certificates into resty conveniently.
// See `Client.SetCertificates` for more information and example.
func SetCertificates(certs ...tls.Certificate) *Client {
return DefaultClient.SetCertificates(certs...)
}
// SetRootCertificate method helps to add one or more root certificates into resty client.
// See `Client.SetRootCertificate` for more information.
func SetRootCertificate(pemFilePath string) *Client {
return DefaultClient.SetRootCertificate(pemFilePath)
}
// SetOutputDirectory method sets output directory. See `Client.SetOutputDirectory` for more information.
func SetOutputDirectory(dirPath string) *Client {
return DefaultClient.SetOutputDirectory(dirPath)
}
// SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
// compatible interface implementation in the resty client.
// See `Client.SetTransport` for more information.
func SetTransport(transport http.RoundTripper) *Client {
return DefaultClient.SetTransport(transport)
}
// SetScheme method sets custom scheme in the resty client.
// See `Client.SetScheme` for more information.
func SetScheme(scheme string) *Client {
return DefaultClient.SetScheme(scheme)
}
// SetCloseConnection method sets close connection value in the resty client.
// See `Client.SetCloseConnection` for more information.
func SetCloseConnection(close bool) *Client {
return DefaultClient.SetCloseConnection(close)
}
// SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
// See `Client.SetDoNotParseResponse` for more information.
func SetDoNotParseResponse(parse bool) *Client {
return DefaultClient.SetDoNotParseResponse(parse)
}
// SetPathParams method sets the Request path parameter key-value pairs. See
// `Client.SetPathParams` for more information.
func SetPathParams(params map[string]string) *Client {
return DefaultClient.SetPathParams(params)
}
// IsProxySet method returns the true if proxy is set on client otherwise false.
// See `Client.IsProxySet` for more information.
func IsProxySet() bool {
return DefaultClient.IsProxySet()
}
// GetClient method returns the current `http.Client` used by the default resty client.
func GetClient() *http.Client {
return DefaultClient.httpClient
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported methods
//___________________________________
func createClient(hc *http.Client) *Client {
c := &Client{
HostURL: "",
QueryParam: url.Values{},
FormData: url.Values{},
Header: http.Header{},
UserInfo: nil,
Token: "",
Cookies: make([]*http.Cookie, 0),
Debug: false,
Log: getLogger(os.Stderr),
RetryCount: 0,
RetryWaitTime: defaultWaitTime,
RetryMaxWaitTime: defaultMaxWaitTime,
JSONMarshal: json.Marshal,
JSONUnmarshal: json.Unmarshal,
jsonEscapeHTML: true,
httpClient: hc,
debugBodySizeLimit: math.MaxInt32,
pathParams: make(map[string]string),
}
// Log Prefix
c.SetLogPrefix("RESTY ")
// Default redirect policy
c.SetRedirectPolicy(NoRedirectPolicy())
// default before request middlewares
c.beforeRequest = []func(*Client, *Request) error{
parseRequestURL,
parseRequestHeader,
parseRequestBody,
createHTTPRequest,
addCredentials,
}
// user defined request middlewares
c.udBeforeRequest = []func(*Client, *Request) error{}
// default after response middlewares
c.afterResponse = []func(*Client, *Response) error{
responseLogger,
parseResponseBody,
saveResponseIntoFile,
}
return c
}
func init() {
DefaultClient = New()
}

3
vendor/gopkg.in/resty.v1/go.mod generated vendored
View File

@ -1,3 +0,0 @@
module gopkg.in/resty.v1
require golang.org/x/net v0.0.0-20181220203305-927f97764cc3

View File

@ -1,63 +0,0 @@
// +build !go1.7
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
// 2016 Andrew Grigorev (https://github.com/ei-grad)
// All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"time"
)
// Request type is used to compose and send individual request from client
// go-resty is provide option override client level settings such as
// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
// and also you can add more options for that particular request
type Request struct {
URL string
Method string
Token string
QueryParam url.Values
FormData url.Values
Header http.Header
Time time.Time
Body interface{}
Result interface{}
Error interface{}
RawRequest *http.Request
SRV *SRVRecord
UserInfo *User
isMultiPart bool
isFormData bool
setContentLength bool
isSaveResponse bool
notParseResponse bool
jsonEscapeHTML bool
outputFile string
fallbackContentType string
pathParams map[string]string
client *Client
bodyBuf *bytes.Buffer
multipartFiles []*File
multipartFields []*MultipartField
}
func (r *Request) addContextIfAvailable() {
// nothing to do for golang<1.7
}
func (r *Request) isContextCancelledIfAvailable() bool {
// just always return false golang<1.7
return false
}
// for !go1.7
var noescapeJSONMarshal = json.Marshal

View File

@ -1,96 +0,0 @@
// +build go1.7 go1.8
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com)
// 2016 Andrew Grigorev (https://github.com/ei-grad)
// All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/url"
"time"
)
// Request type is used to compose and send individual request from client
// go-resty is provide option override client level settings such as
// Auth Token, Basic Auth credentials, Header, Query Param, Form Data, Error object
// and also you can add more options for that particular request
type Request struct {
URL string
Method string
Token string
QueryParam url.Values
FormData url.Values
Header http.Header
Time time.Time
Body interface{}
Result interface{}
Error interface{}
RawRequest *http.Request
SRV *SRVRecord
UserInfo *User
isMultiPart bool
isFormData bool
setContentLength bool
isSaveResponse bool
notParseResponse bool
jsonEscapeHTML bool
outputFile string
fallbackContentType string
ctx context.Context
pathParams map[string]string
client *Client
bodyBuf *bytes.Buffer
multipartFiles []*File
multipartFields []*MultipartField
}
// Context method returns the Context if its already set in request
// otherwise it creates new one using `context.Background()`.
func (r *Request) Context() context.Context {
if r.ctx == nil {
return context.Background()
}
return r.ctx
}
// SetContext method sets the context.Context for current Request. It allows
// to interrupt the request execution if ctx.Done() channel is closed.
// See https://blog.golang.org/context article and the "context" package
// documentation.
func (r *Request) SetContext(ctx context.Context) *Request {
r.ctx = ctx
return r
}
func (r *Request) addContextIfAvailable() {
if r.ctx != nil {
r.RawRequest = r.RawRequest.WithContext(r.ctx)
}
}
func (r *Request) isContextCancelledIfAvailable() bool {
if r.ctx != nil {
if r.ctx.Err() != nil {
return true
}
}
return false
}
// for go1.7+
var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
buf := acquireBuffer()
defer releaseBuffer(buf)
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(v)
return buf.Bytes(), err
}

9
vendor/gopkg.in/resty.v1/resty.go generated vendored
View File

@ -1,9 +0,0 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// Package resty provides Simple HTTP and REST client library for Go.
package resty
// Version # of resty
const Version = "1.12.0"

118
vendor/gopkg.in/resty.v1/retry.go generated vendored
View File

@ -1,118 +0,0 @@
// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package resty
import (
"math"
"math/rand"
"time"
)
const (
defaultMaxRetries = 3
defaultWaitTime = time.Duration(100) * time.Millisecond
defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
)
type (
// Option is to create convenient retry options like wait time, max retries, etc.
Option func(*Options)
// RetryConditionFunc type is for retry condition function
RetryConditionFunc func(*Response) (bool, error)
// Options to hold go-resty retry values
Options struct {
maxRetries int
waitTime time.Duration
maxWaitTime time.Duration
retryConditions []RetryConditionFunc
}
)
// Retries sets the max number of retries
func Retries(value int) Option {
return func(o *Options) {
o.maxRetries = value
}
}
// WaitTime sets the default wait time to sleep between requests
func WaitTime(value time.Duration) Option {
return func(o *Options) {
o.waitTime = value
}
}
// MaxWaitTime sets the max wait time to sleep between requests
func MaxWaitTime(value time.Duration) Option {
return func(o *Options) {
o.maxWaitTime = value
}
}
// RetryConditions sets the conditions that will be checked for retry.
func RetryConditions(conditions []RetryConditionFunc) Option {
return func(o *Options) {
o.retryConditions = conditions
}
}
// Backoff retries with increasing timeout duration up until X amount of retries
// (Default is 3 attempts, Override with option Retries(n))
func Backoff(operation func() (*Response, error), options ...Option) error {
// Defaults
opts := Options{
maxRetries: defaultMaxRetries,
waitTime: defaultWaitTime,
maxWaitTime: defaultMaxWaitTime,
retryConditions: []RetryConditionFunc{},
}
for _, o := range options {
o(&opts)
}
var (
resp *Response
err error
)
base := float64(opts.waitTime) // Time to wait between each attempt
capLevel := float64(opts.maxWaitTime) // Maximum amount of wait time for the retry
for attempt := 0; attempt < opts.maxRetries; attempt++ {
resp, err = operation()
var needsRetry bool
var conditionErr error
for _, condition := range opts.retryConditions {
needsRetry, conditionErr = condition(resp)
if needsRetry || conditionErr != nil {
break
}
}
// If the operation returned no error, there was no condition satisfied and
// there was no error caused by the conditional functions.
if err == nil && !needsRetry && conditionErr == nil {
return nil
}
// Adding capped exponential backup with jitter
// See the following article...
// http://www.awsarchitectureblog.com/2015/03/backoff.html
temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
ri := int(temp / 2)
if ri <= 0 {
ri = 1<<31 - 1 // max int for arch 386
}
sleepDuration := time.Duration(math.Abs(float64(ri + rand.Intn(ri))))
if sleepDuration < opts.waitTime {
sleepDuration = opts.waitTime
}
time.Sleep(sleepDuration)
}
return err
}

10
vendor/modules.txt vendored
View File

@ -222,6 +222,8 @@ github.com/go-ini/ini
# github.com/go-ole/go-ole v1.2.4
github.com/go-ole/go-ole
github.com/go-ole/go-ole/oleutil
# github.com/go-resty/resty/v2 v2.3.0
github.com/go-resty/resty/v2
# github.com/gobwas/glob v0.2.3
github.com/gobwas/glob
github.com/gobwas/glob/compiler
@ -435,8 +437,10 @@ github.com/klauspost/pgzip
github.com/konsorten/go-windows-terminal-sequences
# github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169
github.com/kr/fs
# github.com/linode/linodego v0.7.1
# github.com/linode/linodego v0.14.0
github.com/linode/linodego
github.com/linode/linodego/internal/duration
github.com/linode/linodego/internal/parseabletime
# github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c
github.com/masterzen/azure-sdk-for-go/core/http
github.com/masterzen/azure-sdk-for-go/core/tls
@ -702,7 +706,7 @@ golang.org/x/mobile/event/key
# golang.org/x/mod v0.2.0
golang.org/x/mod/module
golang.org/x/mod/semver
# golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
# golang.org/x/net v0.0.0-20200602114024-627f9648deb9
golang.org/x/net/context
golang.org/x/net/context/ctxhttp
golang.org/x/net/html
@ -851,8 +855,6 @@ google.golang.org/grpc/status
google.golang.org/grpc/tap
# gopkg.in/ini.v1 v1.42.0
gopkg.in/ini.v1
# gopkg.in/resty.v1 v1.12.0
gopkg.in/resty.v1
# gopkg.in/square/go-jose.v2 v2.3.1
gopkg.in/square/go-jose.v2
gopkg.in/square/go-jose.v2/cipher