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"`
|
RootSSHKey string `mapstructure:"root_ssh_key"`
|
||||||
ImageLabel string `mapstructure:"image_label"`
|
ImageLabel string `mapstructure:"image_label"`
|
||||||
Description string `mapstructure:"image_description"`
|
Description string `mapstructure:"image_description"`
|
||||||
|
|
||||||
interCtx interpolate.Context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRandomRootPassword() (string, error) {
|
func createRandomRootPassword() (string, error) {
|
||||||
|
@ -130,7 +128,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
for _, t := range c.Tags {
|
for _, t := range c.Tags {
|
||||||
if !tagRe.MatchString(t) {
|
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-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
|
||||||
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
|
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
|
||||||
github.com/digitalocean/godo v1.11.1
|
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/docker/docker v0.0.0-20180422163414-57142e89befe // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/dylanmei/iso8601 v0.1.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/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-ini/ini v1.25.4
|
github.com/go-ini/ini v1.25.4
|
||||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
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/gobwas/glob v0.2.3
|
||||||
github.com/gocolly/colly v1.2.0
|
github.com/gocolly/colly v1.2.0
|
||||||
github.com/gofrs/flock v0.7.1
|
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/crc32 v0.0.0-20160114101742-999f3125931f // indirect
|
||||||
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
|
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
|
||||||
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
|
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/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
|
||||||
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
|
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
|
||||||
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939
|
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-homedir v1.1.0
|
||||||
github.com/mitchellh/go-testing-interface v1.0.3 // indirect
|
github.com/mitchellh/go-testing-interface v1.0.3 // indirect
|
||||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
|
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/iochan v1.0.0
|
||||||
github.com/mitchellh/mapstructure v1.2.3
|
github.com/mitchellh/mapstructure v1.2.3
|
||||||
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
|
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
|
||||||
|
@ -145,7 +146,7 @@ require (
|
||||||
github.com/zclconf/go-cty-yaml v1.0.1
|
github.com/zclconf/go-cty-yaml v1.0.1
|
||||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825
|
golang.org/x/crypto v0.0.0-20200422194213-44a606286825
|
||||||
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8
|
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/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
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-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 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
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-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.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
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.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 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
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 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
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=
|
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/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 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE=
|
||||||
github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY=
|
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 h1:FMUOnVGy8nWk1cvlMCAoftRItQGMxI0vzJ3dQjeZTCE=
|
||||||
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
|
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=
|
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 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
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 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 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
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-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 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-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-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-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=
|
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-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 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-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-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 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
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/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 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
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.out
|
||||||
coverage.txt
|
coverage.txt
|
||||||
go.sum
|
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
|
sudo: false
|
||||||
|
|
||||||
go:
|
go: # use travis ci resource effectively, keep always latest 2 versions and tip :)
|
||||||
# - 1.3
|
- 1.14.x
|
||||||
# - 1.4
|
- 1.13.x
|
||||||
# - 1.5
|
|
||||||
# - 1.6
|
|
||||||
# - 1.7
|
|
||||||
# - 1.8.x
|
|
||||||
# - 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get -v -t ./...
|
- go get -v -t ./...
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test ./... -coverprofile=coverage.txt -covermode=atomic
|
- go test ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
|
@ -6,7 +6,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
gazelle(
|
gazelle(
|
||||||
name = "gazelle",
|
name = "gazelle",
|
||||||
command = "fix",
|
command = "fix",
|
||||||
prefix = "gopkg.in/resty.v1",
|
prefix = "github.com/go-resty/resty/v2",
|
||||||
)
|
)
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
|
@ -15,7 +15,7 @@ go_library(
|
||||||
["*.go"],
|
["*.go"],
|
||||||
exclude = ["*_test.go"],
|
exclude = ["*_test.go"],
|
||||||
),
|
),
|
||||||
importpath = "gopkg.in/resty.v1",
|
importpath = "github.com/go-resty/resty/v2",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
|
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,7 @@ go_test(
|
||||||
),
|
),
|
||||||
data = glob([".testdata/*"]),
|
data = glob([".testdata/*"]),
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
importpath = "gopkg.in/resty.v1",
|
importpath = "github.com/go-resty/resty/v2",
|
||||||
deps = [
|
deps = [
|
||||||
"@org_golang_x_net//proxy:go_default_library",
|
"@org_golang_x_net//proxy:go_default_library",
|
||||||
],
|
],
|
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
397
vendor/gopkg.in/resty.v1/README.md → vendor/github.com/go-resty/resty/v2/README.md
generated
vendored
397
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 align="center"><a href="#features">Features</a> section describes in detail about Resty capabilities</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<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>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<h4 align="center">Resty Communication Channels</h4>
|
<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>
|
</p>
|
||||||
|
|
||||||
## News
|
## News
|
||||||
|
|
||||||
* Resty `v2` development is in-progress :smile:
|
* v2.3.0 [released](https://github.com/go-resty/resty/releases/tag/v2.3.0) and tagged on May 20, 2020.
|
||||||
|
* v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
|
||||||
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
|
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
|
||||||
* v1.11.0 [released](https://github.com/go-resty/resty/releases/tag/v1.11.0) and tagged on Jan 06, 2019.
|
|
||||||
* v1.10.3 [released](https://github.com/go-resty/resty/releases/tag/v1.10.3) and tagged on Dec 04, 2018.
|
|
||||||
* v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).
|
* 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
|
## Features
|
||||||
|
|
||||||
* GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
|
* GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc.
|
||||||
* Simple and chainable methods for settings and request
|
* 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`
|
* Auto detects `Content-Type`
|
||||||
* Buffer less processing for `io.Reader`
|
* 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()`
|
* Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()`
|
||||||
* Know your `response.Time()` and when we `response.ReceivedAt()`
|
* Know your `response.Time()` and when we `response.ReceivedAt()`
|
||||||
* Automatic marshal and unmarshal for `JSON` and `XML` content type
|
* Automatic marshal and unmarshal for `JSON` and `XML` content type
|
||||||
* Default is `JSON`, if you supply `struct/map` without header `Content-Type`
|
* Default is `JSON`, if you supply `struct/map` without header `Content-Type`
|
||||||
* For auto-unmarshal, refer to -
|
* 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).
|
- 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/gopkg.in/resty.v1#Request.SetError) and [Response.Error()](https://godoc.org/gopkg.in/resty.v1#Response.Error).
|
- 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`
|
- 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`
|
* Easy to upload one or more file(s) via `multipart/form-data`
|
||||||
* Auto detects file content type
|
* 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)
|
* 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
|
* 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 `go1.7` and above
|
* `Request.SetContext` supported
|
||||||
* Authorization option of `BasicAuth` and `Bearer` token
|
* Authorization option of `BasicAuth` and `Bearer` token
|
||||||
* Set request `ContentLength` value for all request or particular request
|
* Set request `ContentLength` value for all request or particular request
|
||||||
* Choose between HTTP and REST mode. Default is `REST`
|
* 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)
|
||||||
* `HTTP` - default up to 10 redirects and no automatic response unmarshal
|
* 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).
|
||||||
* `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).
|
|
||||||
* Cookies for your request and CookieJar support
|
* Cookies for your request and CookieJar support
|
||||||
* SRV Record based request instead of Host URL
|
* SRV Record based request instead of Host URL
|
||||||
* Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc.
|
* 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)
|
* 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)
|
* 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)
|
* 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
|
* Have client level settings & options and also override at Request level if you want to
|
||||||
* Request and Response middlewares
|
* Request and Response middlewares
|
||||||
* Create Multiple clients if you want to `resty.New()`
|
* 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
|
* 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
|
* Debug mode - clean and informative logging presentation
|
||||||
* Gzip - Go does it automatically also resty has fallback handling too
|
* Gzip - Go does it automatically also resty has fallback handling too
|
||||||
* Works fine with `HTTP/2` and `HTTP/1.1`
|
* Works fine with `HTTP/2` and `HTTP/1.1`
|
||||||
* [Bazel support](#bazel-support)
|
* [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
|
* Well tested client library
|
||||||
|
|
||||||
Resty works with `go1.3` and above.
|
|
||||||
|
|
||||||
### Included Batteries
|
### Included Batteries
|
||||||
|
|
||||||
* Redirect Policies - see [how to use](#redirect-policy)
|
* 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)
|
* 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)).
|
* 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
|
- 1.9.7+
|
||||||
require gopkg.in/resty.v1 v1.12.0
|
- 1.10.3+
|
||||||
```
|
- 1.11+
|
||||||
|
|
||||||
##### 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`.
|
|
||||||
|
|
||||||
|
|
||||||
## It might be beneficial for your project :smile:
|
## 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.
|
Resty author also published following projects for Go Community.
|
||||||
|
|
||||||
* [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework.
|
* [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`.
|
* [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
|
## Usage
|
||||||
|
|
||||||
The following samples will assist you to become as comfortable as possible with resty library. Resty comes with ready to use DefaultClient.
|
The following samples will assist you to become as comfortable as possible with resty library.
|
||||||
|
|
||||||
Import resty into your code and refer it as `resty`.
|
|
||||||
|
|
||||||
```go
|
```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
|
#### Simple GET
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// GET request
|
// Create a Resty Client
|
||||||
resp, err := resty.R().Get("http://httpbin.org/get")
|
client := resty.New()
|
||||||
|
|
||||||
// explore response object
|
resp, err := client.R().
|
||||||
fmt.Printf("\nError: %v", err)
|
EnableTrace().
|
||||||
fmt.Printf("\nResponse Status Code: %v", resp.StatusCode())
|
Get("https://httpbin.org/get")
|
||||||
fmt.Printf("\nResponse Status: %v", resp.Status())
|
|
||||||
fmt.Printf("\nResponse Time: %v", resp.Time())
|
// Explore response object
|
||||||
fmt.Printf("\nResponse Received At: %v", resp.ReceivedAt())
|
fmt.Println("Response Info:")
|
||||||
fmt.Printf("\nResponse Body: %v", resp) // or resp.String() or string(resp.Body())
|
fmt.Println("Error :", err)
|
||||||
// more...
|
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
|
/* Output
|
||||||
|
Response Info:
|
||||||
Error : <nil>
|
Error : <nil>
|
||||||
Response Status Code: 200
|
Status Code: 200
|
||||||
Response Status: 200 OK
|
Status : 200 OK
|
||||||
Response Time: 160.1151ms
|
Proto : HTTP/2.0
|
||||||
Response Received At: 2018-10-16 16:28:34.8595663 -0700 PDT m=+0.166119401
|
Time : 475.611189ms
|
||||||
Response Body: {
|
Received At: 2020-05-19 00:11:06.828188 -0700 PDT m=+0.476510773
|
||||||
|
Body :
|
||||||
|
{
|
||||||
"args": {},
|
"args": {},
|
||||||
"headers": {
|
"headers": {
|
||||||
"Accept-Encoding": "gzip",
|
"Accept-Encoding": "gzip",
|
||||||
"Connection": "close",
|
|
||||||
"Host": "httpbin.org",
|
"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",
|
"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
|
#### Enhanced GET
|
||||||
|
|
||||||
```go
|
```go
|
||||||
resp, err := resty.R().
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
resp, err := client.R().
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"page_no": "1",
|
"page_no": "1",
|
||||||
"limit": "20",
|
"limit": "20",
|
||||||
|
@ -176,7 +205,7 @@ resp, err := resty.R().
|
||||||
|
|
||||||
|
|
||||||
// Sample of using Request.SetQueryString method
|
// 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").
|
SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more").
|
||||||
SetHeader("Accept", "application/json").
|
SetHeader("Accept", "application/json").
|
||||||
SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
|
SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
|
||||||
|
@ -186,9 +215,12 @@ resp, err := resty.R().
|
||||||
#### Various POST method combinations
|
#### Various POST method combinations
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// POST JSON string
|
// POST JSON string
|
||||||
// No need to set content type, if you have client level setting
|
// 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").
|
SetHeader("Content-Type", "application/json").
|
||||||
SetBody(`{"username":"testuser", "password":"testpass"}`).
|
SetBody(`{"username":"testuser", "password":"testpass"}`).
|
||||||
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
||||||
|
@ -196,21 +228,21 @@ resp, err := resty.R().
|
||||||
|
|
||||||
// POST []byte array
|
// POST []byte array
|
||||||
// No need to set content type, if you have client level setting
|
// 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").
|
SetHeader("Content-Type", "application/json").
|
||||||
SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
|
SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)).
|
||||||
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
||||||
Post("https://myapp.com/login")
|
Post("https://myapp.com/login")
|
||||||
|
|
||||||
// POST Struct, default is JSON content type. No need to set one
|
// 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"}).
|
SetBody(User{Username: "testuser", Password: "testpass"}).
|
||||||
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
||||||
SetError(&AuthError{}). // or SetError(AuthError{}).
|
SetError(&AuthError{}). // or SetError(AuthError{}).
|
||||||
Post("https://myapp.com/login")
|
Post("https://myapp.com/login")
|
||||||
|
|
||||||
// POST Map, default is JSON content type. No need to set one
|
// 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"}).
|
SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
|
||||||
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
|
||||||
SetError(&AuthError{}). // or SetError(AuthError{}).
|
SetError(&AuthError{}). // or SetError(AuthError{}).
|
||||||
|
@ -220,7 +252,7 @@ resp, err := resty.R().
|
||||||
fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
|
fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
|
||||||
|
|
||||||
// See we are not setting content-type header, since go-resty automatically detects Content-Type for you
|
// 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).
|
SetBody(fileBytes).
|
||||||
SetContentLength(true). // Dropbox expects this value
|
SetContentLength(true). // Dropbox expects this value
|
||||||
SetAuthToken("<your-auth-token>").
|
SetAuthToken("<your-auth-token>").
|
||||||
|
@ -239,9 +271,12 @@ You can use various combinations of `PUT` method call like demonstrated for `POS
|
||||||
```go
|
```go
|
||||||
// Note: This is one sample of PUT method usage, refer POST for more combination
|
// 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
|
// Request goes as JSON content type
|
||||||
// No need to set auth token, error, if you have client level settings
|
// No need to set auth token, error, if you have client level settings
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetBody(Article{
|
SetBody(Article{
|
||||||
Title: "go-resty",
|
Title: "go-resty",
|
||||||
Content: "This is my article content, oh ya!",
|
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
|
```go
|
||||||
// Note: This is one sample of PUT method usage, refer POST for more combination
|
// 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
|
// Request goes as JSON content type
|
||||||
// No need to set auth token, error, if you have client level settings
|
// No need to set auth token, error, if you have client level settings
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetBody(Article{
|
SetBody(Article{
|
||||||
Tags: []string{"new tag1", "new tag2"},
|
Tags: []string{"new tag1", "new tag2"},
|
||||||
}).
|
}).
|
||||||
|
@ -274,16 +312,19 @@ resp, err := resty.R().
|
||||||
#### Sample DELETE, HEAD, OPTIONS
|
#### Sample DELETE, HEAD, OPTIONS
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// DELETE a article
|
// DELETE a article
|
||||||
// No need to set auth token, error, if you have client level settings
|
// 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").
|
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
|
||||||
SetError(&Error{}). // or SetError(Error{}).
|
SetError(&Error{}). // or SetError(Error{}).
|
||||||
Delete("https://myapp.com/articles/1234")
|
Delete("https://myapp.com/articles/1234")
|
||||||
|
|
||||||
// DELETE a articles with payload/body as a JSON string
|
// DELETE a articles with payload/body as a JSON string
|
||||||
// No need to set auth token, error, if you have client level settings
|
// 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").
|
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
|
||||||
SetError(&Error{}). // or SetError(Error{}).
|
SetError(&Error{}). // or SetError(Error{}).
|
||||||
SetHeader("Content-Type", "application/json").
|
SetHeader("Content-Type", "application/json").
|
||||||
|
@ -292,13 +333,13 @@ resp, err := resty.R().
|
||||||
|
|
||||||
// HEAD of resource
|
// HEAD of resource
|
||||||
// No need to set auth token, if you have client level settings
|
// 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").
|
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
|
||||||
Head("https://myapp.com/videos/hi-res-video")
|
Head("https://myapp.com/videos/hi-res-video")
|
||||||
|
|
||||||
// OPTIONS of resource
|
// OPTIONS of resource
|
||||||
// No need to set auth token, if you have client level settings
|
// 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").
|
SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD").
|
||||||
Options("https://myapp.com/servers/nyc-dc-01")
|
Options("https://myapp.com/servers/nyc-dc-01")
|
||||||
```
|
```
|
||||||
|
@ -311,7 +352,10 @@ resp, err := resty.R().
|
||||||
profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
|
profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
|
||||||
notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")
|
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("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)).
|
||||||
SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
|
SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)).
|
||||||
SetFormData(map[string]string{
|
SetFormData(map[string]string{
|
||||||
|
@ -324,13 +368,16 @@ resp, err := resty.R().
|
||||||
#### Using File directly from Path
|
#### Using File directly from Path
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Single file scenario
|
// Single file scenario
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetFile("profile_img", "/Users/jeeva/test-img.png").
|
SetFile("profile_img", "/Users/jeeva/test-img.png").
|
||||||
Post("http://myapp.com/upload")
|
Post("http://myapp.com/upload")
|
||||||
|
|
||||||
// Multiple files scenario
|
// Multiple files scenario
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetFiles(map[string]string{
|
SetFiles(map[string]string{
|
||||||
"profile_img": "/Users/jeeva/test-img.png",
|
"profile_img": "/Users/jeeva/test-img.png",
|
||||||
"notes": "/Users/jeeva/text-file.txt",
|
"notes": "/Users/jeeva/text-file.txt",
|
||||||
|
@ -338,7 +385,7 @@ resp, err := resty.R().
|
||||||
Post("http://myapp.com/upload")
|
Post("http://myapp.com/upload")
|
||||||
|
|
||||||
// Multipart of form fields and files
|
// Multipart of form fields and files
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetFiles(map[string]string{
|
SetFiles(map[string]string{
|
||||||
"profile_img": "/Users/jeeva/test-img.png",
|
"profile_img": "/Users/jeeva/test-img.png",
|
||||||
"notes": "/Users/jeeva/text-file.txt",
|
"notes": "/Users/jeeva/text-file.txt",
|
||||||
|
@ -356,9 +403,12 @@ resp, err := resty.R().
|
||||||
#### Sample Form submission
|
#### Sample Form submission
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// just mentioning about POST as an example with simple flow
|
// just mentioning about POST as an example with simple flow
|
||||||
// User Login
|
// User Login
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetFormData(map[string]string{
|
SetFormData(map[string]string{
|
||||||
"username": "jeeva",
|
"username": "jeeva",
|
||||||
"password": "mypass",
|
"password": "mypass",
|
||||||
|
@ -366,7 +416,7 @@ resp, err := resty.R().
|
||||||
Post("http://myapp.com/login")
|
Post("http://myapp.com/login")
|
||||||
|
|
||||||
// Followed by profile update
|
// Followed by profile update
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetFormData(map[string]string{
|
SetFormData(map[string]string{
|
||||||
"first_name": "Jeevanandam",
|
"first_name": "Jeevanandam",
|
||||||
"last_name": "M",
|
"last_name": "M",
|
||||||
|
@ -379,27 +429,30 @@ resp, err := resty.R().
|
||||||
criteria := url.Values{
|
criteria := url.Values{
|
||||||
"search_criteria": []string{"book", "glass", "pencil"},
|
"search_criteria": []string{"book", "glass", "pencil"},
|
||||||
}
|
}
|
||||||
resp, err := resty.R().
|
resp, err := client.R().
|
||||||
SetMultiValueFormData(criteria).
|
SetFormDataFromValues(criteria).
|
||||||
Post("http://myapp.com/search")
|
Post("http://myapp.com/search")
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Save HTTP Response into File
|
#### Save HTTP Response into File
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Setting output directory path, If directory not exists then resty creates one!
|
// Setting output directory path, If directory not exists then resty creates one!
|
||||||
// This is optional one, if you're planning using absoule path in
|
// This is optional one, if you're planning using absoule path in
|
||||||
// `Request.SetOutput` and can used together.
|
// `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
|
// HTTP response gets saved into file, similar to curl -o flag
|
||||||
_, err := resty.R().
|
_, err := client.R().
|
||||||
SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
|
SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip").
|
||||||
Get("http://bit.ly/1LouEKr")
|
Get("http://bit.ly/1LouEKr")
|
||||||
|
|
||||||
// OR using absolute path
|
// OR using absolute path
|
||||||
// Note: output directory path is not used for absoulte path
|
// Note: output directory path is not used for absolute path
|
||||||
_, err := resty.R().
|
_, err := client.R().
|
||||||
SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
|
SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip").
|
||||||
Get("http://bit.ly/1LouEKr")
|
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.
|
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
|
```go
|
||||||
resty.R().SetPathParams(map[string]string{
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
client.R().SetPathParams(map[string]string{
|
||||||
"userId": "sample@sample.com",
|
"userId": "sample@sample.com",
|
||||||
"subAccountId": "100002",
|
"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.
|
Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Registering Request Middleware
|
// 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
|
// Now you have access to Client and current Request object
|
||||||
// manipulate it as per your need
|
// manipulate it as per your need
|
||||||
|
|
||||||
|
@ -433,7 +492,7 @@ resty.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Registering Response Middleware
|
// 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
|
// Now you have access to Client and current Response object
|
||||||
// manipulate it as per your need
|
// 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.
|
Resty provides few ready to use redirect policy(s) also it supports multiple policies together.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Assign Client Redirect Policy. Create one as per you need
|
// 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
|
// 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"))
|
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.
|
Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a look [redirect.go](redirect.go) for more information.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Using raw func into resty.SetRedirectPolicy
|
// 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
|
// Implement your logic here
|
||||||
|
|
||||||
// return nil for continue redirect otherwise return error to stop/prevent redirect
|
// 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
|
// Registering in resty
|
||||||
resty.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
|
client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */})
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Custom Root Certificates and Client Certificates
|
#### Custom Root Certificates and Client Certificates
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Custom Root certificates, just supply .pem file.
|
// Custom Root certificates, just supply .pem file.
|
||||||
// you can add one or more root certificates, its get appended
|
// you can add one or more root certificates, its get appended
|
||||||
resty.SetRootCertificate("/path/to/root/pemFile1.pem")
|
client.SetRootCertificate("/path/to/root/pemFile1.pem")
|
||||||
resty.SetRootCertificate("/path/to/root/pemFile2.pem")
|
client.SetRootCertificate("/path/to/root/pemFile2.pem")
|
||||||
// ... and so on!
|
// ... and so on!
|
||||||
|
|
||||||
// Adding Client Certificates, you add one or more certificates
|
// Adding Client Certificates, you add one or more certificates
|
||||||
|
@ -504,7 +572,30 @@ if err != nil {
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// You add one or more certificates
|
// 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
|
#### 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
|
**Client Level Proxy** settings applied to all the request
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Setting a Proxy URL and Port
|
// Setting a Proxy URL and Port
|
||||||
resty.SetProxy("http://proxyserver:8888")
|
client.SetProxy("http://proxyserver:8888")
|
||||||
|
|
||||||
// Want to remove proxy setting
|
// Want to remove proxy setting
|
||||||
resty.RemoveProxy()
|
client.RemoveProxy()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Retries
|
#### Retries
|
||||||
|
@ -530,8 +624,11 @@ to increase retry intervals after each attempt.
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Retries are configured per client
|
// Retries are configured per client
|
||||||
resty.
|
client.
|
||||||
// Set retry count to non zero to enable retries
|
// Set retry count to non zero to enable retries
|
||||||
SetRetryCount(3).
|
SetRetryCount(3).
|
||||||
// You can override initial retry wait time.
|
// You can override initial retry wait time.
|
||||||
|
@ -539,7 +636,12 @@ resty.
|
||||||
SetRetryWaitTime(5 * time.Second).
|
SetRetryWaitTime(5 * time.Second).
|
||||||
// MaxWaitTime can be overridden as well.
|
// MaxWaitTime can be overridden as well.
|
||||||
// Default is 2 seconds.
|
// 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
|
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:
|
You can optionally provide client with custom retry conditions:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
resty.AddRetryCondition(
|
// Create a Resty Client
|
||||||
// Condition function will be provided with *resty.Response as a
|
client := resty.New()
|
||||||
// parameter. It is expected to return (bool, error) pair. Resty will retry
|
|
||||||
// in case condition returns true or non nil error.
|
client.AddRetryCondition(
|
||||||
func(r *resty.Response) (bool, error) {
|
// RetryConditionFunc type is for retry condition function
|
||||||
return r.StatusCode() == http.StatusTooManyRequests, nil
|
// 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
|
It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios
|
||||||
implemented. [Reference](retry_test.go).
|
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
|
#### Allow GET request with Payload
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Allow GET request with Payload. This is disabled by default.
|
// Allow GET request with Payload. This is disabled by default.
|
||||||
resty.SetAllowGetMethodPayload(true)
|
client.SetAllowGetMethodPayload(true)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Wanna Multiple Clients
|
#### Wanna Multiple Clients
|
||||||
|
@ -603,40 +700,39 @@ client2.R().Head("http://httpbin.org")
|
||||||
#### Remaining Client Settings & its Options
|
#### Remaining Client Settings & its Options
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Create a Resty Client
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
// Unique settings at Client level
|
// Unique settings at Client level
|
||||||
//--------------------------------
|
//--------------------------------
|
||||||
// Enable debug mode
|
// Enable debug mode
|
||||||
resty.SetDebug(true)
|
client.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)
|
|
||||||
|
|
||||||
// Assign Client TLSClientConfig
|
// Assign Client TLSClientConfig
|
||||||
// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
|
// 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)
|
// 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
|
// 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
|
// 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
|
// 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
|
// Headers for all request
|
||||||
resty.SetHeader("Accept", "application/json")
|
client.SetHeader("Accept", "application/json")
|
||||||
resty.SetHeaders(map[string]string{
|
client.SetHeaders(map[string]string{
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": "My custom User Agent String",
|
"User-Agent": "My custom User Agent String",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Cookies for all request
|
// Cookies for all request
|
||||||
resty.SetCookie(&http.Cookie{
|
client.SetCookie(&http.Cookie{
|
||||||
Name:"go-resty",
|
Name:"go-resty",
|
||||||
Value:"This is cookie value",
|
Value:"This is cookie value",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
|
@ -645,32 +741,32 @@ resty.SetCookie(&http.Cookie{
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: false,
|
Secure: false,
|
||||||
})
|
})
|
||||||
resty.SetCookies(cookies)
|
client.SetCookies(cookies)
|
||||||
|
|
||||||
// URL query parameters for all request
|
// URL query parameters for all request
|
||||||
resty.SetQueryParam("user_id", "00001")
|
client.SetQueryParam("user_id", "00001")
|
||||||
resty.SetQueryParams(map[string]string{ // sample of those who use this manner
|
client.SetQueryParams(map[string]string{ // sample of those who use this manner
|
||||||
"api_key": "api-key-here",
|
"api_key": "api-key-here",
|
||||||
"api_secert": "api-secert",
|
"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
|
// 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",
|
"access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Basic Auth for all request
|
// Basic Auth for all request
|
||||||
resty.SetBasicAuth("myuser", "mypass")
|
client.SetBasicAuth("myuser", "mypass")
|
||||||
|
|
||||||
// Bearer Auth Token for all request
|
// Bearer Auth Token for all request
|
||||||
resty.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
|
client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
|
||||||
|
|
||||||
// Enabling Content length value for all request
|
// Enabling Content length value for all request
|
||||||
resty.SetContentLength(true)
|
client.SetContentLength(true)
|
||||||
|
|
||||||
// Registering global Error object structure for JSON/XML request
|
// 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
|
#### 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
|
// Set the previous transport that we created, set the scheme of the communication to the
|
||||||
// socket and set the unixSocket as the HostURL.
|
// 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.
|
// 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
|
#### 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:
|
When using the default resty client, you should pass the client to the library as follow:
|
||||||
|
|
||||||
```go
|
```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).
|
More detailed example of mocking resty http requests using ginko could be found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example).
|
||||||
|
|
||||||
## Versioning
|
## 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.
|
* 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
|
## 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)
|
[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
|
## Contributors
|
||||||
|
|
||||||
Have a look on [Contributors](https://github.com/go-resty/resty/graphs/contributors) page.
|
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
642
vendor/gopkg.in/resty.v1/client.go → vendor/github.com/go-resty/resty/v2/client.go
generated
vendored
642
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
|
111
vendor/gopkg.in/resty.v1/middleware.go → vendor/github.com/go-resty/resty/v2/middleware.go
generated
vendored
111
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
|
// resty source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -20,9 +21,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
const debugRequestLogKey = "__restyDebugRequestLog"
|
||||||
|
|
||||||
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
// Request Middleware(s)
|
// Request Middleware(s)
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
func parseRequestURL(c *Client, r *Request) error {
|
func parseRequestURL(c *Client, r *Request) error {
|
||||||
// GitHub #103 Path Params
|
// GitHub #103 Path Params
|
||||||
|
@ -104,7 +107,7 @@ func parseRequestHeader(c *Client, r *Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
|
if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
|
||||||
hdr.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
|
hdr.Set(hdrUserAgentKey, hdrUserAgentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := hdr.Get(hdrContentTypeKey)
|
ct := hdr.Get(hdrContentTypeKey)
|
||||||
|
@ -176,19 +179,58 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
|
||||||
// Add headers into http request
|
// Add headers into http request
|
||||||
r.RawRequest.Header = r.Header
|
r.RawRequest.Header = r.Header
|
||||||
|
|
||||||
// Add cookies into http request
|
// Add cookies from client instance into http request
|
||||||
for _, cookie := range c.Cookies {
|
for _, cookie := range c.Cookies {
|
||||||
r.RawRequest.AddCookie(cookie)
|
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
|
// it's for non-http scheme option
|
||||||
if r.RawRequest.URL != nil && r.RawRequest.URL.Scheme == "" {
|
if r.RawRequest.URL != nil && r.RawRequest.URL.Scheme == "" {
|
||||||
r.RawRequest.URL.Scheme = c.scheme
|
r.RawRequest.URL.Scheme = c.scheme
|
||||||
r.RawRequest.URL.Host = r.URL
|
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
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
@ -206,15 +248,25 @@ func addCredentials(c *Client, r *Request) error {
|
||||||
|
|
||||||
if !c.DisableWarn {
|
if !c.DisableWarn {
|
||||||
if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
|
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
|
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) {
|
} else if !IsStringEmpty(c.Token) {
|
||||||
r.RawRequest.Header.Set(hdrAuthorizationKey, "Bearer "+c.Token)
|
r.RawRequest.Header.Set(hdrAuthorizationKey, authScheme+" "+c.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -223,30 +275,32 @@ func addCredentials(c *Client, r *Request) error {
|
||||||
func requestLogger(c *Client, r *Request) error {
|
func requestLogger(c *Client, r *Request) error {
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
rr := r.RawRequest
|
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 c.requestLog != nil {
|
||||||
if err := c.requestLog(rl); err != nil {
|
if err := c.requestLog(rl); err != nil {
|
||||||
return err
|
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("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) +
|
||||||
fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
|
fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
|
||||||
fmt.Sprintf("HEADERS:\n") +
|
fmt.Sprintf("HEADERS:\n%s\n", composeHeaders(c, r, rl.Header)) +
|
||||||
composeHeaders(rl.Header) + "\n" +
|
|
||||||
fmt.Sprintf("BODY :\n%v\n", rl.Body) +
|
fmt.Sprintf("BODY :\n%v\n", rl.Body) +
|
||||||
"----------------------------------------------------------\n"
|
"------------------------------------------------------------------------------\n"
|
||||||
|
|
||||||
c.Log.Print(reqLog)
|
r.initValuesMap()
|
||||||
|
r.values[debugRequestLogKey] = reqLog
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
// Response Middleware(s)
|
// Response Middleware(s)
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
func responseLogger(c *Client, res *Response) error {
|
func responseLogger(c *Client, res *Response) error {
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
|
@ -257,20 +311,22 @@ func responseLogger(c *Client, res *Response) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resLog := "\n---------------------- RESPONSE LOG -----------------------\n" +
|
debugLog := res.Request.values[debugRequestLogKey].(string)
|
||||||
|
debugLog += "~~~ RESPONSE ~~~\n" +
|
||||||
fmt.Sprintf("STATUS : %s\n", res.Status()) +
|
fmt.Sprintf("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("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
|
||||||
fmt.Sprintf("RESPONSE TIME : %v\n", res.Time()) +
|
fmt.Sprintf("TIME DURATION: %v\n", res.Time()) +
|
||||||
"HEADERS :\n" +
|
"HEADERS :\n" +
|
||||||
composeHeaders(rl.Header) + "\n"
|
composeHeaders(c, res.Request, rl.Header) + "\n"
|
||||||
if res.Request.isSaveResponse {
|
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 {
|
} 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
|
return nil
|
||||||
|
@ -281,10 +337,11 @@ func parseResponseBody(c *Client, res *Response) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Handles only JSON or XML content type
|
// 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) {
|
if IsJSONType(ct) || IsXMLType(ct) {
|
||||||
// HTTP status code > 199 and < 300, considered as Result
|
// HTTP status code > 199 and < 300, considered as Result
|
||||||
if res.IsSuccess() {
|
if res.IsSuccess() {
|
||||||
|
res.Request.Error = nil
|
||||||
if res.Request.Result != nil {
|
if res.Request.Result != nil {
|
||||||
err = Unmarshalc(c, ct, res.body, res.Request.Result)
|
err = Unmarshalc(c, ct, res.body, res.Request.Result)
|
||||||
return
|
return
|
||||||
|
@ -398,7 +455,7 @@ func handleRequestBody(c *Client, r *Request) (err error) {
|
||||||
r.bodyBuf = nil
|
r.bodyBuf = nil
|
||||||
|
|
||||||
if reader, ok := r.Body.(io.Reader); ok {
|
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()
|
r.bodyBuf = acquireBuffer()
|
||||||
_, err = r.bodyBuf.ReadFrom(reader)
|
_, err = r.bodyBuf.ReadFrom(reader)
|
||||||
r.Body = nil
|
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
|
// resty source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -47,9 +47,7 @@ func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy {
|
||||||
if len(via) >= noOfRedirect {
|
if len(via) >= noOfRedirect {
|
||||||
return fmt.Errorf("stopped after %d redirects", noOfRedirect)
|
return fmt.Errorf("stopped after %d redirects", noOfRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkHostAndAddHeaders(req, via[0])
|
checkHostAndAddHeaders(req, via[0])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -74,6 +72,10 @@ func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy {
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
|
// Package Unexported methods
|
||||||
|
//_______________________________________________________________________
|
||||||
|
|
||||||
func getHostname(host string) (hostname string) {
|
func getHostname(host string) (hostname string) {
|
||||||
if strings.Index(host, ":") > 0 {
|
if strings.Index(host, ":") > 0 {
|
||||||
host, _, _ = net.SplitHostPort(host)
|
host, _, _ = net.SplitHostPort(host)
|
||||||
|
@ -85,7 +87,7 @@ func getHostname(host string) (hostname string) {
|
||||||
// By default Golang will not redirect request headers
|
// By default Golang will not redirect request headers
|
||||||
// after go throughing various discussion comments from thread
|
// after go throughing various discussion comments from thread
|
||||||
// https://github.com/golang/go/issues/4800
|
// 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) {
|
func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
|
||||||
curHostname := getHostname(cur.URL.Host)
|
curHostname := getHostname(cur.URL.Host)
|
||||||
preHostname := getHostname(pre.URL.Host)
|
preHostname := getHostname(pre.URL.Host)
|
||||||
|
@ -94,6 +96,6 @@ func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) {
|
||||||
cur.Header[key] = val
|
cur.Header[key] = val
|
||||||
}
|
}
|
||||||
} else { // only library User-Agent header is added
|
} else { // only library User-Agent header is added
|
||||||
cur.Header.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
|
cur.Header.Set(hdrUserAgentKey, hdrUserAgentValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
455
vendor/gopkg.in/resty.v1/request.go → vendor/github.com/go-resty/resty/v2/request.go
generated
vendored
455
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
|
// resty source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package resty
|
package resty
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SRVRecord holds the data to query the SRV record for the following service
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
type SRVRecord struct {
|
// Request struct and methods
|
||||||
Service string
|
//_______________________________________________________________________
|
||||||
Domain string
|
|
||||||
|
// 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.
|
// 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("Content-Type", "application/json").
|
||||||
// SetHeader("Accept", "application/json")
|
// SetHeader("Accept", "application/json")
|
||||||
//
|
//
|
||||||
// Also you can override header value, which was set at client instance level.
|
// Also you can override header value, which was set at client instance level.
|
||||||
//
|
|
||||||
func (r *Request) SetHeader(header, value string) *Request {
|
func (r *Request) SetHeader(header, value string) *Request {
|
||||||
r.Header.Set(header, value)
|
r.Header.Set(header, value)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeaders method sets multiple headers field and its values at one go in the current request.
|
// 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{
|
// SetHeaders(map[string]string{
|
||||||
// "Content-Type": "application/json",
|
// "Content-Type": "application/json",
|
||||||
// "Accept": "application/json",
|
// "Accept": "application/json",
|
||||||
// })
|
// })
|
||||||
// Also you can override header value, which was set at client instance level.
|
// Also you can override header value, which was set at client instance level.
|
||||||
//
|
|
||||||
func (r *Request) SetHeaders(headers map[string]string) *Request {
|
func (r *Request) SetHeaders(headers map[string]string) *Request {
|
||||||
for h, v := range headers {
|
for h, v := range headers {
|
||||||
r.SetHeader(h, v)
|
r.SetHeader(h, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryParam method sets single parameter and its value in the current request.
|
// SetQueryParam method sets single parameter and its value in the current request.
|
||||||
// It will be formed as query string for the 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("search", "kitchen papers").
|
||||||
// SetQueryParam("size", "large")
|
// 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 {
|
func (r *Request) SetQueryParam(param, value string) *Request {
|
||||||
r.QueryParam.Set(param, value)
|
r.QueryParam.Set(param, value)
|
||||||
return r
|
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.
|
// 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.
|
// 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{
|
// SetQueryParams(map[string]string{
|
||||||
// "search": "kitchen papers",
|
// "search": "kitchen papers",
|
||||||
// "size": "large",
|
// "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 {
|
func (r *Request) SetQueryParams(params map[string]string) *Request {
|
||||||
for p, v := range params {
|
for p, v := range params {
|
||||||
r.SetQueryParam(p, v)
|
r.SetQueryParam(p, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMultiValueQueryParams method appends multiple parameters with multi-value
|
// SetQueryParamsFromValues method appends multiple parameters with multi-value
|
||||||
// at one go in the current request. It will be formed as query string for the request.
|
// (`url.Values`) at one go in the current request. It will be formed as
|
||||||
// Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
|
// query string for the request.
|
||||||
// resty.R().
|
//
|
||||||
// SetMultiValueQueryParams(url.Values{
|
// For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.
|
||||||
|
// client.R().
|
||||||
|
// SetQueryParamsFromValues(url.Values{
|
||||||
// "status": []string{"pending", "approved", "open"},
|
// "status": []string{"pending", "approved", "open"},
|
||||||
// })
|
// })
|
||||||
// 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) SetQueryParamsFromValues(params url.Values) *Request {
|
||||||
func (r *Request) SetMultiValueQueryParams(params url.Values) *Request {
|
|
||||||
for p, v := range params {
|
for p, v := range params {
|
||||||
for _, pv := range v {
|
for _, pv := range v {
|
||||||
r.QueryParam.Add(p, pv)
|
r.QueryParam.Add(p, pv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryString method provides ability to use string as an input to set URL query string for the request.
|
// SetQueryString method provides ability to use string as an input to set URL query string for the request.
|
||||||
//
|
//
|
||||||
// Using String as an input
|
// Using String as an input
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
|
// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more")
|
||||||
//
|
|
||||||
func (r *Request) SetQueryString(query string) *Request {
|
func (r *Request) SetQueryString(query string) *Request {
|
||||||
params, err := url.ParseQuery(strings.TrimSpace(query))
|
params, err := url.ParseQuery(strings.TrimSpace(query))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -118,7 +174,7 @@ func (r *Request) SetQueryString(query string) *Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r.client.Log.Printf("ERROR %v", err)
|
r.client.log.Errorf("%v", err)
|
||||||
}
|
}
|
||||||
return r
|
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.
|
// 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
|
// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as
|
||||||
// `application/x-www-form-urlencoded`.
|
// `application/x-www-form-urlencoded`.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetFormData(map[string]string{
|
// SetFormData(map[string]string{
|
||||||
// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
|
// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
|
||||||
// "user_id": "3455454545",
|
// "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 {
|
func (r *Request) SetFormData(data map[string]string) *Request {
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
r.FormData.Set(k, v)
|
r.FormData.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMultiValueFormData method appends multiple form parameters with multi-value
|
// SetFormDataFromValues method appends multiple form parameters with multi-value
|
||||||
// at one go in the current request.
|
// (`url.Values`) at one go in the current request.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetMultiValueFormData(url.Values{
|
// SetFormDataFromValues(url.Values{
|
||||||
// "search_criteria": []string{"book", "glass", "pencil"},
|
// "search_criteria": []string{"book", "glass", "pencil"},
|
||||||
// })
|
// })
|
||||||
// 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) SetFormDataFromValues(data url.Values) *Request {
|
||||||
func (r *Request) SetMultiValueFormData(params url.Values) *Request {
|
for k, v := range data {
|
||||||
for k, v := range params {
|
|
||||||
for _, kv := range v {
|
for _, kv := range v {
|
||||||
r.FormData.Add(k, kv)
|
r.FormData.Add(k, kv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
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.
|
// Note: `io.Reader` is processed as bufferless mode while sending request.
|
||||||
//
|
//
|
||||||
// Example:
|
// For Example: Struct as a body input, based on content type, it will be marshalled.
|
||||||
//
|
// client.R().
|
||||||
// Struct as a body input, based on content type, it will be marshalled.
|
|
||||||
// resty.R().
|
|
||||||
// SetBody(User{
|
// SetBody(User{
|
||||||
// Username: "jeeva@myjeeva.com",
|
// Username: "jeeva@myjeeva.com",
|
||||||
// Password: "welcome2resty",
|
// Password: "welcome2resty",
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// Map as a body input, based on content type, it will be marshalled.
|
// Map as a body input, based on content type, it will be marshalled.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetBody(map[string]interface{}{
|
// SetBody(map[string]interface{}{
|
||||||
// "username": "jeeva@myjeeva.com",
|
// "username": "jeeva@myjeeva.com",
|
||||||
// "password": "welcome2resty",
|
// "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.
|
// String as a body input. Suitable for any need as a string input.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetBody(`{
|
// SetBody(`{
|
||||||
// "username": "jeeva@getrightcare.com",
|
// "username": "jeeva@getrightcare.com",
|
||||||
// "password": "admin"
|
// "password": "admin"
|
||||||
// }`)
|
// }`)
|
||||||
//
|
//
|
||||||
// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.
|
// []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"))
|
// SetBody([]byte("This is my raw request, sent as-is"))
|
||||||
//
|
|
||||||
func (r *Request) SetBody(body interface{}) *Request {
|
func (r *Request) SetBody(body interface{}) *Request {
|
||||||
r.Body = body
|
r.Body = body
|
||||||
return r
|
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.
|
// 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.
|
// Note: Result object can be pointer or non-pointer.
|
||||||
// resty.R().SetResult(&AuthToken{})
|
// client.R().SetResult(&AuthToken{})
|
||||||
// // OR
|
// // OR
|
||||||
// resty.R().SetResult(AuthToken{})
|
// client.R().SetResult(AuthToken{})
|
||||||
//
|
//
|
||||||
// Accessing a result value
|
// Accessing a result value from response instance.
|
||||||
// response.Result().(*AuthToken)
|
// response.Result().(*AuthToken)
|
||||||
//
|
|
||||||
func (r *Request) SetResult(res interface{}) *Request {
|
func (r *Request) SetResult(res interface{}) *Request {
|
||||||
r.Result = getPointer(res)
|
r.Result = getPointer(res)
|
||||||
return r
|
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.
|
// if response status code is greater than 399 and content type either JSON or XML.
|
||||||
//
|
//
|
||||||
// Note: Error object can be pointer or non-pointer.
|
// Note: Error object can be pointer or non-pointer.
|
||||||
// resty.R().SetError(&AuthError{})
|
// client.R().SetError(&AuthError{})
|
||||||
// // OR
|
// // OR
|
||||||
// resty.R().SetError(AuthError{})
|
// client.R().SetError(AuthError{})
|
||||||
//
|
//
|
||||||
// Accessing a error value
|
// Accessing a error value from response instance.
|
||||||
// response.Error().(*AuthError)
|
// response.Error().(*AuthError)
|
||||||
//
|
|
||||||
func (r *Request) SetError(err interface{}) *Request {
|
func (r *Request) SetError(err interface{}) *Request {
|
||||||
r.Error = getPointer(err)
|
r.Error = getPointer(err)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFile method is to set single file field name and its path for multipart upload.
|
// 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")
|
// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf")
|
||||||
//
|
|
||||||
func (r *Request) SetFile(param, filePath string) *Request {
|
func (r *Request) SetFile(param, filePath string) *Request {
|
||||||
r.isMultiPart = true
|
r.isMultiPart = true
|
||||||
r.FormData.Set("@"+param, filePath)
|
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.
|
// SetFiles method is to set multiple file field name and its path for multipart upload.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetFiles(map[string]string{
|
// SetFiles(map[string]string{
|
||||||
// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
|
// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf",
|
||||||
// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
|
// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf",
|
||||||
// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
|
// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf",
|
||||||
// })
|
// })
|
||||||
//
|
|
||||||
func (r *Request) SetFiles(files map[string]string) *Request {
|
func (r *Request) SetFiles(files map[string]string) *Request {
|
||||||
r.isMultiPart = true
|
r.isMultiPart = true
|
||||||
|
|
||||||
for f, fp := range files {
|
for f, fp := range files {
|
||||||
r.FormData.Set("@"+f, fp)
|
r.FormData.Set("@"+f, fp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFileReader method is to set single file using io.Reader for multipart upload.
|
// 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("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)).
|
||||||
// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
|
// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes))
|
||||||
//
|
|
||||||
func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
|
func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request {
|
||||||
r.isMultiPart = true
|
r.isMultiPart = true
|
||||||
r.multipartFiles = append(r.multipartFiles, &File{
|
r.multipartFiles = append(r.multipartFiles, &File{
|
||||||
|
@ -280,6 +322,15 @@ func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Reque
|
||||||
return r
|
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.
|
// 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 {
|
func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request {
|
||||||
r.isMultiPart = true
|
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.
|
// 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{
|
// &resty.MultipartField{
|
||||||
// Param: "uploadManifest1",
|
// Param: "uploadManifest1",
|
||||||
// FileName: "upload-file-1.json",
|
// 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-
|
// If you have slice already, then simply call-
|
||||||
// resty.R().SetMultipartFields(fields...)
|
// client.R().SetMultipartFields(fields...)
|
||||||
func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
|
func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request {
|
||||||
r.isMultiPart = true
|
r.isMultiPart = true
|
||||||
r.multipartFields = append(r.multipartFields, fields...)
|
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.
|
// 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
|
// By default Resty won't set `Content-Length`. Also you have an option to enable for every
|
||||||
// request. See `resty.SetContentLength`
|
// request.
|
||||||
// resty.R().SetContentLength(true)
|
|
||||||
//
|
//
|
||||||
|
// See `Client.SetContentLength`
|
||||||
|
// client.R().SetContentLength(true)
|
||||||
func (r *Request) SetContentLength(l bool) *Request {
|
func (r *Request) SetContentLength(l bool) *Request {
|
||||||
r.setContentLength = true
|
r.setContentLength = true
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBasicAuth method sets the basic authentication header in the current HTTP request.
|
// SetBasicAuth method sets the basic authentication header in the current HTTP request.
|
||||||
// For Header example:
|
//
|
||||||
|
// For Example:
|
||||||
// Authorization: Basic <base64-encoded-value>
|
// Authorization: Basic <base64-encoded-value>
|
||||||
//
|
//
|
||||||
// To set the header for username "go-resty" and password "welcome"
|
// To set the header for username "go-resty" and password "welcome"
|
||||||
// resty.R().SetBasicAuth("go-resty", "welcome")
|
// client.R().SetBasicAuth("go-resty", "welcome")
|
||||||
//
|
|
||||||
// This method overrides the credentials set by method `resty.SetBasicAuth`.
|
|
||||||
//
|
//
|
||||||
|
// This method overrides the credentials set by method `Client.SetBasicAuth`.
|
||||||
func (r *Request) SetBasicAuth(username, password string) *Request {
|
func (r *Request) SetBasicAuth(username, password string) *Request {
|
||||||
r.UserInfo = &User{Username: username, Password: password}
|
r.UserInfo = &User{Username: username, Password: password}
|
||||||
return r
|
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>
|
// Authorization: Bearer <auth-token-value-comes-here>
|
||||||
//
|
//
|
||||||
// Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
|
// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
|
||||||
//
|
//
|
||||||
// resty.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
|
// client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
|
||||||
//
|
|
||||||
// This method overrides the Auth token set by method `resty.SetAuthToken`.
|
|
||||||
//
|
//
|
||||||
|
// This method overrides the Auth token set by method `Client.SetAuthToken`.
|
||||||
func (r *Request) SetAuthToken(token string) *Request {
|
func (r *Request) SetAuthToken(token string) *Request {
|
||||||
r.Token = token
|
r.Token = token
|
||||||
return r
|
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
|
// 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.
|
// 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
|
// If is it relative path then output file goes under the output directory, as mentioned
|
||||||
// in the `Client.SetOutputDirectory`.
|
// in the `Client.SetOutputDirectory`.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
|
// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip").
|
||||||
// Get("http://bit.ly/1LouEKr")
|
// 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
|
// SetSRV method sets the details to query the service SRV record and execute the
|
||||||
// request.
|
// request.
|
||||||
// resty.R().
|
// client.R().
|
||||||
// SetSRV(SRVRecord{"web", "testservice.com"}).
|
// SetSRV(SRVRecord{"web", "testservice.com"}).
|
||||||
// Get("/get")
|
// Get("/get")
|
||||||
func (r *Request) SetSRV(srv *SRVRecord) *Request {
|
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,
|
// 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.
|
// 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`.
|
// taken over the control of response parsing from `Resty`.
|
||||||
func (r *Request) SetDoNotParseResponse(parse bool) *Request {
|
func (r *Request) SetDoNotParseResponse(parse bool) *Request {
|
||||||
r.notParseResponse = parse
|
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
|
// SetPathParams method sets multiple URL path key-value pairs at one go in the
|
||||||
// resty current request instance.
|
// Resty current request instance.
|
||||||
// resty.R().SetPathParams(map[string]string{
|
// client.R().SetPathParams(map[string]string{
|
||||||
// "userId": "sample@sample.com",
|
// "userId": "sample@sample.com",
|
||||||
// "subAccountId": "100002",
|
// "subAccountId": "100002",
|
||||||
// })
|
// })
|
||||||
|
@ -416,17 +489,122 @@ func (r *Request) ExpectContentType(contentType string) *Request {
|
||||||
return r
|
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.
|
// 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 {
|
func (r *Request) SetJSONEscapeHTML(b bool) *Request {
|
||||||
r.jsonEscapeHTML = b
|
r.jsonEscapeHTML = b
|
||||||
return r
|
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
|
// HTTP verb method starts here
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
|
// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231.
|
||||||
func (r *Request) Get(url string) (*Response, error) {
|
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)
|
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
|
// Execute method performs the HTTP request with given HTTP method and URL
|
||||||
// for current `Request`.
|
// 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) {
|
func (r *Request) Execute(method, url string) (*Response, error) {
|
||||||
var addrs []*net.SRV
|
var addrs []*net.SRV
|
||||||
|
var resp *Response
|
||||||
var err error
|
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)
|
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)
|
r.URL = r.selectAddr(addrs, url, 0)
|
||||||
|
|
||||||
if r.client.RetryCount == 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
|
attempt := 0
|
||||||
_ = Backoff(
|
err = Backoff(
|
||||||
func() (*Response, error) {
|
func() (*Response, error) {
|
||||||
attempt++
|
attempt++
|
||||||
|
|
||||||
|
@ -499,12 +687,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
|
||||||
|
|
||||||
resp, err = r.client.execute(r)
|
resp, err = r.client.execute(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.client.Log.Printf("ERROR %v, Attempt %v", err, attempt)
|
r.client.log.Errorf("%v, Attempt %v", err, attempt)
|
||||||
if r.isContextCancelledIfAvailable() {
|
|
||||||
// stop Backoff from retrying request if request has been
|
|
||||||
// canceled by context
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
|
@ -515,16 +698,30 @@ func (r *Request) Execute(method, url string) (*Response, error) {
|
||||||
RetryConditions(r.client.RetryConditions),
|
RetryConditions(r.client.RetryConditions),
|
||||||
)
|
)
|
||||||
|
|
||||||
return resp, err
|
return resp, unwrapNoRetryErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
// Request Unexported methods
|
// SRVRecord struct
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
func (r *Request) fmtBodyString() (body string) {
|
// SRVRecord struct holds the data to query the SRV record for the
|
||||||
|
// following service.
|
||||||
|
type SRVRecord struct {
|
||||||
|
Service string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
|
// Request Unexported methods
|
||||||
|
//_______________________________________________________________________
|
||||||
|
|
||||||
|
func (r *Request) fmtBodyString(sl int64) (body string) {
|
||||||
body = "***** NO CONTENT *****"
|
body = "***** NO CONTENT *****"
|
||||||
if isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
|
if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := r.Body.(io.Reader); ok {
|
if _, ok := r.Body.(io.Reader); ok {
|
||||||
body = "***** BODY IS io.Reader *****"
|
body = "***** BODY IS io.Reader *****"
|
||||||
return
|
return
|
||||||
|
@ -532,6 +729,11 @@ func (r *Request) fmtBodyString() (body string) {
|
||||||
|
|
||||||
// multipart or form-data
|
// multipart or form-data
|
||||||
if r.isMultiPart || r.isFormData {
|
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()
|
body = r.bodyBuf.String()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -559,15 +761,21 @@ func (r *Request) fmtBodyString() (body string) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
body = b
|
body = b
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else if b, ok := r.Body.([]byte); ok {
|
} else if b, ok := r.Body.([]byte); ok {
|
||||||
body = base64.StdEncoding.EncodeToString(b)
|
body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if prtBodyBytes != nil && err == nil {
|
if prtBodyBytes != nil && err == nil {
|
||||||
body = string(prtBodyBytes)
|
body = string(prtBodyBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(body) > 0 {
|
||||||
|
bodySize := int64(len([]byte(body)))
|
||||||
|
if bodySize > sl {
|
||||||
|
body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -584,3 +792,18 @@ func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string
|
||||||
|
|
||||||
return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path)
|
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
|
// resty source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -13,7 +13,11 @@ import (
|
||||||
"time"
|
"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 {
|
type Response struct {
|
||||||
Request *Request
|
Request *Request
|
||||||
RawResponse *http.Response
|
RawResponse *http.Response
|
||||||
|
@ -24,6 +28,7 @@ type Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body method returns HTTP response as []byte array for the executed request.
|
// Body method returns HTTP response as []byte array for the executed request.
|
||||||
|
//
|
||||||
// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
|
// Note: `Response.Body` might be nil, if `Request.SetOutput` is used.
|
||||||
func (r *Response) Body() []byte {
|
func (r *Response) Body() []byte {
|
||||||
if r.RawResponse == nil {
|
if r.RawResponse == nil {
|
||||||
|
@ -38,7 +43,6 @@ func (r *Response) Status() string {
|
||||||
if r.RawResponse == nil {
|
if r.RawResponse == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.RawResponse.Status
|
return r.RawResponse.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +52,17 @@ func (r *Response) StatusCode() int {
|
||||||
if r.RawResponse == nil {
|
if r.RawResponse == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.RawResponse.StatusCode
|
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
|
// Result method returns the response value as an object if it has one
|
||||||
func (r *Response) Result() interface{} {
|
func (r *Response) Result() interface{} {
|
||||||
return r.Request.Result
|
return r.Request.Result
|
||||||
|
@ -67,7 +78,6 @@ func (r *Response) Header() http.Header {
|
||||||
if r.RawResponse == nil {
|
if r.RawResponse == nil {
|
||||||
return http.Header{}
|
return http.Header{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.RawResponse.Header
|
return r.RawResponse.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +86,6 @@ func (r *Response) Cookies() []*http.Cookie {
|
||||||
if r.RawResponse == nil {
|
if r.RawResponse == nil {
|
||||||
return make([]*http.Cookie, 0)
|
return make([]*http.Cookie, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.RawResponse.Cookies()
|
return r.RawResponse.Cookies()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +94,17 @@ func (r *Response) String() string {
|
||||||
if r.body == nil {
|
if r.body == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(string(r.body))
|
return strings.TrimSpace(string(r.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time method returns the time of HTTP response time that from request we sent and received a request.
|
// 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.
|
// when client sent a request.
|
||||||
func (r *Response) Time() time.Duration {
|
func (r *Response) Time() time.Duration {
|
||||||
|
if r.Request.clientTrace != nil {
|
||||||
|
return r.Request.TraceInfo().TotalTime
|
||||||
|
}
|
||||||
return r.receivedAt.Sub(r.Request.Time)
|
return r.receivedAt.Sub(r.Request.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,16 +132,27 @@ func (r *Response) RawBody() io.ReadCloser {
|
||||||
return r.RawResponse.Body
|
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 {
|
func (r *Response) IsSuccess() bool {
|
||||||
return r.StatusCode() > 199 && r.StatusCode() < 300
|
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 {
|
func (r *Response) IsError() bool {
|
||||||
return r.StatusCode() > 399
|
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 {
|
func (r *Response) fmtBodyString(sl int64) string {
|
||||||
if r.body != nil {
|
if r.body != nil {
|
||||||
if int64(len(r.body)) > sl {
|
if int64(len(r.body)) > sl {
|
||||||
|
@ -139,9 +162,11 @@ func (r *Response) fmtBodyString(sl int64) string {
|
||||||
if IsJSONType(ct) {
|
if IsJSONType(ct) {
|
||||||
out := acquireBuffer()
|
out := acquireBuffer()
|
||||||
defer releaseBuffer(out)
|
defer releaseBuffer(out)
|
||||||
if err := json.Indent(out, r.body, "", " "); err == nil {
|
err := json.Indent(out, r.body, "", " ")
|
||||||
return out.String()
|
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()
|
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
|
// resty source code and usage is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ package resty
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -22,9 +21,52 @@ import (
|
||||||
"strings"
|
"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
|
// Package Helper methods
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
// IsStringEmpty method tells whether given string is empty or not
|
// IsStringEmpty method tells whether given string is empty or not
|
||||||
func IsStringEmpty(str string) bool {
|
func IsStringEmpty(str string) bool {
|
||||||
|
@ -61,18 +103,6 @@ func IsXMLType(ct string) bool {
|
||||||
return xmlCheck.MatchString(ct)
|
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
|
// Unmarshalc content into object from JSON or XML
|
||||||
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
|
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
|
||||||
if IsJSONType(ct) {
|
if IsJSONType(ct) {
|
||||||
|
@ -84,9 +114,9 @@ func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
// RequestLog and ResponseLog type
|
// RequestLog and ResponseLog type
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
// RequestLog struct is used to collected information from resty request
|
// RequestLog struct is used to collected information from resty request
|
||||||
// instance for debug logging. It sent to request log callback before resty
|
// instance for debug logging. It sent to request log callback before resty
|
||||||
|
@ -104,9 +134,9 @@ type ResponseLog struct {
|
||||||
Body string
|
Body string
|
||||||
}
|
}
|
||||||
|
|
||||||
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
// Package Unexported methods
|
// Package Unexported methods
|
||||||
//___________________________________
|
//_______________________________________________________________________
|
||||||
|
|
||||||
// way to disable the HTML escape as opt-in
|
// way to disable the HTML escape as opt-in
|
||||||
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
|
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
|
||||||
|
@ -127,10 +157,6 @@ func firstNonEmpty(v ...string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogger(w io.Writer) *log.Logger {
|
|
||||||
return log.New(w, "RESTY ", log.LstdFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||||
|
|
||||||
func escapeQuotes(s string) string {
|
func escapeQuotes(s string) string {
|
||||||
|
@ -139,9 +165,19 @@ func escapeQuotes(s string) string {
|
||||||
|
|
||||||
func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
|
func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
|
||||||
hdr := make(textproto.MIMEHeader)
|
hdr := make(textproto.MIMEHeader)
|
||||||
hdr.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
|
|
||||||
escapeQuotes(param), escapeQuotes(fileName)))
|
var contentDispositionValue string
|
||||||
hdr.Set("Content-Type", contentType)
|
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
|
return hdr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,22 +285,40 @@ func releaseBuffer(buf *bytes.Buffer) {
|
||||||
|
|
||||||
func closeq(v interface{}) {
|
func closeq(v interface{}) {
|
||||||
if c, ok := v.(io.Closer); ok {
|
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 {
|
func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
|
||||||
var str []string
|
str := make([]string, 0, len(hdrs))
|
||||||
for _, k := range sortHeaderKeys(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")
|
return strings.Join(str, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortHeaderKeys(hdrs http.Header) []string {
|
func sortHeaderKeys(hdrs http.Header) []string {
|
||||||
var keys []string
|
keys := make([]string, 0, len(hdrs))
|
||||||
for key := range hdrs {
|
for key := range hdrs {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
|
@ -279,3 +333,25 @@ func copyHeaders(hdrs http.Header) http.Header {
|
||||||
}
|
}
|
||||||
return nh
|
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:
|
allow_failures:
|
||||||
- go: tip
|
- go: tip
|
||||||
|
|
||||||
go:
|
env:
|
||||||
- "1.10"
|
- GO111MODULE=on
|
||||||
- tip
|
|
||||||
|
|
||||||
install:
|
go:
|
||||||
- go get -u gopkg.in/alecthomas/gometalinter.v2
|
- "1.13"
|
||||||
- gometalinter.v2 --install
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- touch .env
|
- touch .env
|
||||||
- make test ARGS='-v -race -count=2 -coverprofile=coverage.txt -covermode=atomic ./...'
|
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0
|
||||||
- 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
|
- go mod download
|
||||||
- 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
|
- go mod vendor
|
||||||
|
- make test ARGS='-race -count=2 -coverprofile=coverage.txt -covermode=atomic ./...'
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
# API Support
|
# 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
|
## Linodes
|
||||||
|
|
||||||
- `/linode/instances`
|
- `/linode/instances`
|
||||||
|
@ -68,11 +82,11 @@
|
||||||
### IPs
|
### IPs
|
||||||
|
|
||||||
- `/linode/instances/$id/ips`
|
- `/linode/instances/$id/ips`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `POST`
|
- [X] `POST`
|
||||||
- `/linode/instances/$id/ips/$ip_address`
|
- `/linode/instances/$id/ips/$ip_address`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `PUT`
|
- [X] `PUT`
|
||||||
- [ ] `DELETE`
|
- [ ] `DELETE`
|
||||||
- `/linode/instances/$id/ips/sharing`
|
- `/linode/instances/$id/ips/sharing`
|
||||||
- [ ] `POST`
|
- [ ] `POST`
|
||||||
|
@ -97,9 +111,9 @@
|
||||||
### Stats
|
### Stats
|
||||||
|
|
||||||
- `/linode/instances/$id/stats`
|
- `/linode/instances/$id/stats`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- `/linode/instances/$id/stats/$year/$month`
|
- `/linode/instances/$id/stats/$year/$month`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
|
|
||||||
### Types
|
### Types
|
||||||
|
|
||||||
|
@ -127,6 +141,27 @@
|
||||||
- [X] `PUT`
|
- [X] `PUT`
|
||||||
- [X] `DELETE`
|
- [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
|
||||||
|
|
||||||
- `/longview/clients`
|
- `/longview/clients`
|
||||||
|
@ -153,6 +188,8 @@
|
||||||
- [X] `GET`
|
- [X] `GET`
|
||||||
- [X] `PUT`
|
- [X] `PUT`
|
||||||
- [X] `DELETE`
|
- [X] `DELETE`
|
||||||
|
- `/nodebalancers/$id/stats`
|
||||||
|
- [X] `GET`
|
||||||
|
|
||||||
### NodeBalancer Configs
|
### NodeBalancer Configs
|
||||||
|
|
||||||
|
@ -181,7 +218,7 @@
|
||||||
- [ ] `POST`
|
- [ ] `POST`
|
||||||
- `/networking/ips/$address`
|
- `/networking/ips/$address`
|
||||||
- [X] `GET`
|
- [X] `GET`
|
||||||
- [ ] `PUT`
|
- [X] `PUT`
|
||||||
- [ ] `DELETE`
|
- [ ] `DELETE`
|
||||||
|
|
||||||
### IPv6
|
### IPv6
|
||||||
|
@ -191,9 +228,9 @@
|
||||||
- `/networking/ips/$address`
|
- `/networking/ips/$address`
|
||||||
- [X] `GET`
|
- [X] `GET`
|
||||||
- [ ] `PUT`
|
- [ ] `PUT`
|
||||||
- /networking/ipv6/ranges
|
- `/networking/ipv6/ranges`
|
||||||
- [X] `GET`
|
- [X] `GET`
|
||||||
- /networking/ipv6/pools
|
- `/networking/ipv6/pools`
|
||||||
- [X] `GET`
|
- [X] `GET`
|
||||||
|
|
||||||
## Regions
|
## Regions
|
||||||
|
@ -255,25 +292,50 @@
|
||||||
### OAuth Clients
|
### OAuth Clients
|
||||||
|
|
||||||
- `/account/oauth-clients`
|
- `/account/oauth-clients`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `POST`
|
- [X] `POST`
|
||||||
- `/account/oauth-clients/$id`
|
- `/account/oauth-clients/$id`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `PUT`
|
- [X] `PUT`
|
||||||
- [ ] `DELETE`
|
- [X] `DELETE`
|
||||||
- `/account/oauth-clients/$id/reset_secret`
|
- `/account/oauth-clients/$id/reset_secret`
|
||||||
- [ ] `POST`
|
- [ ] `POST`
|
||||||
- `/account/oauth-clients/$id/thumbnail`
|
- `/account/oauth-clients/$id/thumbnail`
|
||||||
- [ ] `GET`
|
- [ ] `GET`
|
||||||
- [ ] `PUT`
|
- [ ] `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
|
### Payments
|
||||||
|
|
||||||
- `/account/payments`
|
- `/account/payments`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `POST`
|
- [X] `POST`
|
||||||
- `/account/payments/$id`
|
- `/account/payments/$id`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- `/account/payments/paypal`
|
- `/account/payments/paypal`
|
||||||
- [ ] `GET`
|
- [ ] `GET`
|
||||||
- `/account/payments/paypal/execute`
|
- `/account/payments/paypal/execute`
|
||||||
|
@ -282,8 +344,8 @@
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
- `/account/settings`
|
- `/account/settings`
|
||||||
- [ ] `GET`
|
- [X] `GET`
|
||||||
- [ ] `PUT`
|
- [X] `PUT`
|
||||||
|
|
||||||
### Users
|
### Users
|
||||||
|
|
||||||
|
|
|
@ -1,239 +1 @@
|
||||||
# Change Log
|
Release notes for this project are kept here: https://github.com/linode/linodego/releases
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
|
@ -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
|
PACKAGES := $(shell go list ./... | grep -v integration)
|
||||||
test: vendor
|
|
||||||
|
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_FIXTURE_MODE="play" \
|
||||||
LINODE_TOKEN="awesometokenawesometokenawesometoken" \
|
LINODE_TOKEN="awesometokenawesometokenawesometoken" \
|
||||||
go test $(ARGS)
|
LINODE_API_VERSION="v4beta" \
|
||||||
|
GO111MODULE="on" \
|
||||||
|
go test -v ./test/integration $(ARGS)
|
||||||
|
|
||||||
$(GOPATH)/bin/dep:
|
build: vet lint
|
||||||
@go get -u github.com/golang/dep/cmd/dep
|
go build ./...
|
||||||
|
|
||||||
vendor: $(GOPATH)/bin/dep
|
vet:
|
||||||
@dep ensure
|
go vet ./...
|
||||||
|
|
||||||
example:
|
lint:
|
||||||
@go run example/main.go
|
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:
|
clean-fixtures:
|
||||||
@-rm fixtures/*.yaml
|
@-rm fixtures/*.yaml
|
||||||
|
|
||||||
refresh-fixtures: clean-fixtures fixtures
|
refresh-fixtures: clean-fixtures fixtures
|
||||||
|
|
||||||
.PHONY: fixtures
|
run_fixtures:
|
||||||
fixtures:
|
|
||||||
@echo "* Running fixtures"
|
@echo "* Running fixtures"
|
||||||
@LINODE_TOKEN=$(LINODE_TOKEN) \
|
@LINODE_FIXTURE_MODE="record" \
|
||||||
LINODE_FIXTURE_MODE="record" go test $(ARGS)
|
LINODE_TOKEN=$(LINODE_TOKEN) \
|
||||||
@echo "* Santizing fixtures"
|
LINODE_API_VERSION="v4beta" \
|
||||||
@for yaml in fixtures/*yaml; do \
|
GO111MODULE="on" \
|
||||||
sed -E -i "" -e "s/$(LINODE_TOKEN)/awesometokenawesometokenawesometoken/g" \
|
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/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/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' \
|
-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' \
|
-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; \
|
$$yaml; \
|
||||||
done
|
done
|
||||||
|
@find test/integration/fixtures -name *yaml.bak -exec rm {} \;
|
||||||
|
|
||||||
|
fixtures: run_fixtures sanitize
|
||||||
|
|
||||||
.PHONY: godoc
|
|
||||||
godoc:
|
godoc:
|
||||||
@godoc -http=:6060
|
@godoc -http=:6060
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# linodego
|
# linodego
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/linode/linodego.svg?branch=master)](https://travis-ci.org/linode/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)
|
[![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)
|
[![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)
|
[![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":
|
Or, use a page value of "0":
|
||||||
|
|
||||||
```go
|
```go
|
||||||
opts := NewListOptions(0,"")
|
opts := linodego.NewListOptions(0,"")
|
||||||
kernels, err := linodego.ListKernels(context.Background(), opts)
|
kernels, err := linodego.ListKernels(context.Background(), opts)
|
||||||
// len(kernels) == 218
|
// len(kernels) == 218
|
||||||
```
|
```
|
||||||
|
@ -92,8 +93,8 @@ kernels, err := linodego.ListKernels(context.Background(), opts)
|
||||||
#### Single Page
|
#### Single Page
|
||||||
|
|
||||||
```go
|
```go
|
||||||
opts := NewListOptions(2,"")
|
opts := linodego.NewListOptions(2,"")
|
||||||
// or opts := ListOptions{PageOptions: &PageOptions: {Page: 2 }}
|
// or opts := linodego.ListOptions{PageOptions: &PageOptions: {Page: 2 }}
|
||||||
kernels, err := linodego.ListKernels(context.Background(), opts)
|
kernels, err := linodego.ListKernels(context.Background(), opts)
|
||||||
// len(kernels) == 100
|
// len(kernels) == 100
|
||||||
```
|
```
|
||||||
|
@ -108,8 +109,8 @@ values are set in the supplied ListOptions.
|
||||||
#### Filtering
|
#### Filtering
|
||||||
|
|
||||||
```go
|
```go
|
||||||
opts := ListOptions{Filter: "{\"mine\":true}"}
|
opts := linodego.ListOptions{Filter: "{\"mine\":true}"}
|
||||||
// or opts := NewListOptions(0, "{\"mine\":true}")
|
// or opts := linodego.NewListOptions(0, "{\"mine\":true}")
|
||||||
stackscripts, err := linodego.ListStackscripts(context.Background(), opts)
|
stackscripts, err := linodego.ListStackscripts(context.Background(), opts)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ stackscripts, err := linodego.ListStackscripts(context.Background(), opts)
|
||||||
#### Getting Single Entities
|
#### Getting Single Entities
|
||||||
|
|
||||||
```go
|
```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
|
// linode == nil: true
|
||||||
// err.Error() == "[404] Not Found"
|
// err.Error() == "[404] Not Found"
|
||||||
// err.Code == "404"
|
// 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.
|
For lists, the list is still returned as `[]`, but `err` works the same way as on the `Get` request.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
linodes, err := linodego.ListLinodes(context.Background(), NewListOptions(0, "{\"foo\":bar}"))
|
linodes, err := linodego.ListInstances(context.Background(), linodego.NewListOptions(0, "{\"foo\":bar}"))
|
||||||
// linodes == []
|
// linodes == []
|
||||||
// err.Error() == "[400] [X-Filter] Cannot filter on foo"
|
// 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:
|
Otherwise sane requests beyond the last page do not trigger an error, just an empty result:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
linodes, err := linodego.ListLinodes(context.Background(), NewListOptions(9999, ""))
|
linodes, err := linodego.ListInstances(context.Background(), linodego.NewListOptions(9999, ""))
|
||||||
// linodes == []
|
// linodes == []
|
||||||
// err = nil
|
// err = nil
|
||||||
```
|
```
|
||||||
|
@ -153,15 +154,9 @@ When performing a `POST` or `PUT` request, multiple field related errors will be
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Run `make test` to run the unit tests. This is the same as running `go test` except that `make test` will
|
Run `make testunit` to run the unit tests.
|
||||||
execute the tests while playing back API response fixtures that were recorded during a previous development build.
|
|
||||||
|
|
||||||
`go test` can be used without the fixtures. Copy `env.sample` to `.env` and configure your persistent test
|
Run `make testint` to run the integration tests. The integration tests use fixtures.
|
||||||
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.
|
|
||||||
|
|
||||||
To update the test fixtures, run `make fixtures`. This will record the API responses into the `fixtures/` directory.
|
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
|
Be careful about committing any sensitive account details. An attempt has been made to sanitize IP addresses and
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Account struct {
|
||||||
Address1 string `json:"address_1"`
|
Address1 string `json:"address_1"`
|
||||||
Address2 string `json:"address_2"`
|
Address2 string `json:"address_2"`
|
||||||
Balance float32 `json:"balance"`
|
Balance float32 `json:"balance"`
|
||||||
|
BalanceUninvoiced float32 `json:"balance_uninvoiced"`
|
||||||
City string `json:"city"`
|
City string `json:"city"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Zip string `json:"zip"`
|
Zip string `json:"zip"`
|
||||||
|
@ -26,20 +27,18 @@ type CreditCard struct {
|
||||||
Expiry string `json:"expiry"`
|
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
|
// GetAccount gets the contact and billing information related to the Account
|
||||||
func (c *Client) GetAccount(ctx context.Context) (*Account, error) {
|
func (c *Client) GetAccount(ctx context.Context) (*Account, error) {
|
||||||
e, err := c.Account.Endpoint()
|
e, err := c.Account.Endpoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Account{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Account{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Account).fixDates(), nil
|
|
||||||
|
return r.Result().(*Account), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/duration"
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event represents an action taken on the Account.
|
// Event represents an action taken on the Account.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
|
|
||||||
// The unique ID of this Event.
|
// The unique ID of this Event.
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|
||||||
|
@ -36,7 +34,6 @@ type Event struct {
|
||||||
Seen bool `json:"seen"`
|
Seen bool `json:"seen"`
|
||||||
|
|
||||||
// The estimated time remaining until the completion of this Event. This value is only returned for in-progress events.
|
// 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.
|
// The username of the User who caused the Event.
|
||||||
|
@ -45,6 +42,9 @@ type Event struct {
|
||||||
// Detailed information about the Event's entity, including ID, type, label, and URL used to access it.
|
// Detailed information about the Event's entity, including ID, type, label, and URL used to access it.
|
||||||
Entity *EventEntity `json:"entity"`
|
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.
|
// When this Event was created.
|
||||||
Created *time.Time `json:"-"`
|
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.
|
// EventAction constants represent the actions that cause an Event. New actions may be added in the future.
|
||||||
const (
|
const (
|
||||||
|
ActionAccountUpdate EventAction = "account_update"
|
||||||
|
ActionAccountSettingsUpdate EventAction = "account_settings_update"
|
||||||
ActionBackupsEnable EventAction = "backups_enable"
|
ActionBackupsEnable EventAction = "backups_enable"
|
||||||
ActionBackupsCancel EventAction = "backups_cancel"
|
ActionBackupsCancel EventAction = "backups_cancel"
|
||||||
ActionBackupsRestore EventAction = "backups_restore"
|
ActionBackupsRestore EventAction = "backups_restore"
|
||||||
ActionCommunityQuestionReply EventAction = "community_question_reply"
|
ActionCommunityQuestionReply EventAction = "community_question_reply"
|
||||||
|
ActionCommunityLike EventAction = "community_like"
|
||||||
ActionCreateCardUpdated EventAction = "credit_card_updated"
|
ActionCreateCardUpdated EventAction = "credit_card_updated"
|
||||||
ActionDiskCreate EventAction = "disk_create"
|
ActionDiskCreate EventAction = "disk_create"
|
||||||
ActionDiskDelete EventAction = "disk_delete"
|
ActionDiskDelete EventAction = "disk_delete"
|
||||||
|
ActionDiskUpdate EventAction = "disk_update"
|
||||||
ActionDiskDuplicate EventAction = "disk_duplicate"
|
ActionDiskDuplicate EventAction = "disk_duplicate"
|
||||||
ActionDiskImagize EventAction = "disk_imagize"
|
ActionDiskImagize EventAction = "disk_imagize"
|
||||||
ActionDiskResize EventAction = "disk_resize"
|
ActionDiskResize EventAction = "disk_resize"
|
||||||
ActionDNSRecordCreate EventAction = "dns_record_create"
|
ActionDNSRecordCreate EventAction = "dns_record_create"
|
||||||
ActionDNSRecordDelete EventAction = "dns_record_delete"
|
ActionDNSRecordDelete EventAction = "dns_record_delete"
|
||||||
|
ActionDNSRecordUpdate EventAction = "dns_record_update"
|
||||||
ActionDNSZoneCreate EventAction = "dns_zone_create"
|
ActionDNSZoneCreate EventAction = "dns_zone_create"
|
||||||
ActionDNSZoneDelete EventAction = "dns_zone_delete"
|
ActionDNSZoneDelete EventAction = "dns_zone_delete"
|
||||||
|
ActionDNSZoneUpdate EventAction = "dns_zone_update"
|
||||||
|
ActionHostReboot EventAction = "host_reboot"
|
||||||
ActionImageDelete EventAction = "image_delete"
|
ActionImageDelete EventAction = "image_delete"
|
||||||
|
ActionImageUpdate EventAction = "image_update"
|
||||||
|
ActionLassieReboot EventAction = "lassie_reboot"
|
||||||
ActionLinodeAddIP EventAction = "linode_addip"
|
ActionLinodeAddIP EventAction = "linode_addip"
|
||||||
ActionLinodeBoot EventAction = "linode_boot"
|
ActionLinodeBoot EventAction = "linode_boot"
|
||||||
ActionLinodeClone EventAction = "linode_clone"
|
ActionLinodeClone EventAction = "linode_clone"
|
||||||
ActionLinodeCreate EventAction = "linode_create"
|
ActionLinodeCreate EventAction = "linode_create"
|
||||||
ActionLinodeDelete EventAction = "linode_delete"
|
ActionLinodeDelete EventAction = "linode_delete"
|
||||||
|
ActionLinodeUpdate EventAction = "linode_update"
|
||||||
ActionLinodeDeleteIP EventAction = "linode_deleteip"
|
ActionLinodeDeleteIP EventAction = "linode_deleteip"
|
||||||
ActionLinodeMigrate EventAction = "linode_migrate"
|
ActionLinodeMigrate EventAction = "linode_migrate"
|
||||||
ActionLinodeMutate EventAction = "linode_mutate"
|
ActionLinodeMutate EventAction = "linode_mutate"
|
||||||
|
ActionLinodeMutateCreate EventAction = "linode_mutate_create"
|
||||||
ActionLinodeReboot EventAction = "linode_reboot"
|
ActionLinodeReboot EventAction = "linode_reboot"
|
||||||
ActionLinodeRebuild EventAction = "linode_rebuild"
|
ActionLinodeRebuild EventAction = "linode_rebuild"
|
||||||
ActionLinodeResize EventAction = "linode_resize"
|
ActionLinodeResize EventAction = "linode_resize"
|
||||||
|
ActionLinodeResizeCreate EventAction = "linode_resize_create"
|
||||||
ActionLinodeShutdown EventAction = "linode_shutdown"
|
ActionLinodeShutdown EventAction = "linode_shutdown"
|
||||||
ActionLinodeSnapshot EventAction = "linode_snapshot"
|
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"
|
ActionLongviewClientCreate EventAction = "longviewclient_create"
|
||||||
ActionLongviewClientDelete EventAction = "longviewclient_delete"
|
ActionLongviewClientDelete EventAction = "longviewclient_delete"
|
||||||
|
ActionLongviewClientUpdate EventAction = "longviewclient_update"
|
||||||
ActionManagedDisabled EventAction = "managed_disabled"
|
ActionManagedDisabled EventAction = "managed_disabled"
|
||||||
ActionManagedEnabled EventAction = "managed_enabled"
|
ActionManagedEnabled EventAction = "managed_enabled"
|
||||||
ActionManagedServiceCreate EventAction = "managed_service_create"
|
ActionManagedServiceCreate EventAction = "managed_service_create"
|
||||||
ActionManagedServiceDelete EventAction = "managed_service_delete"
|
ActionManagedServiceDelete EventAction = "managed_service_delete"
|
||||||
ActionNodebalancerCreate EventAction = "nodebalancer_create"
|
ActionNodebalancerCreate EventAction = "nodebalancer_create"
|
||||||
ActionNodebalancerDelete EventAction = "nodebalancer_delete"
|
ActionNodebalancerDelete EventAction = "nodebalancer_delete"
|
||||||
|
ActionNodebalancerUpdate EventAction = "nodebalancer_update"
|
||||||
ActionNodebalancerConfigCreate EventAction = "nodebalancer_config_create"
|
ActionNodebalancerConfigCreate EventAction = "nodebalancer_config_create"
|
||||||
ActionNodebalancerConfigDelete EventAction = "nodebalancer_config_delete"
|
ActionNodebalancerConfigDelete EventAction = "nodebalancer_config_delete"
|
||||||
|
ActionNodebalancerConfigUpdate EventAction = "nodebalancer_config_update"
|
||||||
ActionPasswordReset EventAction = "password_reset"
|
ActionPasswordReset EventAction = "password_reset"
|
||||||
ActionPaymentSubmitted EventAction = "payment_submitted"
|
ActionPaymentSubmitted EventAction = "payment_submitted"
|
||||||
ActionStackScriptCreate EventAction = "stackscript_create"
|
ActionStackScriptCreate EventAction = "stackscript_create"
|
||||||
ActionStackScriptDelete EventAction = "stackscript_delete"
|
ActionStackScriptDelete EventAction = "stackscript_delete"
|
||||||
|
ActionStackScriptUpdate EventAction = "stackscript_update"
|
||||||
ActionStackScriptPublicize EventAction = "stackscript_publicize"
|
ActionStackScriptPublicize EventAction = "stackscript_publicize"
|
||||||
ActionStackScriptRevise EventAction = "stackscript_revise"
|
ActionStackScriptRevise EventAction = "stackscript_revise"
|
||||||
ActionTFADisabled EventAction = "tfa_disabled"
|
ActionTFADisabled EventAction = "tfa_disabled"
|
||||||
ActionTFAEnabled EventAction = "tfa_enabled"
|
ActionTFAEnabled EventAction = "tfa_enabled"
|
||||||
ActionTicketAttachmentUpload EventAction = "ticket_attachment_upload"
|
ActionTicketAttachmentUpload EventAction = "ticket_attachment_upload"
|
||||||
ActionTicketCreate EventAction = "ticket_create"
|
ActionTicketCreate EventAction = "ticket_create"
|
||||||
ActionTicketReply EventAction = "ticket_reply"
|
ActionTicketUpdate EventAction = "ticket_update"
|
||||||
ActionVolumeAttach EventAction = "volume_attach"
|
ActionVolumeAttach EventAction = "volume_attach"
|
||||||
ActionVolumeClone EventAction = "volume_clone"
|
ActionVolumeClone EventAction = "volume_clone"
|
||||||
ActionVolumeCreate EventAction = "volume_create"
|
ActionVolumeCreate EventAction = "volume_create"
|
||||||
ActionVolumeDelte EventAction = "volume_delete"
|
ActionVolumeDelte EventAction = "volume_delete"
|
||||||
|
ActionVolumeUpdate EventAction = "volume_update"
|
||||||
ActionVolumeDetach EventAction = "volume_detach"
|
ActionVolumeDetach EventAction = "volume_detach"
|
||||||
ActionVolumeResize EventAction = "volume_resize"
|
ActionVolumeResize EventAction = "volume_resize"
|
||||||
)
|
)
|
||||||
|
@ -114,10 +135,12 @@ const (
|
||||||
// EntityType constants start with Entity and include Linode API Event Entity Types
|
// EntityType constants start with Entity and include Linode API Event Entity Types
|
||||||
type EntityType string
|
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 (
|
const (
|
||||||
EntityLinode EntityType = "linode"
|
EntityLinode EntityType = "linode"
|
||||||
EntityDisk EntityType = "disk"
|
EntityDisk EntityType = "disk"
|
||||||
|
EntityDomain EntityType = "domain"
|
||||||
|
EntityNodebalancer EntityType = "nodebalancer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventStatus constants start with Event and include Linode API Event Status values
|
// 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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
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
|
// 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()
|
endpoint, err := c.Events.Endpoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
endpoint = fmt.Sprintf("%s/%d", endpoint, e.ID)
|
|
||||||
|
endpoint = fmt.Sprintf("%s/%d", endpoint, i.ID)
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,12 +227,11 @@ func (resp *EventsPagedResponse) appendData(r *EventsPagedResponse) {
|
||||||
func (c *Client) ListEvents(ctx context.Context, opts *ListOptions) ([]Event, error) {
|
func (c *Client) ListEvents(ctx context.Context, opts *ListOptions) ([]Event, error) {
|
||||||
response := EventsPagedResponse{}
|
response := EventsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,19 +241,15 @@ func (c *Client) GetEvent(ctx context.Context, id int) (*Event, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
r, err := c.R(ctx).SetResult(&Event{}).Get(e)
|
r, err := c.R(ctx).SetResult(&Event{}).Get(e)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Event).fixDates(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
return r.Result().(*Event), nil
|
||||||
func (e *Event) fixDates() *Event {
|
|
||||||
e.Created, _ = parseDates(e.CreatedStr)
|
|
||||||
e.TimeRemaining = unmarshalTimeRemaining(e.TimeRemainingMsg)
|
|
||||||
return e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkEventRead marks a single Event as read.
|
// MarkEventRead marks a single Event as read.
|
||||||
|
@ -228,50 +271,3 @@ func (c *Client) MarkEventsSeen(ctx context.Context, event *Event) error {
|
||||||
|
|
||||||
return err
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Invoice structs reflect an invoice for billable activity on the account.
|
// Invoice structs reflect an invoice for billable activity on the account.
|
||||||
type Invoice struct {
|
type Invoice struct {
|
||||||
DateStr string `json:"date"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Total float32 `json:"total"`
|
Total float32 `json:"total"`
|
||||||
|
@ -18,9 +19,6 @@ type Invoice struct {
|
||||||
|
|
||||||
// InvoiceItem structs reflect an single billable activity associate with an Invoice
|
// InvoiceItem structs reflect an single billable activity associate with an Invoice
|
||||||
type InvoiceItem struct {
|
type InvoiceItem struct {
|
||||||
FromStr string `json:"from"`
|
|
||||||
ToStr string `json:"to"`
|
|
||||||
|
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
UnitPrice int `json:"unitprice"`
|
UnitPrice int `json:"unitprice"`
|
||||||
|
@ -42,6 +40,7 @@ func (InvoicesPagedResponse) endpoint(c *Client) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,26 +53,54 @@ func (resp *InvoicesPagedResponse) appendData(r *InvoicesPagedResponse) {
|
||||||
func (c *Client) ListInvoices(ctx context.Context, opts *ListOptions) ([]Invoice, error) {
|
func (c *Client) ListInvoices(ctx context.Context, opts *ListOptions) ([]Invoice, error) {
|
||||||
response := InvoicesPagedResponse{}
|
response := InvoicesPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
func (v *Invoice) fixDates() *Invoice {
|
func (i *Invoice) UnmarshalJSON(b []byte) error {
|
||||||
v.Date, _ = parseDates(v.DateStr)
|
type Mask Invoice
|
||||||
return v
|
|
||||||
|
p := struct {
|
||||||
|
*Mask
|
||||||
|
Date *parseabletime.ParseableTime `json:"date"`
|
||||||
|
}{
|
||||||
|
Mask: (*Mask)(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
if err := json.Unmarshal(b, &p); err != nil {
|
||||||
func (v *InvoiceItem) fixDates() *InvoiceItem {
|
return err
|
||||||
v.From, _ = parseDates(v.FromStr)
|
}
|
||||||
v.To, _ = parseDates(v.ToStr)
|
|
||||||
return v
|
i.Date = (*time.Time)(p.Date)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Invoice{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Invoice{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Invoice).fixDates(), nil
|
|
||||||
|
return r.Result().(*Invoice), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceItemsPagedResponse represents a paginated Invoice Item API response
|
// InvoiceItemsPagedResponse represents a paginated Invoice Item API response
|
||||||
|
@ -103,6 +132,7 @@ func (InvoiceItemsPagedResponse) endpointWithID(c *Client, id int) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
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) {
|
func (c *Client) ListInvoiceItems(ctx context.Context, id int, opts *ListOptions) ([]InvoiceItem, error) {
|
||||||
response := InvoiceItemsPagedResponse{}
|
response := InvoiceItemsPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, id, opts)
|
err := c.listHelperWithID(ctx, &response, id, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ package linodego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Notification represents a notification on an Account
|
// Notification represents a notification on an Account
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
UntilStr string `json:"until"`
|
|
||||||
WhenStr string `json:"when"`
|
|
||||||
|
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Body *string `json:"body"`
|
Body *string `json:"body"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
@ -68,6 +68,7 @@ func (NotificationsPagedResponse) endpoint(c *Client) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,18 +85,32 @@ func (resp *NotificationsPagedResponse) appendData(r *NotificationsPagedResponse
|
||||||
func (c *Client) ListNotifications(ctx context.Context, opts *ListOptions) ([]Notification, error) {
|
func (c *Client) ListNotifications(ctx context.Context, opts *ListOptions) ([]Notification, error) {
|
||||||
response := NotificationsPagedResponse{}
|
response := NotificationsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
func (v *Notification) fixDates() *Notification {
|
func (i *Notification) UnmarshalJSON(b []byte) error {
|
||||||
v.Until, _ = parseDates(v.UntilStr)
|
type Mask Notification
|
||||||
v.When, _ = parseDates(v.WhenStr)
|
|
||||||
return v
|
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.Username = i.Username
|
||||||
o.Email = i.Email
|
o.Email = i.Email
|
||||||
o.Restricted = i.Restricted
|
o.Restricted = i.Restricted
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ func (i User) GetUpdateOptions() (o UserUpdateOptions) {
|
||||||
o.Username = i.Username
|
o.Username = i.Username
|
||||||
o.Email = i.Email
|
o.Email = i.Email
|
||||||
o.Restricted = copyBool(&i.Restricted)
|
o.Restricted = copyBool(&i.Restricted)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +59,7 @@ func (UsersPagedResponse) endpoint(c *Client) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,18 +72,12 @@ func (resp *UsersPagedResponse) appendData(r *UsersPagedResponse) {
|
||||||
func (c *Client) ListUsers(ctx context.Context, opts *ListOptions) ([]User, error) {
|
func (c *Client) ListUsers(ctx context.Context, opts *ListOptions) ([]User, error) {
|
||||||
response := UsersPagedResponse{}
|
response := UsersPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
return response.Data, nil
|
||||||
func (i *User) fixDates() *User {
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser gets the user with the provided ID
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&User{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&User{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// CreateUser creates a User. The email address must be confirmed before the
|
||||||
// User account can be accessed.
|
// User account can be accessed.
|
||||||
func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (*User, error) {
|
func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (*User, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Users.Endpoint()
|
e, err := c.Users.Endpoint()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -121,16 +123,20 @@ func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*User).fixDates(), nil
|
|
||||||
|
return r.Result().(*User), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser updates the User with the specified id
|
// UpdateUser updates the User with the specified id
|
||||||
func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdateOptions) (*User, error) {
|
func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdateOptions) (*User, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Users.Endpoint()
|
e, err := c.Users.Endpoint()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
|
|
||||||
req := c.R(ctx).SetResult(&User{})
|
req := c.R(ctx).SetResult(&User{})
|
||||||
|
@ -148,7 +154,8 @@ func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdat
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*User).fixDates(), nil
|
|
||||||
|
return r.Result().(*User), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser deletes the User with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
|
|
||||||
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,28 +3,37 @@ package linodego
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/resty.v1"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// APIHost Linode API hostname
|
// APIHost Linode API hostname
|
||||||
APIHost = "api.linode.com"
|
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 Linode API version
|
||||||
APIVersion = "v4"
|
APIVersion = "v4"
|
||||||
|
// APIVersionVar environment var to check for alternate API Version
|
||||||
|
APIVersionVar = "LINODE_API_VERSION"
|
||||||
// APIProto connect to API with http(s)
|
// APIProto connect to API with http(s)
|
||||||
APIProto = "https"
|
APIProto = "https"
|
||||||
// Version of linodego
|
// Version of linodego
|
||||||
Version = "0.7.0"
|
Version = "0.12.0"
|
||||||
// APIEnvVar environment var to check for API token
|
// APIEnvVar environment var to check for API token
|
||||||
APIEnvVar = "LINODE_TOKEN"
|
APIEnvVar = "LINODE_TOKEN"
|
||||||
// APISecondsPerPoll how frequently to poll for new Events or Status in WaitFor functions
|
// APISecondsPerPoll how frequently to poll for new Events or Status in WaitFor functions
|
||||||
APISecondsPerPoll = 3
|
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 is the default User-Agent sent in HTTP request headers
|
||||||
DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
|
DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
|
||||||
)
|
)
|
||||||
|
@ -39,45 +48,58 @@ type Client struct {
|
||||||
userAgent string
|
userAgent string
|
||||||
resources map[string]*Resource
|
resources map[string]*Resource
|
||||||
debug bool
|
debug bool
|
||||||
|
retryConditionals []RetryConditional
|
||||||
|
|
||||||
millisecondsPerPoll time.Duration
|
millisecondsPerPoll time.Duration
|
||||||
|
|
||||||
Images *Resource
|
Account *Resource
|
||||||
InstanceDisks *Resource
|
AccountSettings *Resource
|
||||||
InstanceConfigs *Resource
|
DomainRecords *Resource
|
||||||
InstanceSnapshots *Resource
|
Domains *Resource
|
||||||
InstanceIPs *Resource
|
Events *Resource
|
||||||
InstanceVolumes *Resource
|
Firewalls *Resource
|
||||||
Instances *Resource
|
|
||||||
IPAddresses *Resource
|
IPAddresses *Resource
|
||||||
IPv6Pools *Resource
|
IPv6Pools *Resource
|
||||||
IPv6Ranges *Resource
|
IPv6Ranges *Resource
|
||||||
Regions *Resource
|
Images *Resource
|
||||||
StackScripts *Resource
|
InstanceConfigs *Resource
|
||||||
Volumes *Resource
|
InstanceDisks *Resource
|
||||||
|
InstanceIPs *Resource
|
||||||
|
InstanceSnapshots *Resource
|
||||||
|
InstanceStats *Resource
|
||||||
|
InstanceVolumes *Resource
|
||||||
|
Instances *Resource
|
||||||
|
InvoiceItems *Resource
|
||||||
|
Invoices *Resource
|
||||||
Kernels *Resource
|
Kernels *Resource
|
||||||
Types *Resource
|
LKEClusters *Resource
|
||||||
Domains *Resource
|
LKEClusterPools *Resource
|
||||||
DomainRecords *Resource
|
LKEVersions *Resource
|
||||||
Longview *Resource
|
Longview *Resource
|
||||||
LongviewClients *Resource
|
LongviewClients *Resource
|
||||||
LongviewSubscriptions *Resource
|
LongviewSubscriptions *Resource
|
||||||
NodeBalancers *Resource
|
Managed *Resource
|
||||||
NodeBalancerConfigs *Resource
|
NodeBalancerConfigs *Resource
|
||||||
NodeBalancerNodes *Resource
|
NodeBalancerNodes *Resource
|
||||||
SSHKeys *Resource
|
NodeBalancerStats *Resource
|
||||||
Tickets *Resource
|
NodeBalancers *Resource
|
||||||
Tokens *Resource
|
|
||||||
Token *Resource
|
|
||||||
Account *Resource
|
|
||||||
Invoices *Resource
|
|
||||||
InvoiceItems *Resource
|
|
||||||
Events *Resource
|
|
||||||
Notifications *Resource
|
Notifications *Resource
|
||||||
|
OAuthClients *Resource
|
||||||
|
ObjectStorageBuckets *Resource
|
||||||
|
ObjectStorageClusters *Resource
|
||||||
|
ObjectStorageKeys *Resource
|
||||||
|
Payments *Resource
|
||||||
Profile *Resource
|
Profile *Resource
|
||||||
Managed *Resource
|
Regions *Resource
|
||||||
|
SSHKeys *Resource
|
||||||
|
StackScripts *Resource
|
||||||
Tags *Resource
|
Tags *Resource
|
||||||
|
Tickets *Resource
|
||||||
|
Token *Resource
|
||||||
|
Tokens *Resource
|
||||||
|
Types *Resource
|
||||||
Users *Resource
|
Users *Resource
|
||||||
|
Volumes *Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -90,7 +112,6 @@ func init() {
|
||||||
log.Println("[WARN] LINODE_DEBUG should be an integer, 0 or 1")
|
log.Println("[WARN] LINODE_DEBUG should be an integer, 0 or 1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserAgent sets a custom user-agent for HTTP requests
|
// 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 {
|
func (c *Client) SetDebug(debug bool) *Client {
|
||||||
c.debug = debug
|
c.debug = debug
|
||||||
c.resty.SetDebug(debug)
|
c.resty.SetDebug(debug)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,10 +145,50 @@ func (c *Client) SetBaseURL(url string) *Client {
|
||||||
return c
|
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.
|
// 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 {
|
func (c *Client) SetPollDelay(delay time.Duration) *Client {
|
||||||
c.millisecondsPerPoll = delay
|
c.millisecondsPerPoll = delay
|
||||||
|
c.resty.SetRetryWaitTime(delay * time.Millisecond)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,99 +198,165 @@ func (c Client) Resource(resourceName string) *Resource {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("Could not find resource named '%s', exiting.", resourceName)
|
log.Fatalf("Could not find resource named '%s', exiting.", resourceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedResource
|
return selectedResource
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient factory to create new Client struct
|
// NewClient factory to create new Client struct
|
||||||
func NewClient(hc *http.Client) (client Client) {
|
func NewClient(hc *http.Client) (client Client) {
|
||||||
restyClient := resty.NewWithClient(hc)
|
if hc != nil {
|
||||||
client.resty = restyClient
|
client.resty = resty.NewWithClient(hc)
|
||||||
client.SetUserAgent(DefaultUserAgent)
|
} else {
|
||||||
client.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, APIVersion))
|
client.resty = resty.New()
|
||||||
client.SetPollDelay(1000 * APISecondsPerPoll)
|
}
|
||||||
|
|
||||||
|
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{
|
resources := map[string]*Resource{
|
||||||
stackscriptsName: NewResource(&client, stackscriptsName, stackscriptsEndpoint, false, Stackscript{}, StackscriptsPagedResponse{}),
|
accountName: NewResource(client, accountName, accountEndpoint, false, Account{}, nil), // really?
|
||||||
imagesName: NewResource(&client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
|
accountSettingsName: NewResource(client, accountSettingsName, accountSettingsEndpoint, false, AccountSettings{}, nil), // really?
|
||||||
instancesName: NewResource(&client, instancesName, instancesEndpoint, false, Instance{}, InstancesPagedResponse{}),
|
domainRecordsName: NewResource(client, domainRecordsName, domainRecordsEndpoint, true, DomainRecord{}, DomainRecordsPagedResponse{}),
|
||||||
instanceDisksName: NewResource(&client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
|
domainsName: NewResource(client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
|
||||||
instanceConfigsName: NewResource(&client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
|
eventsName: NewResource(client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
|
||||||
instanceSnapshotsName: NewResource(&client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
|
firewallsName: NewResource(client, firewallsName, firewallsEndpoint, false, Firewall{}, FirewallsPagedResponse{}),
|
||||||
instanceIPsName: NewResource(&client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
|
imagesName: NewResource(client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
|
||||||
instanceVolumesName: NewResource(&client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
|
instanceConfigsName: NewResource(client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
|
||||||
ipaddressesName: NewResource(&client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
|
instanceDisksName: NewResource(client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
|
||||||
ipv6poolsName: NewResource(&client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
|
instanceIPsName: NewResource(client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
|
||||||
ipv6rangesName: NewResource(&client, ipv6rangesName, ipv6rangesEndpoint, false, IPv6Range{}, IPv6RangesPagedResponse{}),
|
instanceSnapshotsName: NewResource(client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
|
||||||
regionsName: NewResource(&client, regionsName, regionsEndpoint, false, Region{}, RegionsPagedResponse{}),
|
instanceStatsName: NewResource(client, instanceStatsName, instanceStatsEndpoint, true, InstanceStats{}, nil),
|
||||||
volumesName: NewResource(&client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}),
|
instanceVolumesName: NewResource(client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
|
||||||
kernelsName: NewResource(&client, kernelsName, kernelsEndpoint, false, LinodeKernel{}, LinodeKernelsPagedResponse{}),
|
instancesName: NewResource(client, instancesName, instancesEndpoint, false, Instance{}, InstancesPagedResponse{}),
|
||||||
typesName: NewResource(&client, typesName, typesEndpoint, false, LinodeType{}, LinodeTypesPagedResponse{}),
|
invoiceItemsName: NewResource(client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
|
||||||
domainsName: NewResource(&client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
|
invoicesName: NewResource(client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
|
||||||
domainRecordsName: NewResource(&client, domainRecordsName, domainRecordsEndpoint, true, DomainRecord{}, DomainRecordsPagedResponse{}),
|
ipaddressesName: NewResource(client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
|
||||||
longviewName: NewResource(&client, longviewName, longviewEndpoint, false, nil, nil), // really?
|
ipv6poolsName: NewResource(client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
|
||||||
longviewclientsName: NewResource(&client, longviewclientsName, longviewclientsEndpoint, false, LongviewClient{}, LongviewClientsPagedResponse{}),
|
ipv6rangesName: NewResource(client, ipv6rangesName, ipv6rangesEndpoint, false, IPv6Range{}, IPv6RangesPagedResponse{}),
|
||||||
longviewsubscriptionsName: NewResource(&client, longviewsubscriptionsName, longviewsubscriptionsEndpoint, false, LongviewSubscription{}, LongviewSubscriptionsPagedResponse{}),
|
kernelsName: NewResource(client, kernelsName, kernelsEndpoint, false, LinodeKernel{}, LinodeKernelsPagedResponse{}),
|
||||||
nodebalancersName: NewResource(&client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
|
lkeClustersName: NewResource(client, lkeClustersName, lkeClustersEndpoint, false, LKECluster{}, LKEClustersPagedResponse{}),
|
||||||
nodebalancerconfigsName: NewResource(&client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
|
lkeClusterPoolsName: NewResource(client, lkeClusterPoolsName, lkeClusterPoolsEndpoint, true, LKEClusterPool{}, LKEClusterPoolsPagedResponse{}),
|
||||||
nodebalancernodesName: NewResource(&client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
|
lkeVersionsName: NewResource(client, lkeVersionsName, lkeVersionsEndpoint, false, LKEVersion{}, LKEVersionsPagedResponse{}),
|
||||||
notificationsName: NewResource(&client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
|
longviewName: NewResource(client, longviewName, longviewEndpoint, false, nil, nil), // really?
|
||||||
sshkeysName: NewResource(&client, sshkeysName, sshkeysEndpoint, false, SSHKey{}, SSHKeysPagedResponse{}),
|
longviewclientsName: NewResource(client, longviewclientsName, longviewclientsEndpoint, false, LongviewClient{}, LongviewClientsPagedResponse{}),
|
||||||
ticketsName: NewResource(&client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}),
|
longviewsubscriptionsName: NewResource(client, longviewsubscriptionsName, longviewsubscriptionsEndpoint, false, LongviewSubscription{}, LongviewSubscriptionsPagedResponse{}),
|
||||||
tokensName: NewResource(&client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}),
|
managedName: NewResource(client, managedName, managedEndpoint, false, nil, nil), // really?
|
||||||
accountName: NewResource(&client, accountName, accountEndpoint, false, Account{}, nil), // really?
|
nodebalancerconfigsName: NewResource(client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
|
||||||
eventsName: NewResource(&client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
|
nodebalancernodesName: NewResource(client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
|
||||||
invoicesName: NewResource(&client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
|
nodebalancerStatsName: NewResource(client, nodebalancerStatsName, nodebalancerStatsEndpoint, true, NodeBalancerStats{}, nil),
|
||||||
invoiceItemsName: NewResource(&client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
|
nodebalancersName: NewResource(client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
|
||||||
profileName: NewResource(&client, profileName, profileEndpoint, false, nil, nil), // really?
|
notificationsName: NewResource(client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
|
||||||
managedName: NewResource(&client, managedName, managedEndpoint, false, nil, nil), // really?
|
oauthClientsName: NewResource(client, oauthClientsName, oauthClientsEndpoint, false, OAuthClient{}, OAuthClientsPagedResponse{}),
|
||||||
tagsName: NewResource(&client, tagsName, tagsEndpoint, false, Tag{}, TagsPagedResponse{}),
|
objectStorageBucketsName: NewResource(client, objectStorageBucketsName, objectStorageBucketsEndpoint, false, ObjectStorageBucket{}, ObjectStorageBucketsPagedResponse{}),
|
||||||
usersName: NewResource(&client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}),
|
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.resources = resources
|
||||||
|
|
||||||
client.SetDebug(envDebug)
|
client.Account = resources[accountName]
|
||||||
client.Images = resources[imagesName]
|
client.DomainRecords = resources[domainRecordsName]
|
||||||
client.StackScripts = resources[stackscriptsName]
|
client.Domains = resources[domainsName]
|
||||||
client.Instances = resources[instancesName]
|
client.Events = resources[eventsName]
|
||||||
client.Regions = resources[regionsName]
|
client.Firewalls = resources[firewallsName]
|
||||||
client.InstanceDisks = resources[instanceDisksName]
|
|
||||||
client.InstanceConfigs = resources[instanceConfigsName]
|
|
||||||
client.InstanceSnapshots = resources[instanceSnapshotsName]
|
|
||||||
client.InstanceIPs = resources[instanceIPsName]
|
|
||||||
client.InstanceVolumes = resources[instanceVolumesName]
|
|
||||||
client.IPAddresses = resources[ipaddressesName]
|
client.IPAddresses = resources[ipaddressesName]
|
||||||
client.IPv6Pools = resources[ipv6poolsName]
|
client.IPv6Pools = resources[ipv6poolsName]
|
||||||
client.IPv6Ranges = resources[ipv6rangesName]
|
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.Kernels = resources[kernelsName]
|
||||||
client.Types = resources[typesName]
|
client.LKEClusters = resources[lkeClustersName]
|
||||||
client.Domains = resources[domainsName]
|
client.LKEClusterPools = resources[lkeClusterPoolsName]
|
||||||
client.DomainRecords = resources[domainRecordsName]
|
client.LKEVersions = resources[lkeVersionsName]
|
||||||
client.Longview = resources[longviewName]
|
client.Longview = resources[longviewName]
|
||||||
client.LongviewSubscriptions = resources[longviewsubscriptionsName]
|
client.LongviewSubscriptions = resources[longviewsubscriptionsName]
|
||||||
client.NodeBalancers = resources[nodebalancersName]
|
client.Managed = resources[managedName]
|
||||||
client.NodeBalancerConfigs = resources[nodebalancerconfigsName]
|
client.NodeBalancerConfigs = resources[nodebalancerconfigsName]
|
||||||
client.NodeBalancerNodes = resources[nodebalancernodesName]
|
client.NodeBalancerNodes = resources[nodebalancernodesName]
|
||||||
|
client.NodeBalancerStats = resources[nodebalancerStatsName]
|
||||||
|
client.NodeBalancers = resources[nodebalancersName]
|
||||||
client.Notifications = resources[notificationsName]
|
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.SSHKeys = resources[sshkeysName]
|
||||||
|
client.StackScripts = resources[stackscriptsName]
|
||||||
|
client.Tags = resources[tagsName]
|
||||||
client.Tickets = resources[ticketsName]
|
client.Tickets = resources[ticketsName]
|
||||||
client.Tokens = resources[tokensName]
|
client.Tokens = resources[tokensName]
|
||||||
client.Account = resources[accountName]
|
client.Types = resources[typesName]
|
||||||
client.Events = resources[eventsName]
|
|
||||||
client.Invoices = resources[invoicesName]
|
|
||||||
client.Profile = resources[profileName]
|
|
||||||
client.Managed = resources[managedName]
|
|
||||||
client.Tags = resources[tagsName]
|
|
||||||
client.Users = resources[usersName]
|
client.Users = resources[usersName]
|
||||||
return
|
client.Volumes = resources[volumesName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyBool(bPtr *bool) *bool {
|
func copyBool(bPtr *bool) *bool {
|
||||||
if bPtr == nil {
|
if bPtr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = *bPtr
|
var t = *bPtr
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +364,9 @@ func copyInt(iPtr *int) *int {
|
||||||
if iPtr == nil {
|
if iPtr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = *iPtr
|
var t = *iPtr
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +374,9 @@ func copyString(sPtr *string) *string {
|
||||||
if sPtr == nil {
|
if sPtr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = *sPtr
|
var t = *sPtr
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +384,8 @@ func copyTime(tPtr *time.Time) *time.Time {
|
||||||
if tPtr == nil {
|
if tPtr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = *tPtr
|
var t = *tPtr
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ func (d DomainRecord) GetUpdateOptions() (du DomainRecordUpdateOptions) {
|
||||||
du.Protocol = copyString(d.Protocol)
|
du.Protocol = copyString(d.Protocol)
|
||||||
du.TTLSec = d.TTLSec
|
du.TTLSec = d.TTLSec
|
||||||
du.Tag = copyString(d.Tag)
|
du.Tag = copyString(d.Tag)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +93,7 @@ func (DomainRecordsPagedResponse) endpointWithID(c *Client, id int) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
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) {
|
func (c *Client) ListDomainRecords(ctx context.Context, domainID int, opts *ListOptions) ([]DomainRecord, error) {
|
||||||
response := DomainRecordsPagedResponse{}
|
response := DomainRecordsPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, domainID, opts)
|
err := c.listHelperWithID(ctx, &response, domainID, opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
return response.Data, nil
|
||||||
func (d *DomainRecord) fixDates() *DomainRecord {
|
|
||||||
return d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDomainRecord gets the domainrecord with the provided ID
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&DomainRecord{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&DomainRecord{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*DomainRecord), nil
|
return r.Result().(*DomainRecord), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDomainRecord creates a DomainRecord
|
// CreateDomainRecord creates a DomainRecord
|
||||||
func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrecord DomainRecordCreateOptions) (*DomainRecord, error) {
|
func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrecord DomainRecordCreateOptions) (*DomainRecord, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.DomainRecords.endpointWithID(domainID)
|
e, err := c.DomainRecords.endpointWithID(domainID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -143,6 +147,7 @@ func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(err)
|
return nil, NewError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body = string(bodyData)
|
body = string(bodyData)
|
||||||
|
|
||||||
r, err := coupleAPIErrors(req.
|
r, err := coupleAPIErrors(req.
|
||||||
|
@ -152,16 +157,20 @@ func (c *Client) CreateDomainRecord(ctx context.Context, domainID int, domainrec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*DomainRecord).fixDates(), nil
|
|
||||||
|
return r.Result().(*DomainRecord), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDomainRecord updates the DomainRecord with the specified id
|
// UpdateDomainRecord updates the DomainRecord with the specified id
|
||||||
func (c *Client) UpdateDomainRecord(ctx context.Context, domainID int, id int, domainrecord DomainRecordUpdateOptions) (*DomainRecord, error) {
|
func (c *Client) UpdateDomainRecord(ctx context.Context, domainID int, id int, domainrecord DomainRecordUpdateOptions) (*DomainRecord, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.DomainRecords.endpointWithID(domainID)
|
e, err := c.DomainRecords.endpointWithID(domainID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
|
|
||||||
req := c.R(ctx).SetResult(&DomainRecord{})
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*DomainRecord).fixDates(), nil
|
|
||||||
|
return r.Result().(*DomainRecord), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDomainRecord deletes the DomainRecord with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
|
|
||||||
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,7 @@ func (d Domain) GetUpdateOptions() (du DomainUpdateOptions) {
|
||||||
du.ExpireSec = d.ExpireSec
|
du.ExpireSec = d.ExpireSec
|
||||||
du.RefreshSec = d.RefreshSec
|
du.RefreshSec = d.RefreshSec
|
||||||
du.TTLSec = d.TTLSec
|
du.TTLSec = d.TTLSec
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +193,7 @@ func (DomainsPagedResponse) endpoint(c *Client) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,15 +206,12 @@ func (resp *DomainsPagedResponse) appendData(r *DomainsPagedResponse) {
|
||||||
func (c *Client) ListDomains(ctx context.Context, opts *ListOptions) ([]Domain, error) {
|
func (c *Client) ListDomains(ctx context.Context, opts *ListOptions) ([]Domain, error) {
|
||||||
response := DomainsPagedResponse{}
|
response := DomainsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
return response.Data, nil
|
||||||
func (d *Domain) fixDates() *Domain {
|
|
||||||
return d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDomain gets the domain with the provided ID
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Domain{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Domain{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Domain).fixDates(), nil
|
|
||||||
|
return r.Result().(*Domain), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDomain creates a Domain
|
// CreateDomain creates a Domain
|
||||||
func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (*Domain, error) {
|
func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (*Domain, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Domains.Endpoint()
|
e, err := c.Domains.Endpoint()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -243,6 +247,7 @@ func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(err)
|
return nil, NewError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body = string(bodyData)
|
body = string(bodyData)
|
||||||
|
|
||||||
r, err := coupleAPIErrors(req.
|
r, err := coupleAPIErrors(req.
|
||||||
|
@ -252,16 +257,20 @@ func (c *Client) CreateDomain(ctx context.Context, domain DomainCreateOptions) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Domain).fixDates(), nil
|
|
||||||
|
return r.Result().(*Domain), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDomain updates the Domain with the specified id
|
// UpdateDomain updates the Domain with the specified id
|
||||||
func (c *Client) UpdateDomain(ctx context.Context, id int, domain DomainUpdateOptions) (*Domain, error) {
|
func (c *Client) UpdateDomain(ctx context.Context, id int, domain DomainUpdateOptions) (*Domain, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Domains.Endpoint()
|
e, err := c.Domains.Endpoint()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
|
|
||||||
req := c.R(ctx).SetResult(&Domain{})
|
req := c.R(ctx).SetResult(&Domain{})
|
||||||
|
@ -279,7 +288,8 @@ func (c *Client) UpdateDomain(ctx context.Context, id int, domain DomainUpdateOp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Domain).fixDates(), nil
|
|
||||||
|
return r.Result().(*Domain), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDomain deletes the Domain with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, id)
|
e = fmt.Sprintf("%s/%d", e, id)
|
||||||
|
|
||||||
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/resty.v1"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,6 +35,7 @@ func (r APIErrorReason) Error() string {
|
||||||
if len(r.Field) == 0 {
|
if len(r.Field) == 0 {
|
||||||
return r.Reason
|
return r.Reason
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("[%s] %s", r.Field, 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 {
|
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)
|
apiError, ok := r.Error().(*APIError)
|
||||||
if !ok || (ok && len(apiError.Errors) == 0) {
|
if !ok || (ok && len(apiError.Errors) == 0) {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, NewError(r)
|
return nil, NewError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +76,11 @@ func coupleAPIErrors(r *resty.Response, err error) (*resty.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e APIError) Error() string {
|
func (e APIError) Error() string {
|
||||||
var x []string
|
x := []string{}
|
||||||
for _, msg := range e.Errors {
|
for _, msg := range e.Errors {
|
||||||
x = append(x, msg.Error())
|
x = append(x, msg.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(x, "; ")
|
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,12 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Image represents a deployable Image object for use with Linode Instances
|
// Image represents a deployable Image object for use with Linode Instances
|
||||||
type Image struct {
|
type Image struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
ExpiryStr string `json:"expiry"`
|
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
CreatedBy string `json:"created_by"`
|
CreatedBy string `json:"created_by"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
|
@ -20,7 +20,6 @@ type Image struct {
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
IsPublic bool `json:"is_public"`
|
IsPublic bool `json:"is_public"`
|
||||||
Deprecated bool `json:"deprecated"`
|
Deprecated bool `json:"deprecated"`
|
||||||
|
|
||||||
Created *time.Time `json:"-"`
|
Created *time.Time `json:"-"`
|
||||||
Expiry *time.Time `json:"-"`
|
Expiry *time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
@ -38,15 +37,26 @@ type ImageUpdateOptions struct {
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) fixDates() *Image {
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
i.Created, _ = parseDates(i.CreatedStr)
|
func (i *Image) UnmarshalJSON(b []byte) error {
|
||||||
|
type Mask Image
|
||||||
|
|
||||||
if len(i.ExpiryStr) > 0 {
|
p := struct {
|
||||||
i.Expiry, _ = parseDates(i.ExpiryStr)
|
*Mask
|
||||||
} else {
|
Created *parseabletime.ParseableTime `json:"created"`
|
||||||
i.Expiry = nil
|
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
|
// 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) {
|
func (c *Client) ListImages(ctx context.Context, opts *ListOptions) ([]Image, error) {
|
||||||
response := ImagesPagedResponse{}
|
response := ImagesPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetImage gets the Image with the provided ID
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
r, err := coupleAPIErrors(c.Images.R(ctx).Get(e))
|
r, err := coupleAPIErrors(c.Images.R(ctx).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Image).fixDates(), nil
|
return r.Result().(*Image), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateImage creates a Image
|
// CreateImage creates a Image
|
||||||
func (c *Client) CreateImage(ctx context.Context, createOpts ImageCreateOptions) (*Image, error) {
|
func (c *Client) CreateImage(ctx context.Context, createOpts ImageCreateOptions) (*Image, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Images.Endpoint()
|
e, err := c.Images.Endpoint()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -125,16 +136,18 @@ func (c *Client) CreateImage(ctx context.Context, createOpts ImageCreateOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Image).fixDates(), nil
|
return r.Result().(*Image), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateImage updates the Image with the specified id
|
// UpdateImage updates the Image with the specified id
|
||||||
func (c *Client) UpdateImage(ctx context.Context, id string, updateOpts ImageUpdateOptions) (*Image, error) {
|
func (c *Client) UpdateImage(ctx context.Context, id string, updateOpts ImageUpdateOptions) (*Image, error) {
|
||||||
var body string
|
var body string
|
||||||
|
|
||||||
e, err := c.Images.Endpoint()
|
e, err := c.Images.Endpoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
|
|
||||||
req := c.R(ctx).SetResult(&Image{})
|
req := c.R(ctx).SetResult(&Image{})
|
||||||
|
@ -152,7 +165,7 @@ func (c *Client) UpdateImage(ctx context.Context, id string, updateOpts ImageUpd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Image).fixDates(), nil
|
return r.Result().(*Image), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteImage deletes the Image with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%s", e, id)
|
e = fmt.Sprintf("%s/%s", e, id)
|
||||||
|
|
||||||
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstanceConfig represents all of the settings that control the boot and run configuration of a Linode Instance
|
// InstanceConfig represents all of the settings that control the boot and run configuration of a Linode Instance
|
||||||
type InstanceConfig struct {
|
type InstanceConfig struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Comments string `json:"comments"`
|
Comments string `json:"comments"`
|
||||||
|
@ -90,6 +89,28 @@ type InstanceConfigUpdateOptions struct {
|
||||||
VirtMode string `json:"virt_mode,omitempty"`
|
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
|
// GetCreateOptions converts a InstanceConfig to InstanceConfigCreateOptions for use in CreateInstanceConfig
|
||||||
func (i InstanceConfig) GetCreateOptions() InstanceConfigCreateOptions {
|
func (i InstanceConfig) GetCreateOptions() InstanceConfigCreateOptions {
|
||||||
initrd := 0
|
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) {
|
func (c *Client) ListInstanceConfigs(ctx context.Context, linodeID int, opts *ListOptions) ([]InstanceConfig, error) {
|
||||||
response := InstanceConfigsPagedResponse{}
|
response := InstanceConfigsPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetInstanceConfig gets the template with the provided ID
|
||||||
func (c *Client) GetInstanceConfig(ctx context.Context, linodeID int, configID int) (*InstanceConfig, error) {
|
func (c *Client) GetInstanceConfig(ctx context.Context, linodeID int, configID int) (*InstanceConfig, error) {
|
||||||
e, err := c.InstanceConfigs.endpointWithID(linodeID)
|
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)
|
e = fmt.Sprintf("%s/%d", e, configID)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceConfig{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceConfig{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*InstanceConfig).fixDates(), nil
|
return r.Result().(*InstanceConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateInstanceConfig creates a new InstanceConfig for the given Instance
|
// CreateInstanceConfig creates a new InstanceConfig for the given Instance
|
||||||
func (c *Client) CreateInstanceConfig(ctx context.Context, linodeID int, createOpts InstanceConfigCreateOptions) (*InstanceConfig, error) {
|
func (c *Client) CreateInstanceConfig(ctx context.Context, linodeID int, createOpts InstanceConfigCreateOptions) (*InstanceConfig, error) {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceConfigs.endpointWithID(linodeID)
|
e, err := c.InstanceConfigs.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -198,13 +212,14 @@ func (c *Client) CreateInstanceConfig(ctx context.Context, linodeID int, createO
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*InstanceConfig).fixDates(), nil
|
return r.Result().(*InstanceConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateInstanceConfig update an InstanceConfig for the given Instance
|
// UpdateInstanceConfig update an InstanceConfig for the given Instance
|
||||||
func (c *Client) UpdateInstanceConfig(ctx context.Context, linodeID int, configID int, updateOpts InstanceConfigUpdateOptions) (*InstanceConfig, error) {
|
func (c *Client) UpdateInstanceConfig(ctx context.Context, linodeID int, configID int, updateOpts InstanceConfigUpdateOptions) (*InstanceConfig, error) {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceConfigs.endpointWithID(linodeID)
|
e, err := c.InstanceConfigs.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -225,7 +240,7 @@ func (c *Client) UpdateInstanceConfig(ctx context.Context, linodeID int, configI
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*InstanceConfig).fixDates(), nil
|
return r.Result().(*InstanceConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameInstanceConfig renames an InstanceConfig
|
// RenameInstanceConfig renames an InstanceConfig
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstanceDisk represents an Instance Disk object
|
// InstanceDisk represents an Instance Disk object
|
||||||
type InstanceDisk struct {
|
type InstanceDisk struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Status DiskStatus `json:"status"`
|
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) {
|
func (c *Client) ListInstanceDisks(ctx context.Context, linodeID int, opts *ListOptions) ([]InstanceDisk, error) {
|
||||||
response := InstanceDisksPagedResponse{}
|
response := InstanceDisksPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixDates converts JSON timestamps to Go time.Time values
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
func (v *InstanceDisk) fixDates() *InstanceDisk {
|
func (i *InstanceDisk) UnmarshalJSON(b []byte) error {
|
||||||
if created, err := parseDates(v.CreatedStr); err == nil {
|
type Mask InstanceDisk
|
||||||
v.Created = *created
|
|
||||||
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e = fmt.Sprintf("%s/%d", e, configID)
|
e = fmt.Sprintf("%s/%d", e, configID)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceDisk{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceDisk{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*InstanceDisk).fixDates(), nil
|
return r.Result().(*InstanceDisk), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateInstanceDisk creates a new InstanceDisk for the given Instance
|
// CreateInstanceDisk creates a new InstanceDisk for the given Instance
|
||||||
func (c *Client) CreateInstanceDisk(ctx context.Context, linodeID int, createOpts InstanceDiskCreateOptions) (*InstanceDisk, error) {
|
func (c *Client) CreateInstanceDisk(ctx context.Context, linodeID int, createOpts InstanceDiskCreateOptions) (*InstanceDisk, error) {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -148,18 +163,19 @@ func (c *Client) CreateInstanceDisk(ctx context.Context, linodeID int, createOpt
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*InstanceDisk).fixDates(), nil
|
return r.Result().(*InstanceDisk), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateInstanceDisk creates a new InstanceDisk for the given Instance
|
// UpdateInstanceDisk creates a new InstanceDisk for the given Instance
|
||||||
func (c *Client) UpdateInstanceDisk(ctx context.Context, linodeID int, diskID int, updateOpts InstanceDiskUpdateOptions) (*InstanceDisk, error) {
|
func (c *Client) UpdateInstanceDisk(ctx context.Context, linodeID int, diskID int, updateOpts InstanceDiskUpdateOptions) (*InstanceDisk, error) {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
e = fmt.Sprintf("%s/%d", e, diskID)
|
|
||||||
|
|
||||||
|
e = fmt.Sprintf("%s/%d", e, diskID)
|
||||||
req := c.R(ctx).SetResult(&InstanceDisk{})
|
req := c.R(ctx).SetResult(&InstanceDisk{})
|
||||||
|
|
||||||
if bodyData, err := json.Marshal(updateOpts); err == nil {
|
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*InstanceDisk).fixDates(), nil
|
return r.Result().(*InstanceDisk), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameInstanceDisk renames an InstanceDisk
|
// 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 {
|
func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID int, size int) error {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (c *Client) PasswordResetInstanceDisk(ctx context.Context, linodeID int, diskID int, password string) error {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
e, err := c.InstanceDisks.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ type InstanceIPv4Response struct {
|
||||||
Public []*InstanceIP `json:"public"`
|
Public []*InstanceIP `json:"public"`
|
||||||
Private []*InstanceIP `json:"private"`
|
Private []*InstanceIP `json:"private"`
|
||||||
Shared []*InstanceIP `json:"shared"`
|
Shared []*InstanceIP `json:"shared"`
|
||||||
|
Reserved []*InstanceIP `json:"reserved"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstanceIP represents an Instance IP with additional DNS and networking details
|
// InstanceIP represents an Instance IP with additional DNS and networking details
|
||||||
|
@ -25,7 +26,7 @@ type InstanceIP struct {
|
||||||
Gateway string `json:"gateway"`
|
Gateway string `json:"gateway"`
|
||||||
SubnetMask string `json:"subnet_mask"`
|
SubnetMask string `json:"subnet_mask"`
|
||||||
Prefix int `json:"prefix"`
|
Prefix int `json:"prefix"`
|
||||||
Type string `json:"type"`
|
Type InstanceIPType `json:"type"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
RDNS string `json:"rdns"`
|
RDNS string `json:"rdns"`
|
||||||
LinodeID int `json:"linode_id"`
|
LinodeID int `json:"linode_id"`
|
||||||
|
@ -43,14 +44,27 @@ type InstanceIPv6Response struct {
|
||||||
type IPv6Range struct {
|
type IPv6Range struct {
|
||||||
Range string `json:"range"`
|
Range string `json:"range"`
|
||||||
Region string `json:"region"`
|
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
|
// GetInstanceIPAddresses gets the IPAddresses for a Linode instance
|
||||||
func (c *Client) GetInstanceIPAddresses(ctx context.Context, linodeID int) (*InstanceIPAddressResponse, error) {
|
func (c *Client) GetInstanceIPAddresses(ctx context.Context, linodeID int) (*InstanceIPAddressResponse, error) {
|
||||||
e, err := c.InstanceIPs.endpointWithID(linodeID)
|
e, err := c.InstanceIPs.endpointWithID(linodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIPAddressResponse{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIPAddressResponse{}).Get(e))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -66,6 +80,7 @@ func (c *Client) GetInstanceIPAddress(ctx context.Context, linodeID int, ipaddre
|
||||||
}
|
}
|
||||||
e = fmt.Sprintf("%s/%s", e, ipaddress)
|
e = fmt.Sprintf("%s/%s", e, ipaddress)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIP{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceIP{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (c *Client) AddInstanceIPAddress(ctx context.Context, linodeID int, public bool) (*InstanceIP, error) {
|
||||||
var body string
|
var body string
|
||||||
e, err := c.InstanceIPs.endpointWithID(linodeID)
|
e, err := c.InstanceIPs.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,3 +120,31 @@ func (c *Client) AddInstanceIPAddress(ctx context.Context, linodeID int, public
|
||||||
|
|
||||||
return r.Result().(*InstanceIP), nil
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstanceBackupsResponse response struct for backup snapshot
|
// InstanceBackupsResponse response struct for backup snapshot
|
||||||
|
@ -27,10 +29,6 @@ type RestoreInstanceOptions struct {
|
||||||
|
|
||||||
// InstanceSnapshot represents a linode backup snapshot
|
// InstanceSnapshot represents a linode backup snapshot
|
||||||
type InstanceSnapshot struct {
|
type InstanceSnapshot struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
FinishedStr string `json:"finished"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Status InstanceSnapshotStatus `json:"status"`
|
Status InstanceSnapshotStatus `json:"status"`
|
||||||
|
@ -63,11 +61,28 @@ var (
|
||||||
SnapshotUserAborted InstanceSnapshotStatus = "userAborted"
|
SnapshotUserAborted InstanceSnapshotStatus = "userAborted"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *InstanceSnapshot) fixDates() *InstanceSnapshot {
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||||
l.Created, _ = parseDates(l.CreatedStr)
|
func (i *InstanceSnapshot) UnmarshalJSON(b []byte) error {
|
||||||
l.Updated, _ = parseDates(l.UpdatedStr)
|
type Mask InstanceSnapshot
|
||||||
l.Finished, _ = parseDates(l.FinishedStr)
|
|
||||||
return l
|
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
|
// 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)
|
e = fmt.Sprintf("%s/%d", e, snapshotID)
|
||||||
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceSnapshot{}).Get(e))
|
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceSnapshot{}).Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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)
|
body := string(o)
|
||||||
e, err := c.InstanceSnapshots.endpointWithID(linodeID)
|
e, err := c.InstanceSnapshots.endpointWithID(linodeID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -105,7 +122,7 @@ func (c *Client) CreateInstanceSnapshot(ctx context.Context, linodeID int, label
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*InstanceSnapshot).fixDates(), nil
|
return r.Result().(*InstanceSnapshot), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInstanceBackups gets the Instance's available Backups.
|
// 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).
|
r, err := coupleAPIErrors(c.R(ctx).
|
||||||
SetResult(&InstanceBackupsResponse{}).
|
SetResult(&InstanceBackupsResponse{}).
|
||||||
Get(e))
|
Get(e))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*InstanceBackupsResponse).fixDates(), nil
|
return r.Result().(*InstanceBackupsResponse), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableInstanceBackups Enables backups for the specified Linode.
|
// 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))
|
_, err = coupleAPIErrors(c.R(ctx).SetBody(body).Post(e))
|
||||||
|
|
||||||
return err
|
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) {
|
func (c *Client) ListInstanceVolumes(ctx context.Context, linodeID int, opts *ListOptions) ([]Volume, error) {
|
||||||
response := InstanceVolumesPagedResponse{}
|
response := InstanceVolumesPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
err := c.listHelperWithID(ctx, &response, linodeID, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -33,9 +35,6 @@ const (
|
||||||
|
|
||||||
// Instance represents a linode object
|
// Instance represents a linode object
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Created *time.Time `json:"-"`
|
Created *time.Time `json:"-"`
|
||||||
Updated *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
|
// InstanceCreateOptions require only Region and Type
|
||||||
type InstanceCreateOptions struct {
|
type InstanceCreateOptions struct {
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
@ -113,15 +124,37 @@ type InstanceUpdateOptions struct {
|
||||||
Tags *[]string `json:"tags,omitempty"`
|
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
|
// GetUpdateOptions converts an Instance to InstanceUpdateOptions for use in UpdateInstance
|
||||||
func (l *Instance) GetUpdateOptions() InstanceUpdateOptions {
|
func (i *Instance) GetUpdateOptions() InstanceUpdateOptions {
|
||||||
return InstanceUpdateOptions{
|
return InstanceUpdateOptions{
|
||||||
Label: l.Label,
|
Label: i.Label,
|
||||||
Group: l.Group,
|
Group: i.Group,
|
||||||
Backups: l.Backups,
|
Backups: i.Backups,
|
||||||
Alerts: l.Alerts,
|
Alerts: i.Alerts,
|
||||||
WatchdogEnabled: &l.WatchdogEnabled,
|
WatchdogEnabled: &i.WatchdogEnabled,
|
||||||
Tags: &l.Tags,
|
Tags: &i.Tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,10 +172,12 @@ type InstanceCloneOptions struct {
|
||||||
Configs []int `json:"configs,omitempty"`
|
Configs []int `json:"configs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Instance) fixDates() *Instance {
|
// InstanceResizeOptions is an options struct used when resizing an instance
|
||||||
l.Created, _ = parseDates(l.CreatedStr)
|
type InstanceResizeOptions struct {
|
||||||
l.Updated, _ = parseDates(l.UpdatedStr)
|
Type string `json:"type"`
|
||||||
return l
|
|
||||||
|
// 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
|
// 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) {
|
func (c *Client) ListInstances(ctx context.Context, opts *ListOptions) ([]Instance, error) {
|
||||||
response := InstancesPagedResponse{}
|
response := InstancesPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -191,7 +224,23 @@ func (c *Client) GetInstance(ctx context.Context, linodeID int) (*Instance, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// CreateInstance creates a Linode instance
|
||||||
|
@ -217,7 +266,7 @@ func (c *Client) CreateInstance(ctx context.Context, instance InstanceCreateOpti
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Instance).fixDates(), nil
|
return r.Result().(*Instance), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateInstance creates a Linode instance
|
// UpdateInstance creates a Linode instance
|
||||||
|
@ -244,7 +293,7 @@ func (c *Client) UpdateInstance(ctx context.Context, id int, instance InstanceUp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Instance).fixDates(), nil
|
return r.Result().(*Instance), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameInstance renames an Instance
|
// RenameInstance renames an Instance
|
||||||
|
@ -316,7 +365,7 @@ func (c *Client) CloneInstance(ctx context.Context, id int, options InstanceClon
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Result().(*Instance).fixDates(), nil
|
return r.Result().(*Instance), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RebootInstance reboots a Linode instance
|
// RebootInstance reboots a Linode instance
|
||||||
|
@ -347,20 +396,20 @@ func (c *Client) RebootInstance(ctx context.Context, id int, configID int) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RebuildInstanceOptions is a struct representing the options to send to the rebuild linode endpoint
|
// InstanceRebuildOptions is a struct representing the options to send to the rebuild linode endpoint
|
||||||
type RebuildInstanceOptions struct {
|
type InstanceRebuildOptions struct {
|
||||||
Image string `json:"image"`
|
Image string `json:"image,omitempty"`
|
||||||
RootPass string `json:"root_pass"`
|
RootPass string `json:"root_pass,omitempty"`
|
||||||
AuthorizedKeys []string `json:"authorized_keys"`
|
AuthorizedKeys []string `json:"authorized_keys,omitempty"`
|
||||||
AuthorizedUsers []string `json:"authorized_users"`
|
AuthorizedUsers []string `json:"authorized_users,omitempty"`
|
||||||
StackscriptID int `json:"stackscript_id"`
|
StackScriptID int `json:"stackscript_id,omitempty"`
|
||||||
StackscriptData map[string]string `json:"stackscript_data"`
|
StackScriptData map[string]string `json:"stackscript_data,omitempty"`
|
||||||
Booted bool `json:"booted"`
|
Booted *bool `json:"booted,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RebuildInstance Deletes all Disks and Configs on this Linode,
|
// RebuildInstance Deletes all Disks and Configs on this Linode,
|
||||||
// then deploys a new Image to this Linode with the given attributes.
|
// 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)
|
o, err := json.Marshal(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(err)
|
return nil, NewError(err)
|
||||||
|
@ -378,11 +427,11 @@ func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstan
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Instance).fixDates(), nil
|
return r.Result().(*Instance), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RescueInstanceOptions fields are those accepted by RescueInstance
|
// InstanceRescueOptions fields are those accepted by RescueInstance
|
||||||
type RescueInstanceOptions struct {
|
type InstanceRescueOptions struct {
|
||||||
Devices InstanceConfigDeviceMap `json:"devices"`
|
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.
|
// 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,
|
// 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.
|
// 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)
|
o, err := json.Marshal(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewError(err)
|
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
|
// ResizeInstance resizes an instance to new Linode type
|
||||||
func (c *Client) ResizeInstance(ctx context.Context, id int, linodeType string) error {
|
func (c *Client) ResizeInstance(ctx context.Context, id int, opts InstanceResizeOptions) error {
|
||||||
body := fmt.Sprintf("{\"type\":\"%s\"}", linodeType)
|
o, err := json.Marshal(opts)
|
||||||
|
if err != nil {
|
||||||
|
return NewError(err)
|
||||||
|
}
|
||||||
|
body := string(o)
|
||||||
e, err := c.Instances.Endpoint()
|
e, err := c.Instances.Endpoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
func (c *Client) ListLongviewClients(ctx context.Context, opts *ListOptions) ([]LongviewClient, error) {
|
||||||
response := LongviewClientsPagedResponse{}
|
response := LongviewClientsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetLongviewClient gets the template with the provided ID
|
||||||
func (c *Client) GetLongviewClient(ctx context.Context, id string) (*LongviewClient, error) {
|
func (c *Client) GetLongviewClient(ctx context.Context, id string) (*LongviewClient, error) {
|
||||||
e, err := c.LongviewClients.Endpoint()
|
e, err := c.LongviewClients.Endpoint()
|
||||||
|
@ -63,5 +54,5 @@ func (c *Client) GetLongviewClient(ctx context.Context, id string) (*LongviewCli
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (c *Client) ListLongviewSubscriptions(ctx context.Context, opts *ListOptions) ([]LongviewSubscription, error) {
|
||||||
response := LongviewSubscriptionsPagedResponse{}
|
response := LongviewSubscriptionsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetLongviewSubscription gets the template with the provided ID
|
||||||
func (c *Client) GetLongviewSubscription(ctx context.Context, id string) (*LongviewSubscription, error) {
|
func (c *Client) GetLongviewSubscription(ctx context.Context, id string) (*LongviewSubscription, error) {
|
||||||
e, err := c.LongviewSubscriptions.Endpoint()
|
e, err := c.LongviewSubscriptions.Endpoint()
|
||||||
|
@ -66,5 +57,5 @@ func (c *Client) GetLongviewSubscription(ctx context.Context, id string) (*Longv
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*LongviewSubscription).fixDates(), nil
|
return r.Result().(*LongviewSubscription), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeBalancer represents a NodeBalancer object
|
// NodeBalancer represents a NodeBalancer object
|
||||||
type NodeBalancer struct {
|
type NodeBalancer struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
// This NodeBalancer's unique ID.
|
// This NodeBalancer's unique ID.
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
// This NodeBalancer's label. These must be unique on your Account.
|
// This NodeBalancer's label. These must be unique on your Account.
|
||||||
|
@ -61,6 +61,28 @@ type NodeBalancerUpdateOptions struct {
|
||||||
Tags *[]string `json:"tags,omitempty"`
|
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
|
// GetCreateOptions converts a NodeBalancer to NodeBalancerCreateOptions for use in CreateNodeBalancer
|
||||||
func (i NodeBalancer) GetCreateOptions() NodeBalancerCreateOptions {
|
func (i NodeBalancer) GetCreateOptions() NodeBalancerCreateOptions {
|
||||||
return NodeBalancerCreateOptions{
|
return NodeBalancerCreateOptions{
|
||||||
|
@ -108,13 +130,6 @@ func (c *Client) ListNodeBalancers(ctx context.Context, opts *ListOptions) ([]No
|
||||||
return response.Data, nil
|
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
|
// GetNodeBalancer gets the NodeBalancer with the provided ID
|
||||||
func (c *Client) GetNodeBalancer(ctx context.Context, id int) (*NodeBalancer, error) {
|
func (c *Client) GetNodeBalancer(ctx context.Context, id int) (*NodeBalancer, error) {
|
||||||
e, err := c.NodeBalancers.Endpoint()
|
e, err := c.NodeBalancers.Endpoint()
|
||||||
|
@ -128,7 +143,7 @@ func (c *Client) GetNodeBalancer(ctx context.Context, id int) (*NodeBalancer, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancer).fixDates(), nil
|
return r.Result().(*NodeBalancer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNodeBalancer creates a NodeBalancer
|
// CreateNodeBalancer creates a NodeBalancer
|
||||||
|
@ -155,7 +170,7 @@ func (c *Client) CreateNodeBalancer(ctx context.Context, nodebalancer NodeBalanc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancer).fixDates(), nil
|
return r.Result().(*NodeBalancer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNodeBalancer updates the NodeBalancer with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancer).fixDates(), nil
|
return r.Result().(*NodeBalancer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNodeBalancer deletes the NodeBalancer with the specified id
|
// 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 is the NodeMode indicating a NodeBalancer Node is not receiving new traffic, but may continue receiving traffic from pinned connections
|
||||||
ModeDrain NodeMode = "drain"
|
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
|
// 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) {
|
func (c *Client) ListNodeBalancerNodes(ctx context.Context, nodebalancerID int, configID int, opts *ListOptions) ([]NodeBalancerNode, error) {
|
||||||
response := NodeBalancerNodesPagedResponse{}
|
response := NodeBalancerNodesPagedResponse{}
|
||||||
err := c.listHelperWithTwoIDs(ctx, &response, nodebalancerID, configID, opts)
|
err := c.listHelperWithTwoIDs(ctx, &response, nodebalancerID, configID, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetNodeBalancerNode gets the template with the provided ID
|
||||||
func (c *Client) GetNodeBalancerNode(ctx context.Context, nodebalancerID int, configID int, nodeID int) (*NodeBalancerNode, error) {
|
func (c *Client) GetNodeBalancerNode(ctx context.Context, nodebalancerID int, configID int, nodeID int) (*NodeBalancerNode, error) {
|
||||||
e, err := c.NodeBalancerNodes.endpointWithID(nodebalancerID, configID)
|
e, err := c.NodeBalancerNodes.endpointWithID(nodebalancerID, configID)
|
||||||
|
@ -117,7 +113,7 @@ func (c *Client) GetNodeBalancerNode(ctx context.Context, nodebalancerID int, co
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerNode).fixDates(), nil
|
return r.Result().(*NodeBalancerNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNodeBalancerNode creates a NodeBalancerNode
|
// CreateNodeBalancerNode creates a NodeBalancerNode
|
||||||
|
@ -143,7 +139,7 @@ func (c *Client) CreateNodeBalancerNode(ctx context.Context, nodebalancerID int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerNode).fixDates(), nil
|
return r.Result().(*NodeBalancerNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNodeBalancerNode updates the NodeBalancerNode with the specified id
|
// UpdateNodeBalancerNode updates the NodeBalancerNode with the specified id
|
||||||
|
@ -170,7 +166,7 @@ func (c *Client) UpdateNodeBalancerNode(ctx context.Context, nodebalancerID int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerNode).fixDates(), nil
|
return r.Result().(*NodeBalancerNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNodeBalancerNode deletes the NodeBalancerNode with the specified id
|
// 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) {
|
func (c *Client) ListNodeBalancerConfigs(ctx context.Context, nodebalancerID int, opts *ListOptions) ([]NodeBalancerConfig, error) {
|
||||||
response := NodeBalancerConfigsPagedResponse{}
|
response := NodeBalancerConfigsPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, nodebalancerID, opts)
|
err := c.listHelperWithID(ctx, &response, nodebalancerID, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetNodeBalancerConfig gets the template with the provided ID
|
||||||
func (c *Client) GetNodeBalancerConfig(ctx context.Context, nodebalancerID int, configID int) (*NodeBalancerConfig, error) {
|
func (c *Client) GetNodeBalancerConfig(ctx context.Context, nodebalancerID int, configID int) (*NodeBalancerConfig, error) {
|
||||||
e, err := c.NodeBalancerConfigs.endpointWithID(nodebalancerID)
|
e, err := c.NodeBalancerConfigs.endpointWithID(nodebalancerID)
|
||||||
|
@ -236,7 +229,7 @@ func (c *Client) GetNodeBalancerConfig(ctx context.Context, nodebalancerID int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerConfig).fixDates(), nil
|
return r.Result().(*NodeBalancerConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNodeBalancerConfig creates a NodeBalancerConfig
|
// CreateNodeBalancerConfig creates a NodeBalancerConfig
|
||||||
|
@ -264,7 +257,7 @@ func (c *Client) CreateNodeBalancerConfig(ctx context.Context, nodebalancerID in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerConfig).fixDates(), nil
|
return r.Result().(*NodeBalancerConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNodeBalancerConfig updates the NodeBalancerConfig with the specified id
|
// UpdateNodeBalancerConfig updates the NodeBalancerConfig with the specified id
|
||||||
|
@ -291,7 +284,7 @@ func (c *Client) UpdateNodeBalancerConfig(ctx context.Context, nodebalancerID in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*NodeBalancerConfig).fixDates(), nil
|
return r.Result().(*NodeBalancerConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNodeBalancerConfig deletes the NodeBalancerConfig with the specified id
|
// DeleteNodeBalancerConfig deletes the NodeBalancerConfig with the specified id
|
||||||
|
@ -330,5 +323,5 @@ func (c *Client) RebuildNodeBalancerConfig(ctx context.Context, nodeBalancerID i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gopkg.in/resty.v1"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PageOptions are the pagination parameters for List endpoints
|
// PageOptions are the pagination parameters for List endpoints
|
||||||
|
@ -28,9 +28,8 @@ type ListOptions struct {
|
||||||
|
|
||||||
// NewListOptions simplified construction of ListOptions using only
|
// NewListOptions simplified construction of ListOptions using only
|
||||||
// the two writable properties, Page and Filter
|
// the two writable properties, Page and Filter
|
||||||
func NewListOptions(Page int, Filter string) *ListOptions {
|
func NewListOptions(page int, filter string) *ListOptions {
|
||||||
return &ListOptions{PageOptions: &PageOptions{Page: Page}, Filter: Filter}
|
return &ListOptions{PageOptions: &PageOptions{Page: page}, Filter: filter}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// listHelper abstracts fetching and pagination for GET endpoints that
|
// 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
|
// When opts (or opts.Page) is nil, all pages will be fetched and
|
||||||
// returned in a single (endpoint-specific)PagedResponse
|
// returned in a single (endpoint-specific)PagedResponse
|
||||||
// opts.results and opts.pages will be updated from the API response
|
// 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 {
|
func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOptions) error {
|
||||||
req := c.R(ctx)
|
req := c.R(ctx)
|
||||||
if opts != nil && opts.PageOptions != nil && opts.Page > 0 {
|
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 {
|
if r, err = coupleAPIErrors(req.SetResult(DomainsPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
||||||
response, ok := r.Result().(*DomainsPagedResponse)
|
response, ok := r.Result().(*DomainsPagedResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Response is not a *DomainsPagedResponse")
|
return fmt.Errorf("response is not a *DomainsPagedResponse")
|
||||||
}
|
}
|
||||||
pages = response.Pages
|
pages = response.Pages
|
||||||
results = response.Results
|
results = response.Results
|
||||||
|
@ -114,6 +114,24 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
|
||||||
results = r.Result().(*EventsPagedResponse).Results
|
results = r.Result().(*EventsPagedResponse).Results
|
||||||
v.appendData(r.Result().(*EventsPagedResponse))
|
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:
|
case *LongviewSubscriptionsPagedResponse:
|
||||||
if r, err = coupleAPIErrors(req.SetResult(LongviewSubscriptionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
if r, err = coupleAPIErrors(req.SetResult(LongviewSubscriptionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
||||||
pages = r.Result().(*LongviewSubscriptionsPagedResponse).Pages
|
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 {
|
if r, err = coupleAPIErrors(req.SetResult(SSHKeysPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
||||||
response, ok := r.Result().(*SSHKeysPagedResponse)
|
response, ok := r.Result().(*SSHKeysPagedResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Response is not a *SSHKeysPagedResponse")
|
return fmt.Errorf("response is not a *SSHKeysPagedResponse")
|
||||||
}
|
}
|
||||||
pages = response.Pages
|
pages = response.Pages
|
||||||
results = response.Results
|
results = response.Results
|
||||||
|
@ -173,6 +191,18 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
|
||||||
results = r.Result().(*NotificationsPagedResponse).Results
|
results = r.Result().(*NotificationsPagedResponse).Results
|
||||||
v.appendData(r.Result().(*NotificationsPagedResponse))
|
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:
|
case *NodeBalancersPagedResponse:
|
||||||
if r, err = coupleAPIErrors(req.SetResult(NodeBalancersPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
if r, err = coupleAPIErrors(req.SetResult(NodeBalancersPagedResponse{}).Get(v.endpoint(c))); err == nil {
|
||||||
pages = r.Result().(*NodeBalancersPagedResponse).Pages
|
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
|
results = r.Result().(*UsersPagedResponse).Results
|
||||||
v.appendData(r.Result().(*UsersPagedResponse))
|
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 ProfileAppsPagedResponse:
|
||||||
case ProfileWhitelistPagedResponse:
|
case ProfileWhitelistPagedResponse:
|
||||||
case ManagedContactsPagedResponse:
|
case ManagedContactsPagedResponse:
|
||||||
|
@ -217,7 +263,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts == nil {
|
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 {
|
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -228,7 +274,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Page == 0 {
|
if opts.Page == 0 {
|
||||||
for page := 2; page <= pages; page = page + 1 {
|
for page := 2; page <= pages; page++ {
|
||||||
opts.Page = page
|
opts.Page = page
|
||||||
if err := c.listHelper(ctx, i, opts); err != nil {
|
if err := c.listHelper(ctx, i, opts); err != nil {
|
||||||
return err
|
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
|
// When opts (or opts.Page) is nil, all pages will be fetched and
|
||||||
// returned in a single (endpoint-specific)PagedResponse
|
// returned in a single (endpoint-specific)PagedResponse
|
||||||
// opts.results and opts.pages will be updated from the API response
|
// 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 {
|
func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw interface{}, opts *ListOptions) error {
|
||||||
req := c.R(ctx)
|
req := c.R(ctx)
|
||||||
if opts != nil && opts.Page > 0 {
|
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) {
|
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:
|
case *DomainRecordsPagedResponse:
|
||||||
if r, err = coupleAPIErrors(req.SetResult(DomainRecordsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
|
if r, err = coupleAPIErrors(req.SetResult(DomainRecordsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
|
||||||
response, ok := r.Result().(*DomainRecordsPagedResponse)
|
response, ok := r.Result().(*DomainRecordsPagedResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Response is not a *DomainRecordsPagedResponse")
|
return fmt.Errorf("response is not a *DomainRecordsPagedResponse")
|
||||||
}
|
}
|
||||||
pages = response.Pages
|
pages = response.Pages
|
||||||
results = response.Results
|
results = response.Results
|
||||||
|
@ -295,18 +336,30 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
|
||||||
results = r.Result().(*InstanceDisksPagedResponse).Results
|
results = r.Result().(*InstanceDisksPagedResponse).Results
|
||||||
v.appendData(r.Result().(*InstanceDisksPagedResponse))
|
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:
|
case *InstanceVolumesPagedResponse:
|
||||||
if r, err = coupleAPIErrors(req.SetResult(InstanceVolumesPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
|
if r, err = coupleAPIErrors(req.SetResult(InstanceVolumesPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
|
||||||
pages = r.Result().(*InstanceVolumesPagedResponse).Pages
|
pages = r.Result().(*InstanceVolumesPagedResponse).Pages
|
||||||
results = r.Result().(*InstanceVolumesPagedResponse).Results
|
results = r.Result().(*InstanceVolumesPagedResponse).Results
|
||||||
v.appendData(r.Result().(*InstanceVolumesPagedResponse))
|
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:
|
case *TaggedObjectsPagedResponse:
|
||||||
idStr := idRaw.(string)
|
idStr := idRaw.(string)
|
||||||
|
|
||||||
|
@ -342,7 +395,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts == nil {
|
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 {
|
if err := c.listHelperWithID(ctx, i, id, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -352,7 +405,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
|
||||||
opts.PageOptions = &PageOptions{}
|
opts.PageOptions = &PageOptions{}
|
||||||
}
|
}
|
||||||
if opts.Page == 0 {
|
if opts.Page == 0 {
|
||||||
for page := 2; page <= pages; page = page + 1 {
|
for page := 2; page <= pages; page++ {
|
||||||
opts.Page = page
|
opts.Page = page
|
||||||
if err := c.listHelperWithID(ctx, i, id, opts); err != nil {
|
if err := c.listHelperWithID(ctx, i, id, opts); err != nil {
|
||||||
return err
|
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
|
// 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 {
|
func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstID, secondID int, opts *ListOptions) error {
|
||||||
req := c.R(ctx)
|
req := c.R(ctx)
|
||||||
|
|
||||||
if opts != nil && opts.Page > 0 {
|
if opts != nil && opts.Page > 0 {
|
||||||
req.SetQueryParam("page", strconv.Itoa(opts.Page))
|
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
|
results = r.Result().(*NodeBalancerNodesPagedResponse).Results
|
||||||
v.appendData(r.Result().(*NodeBalancerNodesPagedResponse))
|
v.appendData(r.Result().(*NodeBalancerNodesPagedResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown listHelperWithTwoIDs interface{} %T used", i)
|
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 {
|
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 {
|
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -415,7 +468,7 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
|
||||||
opts.PageOptions = &PageOptions{}
|
opts.PageOptions = &PageOptions{}
|
||||||
}
|
}
|
||||||
if opts.Page == 0 {
|
if opts.Page == 0 {
|
||||||
for page := 2; page <= pages; page = page + 1 {
|
for page := 2; page <= pages; page++ {
|
||||||
opts.Page = page
|
opts.Page = page
|
||||||
if err := c.listHelperWithTwoIDs(ctx, i, firstID, secondID, opts); err != nil {
|
if err := c.listHelperWithTwoIDs(ctx, i, firstID, secondID, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (i Profile) GetUpdateOptions() (o ProfileUpdateOptions) {
|
||||||
return
|
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) {
|
func (c *Client) GetProfile(ctx context.Context) (*Profile, error) {
|
||||||
e, err := c.Profile.Endpoint()
|
e, err := c.Profile.Endpoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSHKey represents a SSHKey object
|
// SSHKey represents a SSHKey object
|
||||||
|
@ -12,7 +14,6 @@ type SSHKey struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
SSHKey string `json:"ssh_key"`
|
SSHKey string `json:"ssh_key"`
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
Created *time.Time `json:"-"`
|
Created *time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +28,26 @@ type SSHKeyUpdateOptions struct {
|
||||||
Label string `json:"label"`
|
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
|
// GetCreateOptions converts a SSHKey to SSHKeyCreateOptions for use in CreateSSHKey
|
||||||
func (i SSHKey) GetCreateOptions() (o SSHKeyCreateOptions) {
|
func (i SSHKey) GetCreateOptions() (o SSHKeyCreateOptions) {
|
||||||
o.Label = i.Label
|
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) {
|
func (c *Client) ListSSHKeys(ctx context.Context, opts *ListOptions) ([]SSHKey, error) {
|
||||||
response := SSHKeysPagedResponse{}
|
response := SSHKeysPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetSSHKey gets the sshkey with the provided ID
|
||||||
func (c *Client) GetSSHKey(ctx context.Context, id int) (*SSHKey, error) {
|
func (c *Client) GetSSHKey(ctx context.Context, id int) (*SSHKey, error) {
|
||||||
e, err := c.SSHKeys.Endpoint()
|
e, err := c.SSHKeys.Endpoint()
|
||||||
|
@ -90,7 +103,7 @@ func (c *Client) GetSSHKey(ctx context.Context, id int) (*SSHKey, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*SSHKey).fixDates(), nil
|
return r.Result().(*SSHKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSSHKey creates a SSHKey
|
// CreateSSHKey creates a SSHKey
|
||||||
|
@ -116,7 +129,7 @@ func (c *Client) CreateSSHKey(ctx context.Context, createOpts SSHKeyCreateOption
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*SSHKey).fixDates(), nil
|
return r.Result().(*SSHKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSSHKey updates the SSHKey with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*SSHKey).fixDates(), nil
|
return r.Result().(*SSHKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSSHKey deletes the SSHKey with the specified id
|
// 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))
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Token represents a Token object
|
// Token represents a Token object
|
||||||
|
@ -13,6 +15,7 @@ type Token struct {
|
||||||
ID int `json:"id"`
|
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.
|
// 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"`
|
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)
|
// This token's label. This is for display purposes only, but can be used to more easily track what you're using each token for. (1-100 Characters)
|
||||||
|
@ -23,11 +26,9 @@ type Token struct {
|
||||||
|
|
||||||
// The date and time this token was created.
|
// The date and time this token was created.
|
||||||
Created *time.Time `json:"-"`
|
Created *time.Time `json:"-"`
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
|
|
||||||
// When this token will expire. Personal Access Tokens cannot be renewed, so after this time the token will be completely unusable and a new token will need to be generated. Tokens may be created with "null" as their expiry and will never expire unless revoked.
|
// 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:"-"`
|
Expiry *time.Time `json:"-"`
|
||||||
ExpiryStr string `json:"expiry"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenCreateOptions fields are those accepted by CreateToken
|
// TokenCreateOptions fields are those accepted by CreateToken
|
||||||
|
@ -48,6 +49,28 @@ type TokenUpdateOptions struct {
|
||||||
Label string `json:"label"`
|
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
|
// GetCreateOptions converts a Token to TokenCreateOptions for use in CreateToken
|
||||||
func (i Token) GetCreateOptions() (o TokenCreateOptions) {
|
func (i Token) GetCreateOptions() (o TokenCreateOptions) {
|
||||||
o.Label = i.Label
|
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) {
|
func (c *Client) ListTokens(ctx context.Context, opts *ListOptions) ([]Token, error) {
|
||||||
response := TokensPagedResponse{}
|
response := TokensPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetToken gets the token with the provided ID
|
||||||
func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
|
func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
|
||||||
e, err := c.Tokens.Endpoint()
|
e, err := c.Tokens.Endpoint()
|
||||||
|
@ -113,7 +127,7 @@ func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Token).fixDates(), nil
|
return r.Result().(*Token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateToken creates a Token
|
// CreateToken creates a Token
|
||||||
|
@ -152,7 +166,7 @@ func (c *Client) CreateToken(ctx context.Context, createOpts TokenCreateOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Token).fixDates(), nil
|
return r.Result().(*Token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateToken updates the Token with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Token).fixDates(), nil
|
return r.Result().(*Token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteToken deletes the Token with the specified id
|
// 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) {
|
func (c *Client) ListRegions(ctx context.Context, opts *ListOptions) ([]Region, error) {
|
||||||
response := RegionsPagedResponse{}
|
response := RegionsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetRegion gets the template with the provided ID
|
||||||
func (c *Client) GetRegion(ctx context.Context, id string) (*Region, error) {
|
func (c *Client) GetRegion(ctx context.Context, id string) (*Region, error) {
|
||||||
e, err := c.Regions.Endpoint()
|
e, err := c.Regions.Endpoint()
|
||||||
|
@ -60,5 +53,5 @@ func (c *Client) GetRegion(ctx context.Context, id string) (*Region, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Region).fixDates(), nil
|
return r.Result().(*Region), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,86 +6,109 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"gopkg.in/resty.v1"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
stackscriptsName = "stackscripts"
|
accountName = "account"
|
||||||
|
accountSettingsName = "accountsettings"
|
||||||
|
domainRecordsName = "records"
|
||||||
|
domainsName = "domains"
|
||||||
|
eventsName = "events"
|
||||||
|
firewallsName = "firewalls"
|
||||||
imagesName = "images"
|
imagesName = "images"
|
||||||
instancesName = "instances"
|
|
||||||
instanceDisksName = "disks"
|
|
||||||
instanceConfigsName = "configs"
|
instanceConfigsName = "configs"
|
||||||
|
instanceDisksName = "disks"
|
||||||
instanceIPsName = "ips"
|
instanceIPsName = "ips"
|
||||||
instanceSnapshotsName = "snapshots"
|
instanceSnapshotsName = "snapshots"
|
||||||
|
instanceStatsName = "instancestats"
|
||||||
instanceVolumesName = "instancevolumes"
|
instanceVolumesName = "instancevolumes"
|
||||||
|
instancesName = "instances"
|
||||||
|
invoiceItemsName = "invoiceitems"
|
||||||
|
invoicesName = "invoices"
|
||||||
ipaddressesName = "ipaddresses"
|
ipaddressesName = "ipaddresses"
|
||||||
ipv6poolsName = "ipv6pools"
|
ipv6poolsName = "ipv6pools"
|
||||||
ipv6rangesName = "ipv6ranges"
|
ipv6rangesName = "ipv6ranges"
|
||||||
regionsName = "regions"
|
|
||||||
volumesName = "volumes"
|
|
||||||
kernelsName = "kernels"
|
kernelsName = "kernels"
|
||||||
typesName = "types"
|
lkeClustersName = "lkeclusters"
|
||||||
domainsName = "domains"
|
lkeClusterPoolsName = "lkeclusterpools"
|
||||||
domainRecordsName = "records"
|
lkeVersionsName = "lkeversions"
|
||||||
longviewName = "longview"
|
longviewName = "longview"
|
||||||
longviewclientsName = "longviewclients"
|
longviewclientsName = "longviewclients"
|
||||||
longviewsubscriptionsName = "longviewsubscriptions"
|
longviewsubscriptionsName = "longviewsubscriptions"
|
||||||
nodebalancersName = "nodebalancers"
|
managedName = "managed"
|
||||||
nodebalancerconfigsName = "nodebalancerconfigs"
|
nodebalancerconfigsName = "nodebalancerconfigs"
|
||||||
nodebalancernodesName = "nodebalancernodes"
|
nodebalancernodesName = "nodebalancernodes"
|
||||||
|
nodebalancerStatsName = "nodebalancerstats"
|
||||||
|
nodebalancersName = "nodebalancers"
|
||||||
notificationsName = "notifications"
|
notificationsName = "notifications"
|
||||||
|
oauthClientsName = "oauthClients"
|
||||||
|
objectStorageBucketsName = "objectstoragebuckets"
|
||||||
|
objectStorageClustersName = "objectstorageclusters"
|
||||||
|
objectStorageKeysName = "objectstoragekeys"
|
||||||
|
paymentsName = "payments"
|
||||||
|
profileName = "profile"
|
||||||
|
regionsName = "regions"
|
||||||
sshkeysName = "sshkeys"
|
sshkeysName = "sshkeys"
|
||||||
|
stackscriptsName = "stackscripts"
|
||||||
|
tagsName = "tags"
|
||||||
ticketsName = "tickets"
|
ticketsName = "tickets"
|
||||||
tokensName = "tokens"
|
tokensName = "tokens"
|
||||||
accountName = "account"
|
typesName = "types"
|
||||||
eventsName = "events"
|
|
||||||
invoicesName = "invoices"
|
|
||||||
invoiceItemsName = "invoiceitems"
|
|
||||||
profileName = "profile"
|
|
||||||
managedName = "managed"
|
|
||||||
tagsName = "tags"
|
|
||||||
usersName = "users"
|
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"
|
imagesEndpoint = "images"
|
||||||
instancesEndpoint = "linode/instances"
|
|
||||||
instanceConfigsEndpoint = "linode/instances/{{ .ID }}/configs"
|
instanceConfigsEndpoint = "linode/instances/{{ .ID }}/configs"
|
||||||
instanceDisksEndpoint = "linode/instances/{{ .ID }}/disks"
|
instanceDisksEndpoint = "linode/instances/{{ .ID }}/disks"
|
||||||
instanceSnapshotsEndpoint = "linode/instances/{{ .ID }}/backups"
|
|
||||||
instanceIPsEndpoint = "linode/instances/{{ .ID }}/ips"
|
instanceIPsEndpoint = "linode/instances/{{ .ID }}/ips"
|
||||||
|
instanceSnapshotsEndpoint = "linode/instances/{{ .ID }}/backups"
|
||||||
|
instanceStatsEndpoint = "linode/instances/{{ .ID }}/stats"
|
||||||
instanceVolumesEndpoint = "linode/instances/{{ .ID }}/volumes"
|
instanceVolumesEndpoint = "linode/instances/{{ .ID }}/volumes"
|
||||||
|
instancesEndpoint = "linode/instances"
|
||||||
|
invoiceItemsEndpoint = "account/invoices/{{ .ID }}/items"
|
||||||
|
invoicesEndpoint = "account/invoices"
|
||||||
ipaddressesEndpoint = "networking/ips"
|
ipaddressesEndpoint = "networking/ips"
|
||||||
ipv6poolsEndpoint = "networking/ipv6/pools"
|
ipv6poolsEndpoint = "networking/ipv6/pools"
|
||||||
ipv6rangesEndpoint = "networking/ipv6/ranges"
|
ipv6rangesEndpoint = "networking/ipv6/ranges"
|
||||||
regionsEndpoint = "regions"
|
|
||||||
volumesEndpoint = "volumes"
|
|
||||||
kernelsEndpoint = "linode/kernels"
|
kernelsEndpoint = "linode/kernels"
|
||||||
typesEndpoint = "linode/types"
|
lkeClustersEndpoint = "lke/clusters"
|
||||||
domainsEndpoint = "domains"
|
lkeClusterPoolsEndpoint = "lke/clusters/{{ .ID }}/pools"
|
||||||
domainRecordsEndpoint = "domains/{{ .ID }}/records"
|
lkeVersionsEndpoint = "lke/versions"
|
||||||
longviewEndpoint = "longview"
|
longviewEndpoint = "longview"
|
||||||
longviewclientsEndpoint = "longview/clients"
|
longviewclientsEndpoint = "longview/clients"
|
||||||
longviewsubscriptionsEndpoint = "longview/subscriptions"
|
longviewsubscriptionsEndpoint = "longview/subscriptions"
|
||||||
nodebalancersEndpoint = "nodebalancers"
|
managedEndpoint = "managed"
|
||||||
// @TODO we can't use these nodebalancer endpoints unless we include these templated fields
|
// @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)
|
// 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 ..
|
// Parent IDs would be immutable for updates and are ignored in create requests ..
|
||||||
// Should we include these fields in CreateOpts and UpdateOpts?
|
// Should we include these fields in CreateOpts and UpdateOpts?
|
||||||
nodebalancerconfigsEndpoint = "nodebalancers/{{ .ID }}/configs"
|
nodebalancerconfigsEndpoint = "nodebalancers/{{ .ID }}/configs"
|
||||||
nodebalancernodesEndpoint = "nodebalancers/{{ .ID }}/configs/{{ .SecondID }}/nodes"
|
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"
|
sshkeysEndpoint = "profile/sshkeys"
|
||||||
|
stackscriptsEndpoint = "linode/stackscripts"
|
||||||
|
tagsEndpoint = "tags"
|
||||||
ticketsEndpoint = "support/tickets"
|
ticketsEndpoint = "support/tickets"
|
||||||
tokensEndpoint = "profile/tokens"
|
tokensEndpoint = "profile/tokens"
|
||||||
accountEndpoint = "account"
|
typesEndpoint = "linode/types"
|
||||||
eventsEndpoint = "account/events"
|
|
||||||
invoicesEndpoint = "account/invoices"
|
|
||||||
invoiceItemsEndpoint = "account/invoices/{{ .ID }}/items"
|
|
||||||
profileEndpoint = "profile"
|
|
||||||
managedEndpoint = "managed"
|
|
||||||
tagsEndpoint = "tags"
|
|
||||||
usersEndpoint = "account/users"
|
usersEndpoint = "account/users"
|
||||||
notificationsEndpoint = "account/notifications"
|
volumesEndpoint = "volumes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource represents a linode API resource
|
// Resource represents a linode API resource
|
||||||
|
@ -125,16 +148,19 @@ func (r Resource) render(data ...interface{}) (string, error) {
|
||||||
buf := bytes.NewBufferString(out)
|
buf := bytes.NewBufferString(out)
|
||||||
|
|
||||||
var substitutions interface{}
|
var substitutions interface{}
|
||||||
if len(data) == 1 {
|
|
||||||
|
switch len(data) {
|
||||||
|
case 1:
|
||||||
substitutions = struct{ ID interface{} }{data[0]}
|
substitutions = struct{ ID interface{} }{data[0]}
|
||||||
} else if len(data) == 2 {
|
case 2:
|
||||||
substitutions = struct {
|
substitutions = struct {
|
||||||
ID interface{}
|
ID interface{}
|
||||||
SecondID interface{}
|
SecondID interface{}
|
||||||
}{data[0], data[1]}
|
}{data[0], data[1]}
|
||||||
} else {
|
default:
|
||||||
return "", NewError("Too many arguments to render template (expected 1 or 2)")
|
return "", NewError("Too many arguments to render template (expected 1 or 2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.endpointTemplate.Execute(buf, substitutions); err != nil {
|
if err := r.endpointTemplate.Execute(buf, substitutions); err != nil {
|
||||||
return "", NewError(err)
|
return "", NewError(err)
|
||||||
}
|
}
|
||||||
|
@ -147,6 +173,7 @@ func (r Resource) endpointWithID(id ...int) (string, error) {
|
||||||
return r.endpoint, nil
|
return r.endpoint, nil
|
||||||
}
|
}
|
||||||
data := make([]interface{}, len(id))
|
data := make([]interface{}, len(id))
|
||||||
|
|
||||||
for i, v := range id {
|
for i, v := range id {
|
||||||
data[i] = v
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stackscript represents a Linode StackScript
|
// Stackscript represents a Linode StackScript
|
||||||
type Stackscript struct {
|
type Stackscript struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
Ordinal int `json:"ordinal"`
|
||||||
|
LogoURL string `json:"logo_url"`
|
||||||
Images []string `json:"images"`
|
Images []string `json:"images"`
|
||||||
DeploymentsTotal int `json:"deployments_total"`
|
DeploymentsTotal int `json:"deployments_total"`
|
||||||
DeploymentsActive int `json:"deployments_active"`
|
DeploymentsActive int `json:"deployments_active"`
|
||||||
|
@ -62,6 +63,28 @@ type StackscriptCreateOptions struct {
|
||||||
// StackscriptUpdateOptions fields are those accepted by UpdateStackscript
|
// StackscriptUpdateOptions fields are those accepted by UpdateStackscript
|
||||||
type StackscriptUpdateOptions StackscriptCreateOptions
|
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
|
// GetCreateOptions converts a Stackscript to StackscriptCreateOptions for use in CreateStackscript
|
||||||
func (i Stackscript) GetCreateOptions() StackscriptCreateOptions {
|
func (i Stackscript) GetCreateOptions() StackscriptCreateOptions {
|
||||||
return StackscriptCreateOptions{
|
return StackscriptCreateOptions{
|
||||||
|
@ -110,22 +133,13 @@ func (resp *StackscriptsPagedResponse) appendData(r *StackscriptsPagedResponse)
|
||||||
func (c *Client) ListStackscripts(ctx context.Context, opts *ListOptions) ([]Stackscript, error) {
|
func (c *Client) ListStackscripts(ctx context.Context, opts *ListOptions) ([]Stackscript, error) {
|
||||||
response := StackscriptsPagedResponse{}
|
response := StackscriptsPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetStackscript gets the Stackscript with the provided ID
|
||||||
func (c *Client) GetStackscript(ctx context.Context, id int) (*Stackscript, error) {
|
func (c *Client) GetStackscript(ctx context.Context, id int) (*Stackscript, error) {
|
||||||
e, err := c.StackScripts.Endpoint()
|
e, err := c.StackScripts.Endpoint()
|
||||||
|
@ -139,7 +153,7 @@ func (c *Client) GetStackscript(ctx context.Context, id int) (*Stackscript, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Stackscript).fixDates(), nil
|
return r.Result().(*Stackscript), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStackscript creates a StackScript
|
// CreateStackscript creates a StackScript
|
||||||
|
@ -165,7 +179,7 @@ func (c *Client) CreateStackscript(ctx context.Context, createOpts StackscriptCr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Stackscript).fixDates(), nil
|
return r.Result().(*Stackscript), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStackscript updates the StackScript with the specified id
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Stackscript).fixDates(), nil
|
return r.Result().(*Stackscript), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteStackscript deletes the StackScript with the specified id
|
// DeleteStackscript deletes the StackScript with the specified id
|
||||||
|
|
|
@ -22,6 +22,7 @@ type TaggedObject struct {
|
||||||
// SortedObjects currently only includes Instances
|
// SortedObjects currently only includes Instances
|
||||||
type SortedObjects struct {
|
type SortedObjects struct {
|
||||||
Instances []Instance
|
Instances []Instance
|
||||||
|
LKEClusters []LKECluster
|
||||||
Domains []Domain
|
Domains []Domain
|
||||||
Volumes []Volume
|
Volumes []Volume
|
||||||
NodeBalancers []NodeBalancer
|
NodeBalancers []NodeBalancer
|
||||||
|
@ -37,6 +38,8 @@ type TaggedObjectList []TaggedObject
|
||||||
type TagCreateOptions struct {
|
type TagCreateOptions struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Linodes []int `json:"linodes,omitempty"`
|
Linodes []int `json:"linodes,omitempty"`
|
||||||
|
// @TODO is this implemented?
|
||||||
|
LKEClusters []int `json:"lke_clusters,omitempty"`
|
||||||
Domains []int `json:"domains,omitempty"`
|
Domains []int `json:"domains,omitempty"`
|
||||||
Volumes []int `json:"volumes,omitempty"`
|
Volumes []int `json:"volumes,omitempty"`
|
||||||
NodeBalancers []int `json:"nodebalancers,omitempty"`
|
NodeBalancers []int `json:"nodebalancers,omitempty"`
|
||||||
|
@ -108,6 +111,12 @@ func (i *TaggedObject) fixData() (*TaggedObject, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i.Data = obj
|
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":
|
case "nodebalancer":
|
||||||
obj := NodeBalancer{}
|
obj := NodeBalancer{}
|
||||||
if err := json.Unmarshal(i.RawData, &obj); err != nil {
|
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) {
|
func (c *Client) ListTaggedObjects(ctx context.Context, label string, opts *ListOptions) (TaggedObjectList, error) {
|
||||||
response := TaggedObjectsPagedResponse{}
|
response := TaggedObjectsPagedResponse{}
|
||||||
err := c.listHelperWithID(ctx, &response, label, opts)
|
err := c.listHelperWithID(ctx, &response, label, opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range response.Data {
|
for i := range response.Data {
|
||||||
if _, err := response.Data[i].fixData(); err != nil {
|
if _, err := response.Data[i].fixData(); err != nil {
|
||||||
return nil, err
|
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
|
// SortedObjects converts a list of TaggedObjects into a Sorted Objects struct, for easier access
|
||||||
func (t TaggedObjectList) SortedObjects() (SortedObjects, error) {
|
func (t TaggedObjectList) SortedObjects() (SortedObjects, error) {
|
||||||
so := SortedObjects{}
|
so := SortedObjects{}
|
||||||
|
|
||||||
for _, o := range t {
|
for _, o := range t {
|
||||||
switch o.Type {
|
switch o.Type {
|
||||||
case "linode":
|
case "linode":
|
||||||
if instance, ok := o.Data.(Instance); ok {
|
if instance, ok := o.Data.(Instance); ok {
|
||||||
so.Instances = append(so.Instances, instance)
|
so.Instances = append(so.Instances, instance)
|
||||||
} else {
|
} 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":
|
case "domain":
|
||||||
if domain, ok := o.Data.(Domain); ok {
|
if domain, ok := o.Data.(Domain); ok {
|
||||||
so.Domains = append(so.Domains, domain)
|
so.Domains = append(so.Domains, domain)
|
||||||
} else {
|
} 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":
|
case "volume":
|
||||||
if volume, ok := o.Data.(Volume); ok {
|
if volume, ok := o.Data.(Volume); ok {
|
||||||
so.Volumes = append(so.Volumes, volume)
|
so.Volumes = append(so.Volumes, volume)
|
||||||
} else {
|
} 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":
|
case "nodebalancer":
|
||||||
if nodebalancer, ok := o.Data.(NodeBalancer); ok {
|
if nodebalancer, ok := o.Data.(NodeBalancer); ok {
|
||||||
so.NodeBalancers = append(so.NodeBalancers, nodebalancer)
|
so.NodeBalancers = append(so.NodeBalancers, nodebalancer)
|
||||||
} else {
|
} 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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/linode/linodego/internal/parseabletime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VolumeStatus indicates the status of the Volume
|
// VolumeStatus indicates the status of the Volume
|
||||||
|
@ -26,9 +28,6 @@ const (
|
||||||
|
|
||||||
// Volume represents a linode volume object
|
// Volume represents a linode volume object
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
CreatedStr string `json:"created"`
|
|
||||||
UpdatedStr string `json:"updated"`
|
|
||||||
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Status VolumeStatus `json:"status"`
|
Status VolumeStatus `json:"status"`
|
||||||
|
@ -51,6 +50,7 @@ type VolumeCreateOptions struct {
|
||||||
Size int `json:"size,omitempty"`
|
Size int `json:"size,omitempty"`
|
||||||
// An array of tags applied to this object. Tags are for organizational purposes only.
|
// 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
|
// VolumeUpdateOptions fields are those accepted by UpdateVolume
|
||||||
|
@ -63,6 +63,7 @@ type VolumeUpdateOptions struct {
|
||||||
type VolumeAttachOptions struct {
|
type VolumeAttachOptions struct {
|
||||||
LinodeID int `json:"linode_id"`
|
LinodeID int `json:"linode_id"`
|
||||||
ConfigID int `json:"config_id,omitempty"`
|
ConfigID int `json:"config_id,omitempty"`
|
||||||
|
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VolumesPagedResponse represents a linode API response for listing of volumes
|
// VolumesPagedResponse represents a linode API response for listing of volumes
|
||||||
|
@ -71,6 +72,28 @@ type VolumesPagedResponse struct {
|
||||||
Data []Volume `json:"data"`
|
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
|
// GetUpdateOptions converts a Volume to VolumeUpdateOptions for use in UpdateVolume
|
||||||
func (v Volume) GetUpdateOptions() (updateOpts VolumeUpdateOptions) {
|
func (v Volume) GetUpdateOptions() (updateOpts VolumeUpdateOptions) {
|
||||||
updateOpts.Label = v.Label
|
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) {
|
func (c *Client) ListVolumes(ctx context.Context, opts *ListOptions) ([]Volume, error) {
|
||||||
response := VolumesPagedResponse{}
|
response := VolumesPagedResponse{}
|
||||||
err := c.listHelper(ctx, &response, opts)
|
err := c.listHelper(ctx, &response, opts)
|
||||||
for i := range response.Data {
|
|
||||||
response.Data[i].fixDates()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Data, nil
|
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
|
// GetVolume gets the template with the provided ID
|
||||||
func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) {
|
func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) {
|
||||||
e, err := c.Volumes.Endpoint()
|
e, err := c.Volumes.Endpoint()
|
||||||
|
@ -139,7 +149,7 @@ func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Volume).fixDates(), nil
|
return r.Result().(*Volume), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachVolume attaches a volume to a Linode instance
|
// 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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Result().(*Volume).fixDates(), nil
|
return resp.Result().(*Volume), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVolume creates a Linode Volume
|
// CreateVolume creates a Linode Volume
|
||||||
|
@ -192,7 +202,7 @@ func (c *Client) CreateVolume(ctx context.Context, createOpts VolumeCreateOption
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Result().(*Volume).fixDates(), nil
|
return resp.Result().(*Volume), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameVolume renames the label of a Linode volume
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.Result().(*Volume).fixDates(), nil
|
return r.Result().(*Volume), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloneVolume clones a Linode volume
|
// 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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Result().(*Volume).fixDates(), nil
|
return resp.Result().(*Volume), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetachVolume detaches a Linode volume
|
// 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)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -44,6 +45,7 @@ func (client Client) WaitForInstanceDiskStatus(ctx context.Context, instanceID i
|
||||||
|
|
||||||
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -53,16 +55,18 @@ func (client Client) WaitForInstanceDiskStatus(ctx context.Context, instanceID i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, disk := range disks {
|
for _, disk := range disks {
|
||||||
|
disk := disk
|
||||||
if disk.ID == diskID {
|
if disk.ID == diskID {
|
||||||
complete := (disk.Status == status)
|
complete := (disk.Status == status)
|
||||||
if complete {
|
if complete {
|
||||||
return &disk, nil
|
return &disk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, fmt.Errorf("Error waiting for Instance %d Disk %d status %s: %s", instanceID, diskID, status, ctx.Err())
|
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)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -103,6 +108,7 @@ func (client Client) WaitForSnapshotStatus(ctx context.Context, instanceID int,
|
||||||
|
|
||||||
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -131,6 +137,7 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
|
||||||
|
|
||||||
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -139,34 +146,54 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
|
||||||
return volume, err
|
return volume, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if linodeID == nil && volume.LinodeID == nil {
|
switch {
|
||||||
|
case linodeID == nil && volume.LinodeID == nil:
|
||||||
return volume, nil
|
return volume, nil
|
||||||
} else if linodeID == nil || volume.LinodeID == nil {
|
case linodeID == nil || volume.LinodeID == nil:
|
||||||
// continue waiting
|
// continue waiting
|
||||||
} else if *volume.LinodeID == *linodeID {
|
case *volume.LinodeID == *linodeID:
|
||||||
return volume, nil
|
return volume, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, fmt.Errorf("Error waiting for Volume %d to have Instance %v: %s", volumeID, linodeID, ctx.Err())
|
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
|
// WaitForEventFinished waits for an entity action to reach the 'finished' state
|
||||||
// before returning. It will timeout with an error after timeoutSeconds.
|
// 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.
|
// 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) {
|
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))
|
titledEntityType := strings.Title(string(entityType))
|
||||||
filter, _ := json.Marshal(map[string]interface{}{
|
filterStruct := 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,
|
|
||||||
//},
|
|
||||||
|
|
||||||
// Nor is action
|
// Nor is action
|
||||||
//"action": 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
|
// 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,
|
"seen": false,
|
||||||
|
|
||||||
// Float the latest events to page 1
|
// Float the latest events to page 1
|
||||||
"+order_by": "created",
|
"+order_by": "created",
|
||||||
"+order": "desc",
|
"+order": "desc",
|
||||||
})
|
}
|
||||||
|
|
||||||
// Optimistically restrict results to page 1. We should remove this when more
|
// Optimistically restrict results to page 1. We should remove this when more
|
||||||
// precise filtering options exist.
|
// 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)
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -197,10 +241,27 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
|
||||||
|
|
||||||
|
// avoid repeating log messages
|
||||||
|
nextLog := ""
|
||||||
|
lastLog := ""
|
||||||
|
lastEventID := 0
|
||||||
|
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
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)
|
events, err := client.ListEvents(ctx, listOptions)
|
||||||
if err != nil {
|
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
|
// If there are events for this instance + action, inspect them
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
|
event := event
|
||||||
|
|
||||||
if event.Action != action {
|
if event.Action != action {
|
||||||
// log.Println("action mismatch", event.Action, action)
|
// log.Println("action mismatch", event.Action, action)
|
||||||
continue
|
continue
|
||||||
|
@ -220,21 +283,21 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
|
||||||
|
|
||||||
var entID string
|
var entID string
|
||||||
|
|
||||||
switch event.Entity.ID.(type) {
|
switch id := event.Entity.ID.(type) {
|
||||||
case float64, float32:
|
case float64, float32:
|
||||||
entID = fmt.Sprintf("%.f", event.Entity.ID)
|
entID = fmt.Sprintf("%.f", id)
|
||||||
case int:
|
case int:
|
||||||
entID = strconv.Itoa(event.Entity.ID.(int))
|
entID = strconv.Itoa(id)
|
||||||
default:
|
default:
|
||||||
entID = fmt.Sprintf("%v", event.Entity.ID)
|
entID = fmt.Sprintf("%v", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var findID string
|
var findID string
|
||||||
switch id.(type) {
|
switch id := id.(type) {
|
||||||
case float64, float32:
|
case float64, float32:
|
||||||
findID = fmt.Sprintf("%.f", id)
|
findID = fmt.Sprintf("%.f", id)
|
||||||
case int:
|
case int:
|
||||||
findID = strconv.Itoa(id.(int))
|
findID = strconv.Itoa(id)
|
||||||
default:
|
default:
|
||||||
findID = fmt.Sprintf("%v", id)
|
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
|
// @TODO(displague) This event.Created check shouldn't be needed, but it appears
|
||||||
// that the ListEvents method is not populating it correctly
|
// that the ListEvents method is not populating it correctly
|
||||||
if event.Created == nil {
|
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) {
|
} else if *event.Created != minStart && !event.Created.After(minStart) {
|
||||||
// Not the event we were looking for
|
// Not the event we were looking for
|
||||||
// log.Println(event.Created, "is not >=", minStart)
|
// log.Println(event.Created, "is not >=", minStart)
|
||||||
continue
|
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)
|
return &event, fmt.Errorf("%s %v action %s failed", titledEntityType, id, action)
|
||||||
} else if event.Status == EventScheduled {
|
case EventFinished:
|
||||||
log.Printf("[INFO] %s %v action %s is scheduled", titledEntityType, id, action)
|
|
||||||
} else if event.Status == EventFinished {
|
|
||||||
log.Printf("[INFO] %s %v action %s is finished", titledEntityType, id, action)
|
log.Printf("[INFO] %s %v action %s is finished", titledEntityType, id, action)
|
||||||
return &event, nil
|
return &event, nil
|
||||||
}
|
}
|
||||||
// TODO(displague) can we bump the ticker to TimeRemaining/2 (>=1) when non-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():
|
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())
|
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.
|
// dialCall is an in-flight Transport dial call to a host.
|
||||||
type dialCall struct {
|
type dialCall struct {
|
||||||
|
_ incomparable
|
||||||
p *clientConnPool
|
p *clientConnPool
|
||||||
done chan struct{} // closed when done
|
done chan struct{} // closed when done
|
||||||
res *ClientConn // valid after done is closed
|
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 {
|
type addConnCall struct {
|
||||||
|
_ incomparable
|
||||||
p *clientConnPool
|
p *clientConnPool
|
||||||
done chan struct{} // closed when done
|
done chan struct{} // closed when done
|
||||||
err error
|
err error
|
||||||
|
@ -200,12 +202,6 @@ func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||||
close(c.done)
|
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
|
// p.mu must be held
|
||||||
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||||
for _, v := range p.conns[key] {
|
for _, v := range p.conns[key] {
|
||||||
|
|
|
@ -8,6 +8,8 @@ package http2
|
||||||
|
|
||||||
// flow is the flow control window's size.
|
// flow is the flow control window's size.
|
||||||
type flow struct {
|
type flow struct {
|
||||||
|
_ incomparable
|
||||||
|
|
||||||
// n is the number of DATA bytes we're allowed to send.
|
// n is the number of DATA bytes we're allowed to send.
|
||||||
// A flow is kept both on a conn and a per-stream.
|
// A flow is kept both on a conn and a per-stream.
|
||||||
n int32
|
n int32
|
||||||
|
|
|
@ -105,7 +105,14 @@ func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error {
|
||||||
return nil
|
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 {
|
type node struct {
|
||||||
|
_ incomparable
|
||||||
|
|
||||||
// children is non-nil for internal nodes
|
// children is non-nil for internal nodes
|
||||||
children *[256]*node
|
children *[256]*node
|
||||||
|
|
||||||
|
|
|
@ -241,6 +241,7 @@ func (cw closeWaiter) Wait() {
|
||||||
// Its buffered writer is lazily allocated as needed, to minimize
|
// Its buffered writer is lazily allocated as needed, to minimize
|
||||||
// idle memory usage with many connections.
|
// idle memory usage with many connections.
|
||||||
type bufferedWriter struct {
|
type bufferedWriter struct {
|
||||||
|
_ incomparable
|
||||||
w io.Writer // immutable
|
w io.Writer // immutable
|
||||||
bw *bufio.Writer // non-nil when data is buffered
|
bw *bufio.Writer // non-nil when data is buffered
|
||||||
}
|
}
|
||||||
|
@ -313,6 +314,7 @@ func bodyAllowedForStatus(status int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpError struct {
|
type httpError struct {
|
||||||
|
_ incomparable
|
||||||
msg string
|
msg string
|
||||||
timeout bool
|
timeout bool
|
||||||
}
|
}
|
||||||
|
@ -376,3 +378,8 @@ func (s *sorter) SortStrings(ss []string) {
|
||||||
func validPseudoPath(v string) bool {
|
func validPseudoPath(v string) bool {
|
||||||
return (len(v) > 0 && v[0] == '/') || v == "*"
|
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.
|
// frameWriteResult is the message passed from writeFrameAsync to the serve goroutine.
|
||||||
type frameWriteResult struct {
|
type frameWriteResult struct {
|
||||||
|
_ incomparable
|
||||||
wr FrameWriteRequest // what was written (or attempted)
|
wr FrameWriteRequest // what was written (or attempted)
|
||||||
err error // result of the writeFrame call
|
err error // result of the writeFrame call
|
||||||
}
|
}
|
||||||
|
@ -771,7 +772,7 @@ type frameWriteResult struct {
|
||||||
// serverConn.
|
// serverConn.
|
||||||
func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest) {
|
func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest) {
|
||||||
err := wr.write.writeFrame(sc)
|
err := wr.write.writeFrame(sc)
|
||||||
sc.wroteFrameCh <- frameWriteResult{wr, err}
|
sc.wroteFrameCh <- frameWriteResult{wr: wr, err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *serverConn) closeAllStreamsOnConnClose() {
|
func (sc *serverConn) closeAllStreamsOnConnClose() {
|
||||||
|
@ -1161,7 +1162,7 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) {
|
||||||
if wr.write.staysWithinBuffer(sc.bw.Available()) {
|
if wr.write.staysWithinBuffer(sc.bw.Available()) {
|
||||||
sc.writingFrameAsync = false
|
sc.writingFrameAsync = false
|
||||||
err := wr.write.writeFrame(sc)
|
err := wr.write.writeFrame(sc)
|
||||||
sc.wroteFrame(frameWriteResult{wr, err})
|
sc.wroteFrame(frameWriteResult{wr: wr, err: err})
|
||||||
} else {
|
} else {
|
||||||
sc.writingFrameAsync = true
|
sc.writingFrameAsync = true
|
||||||
go sc.writeFrameAsync(wr)
|
go sc.writeFrameAsync(wr)
|
||||||
|
@ -2057,7 +2058,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
|
||||||
var trailer http.Header
|
var trailer http.Header
|
||||||
for _, v := range rp.header["Trailer"] {
|
for _, v := range rp.header["Trailer"] {
|
||||||
for _, key := range strings.Split(v, ",") {
|
for _, key := range strings.Split(v, ",") {
|
||||||
key = http.CanonicalHeaderKey(strings.TrimSpace(key))
|
key = http.CanonicalHeaderKey(textproto.TrimString(key))
|
||||||
switch key {
|
switch key {
|
||||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||||
// Bogus. (copy of http1 rules)
|
// 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.
|
// requestBody is the Handler's Request.Body type.
|
||||||
// Read and Close may be called concurrently.
|
// Read and Close may be called concurrently.
|
||||||
type requestBody struct {
|
type requestBody struct {
|
||||||
|
_ incomparable
|
||||||
stream *stream
|
stream *stream
|
||||||
conn *serverConn
|
conn *serverConn
|
||||||
closed bool // for use by Close only
|
closed bool // for use by Close only
|
||||||
|
|
|
@ -108,6 +108,19 @@ type Transport struct {
|
||||||
// waiting for their turn.
|
// waiting for their turn.
|
||||||
StrictMaxConcurrentStreams bool
|
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
|
// t1, if non-nil, is the standard library Transport using
|
||||||
// this transport. Its settings are used (but not its
|
// this transport. Its settings are used (but not its
|
||||||
// RoundTrip method, etc).
|
// RoundTrip method, etc).
|
||||||
|
@ -131,6 +144,14 @@ func (t *Transport) disableCompression() bool {
|
||||||
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
|
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.
|
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
|
||||||
// It returns an error if t1 has already been HTTP/2-enabled.
|
// It returns an error if t1 has already been HTTP/2-enabled.
|
||||||
func ConfigureTransport(t1 *http.Transport) error {
|
func ConfigureTransport(t1 *http.Transport) error {
|
||||||
|
@ -675,6 +696,20 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
||||||
return cc, nil
|
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) {
|
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
|
@ -846,14 +881,12 @@ func (cc *ClientConn) sendGoAway() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the client connection immediately.
|
// closes the client connection immediately. In-flight requests are interrupted.
|
||||||
//
|
// err is sent to streams.
|
||||||
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
|
func (cc *ClientConn) closeForError(err error) error {
|
||||||
func (cc *ClientConn) Close() error {
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.cond.Broadcast()
|
defer cc.cond.Broadcast()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
err := errors.New("http2: client connection force closed via ClientConn.Close")
|
|
||||||
for id, cs := range cc.streams {
|
for id, cs := range cc.streams {
|
||||||
select {
|
select {
|
||||||
case cs.resc <- resAndError{err: err}:
|
case cs.resc <- resAndError{err: err}:
|
||||||
|
@ -866,6 +899,20 @@ func (cc *ClientConn) Close() error {
|
||||||
return cc.tconn.Close()
|
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
|
const maxAllocFrameSize = 512 << 10
|
||||||
|
|
||||||
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
|
// 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)
|
k = http.CanonicalHeaderKey(k)
|
||||||
switch k {
|
switch k {
|
||||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||||
return "", &badStringError{"invalid Trailer key", k}
|
return "", fmt.Errorf("invalid Trailer key %q", k)
|
||||||
}
|
}
|
||||||
keys = append(keys, 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.
|
// requires cc.mu be held.
|
||||||
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
|
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
|
||||||
cc.hbuf.Reset()
|
cc.hbuf.Reset()
|
||||||
|
@ -1616,6 +1656,7 @@ func (cc *ClientConn) writeHeader(name, value string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type resAndError struct {
|
type resAndError struct {
|
||||||
|
_ incomparable
|
||||||
res *http.Response
|
res *http.Response
|
||||||
err error
|
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.
|
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
|
||||||
type clientConnReadLoop struct {
|
type clientConnReadLoop struct {
|
||||||
|
_ incomparable
|
||||||
cc *ClientConn
|
cc *ClientConn
|
||||||
closeWhenIdle bool
|
closeWhenIdle bool
|
||||||
}
|
}
|
||||||
|
@ -1742,8 +1784,17 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
|
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
|
||||||
gotReply := false // ever saw a HEADERS reply
|
gotReply := false // ever saw a HEADERS reply
|
||||||
gotSettings := false
|
gotSettings := false
|
||||||
|
readIdleTimeout := cc.t.ReadIdleTimeout
|
||||||
|
var t *time.Timer
|
||||||
|
if readIdleTimeout != 0 {
|
||||||
|
t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
|
||||||
|
defer t.Stop()
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
f, err := cc.fr.ReadFrame()
|
f, err := cc.fr.ReadFrame()
|
||||||
|
if t != nil {
|
||||||
|
t.Reset(readIdleTimeout)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
|
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
|
// gzipReader wraps a response body so it can lazily
|
||||||
// call gzip.NewReader on the first call to Read
|
// call gzip.NewReader on the first call to Read
|
||||||
type gzipReader struct {
|
type gzipReader struct {
|
||||||
|
_ incomparable
|
||||||
body io.ReadCloser // underlying Response.Body
|
body io.ReadCloser // underlying Response.Body
|
||||||
zr *gzip.Reader // lazily-initialized gzip reader
|
zr *gzip.Reader // lazily-initialized gzip reader
|
||||||
zerr error // sticky error
|
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 v1.2.4
|
||||||
github.com/go-ole/go-ole
|
github.com/go-ole/go-ole
|
||||||
github.com/go-ole/go-ole/oleutil
|
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 v0.2.3
|
||||||
github.com/gobwas/glob
|
github.com/gobwas/glob
|
||||||
github.com/gobwas/glob/compiler
|
github.com/gobwas/glob/compiler
|
||||||
|
@ -435,8 +437,10 @@ github.com/klauspost/pgzip
|
||||||
github.com/konsorten/go-windows-terminal-sequences
|
github.com/konsorten/go-windows-terminal-sequences
|
||||||
# github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169
|
# github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169
|
||||||
github.com/kr/fs
|
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
|
||||||
|
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 v0.0.0-20161014135628-ee4f0065d00c
|
||||||
github.com/masterzen/azure-sdk-for-go/core/http
|
github.com/masterzen/azure-sdk-for-go/core/http
|
||||||
github.com/masterzen/azure-sdk-for-go/core/tls
|
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 v0.2.0
|
||||||
golang.org/x/mod/module
|
golang.org/x/mod/module
|
||||||
golang.org/x/mod/semver
|
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
|
||||||
golang.org/x/net/context/ctxhttp
|
golang.org/x/net/context/ctxhttp
|
||||||
golang.org/x/net/html
|
golang.org/x/net/html
|
||||||
|
@ -851,8 +855,6 @@ google.golang.org/grpc/status
|
||||||
google.golang.org/grpc/tap
|
google.golang.org/grpc/tap
|
||||||
# gopkg.in/ini.v1 v1.42.0
|
# gopkg.in/ini.v1 v1.42.0
|
||||||
gopkg.in/ini.v1
|
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 v2.3.1
|
||||||
gopkg.in/square/go-jose.v2
|
gopkg.in/square/go-jose.v2
|
||||||
gopkg.in/square/go-jose.v2/cipher
|
gopkg.in/square/go-jose.v2/cipher
|
||||||
|
|
Loading…
Reference in New Issue