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:
parent
4afcc794be
commit
70a2c7d364
|
@ -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
7
go.mod
|
@ -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
14
go.sum
|
@ -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=
|
||||
|
|
3
vendor/gopkg.in/resty.v1/.gitignore → vendor/github.com/go-resty/resty/v2/.gitignore
generated
vendored
3
vendor/gopkg.in/resty.v1/.gitignore → vendor/github.com/go-resty/resty/v2/.gitignore
generated
vendored
|
@ -26,3 +26,6 @@ _testmain.go
|
|||
coverage.out
|
||||
coverage.txt
|
||||
go.sum
|
||||
|
||||
# Exclude intellij IDE folders
|
||||
.idea/*
|
15
vendor/gopkg.in/resty.v1/.travis.yml → vendor/github.com/go-resty/resty/v2/.travis.yml
generated
vendored
15
vendor/gopkg.in/resty.v1/.travis.yml → vendor/github.com/go-resty/resty/v2/.travis.yml
generated
vendored
|
@ -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)
|
|
@ -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",
|
||||
],
|
|
@ -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
|
401
vendor/gopkg.in/resty.v1/README.md → vendor/github.com/go-resty/resty/v2/README.md
generated
vendored
401
vendor/gopkg.in/resty.v1/README.md → vendor/github.com/go-resty/resty/v2/README.md
generated
vendored
|
@ -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:
|
||||
* 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.
|
||||
* 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.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.
|
0
vendor/gopkg.in/resty.v1/WORKSPACE → vendor/github.com/go-resty/resty/v2/WORKSPACE
generated
vendored
0
vendor/gopkg.in/resty.v1/WORKSPACE → vendor/github.com/go-resty/resty/v2/WORKSPACE
generated
vendored
652
vendor/gopkg.in/resty.v1/client.go → vendor/github.com/go-resty/resty/v2/client.go
generated
vendored
652
vendor/gopkg.in/resty.v1/client.go → vendor/github.com/go-resty/resty/v2/client.go
generated
vendored
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
module github.com/go-resty/resty/v2
|
||||
|
||||
require golang.org/x/net v0.0.0-20200513185701-a91f0712d120
|
||||
|
||||
go 1.11
|
117
vendor/gopkg.in/resty.v1/middleware.go → vendor/github.com/go-resty/resty/v2/middleware.go
generated
vendored
117
vendor/gopkg.in/resty.v1/middleware.go → vendor/github.com/go-resty/resty/v2/middleware.go
generated
vendored
|
@ -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" +
|
||||
fmt.Sprintf("STATUS : %s\n", res.Status()) +
|
||||
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"
|
||||
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("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
|
12
vendor/gopkg.in/resty.v1/redirect.go → vendor/github.com/go-resty/resty/v2/redirect.go
generated
vendored
12
vendor/gopkg.in/resty.v1/redirect.go → vendor/github.com/go-resty/resty/v2/redirect.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
}
|
519
vendor/gopkg.in/resty.v1/request.go → vendor/github.com/go-resty/resty/v2/request.go
generated
vendored
519
vendor/gopkg.in/resty.v1/request.go → vendor/github.com/go-resty/resty/v2/request.go
generated
vendored
|
@ -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,58 +698,83 @@ func (r *Request) Execute(method, url string) (*Response, error) {
|
|||
RetryConditions(r.client.RetryConditions),
|
||||
)
|
||||
|
||||
return resp, err
|
||||
return resp, unwrapNoRetryErr(err)
|
||||
}
|
||||
|
||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
// SRVRecord struct
|
||||
//_______________________________________________________________________
|
||||
|
||||
// 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() (body string) {
|
||||
func (r *Request) fmtBodyString(sl int64) (body string) {
|
||||
body = "***** NO CONTENT *****"
|
||||
if isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
|
||||
if _, ok := r.Body.(io.Reader); ok {
|
||||
body = "***** BODY IS io.Reader *****"
|
||||
if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := r.Body.(io.Reader); ok {
|
||||
body = "***** BODY IS io.Reader *****"
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// multipart or form-data
|
||||
if r.isMultiPart || r.isFormData {
|
||||
body = r.bodyBuf.String()
|
||||
return
|
||||
}
|
||||
// request body data
|
||||
if r.Body == nil {
|
||||
return
|
||||
}
|
||||
var prtBodyBytes []byte
|
||||
var err error
|
||||
|
||||
// request body data
|
||||
if r.Body == nil {
|
||||
return
|
||||
}
|
||||
var prtBodyBytes []byte
|
||||
var err error
|
||||
|
||||
contentType := r.Header.Get(hdrContentTypeKey)
|
||||
kind := kindOf(r.Body)
|
||||
if canJSONMarshal(contentType, kind) {
|
||||
prtBodyBytes, err = json.MarshalIndent(&r.Body, "", " ")
|
||||
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
|
||||
prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ")
|
||||
} else if b, ok := r.Body.(string); ok {
|
||||
if IsJSONType(contentType) {
|
||||
bodyBytes := []byte(b)
|
||||
out := acquireBuffer()
|
||||
defer releaseBuffer(out)
|
||||
if err = json.Indent(out, bodyBytes, "", " "); err == nil {
|
||||
prtBodyBytes = out.Bytes()
|
||||
}
|
||||
} else {
|
||||
body = b
|
||||
return
|
||||
contentType := r.Header.Get(hdrContentTypeKey)
|
||||
kind := kindOf(r.Body)
|
||||
if canJSONMarshal(contentType, kind) {
|
||||
prtBodyBytes, err = json.MarshalIndent(&r.Body, "", " ")
|
||||
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
|
||||
prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ")
|
||||
} else if b, ok := r.Body.(string); ok {
|
||||
if IsJSONType(contentType) {
|
||||
bodyBytes := []byte(b)
|
||||
out := acquireBuffer()
|
||||
defer releaseBuffer(out)
|
||||
if err = json.Indent(out, bodyBytes, "", " "); err == nil {
|
||||
prtBodyBytes = out.Bytes()
|
||||
}
|
||||
} else if b, ok := r.Body.([]byte); ok {
|
||||
body = base64.StdEncoding.EncodeToString(b)
|
||||
} else {
|
||||
body = b
|
||||
}
|
||||
} else if b, ok := r.Body.([]byte); ok {
|
||||
body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
|
||||
return
|
||||
}
|
||||
|
||||
if prtBodyBytes != nil && err == nil {
|
||||
body = string(prtBodyBytes)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
49
vendor/gopkg.in/resty.v1/response.go → vendor/github.com/go-resty/resty/v2/response.go
generated
vendored
49
vendor/gopkg.in/resty.v1/response.go → vendor/github.com/go-resty/resty/v2/response.go
generated
vendored
|
@ -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()
|
||||
}
|
|
@ -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),
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
142
vendor/gopkg.in/resty.v1/util.go → vendor/github.com/go-resty/resty/v2/util.go
generated
vendored
142
vendor/gopkg.in/resty.v1/util.go → vendor/github.com/go-resty/resty/v2/util.go
generated
vendored
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -330,7 +392,7 @@
|
|||
- [x] `GET`
|
||||
- [x] `PUT`
|
||||
- [x] `DELETE`
|
||||
|
||||
|
||||
### Two-Factor
|
||||
|
||||
- `/profile/tfa-disable`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,20 +4,21 @@ import "context"
|
|||
|
||||
// Account associated with the token in use
|
||||
type Account struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Company string `json:"company"`
|
||||
Address1 string `json:"address_1"`
|
||||
Address2 string `json:"address_2"`
|
||||
Balance float32 `json:"balance"`
|
||||
City string `json:"city"`
|
||||
State string `json:"state"`
|
||||
Zip string `json:"zip"`
|
||||
Country string `json:"country"`
|
||||
TaxID string `json:"tax_id"`
|
||||
Phone string `json:"phone"`
|
||||
CreditCard *CreditCard `json:"credit_card"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Company string `json:"company"`
|
||||
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"`
|
||||
Country string `json:"country"`
|
||||
TaxID string `json:"tax_id"`
|
||||
Phone string `json:"phone"`
|
||||
CreditCard *CreditCard `json:"credit_card"`
|
||||
}
|
||||
|
||||
// CreditCard information associated with the Account.
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,8 +34,7 @@ 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:"-"`
|
||||
TimeRemaining *int `json:"-"`
|
||||
|
||||
// The username of the User who caused the Event.
|
||||
Username string `json:"username"`
|
||||
|
@ -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"
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -35,49 +44,62 @@ var (
|
|||
|
||||
// Client is a wrapper around the Resty client
|
||||
type Client struct {
|
||||
resty *resty.Client
|
||||
userAgent string
|
||||
resources map[string]*Resource
|
||||
debug bool
|
||||
resty *resty.Client
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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, "; ")
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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=
|
|
@ -5,24 +5,23 @@ 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"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Vendor string `json:"vendor"`
|
||||
Size int `json:"size"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
Deprecated bool `json:"deprecated"`
|
||||
|
||||
Created *time.Time `json:"-"`
|
||||
Expiry *time.Time `json:"-"`
|
||||
ID string `json:"id"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Vendor string `json:"vendor"`
|
||||
Size int `json:"size"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
Deprecated bool `json:"deprecated"`
|
||||
Created *time.Time `json:"-"`
|
||||
Expiry *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// ImageCreateOptions fields are those accepted by CreateImage
|
||||
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -14,22 +14,23 @@ type InstanceIPAddressResponse struct {
|
|||
|
||||
// InstanceIPv4Response contains the details of all IPv4 addresses associated with an Instance
|
||||
type InstanceIPv4Response struct {
|
||||
Public []*InstanceIP `json:"public"`
|
||||
Private []*InstanceIP `json:"private"`
|
||||
Shared []*InstanceIP `json:"shared"`
|
||||
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
|
||||
type InstanceIP struct {
|
||||
Address string `json:"address"`
|
||||
Gateway string `json:"gateway"`
|
||||
SubnetMask string `json:"subnet_mask"`
|
||||
Prefix int `json:"prefix"`
|
||||
Type string `json:"type"`
|
||||
Public bool `json:"public"`
|
||||
RDNS string `json:"rdns"`
|
||||
LinodeID int `json:"linode_id"`
|
||||
Region string `json:"region"`
|
||||
Address string `json:"address"`
|
||||
Gateway string `json:"gateway"`
|
||||
SubnetMask string `json:"subnet_mask"`
|
||||
Prefix int `json:"prefix"`
|
||||
Type InstanceIPType `json:"type"`
|
||||
Public bool `json:"public"`
|
||||
RDNS string `json:"rdns"`
|
||||
LinodeID int `json:"linode_id"`
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
// InstanceIPv6Response contains the IPv6 addresses and ranges for an Instance
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
22
vendor/github.com/linode/linodego/internal/parseabletime/parseable_time.go
generated
vendored
Normal file
22
vendor/github.com/linode/linodego/internal/parseabletime/parseable_time.go
generated
vendored
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -5,15 +5,16 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/linode/linodego/internal/parseabletime"
|
||||
)
|
||||
|
||||
// SSHKey represents a SSHKey object
|
||||
type SSHKey struct {
|
||||
ID int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
SSHKey string `json:"ssh_key"`
|
||||
CreatedStr string `json:"created"`
|
||||
Created *time.Time `json:"-"`
|
||||
ID int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
SSHKey string `json:"ssh_key"`
|
||||
Created *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// SSHKeyCreateOptions fields are those accepted by CreateSSHKey
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -22,12 +25,10 @@ type Token struct {
|
|||
Token string `json:"token"`
|
||||
|
||||
// The date and time this token was created.
|
||||
Created *time.Time `json:"-"`
|
||||
CreatedStr string `json:"created"`
|
||||
Created *time.Time `json:"-"`
|
||||
|
||||
// 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"`
|
||||
Expiry *time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
sshkeysEndpoint = "profile/sshkeys"
|
||||
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"
|
||||
usersEndpoint = "account/users"
|
||||
notificationsEndpoint = "account/notifications"
|
||||
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"
|
||||
typesEndpoint = "linode/types"
|
||||
usersEndpoint = "account/users"
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -35,11 +36,13 @@ type TaggedObjectList []TaggedObject
|
|||
|
||||
// TagCreateOptions fields are those accepted by CreateTag
|
||||
type TagCreateOptions struct {
|
||||
Label string `json:"label"`
|
||||
Linodes []int `json:"linodes,omitempty"`
|
||||
Domains []int `json:"domains,omitempty"`
|
||||
Volumes []int `json:"volumes,omitempty"`
|
||||
NodeBalancers []int `json:"nodebalancers,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// GetCreateOptions converts a Tag to TagCreateOptions for use in CreateTag
|
||||
|
@ -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\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
|
@ -50,7 +49,8 @@ type VolumeCreateOptions struct {
|
|||
// The Volume's size, in GiB. Minimum size is 10GiB, maximum size is 10240GiB. A "0" value will result in the default size.
|
||||
Size int `json:"size,omitempty"`
|
||||
// An array of tags applied to this object. Tags are for organizational purposes only.
|
||||
Tags []string `json:"tags"`
|
||||
Tags []string `json:"tags"`
|
||||
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeUpdateOptions fields are those accepted by UpdateVolume
|
||||
|
@ -61,8 +61,9 @@ type VolumeUpdateOptions struct {
|
|||
|
||||
// VolumeAttachOptions fields are those accepted by AttachVolume
|
||||
type VolumeAttachOptions struct {
|
||||
LinodeID int `json:"linode_id"`
|
||||
ConfigID int `json:"config_id,omitempty"`
|
||||
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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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()
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module gopkg.in/resty.v1
|
||||
|
||||
require golang.org/x/net v0.0.0-20181220203305-927f97764cc3
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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"
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue