Vendor exoscale-import plugin
This change will vendor the new version of the exoscale-import post-processor component, but remove all of its code from Packer. After the v1.8.0 release this change should be removed entirely. This vendor process is being used as a workaround for decoupling the exoscale-import component without causing a breaking change in Packer. Users of Exoscale are encouraged to leverage `packer init` for installing the latest version of packer-plugin-exoscale.
This commit is contained in:
parent
edd4567096
commit
6f23bc0d97
9
go.mod
9
go.mod
|
@ -25,6 +25,7 @@ require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
|
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
|
||||||
github.com/digitalocean/godo v1.11.1
|
github.com/digitalocean/godo v1.11.1
|
||||||
|
github.com/exoscale/packer-plugin-exoscale v0.1.0
|
||||||
github.com/fatih/camelcase v1.0.0
|
github.com/fatih/camelcase v1.0.0
|
||||||
github.com/fatih/structtag v1.0.0
|
github.com/fatih/structtag v1.0.0
|
||||||
github.com/go-ini/ini v1.25.4
|
github.com/go-ini/ini v1.25.4
|
||||||
|
@ -76,7 +77,7 @@ require (
|
||||||
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
|
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
|
||||||
github.com/shirou/gopsutil v3.21.1+incompatible
|
github.com/shirou/gopsutil v3.21.1+incompatible
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible
|
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible
|
||||||
github.com/ucloud/ucloud-sdk-go v0.16.3
|
github.com/ucloud/ucloud-sdk-go v0.16.3
|
||||||
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6
|
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6
|
||||||
|
@ -87,13 +88,13 @@ require (
|
||||||
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c
|
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c
|
||||||
github.com/zclconf/go-cty v1.7.0
|
github.com/zclconf/go-cty v1.7.0
|
||||||
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-20201208171446-5f87f3452ae9
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2
|
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2
|
||||||
golang.org/x/mod v0.3.0
|
golang.org/x/mod v0.3.0
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
|
||||||
golang.org/x/tools v0.0.0-20201111133315-69daaf961d65
|
golang.org/x/tools v0.0.0-20201111133315-69daaf961d65
|
||||||
google.golang.org/api v0.32.0
|
google.golang.org/api v0.32.0
|
||||||
google.golang.org/grpc v1.32.0
|
google.golang.org/grpc v1.32.0
|
||||||
|
|
66
go.sum
66
go.sum
|
@ -148,10 +148,14 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
|
||||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
|
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
|
||||||
|
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
|
||||||
|
github.com/deepmap/oapi-codegen v1.5.1 h1:Xx8/OynzWhQeKFWr182hg6Ja2evS1gcqdja7qMn05yU=
|
||||||
|
github.com/deepmap/oapi-codegen v1.5.1/go.mod h1:Eb1vtV3f58zvm37CJV4UAQ1bECb0fgAVvTdonC1ftJg=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw=
|
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw=
|
||||||
|
@ -177,6 +181,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||||
|
github.com/exoscale/egoscale v0.43.1 h1:Lhr0UOfg3t3Y56yh1DsYCjQuUHqFvsC8iUVqvub8+0Q=
|
||||||
|
github.com/exoscale/egoscale v0.43.1/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc=
|
||||||
|
github.com/exoscale/packer-plugin-exoscale v0.1.0 h1:p4ymqF1tNiTuxgSdnEjGqXehMdDQbV7BPaLsMxGav24=
|
||||||
|
github.com/exoscale/packer-plugin-exoscale v0.1.0/go.mod h1:ZmJRkxsAlmEsVYOMxYPupDkax54uZ+ph0h3W59aIMZ8=
|
||||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
@ -185,8 +193,12 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
|
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
|
||||||
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
|
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
|
||||||
|
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||||
|
github.com/getkin/kin-openapi v0.37.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
|
||||||
|
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
@ -196,6 +208,8 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
|
||||||
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-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
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.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 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
|
||||||
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
|
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
|
||||||
|
@ -207,8 +221,9 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/gofrs/flock v0.7.3 h1:I0EKY9l8HZCXTMYC4F80vwT6KNypV9uYKP3Alm/hjmQ=
|
github.com/gofrs/flock v0.7.3 h1:I0EKY9l8HZCXTMYC4F80vwT6KNypV9uYKP3Alm/hjmQ=
|
||||||
github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||||
|
@ -241,6 +256,7 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
@ -403,6 +419,9 @@ github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2
|
||||||
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4 h1:mSmyzhwBeQt2TlHbsXYLona9pwjWAvYGwQJ2Cq/k3VE=
|
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4 h1:mSmyzhwBeQt2TlHbsXYLona9pwjWAvYGwQJ2Cq/k3VE=
|
||||||
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4/go.mod h1:yNUVHSleURKSaYUKq4Wx0i/vjCen2aq7CvPyHd/Vj2Q=
|
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4/go.mod h1:yNUVHSleURKSaYUKq4Wx0i/vjCen2aq7CvPyHd/Vj2Q=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||||
|
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
|
||||||
|
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||||
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961 h1:a2/K4HRhg31A5vafiz5yYiGMjaCxwRpyjJStfVquKds=
|
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961 h1:a2/K4HRhg31A5vafiz5yYiGMjaCxwRpyjJStfVquKds=
|
||||||
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
|
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
|
||||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
||||||
|
@ -450,21 +469,31 @@ 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=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||||
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/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
|
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/linode/linodego v0.14.0 h1:0APKMjiVGyry2TTUVDiok72H6cWpFNMMrFWBFn14aFU=
|
github.com/linode/linodego v0.14.0 h1:0APKMjiVGyry2TTUVDiok72H6cWpFNMMrFWBFn14aFU=
|
||||||
github.com/linode/linodego v0.14.0/go.mod h1:2ce3S00NrDqJfp4i55ZuSlT0U3cKNELNYACWBPI8Tnw=
|
github.com/linode/linodego v0.14.0/go.mod h1:2ce3S00NrDqJfp4i55ZuSlT0U3cKNELNYACWBPI8Tnw=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
|
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
|
||||||
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=
|
||||||
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
|
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
|
||||||
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
|
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
|
||||||
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380 h1:uKhPH5dYpx3Z8ZAnaTGfGZUiHOWa5p5mdG8wZlh+tLo=
|
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380 h1:uKhPH5dYpx3Z8ZAnaTGfGZUiHOWa5p5mdG8wZlh+tLo=
|
||||||
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
|
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
|
||||||
|
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
@ -556,6 +585,7 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
|
||||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
|
@ -584,14 +614,16 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||||
|
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84=
|
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
@ -606,6 +638,10 @@ github.com/ugorji/go/codec v1.2.4 h1:C5VurWRRCKjuENsbM6GYVw8W++WVW9rSxoACKIvxzz8
|
||||||
github.com/ugorji/go/codec v1.2.4/go.mod h1:bWBu1+kIRWcF8uMklKaJrR6fTWQOwAlrIzX22pHwryA=
|
github.com/ugorji/go/codec v1.2.4/go.mod h1:bWBu1+kIRWcF8uMklKaJrR6fTWQOwAlrIzX22pHwryA=
|
||||||
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
||||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||||
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
||||||
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||||
|
@ -645,14 +681,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -706,6 +746,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/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-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/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=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -725,8 +766,9 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
|
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -762,12 +804,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -786,11 +830,13 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
@ -799,8 +845,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
|
@ -975,8 +1022,9 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2021 DeepMap, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// Binder is the interface implemented by types that can be bound to a query string or a parameter string
|
||||||
|
// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all
|
||||||
|
// data being completely bound to the type.
|
||||||
|
//
|
||||||
|
// By convention, to approximate the behavior of Bind functions themselves,
|
||||||
|
// Binder implements Bind("") as a no-op.
|
||||||
|
type Binder interface {
|
||||||
|
Bind(src string) error
|
||||||
|
}
|
|
@ -0,0 +1,463 @@
|
||||||
|
// Copyright 2019 DeepMap, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function binds a parameter as described in the Path Parameters
|
||||||
|
// section here to a Go object:
|
||||||
|
// https://swagger.io/docs/specification/serialization/
|
||||||
|
func BindStyledParameter(style string, explode bool, paramName string,
|
||||||
|
value string, dest interface{}) error {
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything comes in by pointer, dereference it
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(dest))
|
||||||
|
|
||||||
|
// This is the basic type of the destination object.
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
if t.Kind() == reflect.Struct {
|
||||||
|
// We've got a destination object, we'll create a JSON representation
|
||||||
|
// of the input value, and let the json library deal with the unmarshaling
|
||||||
|
parts, err := splitStyledParameter(style, explode, true, paramName, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindSplitPartsToDestinationStruct(paramName, parts, explode, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
// Chop up the parameter into parts based on its style
|
||||||
|
parts, err := splitStyledParameter(style, explode, false, paramName, value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error splitting input '%s' into parts: %s", value, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindSplitPartsToDestinationArray(parts, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to bind the remaining types as a base type.
|
||||||
|
return BindStringToObject(value, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a complex set of operations, but each given parameter style can be
|
||||||
|
// packed together in multiple ways, using different styles of separators, and
|
||||||
|
// different packing strategies based on the explode flag. This function takes
|
||||||
|
// as input any parameter format, and unpacks it to a simple list of strings
|
||||||
|
// or key-values which we can then treat generically.
|
||||||
|
// Why, oh why, great Swagger gods, did you have to make this so complicated?
|
||||||
|
func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) {
|
||||||
|
switch style {
|
||||||
|
case "simple":
|
||||||
|
// In the simple case, we always split on comma
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
return parts, nil
|
||||||
|
case "label":
|
||||||
|
// In the label case, it's more tricky. In the no explode case, we have
|
||||||
|
// /users/.3,4,5 for arrays
|
||||||
|
// /users/.role,admin,firstName,Alex for objects
|
||||||
|
// in the explode case, we have:
|
||||||
|
// /users/.3.4.5
|
||||||
|
// /users/.role=admin.firstName=Alex
|
||||||
|
if explode {
|
||||||
|
// In the exploded case, split everything on periods.
|
||||||
|
parts := strings.Split(value, ".")
|
||||||
|
// The first part should be an empty string because we have a
|
||||||
|
// leading period.
|
||||||
|
if parts[0] != "" {
|
||||||
|
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
|
||||||
|
}
|
||||||
|
return parts[1:], nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// In the unexploded case, we strip off the leading period.
|
||||||
|
if value[0] != '.' {
|
||||||
|
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
|
||||||
|
}
|
||||||
|
// The rest is comma separated.
|
||||||
|
return strings.Split(value[1:], ","), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case "matrix":
|
||||||
|
if explode {
|
||||||
|
// In the exploded case, we break everything up on semicolon
|
||||||
|
parts := strings.Split(value, ";")
|
||||||
|
// The first part should always be empty string, since we started
|
||||||
|
// with ;something
|
||||||
|
if parts[0] != "" {
|
||||||
|
return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName)
|
||||||
|
}
|
||||||
|
parts = parts[1:]
|
||||||
|
// Now, if we have an object, we just have a list of x=y statements.
|
||||||
|
// for a non-object, like an array, we have id=x, id=y. id=z, etc,
|
||||||
|
// so we need to strip the prefix from each of them.
|
||||||
|
if !object {
|
||||||
|
prefix := paramName + "="
|
||||||
|
for i := range parts {
|
||||||
|
parts[i] = strings.TrimPrefix(parts[i], prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts, nil
|
||||||
|
} else {
|
||||||
|
// In the unexploded case, parameters will start with ;paramName=
|
||||||
|
prefix := ";" + paramName + "="
|
||||||
|
if !strings.HasPrefix(value, prefix) {
|
||||||
|
return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix)
|
||||||
|
}
|
||||||
|
str := strings.TrimPrefix(value, prefix)
|
||||||
|
return strings.Split(str, ","), nil
|
||||||
|
}
|
||||||
|
case "form":
|
||||||
|
var parts []string
|
||||||
|
if explode {
|
||||||
|
parts = strings.Split(value, "&")
|
||||||
|
if !object {
|
||||||
|
prefix := paramName + "="
|
||||||
|
for i := range parts {
|
||||||
|
parts[i] = strings.TrimPrefix(parts[i], prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts, nil
|
||||||
|
} else {
|
||||||
|
parts = strings.Split(value, ",")
|
||||||
|
prefix := paramName + "="
|
||||||
|
for i := range parts {
|
||||||
|
parts[i] = strings.TrimPrefix(parts[i], prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unhandled parameter style: %s", style)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a set of values as a slice, create a slice to hold them all, and
|
||||||
|
// assign to each one by one.
|
||||||
|
func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error {
|
||||||
|
// Everything comes in by pointer, dereference it
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(dest))
|
||||||
|
|
||||||
|
// This is the basic type of the destination object.
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
// We've got a destination array, bind each object one by one.
|
||||||
|
// This generates a slice of the correct element type and length to
|
||||||
|
// hold all the parts.
|
||||||
|
newArray := reflect.MakeSlice(t, len(parts), len(parts))
|
||||||
|
for i, p := range parts {
|
||||||
|
err := BindStringToObject(p, newArray.Index(i).Addr().Interface())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error setting array element: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Set(newArray)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a set of chopped up parameter parts, bind them to a destination
|
||||||
|
// struct. The exploded parameter controls whether we send key value pairs
|
||||||
|
// in the exploded case, or a sequence of values which are interpreted as
|
||||||
|
// tuples.
|
||||||
|
// Given the struct Id { firstName string, role string }, as in the canonical
|
||||||
|
// swagger examples, in the exploded case, we would pass
|
||||||
|
// ["firstName=Alex", "role=admin"], where in the non-exploded case, we would
|
||||||
|
// pass "firstName", "Alex", "role", "admin"]
|
||||||
|
//
|
||||||
|
// We punt the hard work of binding these values to the object to the json
|
||||||
|
// library. We'll turn those arrays into JSON strings, and unmarshal
|
||||||
|
// into the struct.
|
||||||
|
func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error {
|
||||||
|
// We've got a destination object, we'll create a JSON representation
|
||||||
|
// of the input value, and let the json library deal with the unmarshaling
|
||||||
|
var fields []string
|
||||||
|
if explode {
|
||||||
|
fields = make([]string, len(parts))
|
||||||
|
for i, property := range parts {
|
||||||
|
propertyParts := strings.Split(property, "=")
|
||||||
|
if len(propertyParts) != 2 {
|
||||||
|
return fmt.Errorf("parameter '%s' has invalid exploded format", paramName)
|
||||||
|
}
|
||||||
|
fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(parts)%2 != 0 {
|
||||||
|
return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName)
|
||||||
|
}
|
||||||
|
fields = make([]string, len(parts)/2)
|
||||||
|
for i := 0; i < len(parts); i += 2 {
|
||||||
|
key := parts[i]
|
||||||
|
value := parts[i+1]
|
||||||
|
fields[i/2] = "\"" + key + "\":\"" + value + "\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonParam := "{" + strings.Join(fields, ",") + "}"
|
||||||
|
err := json.Unmarshal([]byte(jsonParam), dest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error binding parameter %s fields: %s", paramName, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This works much like BindStyledParameter, however it takes a query argument
|
||||||
|
// input array from the url package, since query arguments come through a
|
||||||
|
// different path than the styled arguments. They're also exceptionally fussy.
|
||||||
|
// For example, consider the exploded and unexploded form parameter examples:
|
||||||
|
// (exploded) /users?role=admin&firstName=Alex
|
||||||
|
// (unexploded) /users?id=role,admin,firstName,Alex
|
||||||
|
//
|
||||||
|
// In the first case, we can pull the "id" parameter off the context,
|
||||||
|
// and unmarshal via json as an intermediate. Easy. In the second case, we
|
||||||
|
// don't have the id QueryParam present, but must find "role", and "firstName".
|
||||||
|
// what if there is another parameter similar to "ID" named "role"? We can't
|
||||||
|
// tell them apart. This code tries to fail, but the moral of the story is that
|
||||||
|
// you shouldn't pass objects via form styled query arguments, just use
|
||||||
|
// the Content parameter form.
|
||||||
|
func BindQueryParameter(style string, explode bool, required bool, paramName string,
|
||||||
|
queryParams url.Values, dest interface{}) error {
|
||||||
|
|
||||||
|
// dv = destination value.
|
||||||
|
dv := reflect.Indirect(reflect.ValueOf(dest))
|
||||||
|
|
||||||
|
// intermediate value form which is either dv or dv dereferenced.
|
||||||
|
v := dv
|
||||||
|
|
||||||
|
// inner code will bind the string's value to this interface.
|
||||||
|
var output interface{}
|
||||||
|
|
||||||
|
if required {
|
||||||
|
// If the parameter is required, then the generated code will pass us
|
||||||
|
// a pointer to it: &int, &object, and so forth. We can directly set
|
||||||
|
// them.
|
||||||
|
output = dest
|
||||||
|
} else {
|
||||||
|
// For optional parameters, we have an extra indirect. An optional
|
||||||
|
// parameter of type "int" will be *int on the struct. We pass that
|
||||||
|
// in by pointer, and have **int.
|
||||||
|
|
||||||
|
// If the destination, is a nil pointer, we need to allocate it.
|
||||||
|
if v.IsNil() {
|
||||||
|
t := v.Type()
|
||||||
|
newValue := reflect.New(t.Elem())
|
||||||
|
// for now, hang onto the output buffer separately from destination,
|
||||||
|
// as we don't want to write anything to destination until we can
|
||||||
|
// unmarshal successfully, and check whether a field is required.
|
||||||
|
output = newValue.Interface()
|
||||||
|
} else {
|
||||||
|
// If the destination isn't nil, just use that.
|
||||||
|
output = v.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rid of that extra indirect as compared to the required case,
|
||||||
|
// so the code below doesn't have to care.
|
||||||
|
v = reflect.Indirect(reflect.ValueOf(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the basic type of the destination object.
|
||||||
|
t := v.Type()
|
||||||
|
k := t.Kind()
|
||||||
|
|
||||||
|
switch style {
|
||||||
|
case "form":
|
||||||
|
var parts []string
|
||||||
|
if explode {
|
||||||
|
// ok, the explode case in query arguments is very, very annoying,
|
||||||
|
// because an exploded object, such as /users?role=admin&firstName=Alex
|
||||||
|
// isn't actually present in the parameter array. We have to do
|
||||||
|
// different things based on destination type.
|
||||||
|
values, found := queryParams[paramName]
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Slice:
|
||||||
|
// In the slice case, we simply use the arguments provided by
|
||||||
|
// http library.
|
||||||
|
if !found {
|
||||||
|
if required {
|
||||||
|
return fmt.Errorf("query parameter '%s' is required", paramName)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = bindSplitPartsToDestinationArray(values, output)
|
||||||
|
case reflect.Struct:
|
||||||
|
// This case is really annoying, and error prone, but the
|
||||||
|
// form style object binding doesn't tell us which arguments
|
||||||
|
// in the query string correspond to the object's fields. We'll
|
||||||
|
// try to bind field by field.
|
||||||
|
err = bindParamsToExplodedObject(paramName, queryParams, output)
|
||||||
|
default:
|
||||||
|
// Primitive object case. We expect to have 1 value to
|
||||||
|
// unmarshal.
|
||||||
|
if len(values) == 0 {
|
||||||
|
if required {
|
||||||
|
return fmt.Errorf("query parameter '%s' is required", paramName)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(values) != 1 {
|
||||||
|
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
|
||||||
|
}
|
||||||
|
err = BindStringToObject(values[0], output)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// If the parameter is required, and we've successfully unmarshaled
|
||||||
|
// it, this assigns the new object to the pointer pointer.
|
||||||
|
if !required {
|
||||||
|
dv.Set(reflect.ValueOf(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
values, found := queryParams[paramName]
|
||||||
|
if !found {
|
||||||
|
if required {
|
||||||
|
return fmt.Errorf("query parameter '%s' is required", paramName)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(values) != 1 {
|
||||||
|
return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
|
||||||
|
}
|
||||||
|
parts = strings.Split(values[0], ",")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch k {
|
||||||
|
case reflect.Slice:
|
||||||
|
err = bindSplitPartsToDestinationArray(parts, output)
|
||||||
|
case reflect.Struct:
|
||||||
|
err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
|
||||||
|
default:
|
||||||
|
if len(parts) == 0 {
|
||||||
|
if required {
|
||||||
|
return fmt.Errorf("query parameter '%s' is required", paramName)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) != 1 {
|
||||||
|
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
|
||||||
|
}
|
||||||
|
err = BindStringToObject(parts[0], output)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !required {
|
||||||
|
dv.Set(reflect.ValueOf(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "deepObject":
|
||||||
|
if !explode {
|
||||||
|
return errors.New("deepObjects must be exploded")
|
||||||
|
}
|
||||||
|
return UnmarshalDeepObject(dest, paramName, queryParams)
|
||||||
|
case "spaceDelimited", "pipeDelimited":
|
||||||
|
return fmt.Errorf("query arguments of style '%s' aren't yet supported", style)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function reflects the destination structure, and pulls the value for
|
||||||
|
// each settable field from the given parameters map. This is to deal with the
|
||||||
|
// exploded form styled object which may occupy any number of parameter names.
|
||||||
|
// We don't try to be smart here, if the field exists as a query argument,
|
||||||
|
// set its value.
|
||||||
|
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) error {
|
||||||
|
// Dereference pointers to their destination values
|
||||||
|
binder, v, t := indirect(dest)
|
||||||
|
if binder != nil {
|
||||||
|
return BindStringToObject(values.Get(paramName), dest)
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
fieldT := t.Field(i)
|
||||||
|
|
||||||
|
// Skip unsettable fields, such as internal ones.
|
||||||
|
if !v.Field(i).CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the json annotation on the field, and use the json specified
|
||||||
|
// name if available, otherwise, just the field name.
|
||||||
|
tag := fieldT.Tag.Get("json")
|
||||||
|
fieldName := fieldT.Name
|
||||||
|
if tag != "" {
|
||||||
|
tagParts := strings.Split(tag, ",")
|
||||||
|
name := tagParts[0]
|
||||||
|
if name != "" {
|
||||||
|
fieldName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we look up field name in the parameter list.
|
||||||
|
fieldVal, found := values[fieldName]
|
||||||
|
if found {
|
||||||
|
if len(fieldVal) != 1 {
|
||||||
|
return fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
|
||||||
|
}
|
||||||
|
err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect
|
||||||
|
func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) {
|
||||||
|
v := reflect.ValueOf(dest)
|
||||||
|
if v.Type().NumMethod() > 0 && v.CanInterface() {
|
||||||
|
if u, ok := v.Interface().(Binder); ok {
|
||||||
|
return u, reflect.Value{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
t := v.Type()
|
||||||
|
// special handling for custom types which might look like an object. We
|
||||||
|
// don't want to use object binding on them, but rather treat them as
|
||||||
|
// primitive types. time.Time{} is a unique case since we can't add a Binder
|
||||||
|
// to it without changing the underlying generated code.
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
|
||||||
|
return dest, reflect.Value{}, nil
|
||||||
|
}
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
|
||||||
|
return dest, reflect.Value{}, nil
|
||||||
|
}
|
||||||
|
return nil, v, t
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright 2019 DeepMap, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function takes a string, and attempts to assign it to the destination
|
||||||
|
// interface via whatever type conversion is necessary. We have to do this
|
||||||
|
// via reflection instead of a much simpler type switch so that we can handle
|
||||||
|
// type aliases. This function was the easy way out, the better way, since we
|
||||||
|
// know the destination type each place that we use this, is to generate code
|
||||||
|
// to read each specific type.
|
||||||
|
func BindStringToObject(src string, dst interface{}) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
t := reflect.TypeOf(dst)
|
||||||
|
|
||||||
|
// We need to dereference pointers
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
t = v.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resulting type must be settable. reflect will catch issues like
|
||||||
|
// passing the destination by value.
|
||||||
|
if !v.CanSet() {
|
||||||
|
return errors.New("destination is not settable")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
var val int64
|
||||||
|
val, err = strconv.ParseInt(src, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
v.SetInt(val)
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
var val uint64
|
||||||
|
val, err = strconv.ParseUint(src, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
v.SetUint(val)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
v.SetString(src)
|
||||||
|
err = nil
|
||||||
|
case reflect.Float64, reflect.Float32:
|
||||||
|
var val float64
|
||||||
|
val, err = strconv.ParseFloat(src, 64)
|
||||||
|
if err == nil {
|
||||||
|
v.SetFloat(val)
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
var val bool
|
||||||
|
val, err = strconv.ParseBool(src)
|
||||||
|
if err == nil {
|
||||||
|
v.SetBool(val)
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
// if this is not of type Time or of type Date look to see if this is of type Binder.
|
||||||
|
if dstType, ok := dst.(Binder); ok {
|
||||||
|
return dstType.Bind(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
|
||||||
|
// Don't fail on empty string.
|
||||||
|
if src == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Time is a special case of a struct that we handle
|
||||||
|
parsedTime, err := time.Parse(time.RFC3339Nano, src)
|
||||||
|
if err != nil {
|
||||||
|
parsedTime, err = time.Parse(types.DateFormat, src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// So, assigning this gets a little fun. We have a value to the
|
||||||
|
// dereference destination. We can't do a conversion to
|
||||||
|
// time.Time because the result isn't assignable, so we need to
|
||||||
|
// convert pointers.
|
||||||
|
if t != reflect.TypeOf(time.Time{}) {
|
||||||
|
vPtr := v.Addr()
|
||||||
|
vtPtr := vPtr.Convert(reflect.TypeOf(&time.Time{}))
|
||||||
|
v = reflect.Indirect(vtPtr)
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(parsedTime))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
|
||||||
|
// Don't fail on empty string.
|
||||||
|
if src == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parsedTime, err := time.Parse(types.DateFormat, src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing '%s' as date: %s", src, err)
|
||||||
|
}
|
||||||
|
parsedDate := types.Date{Time: parsedTime}
|
||||||
|
|
||||||
|
// We have to do the same dance here to assign, just like with times
|
||||||
|
// above.
|
||||||
|
if t != reflect.TypeOf(types.Date{}) {
|
||||||
|
vPtr := v.Addr()
|
||||||
|
vtPtr := vPtr.Convert(reflect.TypeOf(&types.Date{}))
|
||||||
|
v = reflect.Indirect(vtPtr)
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(parsedDate))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fall through to the error case below if we haven't handled the
|
||||||
|
// destination type above.
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// We've got a bunch of types unimplemented, don't fail silently.
|
||||||
|
err = fmt.Errorf("can not bind to destination of type: %s", t.Kind())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error binding string parameter: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,357 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
switch t := in.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
// For the array, we will use numerical subscripts of the form [x],
|
||||||
|
// in the same order as the array.
|
||||||
|
for i, iface := range t {
|
||||||
|
newPath := append(path, strconv.Itoa(i))
|
||||||
|
fields, err := marshalDeepObject(iface, newPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error traversing array")
|
||||||
|
}
|
||||||
|
result = append(result, fields...)
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
// For a map, each key (field name) becomes a member of the path, and
|
||||||
|
// we recurse. First, sort the keys.
|
||||||
|
keys := make([]string, len(t))
|
||||||
|
i := 0
|
||||||
|
for k := range t {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// Now, for each key, we recursively marshal it.
|
||||||
|
for _, k := range keys {
|
||||||
|
newPath := append(path, k)
|
||||||
|
fields, err := marshalDeepObject(t[k], newPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error traversing map")
|
||||||
|
}
|
||||||
|
result = append(result, fields...)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Now, for a concrete value, we will turn the path elements
|
||||||
|
// into a deepObject style set of subscripts. [a, b, c] turns into
|
||||||
|
// [a][b][c]
|
||||||
|
prefix := "[" + strings.Join(path, "][") + "]"
|
||||||
|
result = []string{
|
||||||
|
prefix + fmt.Sprintf("=%v", t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalDeepObject(i interface{}, paramName string) (string, error) {
|
||||||
|
// We're going to marshal to JSON and unmarshal into an interface{},
|
||||||
|
// which will use the json pkg to deal with all the field annotations. We
|
||||||
|
// can then walk the generic object structure to produce a deepObject. This
|
||||||
|
// isn't efficient and it would be more efficient to reflect on our own,
|
||||||
|
// but it's complicated, error-prone code.
|
||||||
|
buf, err := json.Marshal(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to marshal input to JSON")
|
||||||
|
}
|
||||||
|
var i2 interface{}
|
||||||
|
err = json.Unmarshal(buf, &i2)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to unmarshal JSON")
|
||||||
|
}
|
||||||
|
fields, err := marshalDeepObject(i2, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "error traversing JSON structure")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix the param name to each subscripted field.
|
||||||
|
for i := range fields {
|
||||||
|
fields[i] = paramName + fields[i]
|
||||||
|
}
|
||||||
|
return strings.Join(fields, "&"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldOrValue struct {
|
||||||
|
fields map[string]fieldOrValue
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fieldOrValue) appendPathValue(path []string, value string) {
|
||||||
|
fieldName := path[0]
|
||||||
|
if len(path) == 1 {
|
||||||
|
f.fields[fieldName] = fieldOrValue{value: value}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pv, found := f.fields[fieldName]
|
||||||
|
if !found {
|
||||||
|
pv = fieldOrValue{
|
||||||
|
fields: make(map[string]fieldOrValue),
|
||||||
|
}
|
||||||
|
f.fields[fieldName] = pv
|
||||||
|
}
|
||||||
|
pv.appendPathValue(path[1:], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFieldOrValue(paths [][]string, values []string) fieldOrValue {
|
||||||
|
|
||||||
|
f := fieldOrValue{
|
||||||
|
fields: make(map[string]fieldOrValue),
|
||||||
|
}
|
||||||
|
for i := range paths {
|
||||||
|
path := paths[i]
|
||||||
|
value := values[i]
|
||||||
|
f.appendPathValue(path, value)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error {
|
||||||
|
// Params are all the query args, so we need those that look like
|
||||||
|
// "paramName["...
|
||||||
|
var fieldNames []string
|
||||||
|
var fieldValues []string
|
||||||
|
searchStr := paramName + "["
|
||||||
|
for pName, pValues := range params {
|
||||||
|
if strings.HasPrefix(pName, searchStr) {
|
||||||
|
// trim the parameter name from the full name.
|
||||||
|
pName = pName[len(paramName):]
|
||||||
|
fieldNames = append(fieldNames, pName)
|
||||||
|
if len(pValues) != 1 {
|
||||||
|
return fmt.Errorf("%s has multiple values", pName)
|
||||||
|
}
|
||||||
|
fieldValues = append(fieldValues, pValues[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, for each field, reconstruct its subscript path and value
|
||||||
|
paths := make([][]string, len(fieldNames))
|
||||||
|
for i, path := range fieldNames {
|
||||||
|
path = strings.TrimLeft(path, "[")
|
||||||
|
path = strings.TrimRight(path, "]")
|
||||||
|
paths[i] = strings.Split(path, "][")
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldPaths := makeFieldOrValue(paths, fieldValues)
|
||||||
|
err := assignPathValues(dst, fieldPaths)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error assigning value to destination")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This returns a field name, either using the variable name, or the json
|
||||||
|
// annotation if that exists.
|
||||||
|
func getFieldName(f reflect.StructField) string {
|
||||||
|
n := f.Name
|
||||||
|
tag, found := f.Tag.Lookup("json")
|
||||||
|
if found {
|
||||||
|
// If we have a json field, and the first part of it before the
|
||||||
|
// first comma is non-empty, that's our field name.
|
||||||
|
parts := strings.Split(tag, ",")
|
||||||
|
if parts[0] != "" {
|
||||||
|
n = parts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map of field names that we'll see in the deepObject to reflect
|
||||||
|
// field indices on the given type.
|
||||||
|
func fieldIndicesByJsonTag(i interface{}) (map[string]int, error) {
|
||||||
|
t := reflect.TypeOf(i)
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil, errors.New("expected a struct as input")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := t.NumField()
|
||||||
|
fieldMap := make(map[string]int)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
fieldName := getFieldName(field)
|
||||||
|
fieldMap[fieldName] = i
|
||||||
|
}
|
||||||
|
return fieldMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
|
||||||
|
//t := reflect.TypeOf(dst)
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
|
||||||
|
iv := reflect.Indirect(v)
|
||||||
|
it := iv.Type()
|
||||||
|
|
||||||
|
switch it.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
sliceLength := len(pathValues.fields)
|
||||||
|
dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength)
|
||||||
|
err := assignSlice(dstSlice, pathValues)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error assigning slice")
|
||||||
|
}
|
||||||
|
iv.Set(dstSlice)
|
||||||
|
return nil
|
||||||
|
case reflect.Struct:
|
||||||
|
// Some special types we care about are structs. Handle them
|
||||||
|
// here. They may be redefined, so we need to do some hoop
|
||||||
|
// jumping. If the types are aliased, we need to type convert
|
||||||
|
// the pointer, then set the value of the dereference pointer.
|
||||||
|
|
||||||
|
// We check to see if the object implements the Binder interface first.
|
||||||
|
if dst, isBinder := v.Interface().(Binder); isBinder {
|
||||||
|
return dst.Bind(pathValues.value)
|
||||||
|
}
|
||||||
|
// Then check the legacy types
|
||||||
|
if it.ConvertibleTo(reflect.TypeOf(types.Date{})) {
|
||||||
|
var date types.Date
|
||||||
|
var err error
|
||||||
|
date.Time, err = time.Parse(types.DateFormat, pathValues.value)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "invalid date format")
|
||||||
|
}
|
||||||
|
dst := iv
|
||||||
|
if it != reflect.TypeOf(types.Date{}) {
|
||||||
|
// Types are aliased, convert the pointers.
|
||||||
|
ivPtr := iv.Addr()
|
||||||
|
aPtr := ivPtr.Convert(reflect.TypeOf(&types.Date{}))
|
||||||
|
dst = reflect.Indirect(aPtr)
|
||||||
|
}
|
||||||
|
dst.Set(reflect.ValueOf(date))
|
||||||
|
}
|
||||||
|
if it.ConvertibleTo(reflect.TypeOf(time.Time{})) {
|
||||||
|
var tm time.Time
|
||||||
|
var err error
|
||||||
|
tm, err = time.Parse(time.RFC3339Nano, pathValues.value)
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to parsing it as a date.
|
||||||
|
tm, err = time.Parse(types.DateFormat, pathValues.value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing tim as RFC3339 or 2006-01-02 time: %s", err)
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "invalid date format")
|
||||||
|
}
|
||||||
|
dst := iv
|
||||||
|
if it != reflect.TypeOf(time.Time{}) {
|
||||||
|
// Types are aliased, convert the pointers.
|
||||||
|
ivPtr := iv.Addr()
|
||||||
|
aPtr := ivPtr.Convert(reflect.TypeOf(&time.Time{}))
|
||||||
|
dst = reflect.Indirect(aPtr)
|
||||||
|
}
|
||||||
|
dst.Set(reflect.ValueOf(tm))
|
||||||
|
}
|
||||||
|
fieldMap, err := fieldIndicesByJsonTag(iv.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed enumerating fields")
|
||||||
|
}
|
||||||
|
for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) {
|
||||||
|
fieldValue := pathValues.fields[fieldName]
|
||||||
|
fieldIndex, found := fieldMap[fieldName]
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("field [%s] is not present in destination object", fieldName)
|
||||||
|
}
|
||||||
|
field := iv.Field(fieldIndex)
|
||||||
|
err = assignPathValues(field.Addr().Interface(), fieldValue)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error assigning field [%s]", fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case reflect.Ptr:
|
||||||
|
// If we have a pointer after redirecting, it means we're dealing with
|
||||||
|
// an optional field, such as *string, which was passed in as &foo. We
|
||||||
|
// will allocate it if necessary, and call ourselves with a different
|
||||||
|
// interface.
|
||||||
|
dstVal := reflect.New(it.Elem())
|
||||||
|
dstPtr := dstVal.Interface()
|
||||||
|
err := assignPathValues(dstPtr, pathValues)
|
||||||
|
iv.Set(dstVal)
|
||||||
|
return err
|
||||||
|
case reflect.Bool:
|
||||||
|
val, err := strconv.ParseBool(pathValues.value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expected a valid bool, got %s", pathValues.value)
|
||||||
|
}
|
||||||
|
iv.SetBool(val)
|
||||||
|
return nil
|
||||||
|
case reflect.Float32:
|
||||||
|
val, err := strconv.ParseFloat(pathValues.value, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
||||||
|
}
|
||||||
|
iv.SetFloat(val)
|
||||||
|
return nil
|
||||||
|
case reflect.Float64:
|
||||||
|
val, err := strconv.ParseFloat(pathValues.value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
||||||
|
}
|
||||||
|
iv.SetFloat(val)
|
||||||
|
return nil
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
val, err := strconv.ParseInt(pathValues.value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expected a valid int, got %s", pathValues.value)
|
||||||
|
}
|
||||||
|
iv.SetInt(val)
|
||||||
|
return nil
|
||||||
|
case reflect.String:
|
||||||
|
iv.SetString(pathValues.value)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("unhandled type: " + it.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
|
||||||
|
// Gather up the values
|
||||||
|
nValues := len(pathValues.fields)
|
||||||
|
values := make([]string, nValues)
|
||||||
|
// We expect to have consecutive array indices in the map
|
||||||
|
for i := 0; i < nValues; i++ {
|
||||||
|
indexStr := strconv.Itoa(i)
|
||||||
|
fv, found := pathValues.fields[indexStr]
|
||||||
|
if !found {
|
||||||
|
return errors.New("array deepObjects must have consecutive indices")
|
||||||
|
}
|
||||||
|
values[i] = fv.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could be cleaner, but we can call into assignPathValues to
|
||||||
|
// avoid recreating this logic.
|
||||||
|
for i := 0; i < nValues; i++ {
|
||||||
|
dstElem := dst.Index(i).Addr()
|
||||||
|
err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error binding array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string {
|
||||||
|
keys := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
// Copyright 2019 DeepMap, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Given an input value, such as a primitive type, array or object, turn it
|
||||||
|
// into a parameter based on style/explode definition.
|
||||||
|
func StyleParam(style string, explode bool, paramName string, value interface{}) (string, error) {
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
|
||||||
|
// Things may be passed in by pointer, we need to dereference, so return
|
||||||
|
// error on nil.
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return "", fmt.Errorf("value is a nil pointer")
|
||||||
|
}
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
t = v.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
n := v.Len()
|
||||||
|
sliceVal := make([]interface{}, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
sliceVal[i] = v.Index(i).Interface()
|
||||||
|
}
|
||||||
|
return styleSlice(style, explode, paramName, sliceVal)
|
||||||
|
case reflect.Struct:
|
||||||
|
return styleStruct(style, explode, paramName, value)
|
||||||
|
case reflect.Map:
|
||||||
|
return styleMap(style, explode, paramName, value)
|
||||||
|
default:
|
||||||
|
return stylePrimitive(style, explode, paramName, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleSlice(style string, explode bool, paramName string, values []interface{}) (string, error) {
|
||||||
|
if style == "deepObject" {
|
||||||
|
if !explode {
|
||||||
|
return "", errors.New("deepObjects must be exploded")
|
||||||
|
}
|
||||||
|
return MarshalDeepObject(values, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
var separator string
|
||||||
|
|
||||||
|
switch style {
|
||||||
|
case "simple":
|
||||||
|
separator = ","
|
||||||
|
case "label":
|
||||||
|
prefix = "."
|
||||||
|
if explode {
|
||||||
|
separator = "."
|
||||||
|
} else {
|
||||||
|
separator = ","
|
||||||
|
}
|
||||||
|
case "matrix":
|
||||||
|
prefix = fmt.Sprintf(";%s=", paramName)
|
||||||
|
if explode {
|
||||||
|
separator = prefix
|
||||||
|
} else {
|
||||||
|
separator = ","
|
||||||
|
}
|
||||||
|
case "form":
|
||||||
|
prefix = fmt.Sprintf("%s=", paramName)
|
||||||
|
if explode {
|
||||||
|
separator = "&" + prefix
|
||||||
|
} else {
|
||||||
|
separator = ","
|
||||||
|
}
|
||||||
|
case "spaceDelimited":
|
||||||
|
prefix = fmt.Sprintf("%s=", paramName)
|
||||||
|
if explode {
|
||||||
|
separator = "&" + prefix
|
||||||
|
} else {
|
||||||
|
separator = " "
|
||||||
|
}
|
||||||
|
case "pipeDelimited":
|
||||||
|
prefix = fmt.Sprintf("%s=", paramName)
|
||||||
|
if explode {
|
||||||
|
separator = "&" + prefix
|
||||||
|
} else {
|
||||||
|
separator = "|"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported style '%s'", style)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to assume here that the array is one of simple types.
|
||||||
|
var err error
|
||||||
|
parts := make([]string, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
parts[i], err = primitiveToString(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefix + strings.Join(parts, separator), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys(strMap map[string]string) []string {
|
||||||
|
keys := make([]string, len(strMap))
|
||||||
|
i := 0
|
||||||
|
for k := range strMap {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a special case. The struct may be a date or time, in
|
||||||
|
// which case, marshal it in correct format.
|
||||||
|
func marshalDateTimeValue(value interface{}) (string, bool) {
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(value))
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
|
||||||
|
tt := v.Convert(reflect.TypeOf(time.Time{}))
|
||||||
|
timeVal := tt.Interface().(time.Time)
|
||||||
|
return timeVal.Format(time.RFC3339Nano), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
|
||||||
|
d := v.Convert(reflect.TypeOf(types.Date{}))
|
||||||
|
dateVal := d.Interface().(types.Date)
|
||||||
|
return dateVal.Format(types.DateFormat), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleStruct(style string, explode bool, paramName string, value interface{}) (string, error) {
|
||||||
|
|
||||||
|
if timeVal, ok := marshalDateTimeValue(value); ok {
|
||||||
|
styledVal, err := stylePrimitive(style, explode, paramName, timeVal)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to style time")
|
||||||
|
}
|
||||||
|
return styledVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if style == "deepObject" {
|
||||||
|
if !explode {
|
||||||
|
return "", errors.New("deepObjects must be exploded")
|
||||||
|
}
|
||||||
|
return MarshalDeepObject(value, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we need to build a dictionary of the struct's fields. Each
|
||||||
|
// field may only be a primitive value.
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
fieldDict := make(map[string]string)
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
fieldT := t.Field(i)
|
||||||
|
// Find the json annotation on the field, and use the json specified
|
||||||
|
// name if available, otherwise, just the field name.
|
||||||
|
tag := fieldT.Tag.Get("json")
|
||||||
|
fieldName := fieldT.Name
|
||||||
|
if tag != "" {
|
||||||
|
tagParts := strings.Split(tag, ",")
|
||||||
|
name := tagParts[0]
|
||||||
|
if name != "" {
|
||||||
|
fieldName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := v.Field(i)
|
||||||
|
|
||||||
|
// Unset optional fields will be nil pointers, skip over those.
|
||||||
|
if f.Type().Kind() == reflect.Ptr && f.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
str, err := primitiveToString(f.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
|
||||||
|
}
|
||||||
|
fieldDict[fieldName] = str
|
||||||
|
}
|
||||||
|
|
||||||
|
return processFieldDict(style, explode, paramName, fieldDict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleMap(style string, explode bool, paramName string, value interface{}) (string, error) {
|
||||||
|
if style == "deepObject" {
|
||||||
|
if !explode {
|
||||||
|
return "", errors.New("deepObjects must be exploded")
|
||||||
|
}
|
||||||
|
return MarshalDeepObject(value, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dict, ok := value.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("map not of type map[string]interface{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldDict := make(map[string]string)
|
||||||
|
for fieldName, value := range dict {
|
||||||
|
str, err := primitiveToString(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
|
||||||
|
}
|
||||||
|
fieldDict[fieldName] = str
|
||||||
|
}
|
||||||
|
|
||||||
|
return processFieldDict(style, explode, paramName, fieldDict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFieldDict(style string, explode bool, paramName string, fieldDict map[string]string) (string, error) {
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
// This works for everything except deepObject. We'll handle that one
|
||||||
|
// separately.
|
||||||
|
if style != "deepObject" {
|
||||||
|
if explode {
|
||||||
|
for _, k := range sortedKeys(fieldDict) {
|
||||||
|
v := fieldDict[k]
|
||||||
|
parts = append(parts, k+"="+v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, k := range sortedKeys(fieldDict) {
|
||||||
|
v := fieldDict[k]
|
||||||
|
parts = append(parts, k)
|
||||||
|
parts = append(parts, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
var separator string
|
||||||
|
|
||||||
|
switch style {
|
||||||
|
case "simple":
|
||||||
|
separator = ","
|
||||||
|
case "label":
|
||||||
|
prefix = "."
|
||||||
|
if explode {
|
||||||
|
separator = prefix
|
||||||
|
} else {
|
||||||
|
separator = ","
|
||||||
|
}
|
||||||
|
case "matrix":
|
||||||
|
if explode {
|
||||||
|
separator = ";"
|
||||||
|
prefix = ";"
|
||||||
|
} else {
|
||||||
|
separator = ","
|
||||||
|
prefix = fmt.Sprintf(";%s=", paramName)
|
||||||
|
}
|
||||||
|
case "form":
|
||||||
|
if explode {
|
||||||
|
separator = "&"
|
||||||
|
} else {
|
||||||
|
prefix = fmt.Sprintf("%s=", paramName)
|
||||||
|
separator = ","
|
||||||
|
}
|
||||||
|
case "deepObject":
|
||||||
|
{
|
||||||
|
if !explode {
|
||||||
|
return "", fmt.Errorf("deepObject parameters must be exploded")
|
||||||
|
}
|
||||||
|
for _, k := range sortedKeys(fieldDict) {
|
||||||
|
v := fieldDict[k]
|
||||||
|
part := fmt.Sprintf("%s[%s]=%s", paramName, k, v)
|
||||||
|
parts = append(parts, part)
|
||||||
|
}
|
||||||
|
separator = "&"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported style '%s'", style)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + strings.Join(parts, separator), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stylePrimitive(style string, explode bool, paramName string, value interface{}) (string, error) {
|
||||||
|
strVal, err := primitiveToString(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
switch style {
|
||||||
|
case "simple":
|
||||||
|
case "label":
|
||||||
|
prefix = "."
|
||||||
|
case "matrix":
|
||||||
|
prefix = fmt.Sprintf(";%s=", paramName)
|
||||||
|
case "form":
|
||||||
|
prefix = fmt.Sprintf("%s=", paramName)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported style '%s'", style)
|
||||||
|
}
|
||||||
|
return prefix + url.QueryEscape(strVal), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a primitive value to a string. We need to do this based on the
|
||||||
|
// Kind of an interface, not the Type to work with aliased types.
|
||||||
|
func primitiveToString(value interface{}) (string, error) {
|
||||||
|
var output string
|
||||||
|
|
||||||
|
// Values may come in by pointer for optionals, so make sure to dereferene.
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(value))
|
||||||
|
t := v.Type()
|
||||||
|
kind := t.Kind()
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
output = strconv.FormatInt(v.Int(), 10)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
output = strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
output = "true"
|
||||||
|
} else {
|
||||||
|
output = "false"
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
output = v.String()
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported type %s", reflect.TypeOf(value).String())
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DateFormat = "2006-01-02"
|
||||||
|
|
||||||
|
type Date struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Date) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(d.Time.Format(DateFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Date) UnmarshalJSON(data []byte) error {
|
||||||
|
var dateStr string
|
||||||
|
err := json.Unmarshal(data, &dateStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parsed, err := time.Parse(DateFormat, dateStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Time = parsed
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Email string
|
||||||
|
|
||||||
|
func (e Email) MarshalJSON() ([]byte, error) {
|
||||||
|
if !emailRegex.MatchString(string(e)) {
|
||||||
|
return nil, errors.New("email: failed to pass regex validation")
|
||||||
|
}
|
||||||
|
return json.Marshal(string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Email) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !emailRegex.MatchString(s) {
|
||||||
|
return errors.New("email: failed to pass regex validation")
|
||||||
|
}
|
||||||
|
*e = Email(s)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
const (
|
||||||
|
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emailRegex = regexp.MustCompile(emailRegexString)
|
||||||
|
)
|
|
@ -0,0 +1,5 @@
|
||||||
|
.token
|
||||||
|
dist
|
||||||
|
ops.asc
|
||||||
|
vendor
|
||||||
|
listApis.json
|
|
@ -0,0 +1,30 @@
|
||||||
|
run:
|
||||||
|
timeout: 10m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
golint:
|
||||||
|
min-confidence: 0.3
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 28
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- megacheck
|
||||||
|
- nakedret
|
||||||
|
- scopelint
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
disable-all: true
|
|
@ -0,0 +1,9 @@
|
||||||
|
Pierre-Yves Ritschard
|
||||||
|
Vincent Bernat
|
||||||
|
Chris Baumbauer
|
||||||
|
Marc-Aurèle Brothier
|
||||||
|
Sebastien Goasguen
|
||||||
|
Yoan Blanc
|
||||||
|
Stefano Marengo
|
||||||
|
Pierre-Emmanuel Jacquier
|
||||||
|
Fabrizio Steiner
|
|
@ -0,0 +1,706 @@
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
0.43.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: in `NewClient()`, the `v2.Client` embedded in the `Client` struct doesn't inherit the custom `http.Client` set using `WithHTTPClient()`.
|
||||||
|
|
||||||
|
0.43.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: [Exoscale API V2](https://openapi-v2.exoscale.com/) related code has been relocated under the `github.com/exoscale/egoscale/v2` package.
|
||||||
|
Note: `egoscale.Client` embeds a `v2.Client` initialized implicitly as a convenience.
|
||||||
|
|
||||||
|
0.42.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: new `SKSNodepool.AntiAffinityGroupIDs` field
|
||||||
|
- change: `SKSCluster.Level` field renamed as `SKSCluster.ServiceLevel`
|
||||||
|
|
||||||
|
0.41.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: new method `ListZones()`
|
||||||
|
|
||||||
|
0.40.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- Improve API v2 async job tests and error reporting (#466)
|
||||||
|
|
||||||
|
0.40.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: new method `UpgradeSKSCluster()`
|
||||||
|
- feature: new fields `SKSCluster.Level` and `SKSCluster.CNI`
|
||||||
|
- change: `SKSCluster.EnableExoscaleCloudController` replaced with `SKSCluster.AddOns`
|
||||||
|
|
||||||
|
0.39.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: add missing `UpdateVirtualMachineSecurityGroups` operation metadata
|
||||||
|
|
||||||
|
0.39.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add `UpdateVirtualMachineSecurityGroups` operation (#464)
|
||||||
|
|
||||||
|
0.38.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add `SKSCluster.EvictNodepoolMembers()` and `ListSKSClusterVersions()` methods
|
||||||
|
|
||||||
|
0.37.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `UpdateIPAddress.HealthcheckTLSSkipVerify` field always set to `false` (#462)
|
||||||
|
|
||||||
|
0.37.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: `NewClient()` now accepts options (460)
|
||||||
|
- fix: NLB service healthcheck TLS SNI bug (#461)
|
||||||
|
|
||||||
|
0.36.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `CreateInstancePool.AntiAffinityGroupIDs` field is optional (#459)
|
||||||
|
|
||||||
|
0.36.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for Exoscale Cloud Controller in SKS clusters
|
||||||
|
- fix: add missing tests for SKS Nodepools Security Groups
|
||||||
|
|
||||||
|
0.36.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for Anti-Affinity Groups to Instance Pools
|
||||||
|
- feature: add support for Security Groups to SKS Nodepools
|
||||||
|
|
||||||
|
0.35.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fix typo in version.go
|
||||||
|
|
||||||
|
0.35.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- Improve API v2 errors handling (#455)
|
||||||
|
|
||||||
|
0.35.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: various SKS-related bugs (#454)
|
||||||
|
|
||||||
|
0.35.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for SKS resources (#453)
|
||||||
|
|
||||||
|
0.34.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: `BucketUsage.Usage` is now an `int64` (#451)
|
||||||
|
|
||||||
|
0.33.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: make `GetWithContext` return more relevant errors (#450)
|
||||||
|
|
||||||
|
0.33.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `UpdateNetworkLoadBalancer` call panicking following a public API change
|
||||||
|
|
||||||
|
0.33.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for Network Load Balancer service HTTPS health checking (#449)
|
||||||
|
|
||||||
|
0.32.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for Instance Pool root disk size update (#448)
|
||||||
|
|
||||||
|
0.31.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: add missing TLS-specific parameters to `AssociateIPAddress`
|
||||||
|
|
||||||
|
0.31.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: Instance Pool IPv6 flag handling
|
||||||
|
|
||||||
|
0.31.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add support for IPv6 in Instance Pools (#446)
|
||||||
|
|
||||||
|
0.30.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add new TLS-specific parameters to managed EIP
|
||||||
|
|
||||||
|
0.29.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: `ListVirtualMachines` call to allow searching by `ManagerID` (#442)
|
||||||
|
- fix: remove duplicate `User-Agent` HTTP header in Runstatus calls
|
||||||
|
- tests: `*NetworkLoadBalancer*` calls are now tested using HTTP mocks
|
||||||
|
- codegen: `internal/v2` updated
|
||||||
|
|
||||||
|
0.28.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: Fix `ListVolumes` call to allow searching by ID (#440)
|
||||||
|
|
||||||
|
0.28.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add `Manager`/`ManagerID` fields to `VirtualMachine` structure (#438)
|
||||||
|
- fix: HTTP request User Agent header handling (#439)
|
||||||
|
|
||||||
|
0.27.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add `evictInstancePoolMembers` call to Instance Pool (#437)
|
||||||
|
|
||||||
|
0.26.6
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: Add support for Compute instance templates boot mode (#436)
|
||||||
|
|
||||||
|
0.26.5
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: bug in the ListNetworkLoadBalancers call (#435)
|
||||||
|
|
||||||
|
0.26.4
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fixing typo in previous release
|
||||||
|
|
||||||
|
0.26.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: updated API V2 async operation code (#434)
|
||||||
|
|
||||||
|
0.26.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: updated OpenAPI code-generated API V2 bindings
|
||||||
|
|
||||||
|
0.26.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: the `DisplayText` property of `RegisterCustomTemplate` is now optional (#433)
|
||||||
|
|
||||||
|
0.26.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add support for Network Load Balancer resources (#432)
|
||||||
|
|
||||||
|
0.25.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add support for `listBucketsUsage` (#431)
|
||||||
|
- change: Switch CI to Github Actions (#430)
|
||||||
|
|
||||||
|
0.24.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add export snapshot implementation (#427)
|
||||||
|
- feature: Add support for public API V2 (#425)
|
||||||
|
- change: Switch module to Go 1.14 (#429)
|
||||||
|
- change: Travis CI: set minimum Go version to 1.13
|
||||||
|
- doc: Annotate API doc regarding use of tags (#423)
|
||||||
|
- tests: fix request client timeout handling (#422)
|
||||||
|
|
||||||
|
0.23.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: Add `Resources` field to `APIKey` (#420)
|
||||||
|
|
||||||
|
0.22.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: Remove all references to Network Offerings (#418)
|
||||||
|
|
||||||
|
0.21.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add const `NotFound` 404 on type `ErrorCode` (#417)
|
||||||
|
|
||||||
|
0.20.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: update the `ListAPIKeysResponse` field (#415)
|
||||||
|
|
||||||
|
0.20.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add Instance pool implementation (#410)
|
||||||
|
- feature: Add IAM implementation (#411)
|
||||||
|
|
||||||
|
0.19.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add field `Description` on type `IPAddress` (#413)
|
||||||
|
- change: add Json tag `omitempty` on field `TemplateFilter` in type `ListTemplates` (#412)
|
||||||
|
|
||||||
|
0.18.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: make the "User-Agent" HTTP request header more informative and exposed
|
||||||
|
|
||||||
|
0.18.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add method `DeepCopy` on type `AsyncJobResult` (#403)
|
||||||
|
|
||||||
|
0.17.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- remove: remove the `IsFeatured` parameter from call `RegisterCustomTemplate` (#402)
|
||||||
|
|
||||||
|
0.17.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add parameter `RescueProfile` to call `StartVirtualMachine` (#401)
|
||||||
|
|
||||||
|
0.17.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: add new call `RegisterCustomTemplate` (#400)
|
||||||
|
- feature: add new call `DeleteTemplate` (#399)
|
||||||
|
|
||||||
|
0.16.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feature: Add `Healthcheck*` parameters to call `UpdateIPAddress`
|
||||||
|
- change: Replace satori/go.uuid by gofrs/uuid
|
||||||
|
|
||||||
|
0.15.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: prefix the healthcheck-related params with `Healthcheck` on call `AssociateIPAddress`
|
||||||
|
- EIP: the healthcheck should be a pointer
|
||||||
|
- ip addresses: Add the Healthcheck parameters
|
||||||
|
- readme: point to new lego org (#395)
|
||||||
|
- dns: user_id is not sent back (#394)
|
||||||
|
|
||||||
|
0.14.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `AffinityGroup` lists virtual machines with `UUID` rather than string
|
||||||
|
|
||||||
|
0.14.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `ListVirtualMachines` by `IDs` to accept `UUID` rather than string
|
||||||
|
|
||||||
|
0.14.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `GetRunstatusPage` to always contain the subresources
|
||||||
|
- fix: `ListRunstatus*` to fetch all the subresources
|
||||||
|
- feature: `PaginateRunstatus*` used by list
|
||||||
|
|
||||||
|
0.14.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: all DNS calls require a context
|
||||||
|
- fix: `CreateAffinityGroup` allows empty `name`
|
||||||
|
|
||||||
|
0.13.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: runstatus unmarshalling errors
|
||||||
|
- feature: `UUID` implements DeepCopy, DeepCopyInto
|
||||||
|
- change: export `BooleanResponse`
|
||||||
|
|
||||||
|
0.13.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: initial Runstatus API support
|
||||||
|
- feat: `admin` namespace containing `ListVirtualMachines` for admin usage
|
||||||
|
|
||||||
|
0.13.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Iso` support `ListIsos`, `AttachIso`, and `DetachIso`
|
||||||
|
|
||||||
|
0.13.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: `Paginate` to accept `Listable`
|
||||||
|
- change: `ListCommand` is also `Listable`
|
||||||
|
- change: `client.Get` doesn't modify the given resource, returns a new one
|
||||||
|
- change: `Command` and `AsyncCommand` are fully public, thus extensible
|
||||||
|
- remove: `Gettable`
|
||||||
|
|
||||||
|
0.12.5
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `AuthorizeSecurityGroupEgress` could return `authorizeSecurityGroupIngress` as name
|
||||||
|
|
||||||
|
0.12.4
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Snapshot` is `Listable`
|
||||||
|
|
||||||
|
0.12.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: replace dep by Go modules
|
||||||
|
- change: remove domainid,domain,regionid,listall,isrecursive,... fields
|
||||||
|
- remove: `MigrateVirtualMachine`, `CreateUser`, `EnableAccount`, and other admin calls
|
||||||
|
|
||||||
|
0.12.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `ListNics` has no virtualmachineid limitations anymore
|
||||||
|
- fix: `PCIDevice` ids are not UUIDs
|
||||||
|
|
||||||
|
0.12.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `UpdateVMNicIP` is async
|
||||||
|
|
||||||
|
0.12.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: new VM state `Moving`
|
||||||
|
- feat: `UpdateNetwork` with `startip`, `endip`, `netmask`
|
||||||
|
- feat: `NetworkOffering` is `Listable`
|
||||||
|
- feat: when it fails parsing the body, it shows it
|
||||||
|
- fix: `Snapshot.State` is a string, rather than an scalar
|
||||||
|
- change: signature are now using the v3 version with expires by default
|
||||||
|
|
||||||
|
0.11.6
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `Network.ListRequest` accepts a `Name` argument
|
||||||
|
- change: `SecurityGroup` and the rules aren't `Taggable` anymore
|
||||||
|
|
||||||
|
0.11.5
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: addition of `UpdateVMNicIP`
|
||||||
|
- fix: `UpdateVMAffinityGroup` expected response
|
||||||
|
|
||||||
|
0.11.4
|
||||||
|
------
|
||||||
|
|
||||||
|
*no changes in the core library*
|
||||||
|
|
||||||
|
0.11.3
|
||||||
|
------
|
||||||
|
|
||||||
|
*no changes in the core library*
|
||||||
|
|
||||||
|
0.11.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: empty list responses
|
||||||
|
|
||||||
|
0.11.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `client.Sign` handles correctly the brackets (kudos to @stffabi)
|
||||||
|
- change: `client.Payload` returns a `url.Values`
|
||||||
|
|
||||||
|
0.11.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `listOSCategories` and `OSCategory` type
|
||||||
|
- feat: `listApis` supports recursive response structures
|
||||||
|
- feat: `GetRecordsWithFilters` to list records with name or record_type filters
|
||||||
|
- fix: better `DNSErrorResponse`
|
||||||
|
- fix: `ListResourceLimits` type
|
||||||
|
- change: use UUID everywhere
|
||||||
|
|
||||||
|
0.10.5
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Client.Logger` to plug in any `*log.Logger`
|
||||||
|
- feat: `Client.TraceOn`/`ClientTraceOff` to toggle the HTTP tracing
|
||||||
|
|
||||||
|
0.10.4
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `CIDR` to replace string string
|
||||||
|
- fix: prevent panic on nil
|
||||||
|
|
||||||
|
0.10.3
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Account` is Listable
|
||||||
|
- feat: `MACAddress` to replace string type
|
||||||
|
- fix: Go 1.7 support
|
||||||
|
|
||||||
|
0.10.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: ActivateIP6 response
|
||||||
|
|
||||||
|
0.10.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: expose `SyncRequest` and `SyncRequestWithContext`
|
||||||
|
- feat: addition of reverse DNS calls
|
||||||
|
- feat: addition of `SecurityGroup.UserSecurityGroup`
|
||||||
|
|
||||||
|
0.10.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- global: cloudstack documentation links are moved into cs
|
||||||
|
- global: removal of all the `...Response` types
|
||||||
|
- feat: `Network` is `Listable`
|
||||||
|
- feat: addition of `deleteUser`
|
||||||
|
- feat: addition of `listHosts`
|
||||||
|
- feat: addition of `updateHost`
|
||||||
|
- feat: exo cmd (kudos to @pierre-emmanuelJ)
|
||||||
|
- change: refactor `Gettable` to use `ListRequest`
|
||||||
|
|
||||||
|
0.9.31
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `IPAddress`.`ListRequest` with boolean fields
|
||||||
|
- fix: `Network`.`ListRequest` with boolean fields
|
||||||
|
- fix: `ServiceOffering`.`ListRequest` with boolean fields
|
||||||
|
|
||||||
|
0.9.30
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `VirtualMachine` `PCIDevice` representation was incomplete
|
||||||
|
|
||||||
|
0.9.29
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: `DNSErrorResponse` is a proper `error`
|
||||||
|
|
||||||
|
0.9.28
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: addition of `GetDomains`
|
||||||
|
- fix: `UpdateDomain` may contain more empty fields than `CreateDomain`
|
||||||
|
|
||||||
|
0.9.27
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: expects body to be `application/json`
|
||||||
|
|
||||||
|
0.9.26
|
||||||
|
------
|
||||||
|
|
||||||
|
- change: async timeout strategy wait two seconds and not fib(n) seconds
|
||||||
|
|
||||||
|
0.9.25
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `GetVirtualUserData` response with `Decode` method handling base64 and gzip
|
||||||
|
|
||||||
|
0.9.24
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Template` is `Gettable`
|
||||||
|
- feat: `ServiceOffering` is `Gettable`
|
||||||
|
- feat: addition of `GetAPILimit`
|
||||||
|
- feat: addition of `CreateTemplate`, `PrepareTemplate`, `CopyTemplate`, `UpdateTemplate`, `RegisterTemplate`
|
||||||
|
- feat: addition of `MigrateVirtualMachine`
|
||||||
|
- feat: cmd cli
|
||||||
|
- change: remove useless fields related to Project and VPC
|
||||||
|
|
||||||
|
0.9.23
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `booleanResponse` supports true booleans: https://github.com/apache/cloudstack/pull/2428
|
||||||
|
|
||||||
|
0.9.22
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `ListUsers`, `CreateUser`, `UpdateUser`
|
||||||
|
- feat: `ListResourceDetails`
|
||||||
|
- feat: `SecurityGroup` helper `RuleByID`
|
||||||
|
- feat: `Sign` signs the payload
|
||||||
|
- feat: `UpdateNetworkOffering`
|
||||||
|
- feat: `GetVirtualMachineUserData`
|
||||||
|
- feat: `EnableAccount` and `DisableAccount` (admin stuff)
|
||||||
|
- feat: `AsyncRequest` and `AsyncRequestWithContext` to examine the polling
|
||||||
|
- fix: `AuthorizeSecurityGroupIngress` support for ICMPv6
|
||||||
|
- change: move `APIName()` into the `Client`, nice godoc
|
||||||
|
- change: `Payload` doesn't sign the request anymore
|
||||||
|
- change: `Client` exposes more of its underlying data
|
||||||
|
- change: requests are sent as GET unless it body size is too big
|
||||||
|
|
||||||
|
0.9.21
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Network` is `Listable`
|
||||||
|
- feat: `Zone` is `Gettable`
|
||||||
|
- feat: `Client.Payload` to help preview the HTTP parameters
|
||||||
|
- feat: generate command utility
|
||||||
|
- fix: `CreateSnapshot` was missing the `Name` attribute
|
||||||
|
- fix: `ListSnapshots` was missing the `IDs` attribute
|
||||||
|
- fix: `ListZones` was missing the `NetworkType` attribute
|
||||||
|
- fix: `ListAsyncJobs` was missing the `ListAll` attribute
|
||||||
|
- change: ICMP Type/Code are uint8 and TCP/UDP port are uint16
|
||||||
|
|
||||||
|
0.9.20
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `Template` is `Listable`
|
||||||
|
- feat: `IPAddress` is `Listable`
|
||||||
|
- change: `List` and `Paginate` return pointers
|
||||||
|
- fix: `Template` was missing `tags`
|
||||||
|
|
||||||
|
0.9.19
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `SSHKeyPair` is `Listable`
|
||||||
|
|
||||||
|
0.9.18
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `VirtualMachine` is `Listable`
|
||||||
|
- feat: new `Client.Paginate` and `Client.PaginateWithContext`
|
||||||
|
- change: the inner logic of `Listable`
|
||||||
|
- remove: not working `Client.AsyncList`
|
||||||
|
|
||||||
|
0.9.17
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `AuthorizeSecurityGroup(In|E)gress` startport may be zero
|
||||||
|
|
||||||
|
0.9.16
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: new `Listable` interface
|
||||||
|
- feat: `Nic` is `Listable`
|
||||||
|
- feat: `Volume` is `Listable`
|
||||||
|
- feat: `Zone` is `Listable`
|
||||||
|
- feat: `AffinityGroup` is `Listable`
|
||||||
|
- remove: deprecated methods `ListNics`, `AddIPToNic`, and `RemoveIPFromNic`
|
||||||
|
- remove: deprecated method `GetRootVolumeForVirtualMachine`
|
||||||
|
|
||||||
|
0.9.15
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: `IPAddress` is `Gettable` and `Deletable`
|
||||||
|
- fix: serialization of *bool
|
||||||
|
|
||||||
|
0.9.14
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: `GetVMPassword` response
|
||||||
|
- remove: deprecated `GetTopology`, `GetImages`, and al
|
||||||
|
|
||||||
|
0.9.13
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: IP4 and IP6 flags to DeployVirtualMachine
|
||||||
|
- feat: add ActivateIP6
|
||||||
|
- fix: error message was gobbled on 40x
|
||||||
|
|
||||||
|
0.9.12
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: add `BooleanRequestWithContext`
|
||||||
|
- feat: add `client.Get`, `client.GetWithContext` to fetch a resource
|
||||||
|
- feat: add `cleint.Delete`, `client.DeleteWithContext` to delete a resource
|
||||||
|
- feat: `SSHKeyPair` is `Gettable` and `Deletable`
|
||||||
|
- feat: `VirtualMachine` is `Gettable` and `Deletable`
|
||||||
|
- feat: `AffinityGroup` is `Gettable` and `Deletable`
|
||||||
|
- feat: `SecurityGroup` is `Gettable` and `Deletable`
|
||||||
|
- remove: deprecated methods `CreateAffinityGroup`, `DeleteAffinityGroup`
|
||||||
|
- remove: deprecated methods `CreateKeypair`, `DeleteKeypair`, `RegisterKeypair`
|
||||||
|
- remove: deprecated method `GetSecurityGroupID`
|
||||||
|
|
||||||
|
0.9.11
|
||||||
|
------
|
||||||
|
|
||||||
|
- feat: CloudStack API name is now public `APIName()`
|
||||||
|
- feat: enforce the mutual exclusivity of some fields
|
||||||
|
- feat: add `context.Context` to `RequestWithContext`
|
||||||
|
- change: `AsyncRequest` and `BooleanAsyncRequest` are gone, use `Request` and `BooleanRequest` instead.
|
||||||
|
- change: `AsyncInfo` is no more
|
||||||
|
|
||||||
|
0.9.10
|
||||||
|
------
|
||||||
|
|
||||||
|
- fix: typo made ListAll required in ListPublicIPAddresses
|
||||||
|
- fix: all bool are now *bool, respecting CS default value
|
||||||
|
- feat: (*VM).DefaultNic() to obtain the main Nic
|
||||||
|
|
||||||
|
0.9.9
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: affinity groups virtualmachineIds attribute
|
||||||
|
- fix: uuidList is not a list of strings
|
||||||
|
|
||||||
|
0.9.8
|
||||||
|
-----
|
||||||
|
|
||||||
|
- feat: add RootDiskSize to RestoreVirtualMachine
|
||||||
|
- fix: monotonic polling using Context
|
||||||
|
|
||||||
|
0.9.7
|
||||||
|
-----
|
||||||
|
|
||||||
|
- feat: add Taggable interface to expose ResourceType
|
||||||
|
- feat: add (Create|Update|Delete|List)InstanceGroup(s)
|
||||||
|
- feat: add RegisterUserKeys
|
||||||
|
- feat: add ListResourceLimits
|
||||||
|
- feat: add ListAccounts
|
||||||
|
|
||||||
|
0.9.6
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: update UpdateVirtualMachine userdata
|
||||||
|
- fix: Network's name/displaytext might be empty
|
||||||
|
|
||||||
|
0.9.5
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: serialization of slice
|
||||||
|
|
||||||
|
0.9.4
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: constants
|
||||||
|
|
||||||
|
0.9.3
|
||||||
|
-----
|
||||||
|
|
||||||
|
- change: userdata expects a string
|
||||||
|
- change: no pointer in sub-struct's
|
||||||
|
|
||||||
|
0.9.2
|
||||||
|
-----
|
||||||
|
|
||||||
|
- bug: createNetwork is a sync call
|
||||||
|
- bug: typo in listVirtualMachines' domainid
|
||||||
|
- bug: serialization of map[string], e.g. UpdateVirtualMachine
|
||||||
|
- change: IPAddress's use net.IP type
|
||||||
|
- feat: helpers VM.NicsByType, VM.NicByNetworkID, VM.NicByID
|
||||||
|
- feat: addition of CloudStack ApiErrorCode constants
|
||||||
|
|
||||||
|
0.9.1
|
||||||
|
-----
|
||||||
|
|
||||||
|
- bug: sync calls returns succes as a string rather than a bool
|
||||||
|
- change: unexport BooleanResponse types
|
||||||
|
- feat: original CloudStack error response can be obtained
|
||||||
|
|
||||||
|
0.9.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Big refactoring, addition of the documentation, compliance to golint.
|
||||||
|
|
||||||
|
0.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Initial library
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014 exoscale(tm)
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
title: Egoscale
|
||||||
|
description: the Go library for Exoscale
|
||||||
|
---
|
||||||
|
|
||||||
|
<a href="https://gopherize.me/gopher/9c1bc7cfe1d84cf43e477dbfc4aa86332065f1fd"><img src="gopher.png" align="right" alt=""></a>
|
||||||
|
|
||||||
|
[![Actions Status](https://github.com/exoscale/egoscale/workflows/CI/badge.svg?branch=master)](https://github.com/exoscale/egoscale/actions?query=workflow%3ACI+branch%3Amaster)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/exoscale/egoscale?status.svg)](https://godoc.org/github.com/exoscale/egoscale) [![Go Report Card](https://goreportcard.com/badge/github.com/exoscale/egoscale)](https://goreportcard.com/report/github.com/exoscale/egoscale)
|
||||||
|
|
||||||
|
A wrapper for the [Exoscale public cloud](https://www.exoscale.com) API.
|
||||||
|
|
||||||
|
## Known users
|
||||||
|
|
||||||
|
- [Exoscale CLI](https://github.com/exoscale/cli)
|
||||||
|
- [Exoscale Terraform provider](https://github.com/exoscale/terraform-provider-exoscale)
|
||||||
|
- [ExoIP](https://github.com/exoscale/exoip): IP Watchdog
|
||||||
|
- [Lego](https://github.com/go-acme/lego): Let's Encrypt and ACME library
|
||||||
|
- Kubernetes Incubator: [External DNS](https://github.com/kubernetes-incubator/external-dns)
|
||||||
|
- [Docker machine](https://docs.docker.com/machine/drivers/exoscale/)
|
||||||
|
- [etc.](https://godoc.org/github.com/exoscale/egoscale?importers)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||||
|
may not use this file except in compliance with the License. You may
|
||||||
|
obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
@ -0,0 +1,80 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// Account provides the detailed account information
|
||||||
|
type Account struct {
|
||||||
|
AccountDetails map[string]string `json:"accountdetails,omitempty" doc:"details for the account"`
|
||||||
|
CPUAvailable string `json:"cpuavailable,omitempty" doc:"the total number of cpu cores available to be created for this account"`
|
||||||
|
CPULimit string `json:"cpulimit,omitempty" doc:"the total number of cpu cores the account can own"`
|
||||||
|
CPUTotal int64 `json:"cputotal,omitempty" doc:"the total number of cpu cores owned by account"`
|
||||||
|
DefaultZoneID *UUID `json:"defaultzoneid,omitempty" doc:"the default zone of the account"`
|
||||||
|
EipLimit string `json:"eiplimit,omitempty" doc:"the total number of public elastic ip addresses this account can acquire"`
|
||||||
|
Groups []string `json:"groups,omitempty" doc:"the list of acl groups that account belongs to"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the id of the account"`
|
||||||
|
IPAvailable string `json:"ipavailable,omitempty" doc:"the total number of public ip addresses available for this account to acquire"`
|
||||||
|
IPLimit string `json:"iplimit,omitempty" doc:"the total number of public ip addresses this account can acquire"`
|
||||||
|
IPTotal int64 `json:"iptotal,omitempty" doc:"the total number of public ip addresses allocated for this account"`
|
||||||
|
IsCleanupRequired bool `json:"iscleanuprequired,omitempty" doc:"true if the account requires cleanup"`
|
||||||
|
IsDefault bool `json:"isdefault,omitempty" doc:"true if account is default, false otherwise"`
|
||||||
|
MemoryAvailable string `json:"memoryavailable,omitempty" doc:"the total memory (in MB) available to be created for this account"`
|
||||||
|
MemoryLimit string `json:"memorylimit,omitempty" doc:"the total memory (in MB) the account can own"`
|
||||||
|
MemoryTotal int64 `json:"memorytotal,omitempty" doc:"the total memory (in MB) owned by account"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the account"`
|
||||||
|
NetworkAvailable string `json:"networkavailable,omitempty" doc:"the total number of networks available to be created for this account"`
|
||||||
|
NetworkDomain string `json:"networkdomain,omitempty" doc:"the network domain"`
|
||||||
|
NetworkLimit string `json:"networklimit,omitempty" doc:"the total number of networks the account can own"`
|
||||||
|
NetworkTotal int64 `json:"networktotal,omitempty" doc:"the total number of networks owned by account"`
|
||||||
|
PrimaryStorageAvailable string `json:"primarystorageavailable,omitempty" doc:"the total primary storage space (in GiB) available to be used for this account"`
|
||||||
|
PrimaryStorageLimit string `json:"primarystoragelimit,omitempty" doc:"the total primary storage space (in GiB) the account can own"`
|
||||||
|
PrimaryStorageTotal int64 `json:"primarystoragetotal,omitempty" doc:"the total primary storage space (in GiB) owned by account"`
|
||||||
|
ProjectAvailable string `json:"projectavailable,omitempty" doc:"the total number of projects available for administration by this account"`
|
||||||
|
ProjectLimit string `json:"projectlimit,omitempty" doc:"the total number of projects the account can own"`
|
||||||
|
ProjectTotal int64 `json:"projecttotal,omitempty" doc:"the total number of projects being administrated by this account"`
|
||||||
|
SecondaryStorageAvailable string `json:"secondarystorageavailable,omitempty" doc:"the total secondary storage space (in GiB) available to be used for this account"`
|
||||||
|
SecondaryStorageLimit string `json:"secondarystoragelimit,omitempty" doc:"the total secondary storage space (in GiB) the account can own"`
|
||||||
|
SecondaryStorageTotal int64 `json:"secondarystoragetotal,omitempty" doc:"the total secondary storage space (in GiB) owned by account"`
|
||||||
|
SMTP bool `json:"smtp,omitempty" doc:"if SMTP outbound is allowed"`
|
||||||
|
SnapshotAvailable string `json:"snapshotavailable,omitempty" doc:"the total number of snapshots available for this account"`
|
||||||
|
SnapshotLimit string `json:"snapshotlimit,omitempty" doc:"the total number of snapshots which can be stored by this account"`
|
||||||
|
SnapshotTotal int64 `json:"snapshottotal,omitempty" doc:"the total number of snapshots stored by this account"`
|
||||||
|
State string `json:"state,omitempty" doc:"the state of the account"`
|
||||||
|
TemplateAvailable string `json:"templateavailable,omitempty" doc:"the total number of templates available to be created by this account"`
|
||||||
|
TemplateLimit string `json:"templatelimit,omitempty" doc:"the total number of templates which can be created by this account"`
|
||||||
|
TemplateTotal int64 `json:"templatetotal,omitempty" doc:"the total number of templates which have been created by this account"`
|
||||||
|
User []User `json:"user,omitempty" doc:"the list of users associated with account"`
|
||||||
|
VMAvailable string `json:"vmavailable,omitempty" doc:"the total number of virtual machines available for this account to acquire"`
|
||||||
|
VMLimit string `json:"vmlimit,omitempty" doc:"the total number of virtual machines that can be deployed by this account"`
|
||||||
|
VMRunning int `json:"vmrunning,omitempty" doc:"the total number of virtual machines running for this account"`
|
||||||
|
VMStopped int `json:"vmstopped,omitempty" doc:"the total number of virtual machines stopped for this account"`
|
||||||
|
VMTotal int64 `json:"vmtotal,omitempty" doc:"the total number of virtual machines deployed by this account"`
|
||||||
|
VolumeAvailable string `json:"volumeavailable,omitempty" doc:"the total volume available for this account"`
|
||||||
|
VolumeLimit string `json:"volumelimit,omitempty" doc:"the total volume which can be used by this account"`
|
||||||
|
VolumeTotal int64 `json:"volumetotal,omitempty" doc:"the total volume being used by this account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListAccountsGroups request
|
||||||
|
func (a Account) ListRequest() (ListCommand, error) {
|
||||||
|
return &ListAccounts{
|
||||||
|
ID: a.ID,
|
||||||
|
State: a.State,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListAccounts
|
||||||
|
|
||||||
|
// ListAccounts represents a query to display the accounts
|
||||||
|
type ListAccounts struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List account by account ID"`
|
||||||
|
IsCleanUpRequired *bool `json:"iscleanuprequired,omitempty" doc:"list accounts by cleanuprequired attribute (values are true or false)"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"List account by account name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
State string `json:"state,omitempty" doc:"List accounts by state. Valid states are enabled, disabled, and locked."`
|
||||||
|
_ bool `name:"listAccounts" description:"Lists accounts and provides detailed account information for listed accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccountsResponse represents a list of accounts
|
||||||
|
type ListAccountsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Account []Account `json:"account"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListAccounts) Response() interface{} {
|
||||||
|
return new(ListAccountsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListAccounts) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListAccounts) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListAccounts) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListAccounts) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListAccountsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListAccountsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Account {
|
||||||
|
if !callback(&items.Account[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Healthcheck represents an Healthcheck attached to an IP
|
||||||
|
type Healthcheck struct {
|
||||||
|
Interval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
|
||||||
|
Mode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp' or 'http'"`
|
||||||
|
Path string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
|
||||||
|
Port int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
|
||||||
|
StrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
|
||||||
|
StrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
|
||||||
|
Timeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
|
||||||
|
TLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks"`
|
||||||
|
TLSSkipVerify bool `json:"tls-skip-verify" doc:"healthcheck definition: bypass certificate chain verification for HTTPS checks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress represents an IP Address
|
||||||
|
type IPAddress struct {
|
||||||
|
Allocated string `json:"allocated,omitempty" doc:"date the public IP address was acquired"`
|
||||||
|
Associated string `json:"associated,omitempty" doc:"date the public IP address was associated"`
|
||||||
|
AssociatedNetworkID *UUID `json:"associatednetworkid,omitempty" doc:"the ID of the Network associated with the IP address"`
|
||||||
|
AssociatedNetworkName string `json:"associatednetworkname,omitempty" doc:"the name of the Network associated with the IP address"`
|
||||||
|
Description string `json:"description,omitempty" doc:"The IP address description."`
|
||||||
|
ForVirtualNetwork bool `json:"forvirtualnetwork,omitempty" doc:"the virtual network for the IP address"`
|
||||||
|
Healthcheck *Healthcheck `json:"healthcheck,omitempty" doc:"The IP healthcheck configuration"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"public IP address id"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"public IP address"`
|
||||||
|
IsElastic bool `json:"iselastic,omitempty" doc:"is an elastic ip"`
|
||||||
|
IsPortable bool `json:"isportable,omitempty" doc:"is public IP portable across the zones"`
|
||||||
|
IsSourceNat bool `json:"issourcenat,omitempty" doc:"true if the IP address is a source nat address, false otherwise"`
|
||||||
|
IsStaticNat *bool `json:"isstaticnat,omitempty" doc:"true if this ip is for static nat, false otherwise"`
|
||||||
|
IsSystem bool `json:"issystem,omitempty" doc:"true if this ip is system ip (was allocated as a part of deployVm or createLbRule)"`
|
||||||
|
NetworkID *UUID `json:"networkid,omitempty" doc:"the ID of the Network where ip belongs to"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"the physical network this belongs to"`
|
||||||
|
Purpose string `json:"purpose,omitempty" doc:"purpose of the IP address. In Acton this value is not null for Ips with isSystem=true, and can have either StaticNat or LB value"`
|
||||||
|
ReverseDNS []ReverseDNS `json:"reversedns,omitempty" doc:"the list of PTR record(s) associated with the ip address"`
|
||||||
|
State string `json:"state,omitempty" doc:"State of the ip address. Can be: Allocatin, Allocated and Releasing"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"the list of resource tags associated with ip address"`
|
||||||
|
VirtualMachineDisplayName string `json:"virtualmachinedisplayname,omitempty" doc:"virtual machine display name the ip address is assigned to (not null only for static nat Ip)"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"virtual machine id the ip address is assigned to (not null only for static nat Ip)"`
|
||||||
|
VirtualMachineName string `json:"virtualmachinename,omitempty" doc:"virtual machine name the ip address is assigned to (not null only for static nat Ip)"`
|
||||||
|
VlanID *UUID `json:"vlanid,omitempty" doc:"the ID of the VLAN associated with the IP address. This parameter is visible to ROOT admins only"`
|
||||||
|
VlanName string `json:"vlanname,omitempty" doc:"the VLAN associated with the IP address"`
|
||||||
|
VMIPAddress net.IP `json:"vmipaddress,omitempty" doc:"virtual machine (dnat) ip address (not null only for static nat Ip)"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"the ID of the zone the public IP address belongs to"`
|
||||||
|
ZoneName string `json:"zonename,omitempty" doc:"the name of the zone the public IP address belongs to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceType returns the type of the resource
|
||||||
|
func (IPAddress) ResourceType() string {
|
||||||
|
return "PublicIpAddress"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListAdresses request
|
||||||
|
func (ipaddress IPAddress) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListPublicIPAddresses{
|
||||||
|
AssociatedNetworkID: ipaddress.AssociatedNetworkID,
|
||||||
|
ID: ipaddress.ID,
|
||||||
|
IPAddress: ipaddress.IPAddress,
|
||||||
|
PhysicalNetworkID: ipaddress.PhysicalNetworkID,
|
||||||
|
VlanID: ipaddress.VlanID,
|
||||||
|
ZoneID: ipaddress.ZoneID,
|
||||||
|
}
|
||||||
|
if ipaddress.IsElastic {
|
||||||
|
req.IsElastic = &ipaddress.IsElastic
|
||||||
|
}
|
||||||
|
if ipaddress.IsSourceNat {
|
||||||
|
req.IsSourceNat = &ipaddress.IsSourceNat
|
||||||
|
}
|
||||||
|
if ipaddress.ForVirtualNetwork {
|
||||||
|
req.ForVirtualNetwork = &ipaddress.ForVirtualNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the resource
|
||||||
|
func (ipaddress IPAddress) Delete(ctx context.Context, client *Client) error {
|
||||||
|
if ipaddress.ID == nil {
|
||||||
|
return fmt.Errorf("an IPAddress may only be deleted using ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.BooleanRequestWithContext(ctx, &DisassociateIPAddress{
|
||||||
|
ID: ipaddress.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssociateIPAddress (Async) represents the IP creation
|
||||||
|
type AssociateIPAddress struct {
|
||||||
|
Description string `json:"description,omitempty" doc:"The IP address description."`
|
||||||
|
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
|
||||||
|
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp', 'http', or 'https'"`
|
||||||
|
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http' or 'https', ignored otherwise."`
|
||||||
|
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
|
||||||
|
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
|
||||||
|
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
|
||||||
|
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
|
||||||
|
HealthcheckTLSSkipVerify bool `json:"tls-skip-verify,omitempty" doc:"healthcheck definition: skip TLS verification for HTTPS checks. Default: false"`
|
||||||
|
HealthcheckTLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks. Default: no server name is presented"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"the ID of the availability zone you want to acquire a public IP address from"`
|
||||||
|
_ bool `name:"associateIpAddress" description:"Acquires and associates a public IP to an account."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (AssociateIPAddress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (AssociateIPAddress) AsyncResponse() interface{} {
|
||||||
|
return new(IPAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisassociateIPAddress (Async) represents the IP deletion
|
||||||
|
type DisassociateIPAddress struct {
|
||||||
|
ID *UUID `json:"id" doc:"the id of the public ip address to disassociate"`
|
||||||
|
_ bool `name:"disassociateIpAddress" description:"Disassociates an ip address from the account."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DisassociateIPAddress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DisassociateIPAddress) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIPAddress (Async) represents the IP modification
|
||||||
|
type UpdateIPAddress struct {
|
||||||
|
Description string `json:"description,omitempty" doc:"The IP address description."`
|
||||||
|
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
|
||||||
|
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp', 'http', or 'https'"`
|
||||||
|
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
|
||||||
|
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
|
||||||
|
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
|
||||||
|
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
|
||||||
|
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
|
||||||
|
HealthcheckTLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks"`
|
||||||
|
HealthcheckTLSSkipVerify bool `json:"tls-skip-verify,omitempty" doc:"healthcheck definition: bypass certificate chain verification for HTTPS checks"`
|
||||||
|
ID *UUID `json:"id" doc:"the id of the public IP address to update"`
|
||||||
|
_ bool `name:"updateIpAddress" description:"Updates an IP address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (UpdateIPAddress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (UpdateIPAddress) AsyncResponse() interface{} {
|
||||||
|
return new(IPAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListPublicIPAddresses
|
||||||
|
|
||||||
|
// ListPublicIPAddresses represents a search for public IP addresses
|
||||||
|
type ListPublicIPAddresses struct {
|
||||||
|
AllocatedOnly *bool `json:"allocatedonly,omitempty" doc:"limits search results to allocated public IP addresses"`
|
||||||
|
AssociatedNetworkID *UUID `json:"associatednetworkid,omitempty" doc:"lists all public IP addresses associated to the network specified"`
|
||||||
|
ForLoadBalancing *bool `json:"forloadbalancing,omitempty" doc:"list only ips used for load balancing"`
|
||||||
|
ForVirtualNetwork *bool `json:"forvirtualnetwork,omitempty" doc:"the virtual network for the IP address"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"lists ip address by id"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"lists the specified IP address"`
|
||||||
|
IsElastic *bool `json:"iselastic,omitempty" doc:"list only elastic ip addresses"`
|
||||||
|
IsSourceNat *bool `json:"issourcenat,omitempty" doc:"list only source nat ip addresses"`
|
||||||
|
IsStaticNat *bool `json:"isstaticnat,omitempty" doc:"list only static nat ip addresses"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"lists all public IP addresses by physical network id"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
|
||||||
|
VlanID *UUID `json:"vlanid,omitempty" doc:"lists all public IP addresses by VLAN ID"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"lists all public IP addresses by Zone ID"`
|
||||||
|
_ bool `name:"listPublicIpAddresses" description:"Lists all public ip addresses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPublicIPAddressesResponse represents a list of public IP addresses
|
||||||
|
type ListPublicIPAddressesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
PublicIPAddress []IPAddress `json:"publicipaddress"`
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AffinityGroup represents an Affinity Group.
|
||||||
|
//
|
||||||
|
// Affinity and Anti-Affinity Groups provide a way to influence where VMs should run.
|
||||||
|
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/stable/virtual_machines.html#affinity-groups
|
||||||
|
type AffinityGroup struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account owning the Affinity Group"`
|
||||||
|
Description string `json:"description,omitempty" doc:"the description of the Affinity Group"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the Affinity Group"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the Affinity Group"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the Affinity Group"`
|
||||||
|
VirtualMachineIDs []UUID `json:"virtualmachineIds,omitempty" doc:"virtual machine IDs associated with this Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListAffinityGroups request.
|
||||||
|
func (ag AffinityGroup) ListRequest() (ListCommand, error) {
|
||||||
|
return &ListAffinityGroups{
|
||||||
|
ID: ag.ID,
|
||||||
|
Name: ag.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the given Affinity Group.
|
||||||
|
func (ag AffinityGroup) Delete(ctx context.Context, client *Client) error {
|
||||||
|
if ag.ID == nil && ag.Name == "" {
|
||||||
|
return fmt.Errorf("an Affinity Group may only be deleted using ID or Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &DeleteAffinityGroup{}
|
||||||
|
|
||||||
|
if ag.ID != nil {
|
||||||
|
req.ID = ag.ID
|
||||||
|
} else {
|
||||||
|
req.Name = ag.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.BooleanRequestWithContext(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AffinityGroupType represent an Affinity Group type.
|
||||||
|
type AffinityGroupType struct {
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAffinityGroup (Async) represents a new Affinity Group.
|
||||||
|
type CreateAffinityGroup struct {
|
||||||
|
Description string `json:"description,omitempty" doc:"Optional description of the Affinity Group"`
|
||||||
|
Name string `json:"name" doc:"Name of the Affinity Group"`
|
||||||
|
Type string `json:"type" doc:"Type of the Affinity Group from the available Affinity Group Group types"`
|
||||||
|
_ bool `name:"createAffinityGroup" description:"Creates an Affinity Group Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req CreateAffinityGroup) onBeforeSend(params url.Values) error {
|
||||||
|
// Name must be set, but can be empty.
|
||||||
|
if req.Name == "" {
|
||||||
|
params.Set("name", "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (CreateAffinityGroup) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job.
|
||||||
|
func (CreateAffinityGroup) AsyncResponse() interface{} {
|
||||||
|
return new(AffinityGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVMAffinityGroup (Async) represents a modification of an Affinity Group.
|
||||||
|
type UpdateVMAffinityGroup struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the virtual machine"`
|
||||||
|
AffinityGroupIDs []UUID `json:"affinitygroupids,omitempty" doc:"comma separated list of Affinity Groups id that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupnames parameter"`
|
||||||
|
AffinityGroupNames []string `json:"affinitygroupnames,omitempty" doc:"comma separated list of Affinity Groups names that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupids parameter"`
|
||||||
|
_ bool `name:"updateVMAffinityGroup" description:"Updates the Affinity Group Group associations of a virtual machine. The VM has to be stopped and restarted for the new properties to take effect."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UpdateVMAffinityGroup) onBeforeSend(params url.Values) error {
|
||||||
|
// Either AffinityGroupIDs or AffinityGroupNames must be set.
|
||||||
|
if len(req.AffinityGroupIDs) == 0 && len(req.AffinityGroupNames) == 0 {
|
||||||
|
params.Set("affinitygroupids", "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (UpdateVMAffinityGroup) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job.
|
||||||
|
func (UpdateVMAffinityGroup) AsyncResponse() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAffinityGroup (Async) represents an Affinity Group to be deleted.
|
||||||
|
type DeleteAffinityGroup struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"The ID of the Affinity Group. Mutually exclusive with name parameter"`
|
||||||
|
Name string `json:"name,omitempty" doc:"The name of the Affinity Group. Mutually exclusive with ID parameter"`
|
||||||
|
_ bool `name:"deleteAffinityGroup" description:"Deletes Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (DeleteAffinityGroup) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job.
|
||||||
|
func (DeleteAffinityGroup) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListAffinityGroups
|
||||||
|
|
||||||
|
// ListAffinityGroups represents an Affinity Groups search.
|
||||||
|
type ListAffinityGroups struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List the Affinity Group by the ID provided"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"Lists Affinity Groups by name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
Type string `json:"type,omitempty" doc:"Lists Affinity Groups by type"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Lists Affinity Groups by virtual machine ID"`
|
||||||
|
_ bool `name:"listAffinityGroups" description:"Lists Affinity Groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAffinityGroupsResponse represents a list of Affinity Groups.
|
||||||
|
type ListAffinityGroupsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
AffinityGroup []AffinityGroup `json:"affinitygroup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAffinityGroupTypes represents an Affinity Groups types search.
|
||||||
|
type ListAffinityGroupTypes struct {
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
_ bool `name:"listAffinityGroupTypes" description:"Lists Affinity Group types available"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (ListAffinityGroupTypes) Response() interface{} {
|
||||||
|
return new(ListAffinityGroupTypesResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAffinityGroupTypesResponse represents a list of Affinity Group types.
|
||||||
|
type ListAffinityGroupTypesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
AffinityGroupType []AffinityGroupType `json:"affinitygrouptype"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (ListAffinityGroups) Response() interface{} {
|
||||||
|
return new(ListAffinityGroupsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself.
|
||||||
|
func (ls *ListAffinityGroups) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current page.
|
||||||
|
func (ls *ListAffinityGroups) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size.
|
||||||
|
func (ls *ListAffinityGroups) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue.
|
||||||
|
func (ListAffinityGroups) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListAffinityGroupsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListAffinityGroupsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.AffinityGroup {
|
||||||
|
if !callback(&items.AffinityGroup[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AntiAffinityGroup represents an Anti-Affinity Group.
|
||||||
|
type AntiAffinityGroup struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account owning the Anti-Affinity Group"`
|
||||||
|
Description string `json:"description,omitempty" doc:"the description of the Anti-Affinity Group"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the Anti-Affinity Group"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the Anti-Affinity Group"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the Anti-Affinity Group"`
|
||||||
|
VirtualMachineIDs []UUID `json:"virtualmachineIds,omitempty" doc:"virtual machine IDs associated with this Anti-Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListAntiAffinityGroups request.
|
||||||
|
func (ag AntiAffinityGroup) ListRequest() (ListCommand, error) {
|
||||||
|
return &ListAffinityGroups{
|
||||||
|
ID: ag.ID,
|
||||||
|
Name: ag.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the given Anti-Affinity Group.
|
||||||
|
func (ag AntiAffinityGroup) Delete(ctx context.Context, client *Client) error {
|
||||||
|
if ag.ID == nil && ag.Name == "" {
|
||||||
|
return fmt.Errorf("an Anti-Affinity Group may only be deleted using ID or Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &DeleteAffinityGroup{}
|
||||||
|
|
||||||
|
if ag.ID != nil {
|
||||||
|
req.ID = ag.ID
|
||||||
|
} else {
|
||||||
|
req.Name = ag.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.BooleanRequestWithContext(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAntiAffinityGroup represents an Anti-Affinity Group creation.
|
||||||
|
type CreateAntiAffinityGroup struct {
|
||||||
|
Name string `json:"name" doc:"Name of the Anti-Affinity Group"`
|
||||||
|
Description string `json:"description,omitempty" doc:"Optional description of the Anti-Affinity Group"`
|
||||||
|
_ bool `name:"createAntiAffinityGroup" description:"Creates an Anti-Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req CreateAntiAffinityGroup) onBeforeSend(params url.Values) error {
|
||||||
|
// Name must be set, but can be empty.
|
||||||
|
if req.Name == "" {
|
||||||
|
params.Set("name", "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (CreateAntiAffinityGroup) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job.
|
||||||
|
func (CreateAntiAffinityGroup) AsyncResponse() interface{} {
|
||||||
|
return new(AffinityGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListAntiAffinityGroups
|
||||||
|
|
||||||
|
// ListAntiAffinityGroups represents an Anti-Affinity Groups search.
|
||||||
|
type ListAntiAffinityGroups struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List the Anti-Affinity Group by the ID provided"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"Lists Anti-Affinity Groups by name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Lists Anti-Affinity Groups by virtual machine ID"`
|
||||||
|
_ bool `name:"listAntiAffinityGroups" description:"Lists Anti-Affinity Groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAntiAffinityGroupsResponse represents a list of Anti-Affinity Groups.
|
||||||
|
type ListAntiAffinityGroupsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
AntiAffinityGroup []AffinityGroup `json:"antiaffinitygroup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAntiAffinityGroup (Async) represents an Anti-Affinity Group to be deleted.
|
||||||
|
type DeleteAntiAffinityGroup struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"The ID of the Anti-Affinity Group. Mutually exclusive with name parameter"`
|
||||||
|
Name string `json:"name,omitempty" doc:"The name of the Anti-Affinity Group. Mutually exclusive with ID parameter"`
|
||||||
|
_ bool `name:"deleteAntiAffinityGroup" description:"Deletes Anti-Affinity Group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (DeleteAntiAffinityGroup) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job.
|
||||||
|
func (DeleteAntiAffinityGroup) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
43
vendor/github.com/exoscale/egoscale/antiaffinity_groups_response.go
generated
vendored
Normal file
43
vendor/github.com/exoscale/egoscale/antiaffinity_groups_response.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal.
|
||||||
|
func (ListAntiAffinityGroups) Response() interface{} {
|
||||||
|
return new(ListAntiAffinityGroupsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself.
|
||||||
|
func (ls *ListAntiAffinityGroups) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current page.
|
||||||
|
func (ls *ListAntiAffinityGroups) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size.
|
||||||
|
func (ls *ListAntiAffinityGroups) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue.
|
||||||
|
func (ListAntiAffinityGroups) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListAntiAffinityGroupsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListAntiAffinityGroupsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.AntiAffinityGroup {
|
||||||
|
if !callback(&items.AntiAffinityGroup[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// API represents an API service
|
||||||
|
type API struct {
|
||||||
|
Description string `json:"description,omitempty" doc:"description of the api"`
|
||||||
|
IsAsync bool `json:"isasync" doc:"true if api is asynchronous"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the api command"`
|
||||||
|
Related string `json:"related,omitempty" doc:"comma separated related apis"`
|
||||||
|
Since string `json:"since,omitempty" doc:"version of CloudStack the api was introduced in"`
|
||||||
|
Type string `json:"type,omitempty" doc:"response field type"`
|
||||||
|
Params []APIParam `json:"params,omitempty" doc:"the list params the api accepts"`
|
||||||
|
Response []APIField `json:"response,omitempty" doc:"api response fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIParam represents an API parameter field
|
||||||
|
type APIParam struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Length int64 `json:"length"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Since string `json:"since,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIField represents an API response field
|
||||||
|
type APIField struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Response []APIField `json:"response,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIs represents a query to list the api
|
||||||
|
type ListAPIs struct {
|
||||||
|
Name string `json:"name,omitempty" doc:"API name"`
|
||||||
|
_ bool `name:"listApis" description:"lists all available apis on the server"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIsResponse represents a list of API
|
||||||
|
type ListAPIsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
API []API `json:"api"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*ListAPIs) Response() interface{} {
|
||||||
|
return new(ListAPIsResponse)
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AsyncJobResult represents an asynchronous job result
|
||||||
|
type AsyncJobResult struct {
|
||||||
|
AccountID *UUID `json:"accountid,omitempty" doc:"the account that executed the async command"`
|
||||||
|
Cmd string `json:"cmd,omitempty" doc:"the async command executed"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the created date of the job"`
|
||||||
|
JobID *UUID `json:"jobid" doc:"extra field for the initial async call"`
|
||||||
|
JobInstanceID *UUID `json:"jobinstanceid,omitempty" doc:"the unique ID of the instance/entity object related to the job"`
|
||||||
|
JobInstanceType string `json:"jobinstancetype,omitempty" doc:"the instance/entity object related to the job"`
|
||||||
|
JobProcStatus int `json:"jobprocstatus,omitempty" doc:"the progress information of the PENDING job"`
|
||||||
|
JobResult *json.RawMessage `json:"jobresult,omitempty" doc:"the result reason"`
|
||||||
|
JobResultCode int `json:"jobresultcode,omitempty" doc:"the result code for the job"`
|
||||||
|
JobResultType string `json:"jobresulttype,omitempty" doc:"the result type"`
|
||||||
|
JobStatus JobStatusType `json:"jobstatus,omitempty" doc:"the current job status-should be 0 for PENDING"`
|
||||||
|
UserID *UUID `json:"userid,omitempty" doc:"the user that executed the async command"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy create a true copy of the receiver.
|
||||||
|
func (a *AsyncJobResult) DeepCopy() *AsyncJobResult {
|
||||||
|
if a == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AsyncJobResult{
|
||||||
|
AccountID: a.AccountID.DeepCopy(),
|
||||||
|
Cmd: a.Cmd,
|
||||||
|
Created: a.Created,
|
||||||
|
JobID: a.JobID.DeepCopy(),
|
||||||
|
JobInstanceID: a.JobInstanceID.DeepCopy(),
|
||||||
|
JobInstanceType: a.JobInstanceType,
|
||||||
|
JobProcStatus: a.JobProcStatus,
|
||||||
|
JobResult: a.JobResult,
|
||||||
|
JobResultCode: a.JobResultCode,
|
||||||
|
JobResultType: a.JobResultType,
|
||||||
|
JobStatus: a.JobStatus,
|
||||||
|
UserID: a.UserID.DeepCopy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto copies the receiver into out.
|
||||||
|
//
|
||||||
|
// In (a) must be non nil. out must be non nil
|
||||||
|
func (a *AsyncJobResult) DeepCopyInto(out *AsyncJobResult) {
|
||||||
|
*out = AsyncJobResult{
|
||||||
|
AccountID: a.AccountID.DeepCopy(),
|
||||||
|
Cmd: a.Cmd,
|
||||||
|
Created: a.Created,
|
||||||
|
JobID: a.JobID.DeepCopy(),
|
||||||
|
JobInstanceID: a.JobInstanceID.DeepCopy(),
|
||||||
|
JobInstanceType: a.JobInstanceType,
|
||||||
|
JobProcStatus: a.JobProcStatus,
|
||||||
|
JobResult: a.JobResult,
|
||||||
|
JobResultCode: a.JobResultCode,
|
||||||
|
JobResultType: a.JobResultType,
|
||||||
|
JobStatus: a.JobStatus,
|
||||||
|
UserID: a.UserID.DeepCopy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest buils the (empty) ListAsyncJobs request
|
||||||
|
func (a AsyncJobResult) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListAsyncJobs{
|
||||||
|
StartDate: a.Created,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error builds an error message from the result
|
||||||
|
func (a AsyncJobResult) Error() error {
|
||||||
|
r := new(ErrorResponse)
|
||||||
|
if e := json.Unmarshal(*a.JobResult, r); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAsyncJobResult represents a query to fetch the status of async job
|
||||||
|
type QueryAsyncJobResult struct {
|
||||||
|
JobID *UUID `json:"jobid" doc:"the ID of the asynchronous job"`
|
||||||
|
_ bool `name:"queryAsyncJobResult" description:"Retrieves the current status of asynchronous job."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (QueryAsyncJobResult) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListAsyncJobs
|
||||||
|
|
||||||
|
// ListAsyncJobs list the asynchronous jobs
|
||||||
|
type ListAsyncJobs struct {
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
StartDate string `json:"startdate,omitempty" doc:"the start date of the async job"`
|
||||||
|
_ bool `name:"listAsyncJobs" description:"Lists all pending asynchronous jobs for the account."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAsyncJobsResponse represents a list of job results
|
||||||
|
type ListAsyncJobsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
AsyncJob []AsyncJobResult `json:"asyncjobs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result unmarshals the result of an AsyncJobResult into the given interface
|
||||||
|
func (a AsyncJobResult) Result(i interface{}) error {
|
||||||
|
if a.JobStatus == Failure {
|
||||||
|
return a.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.JobStatus == Success {
|
||||||
|
m := map[string]json.RawMessage{}
|
||||||
|
err := json.Unmarshal(*(a.JobResult), &m)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if len(m) >= 1 {
|
||||||
|
if _, ok := m["success"]; ok {
|
||||||
|
return json.Unmarshal(*(a.JobResult), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, pick the first key
|
||||||
|
for k := range m {
|
||||||
|
return json.Unmarshal(m[k], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("empty response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListAsyncJobs) Response() interface{} {
|
||||||
|
return new(ListAsyncJobsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListAsyncJobs) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListAsyncJobs) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListAsyncJobs) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListAsyncJobs) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListAsyncJobsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListAsyncJobsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.AsyncJob {
|
||||||
|
if !callback(&items.AsyncJob[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CIDR represents a nicely JSON serializable net.IPNet
|
||||||
|
type CIDR struct {
|
||||||
|
net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals the raw JSON into the MAC address
|
||||||
|
func (cidr *CIDR) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c, err := ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*cidr = CIDR{c.IPNet}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts the CIDR to a string representation
|
||||||
|
func (cidr CIDR) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf("%q", cidr)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a CIDR
|
||||||
|
func (cidr CIDR) String() string {
|
||||||
|
return cidr.IPNet.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCIDR parses a CIDR from a string
|
||||||
|
func ParseCIDR(s string) (*CIDR, error) {
|
||||||
|
_, net, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CIDR{*net}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseCIDR forces parseCIDR or panics
|
||||||
|
func MustParseCIDR(s string) *CIDR {
|
||||||
|
cidr, err := ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal compare two CIDR
|
||||||
|
func (cidr CIDR) Equal(c CIDR) bool {
|
||||||
|
return (cidr.IPNet.IP.Equal(c.IPNet.IP) && bytes.Equal(cidr.IPNet.Mask, c.IPNet.Mask))
|
||||||
|
}
|
|
@ -0,0 +1,484 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v2 "github.com/exoscale/egoscale/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultTimeout represents the default API client HTTP request timeout.
|
||||||
|
DefaultTimeout = 60 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserAgent is the "User-Agent" HTTP request header added to outgoing HTTP requests.
|
||||||
|
var UserAgent = fmt.Sprintf("egoscale/%s (%s; %s/%s)",
|
||||||
|
Version,
|
||||||
|
runtime.Version(),
|
||||||
|
runtime.GOOS,
|
||||||
|
runtime.GOARCH)
|
||||||
|
|
||||||
|
// Taggable represents a resource to which tags can be attached
|
||||||
|
//
|
||||||
|
// This is a helper to fill the resourcetype of a CreateTags call
|
||||||
|
type Taggable interface {
|
||||||
|
// ResourceType is the name of the Taggable type
|
||||||
|
ResourceType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletable represents an Interface that can be "Delete" by the client
|
||||||
|
type Deletable interface {
|
||||||
|
// Delete removes the given resource(s) or throws
|
||||||
|
Delete(context context.Context, client *Client) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listable represents an Interface that can be "List" by the client
|
||||||
|
type Listable interface {
|
||||||
|
// ListRequest builds the list command
|
||||||
|
ListRequest() (ListCommand, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents the API client
|
||||||
|
type Client struct {
|
||||||
|
// HTTPClient holds the HTTP client
|
||||||
|
HTTPClient *http.Client
|
||||||
|
// Endpoint is the HTTP URL
|
||||||
|
Endpoint string
|
||||||
|
// APIKey is the API identifier
|
||||||
|
APIKey string
|
||||||
|
// apisecret is the API secret, hence non exposed
|
||||||
|
apiSecret string
|
||||||
|
// PageSize represents the default size for a paginated result
|
||||||
|
PageSize int
|
||||||
|
// Timeout represents the default timeout for the async requests
|
||||||
|
Timeout time.Duration
|
||||||
|
// Expiration representation how long a signed payload may be used
|
||||||
|
Expiration time.Duration
|
||||||
|
// RetryStrategy represents the waiting strategy for polling the async requests
|
||||||
|
RetryStrategy RetryStrategyFunc
|
||||||
|
// Logger contains any log, plug your own
|
||||||
|
Logger *log.Logger
|
||||||
|
|
||||||
|
// noV2 represents a flag disabling v2.Client embedding.
|
||||||
|
noV2 bool
|
||||||
|
|
||||||
|
// Public API secondary client
|
||||||
|
*v2.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryStrategyFunc represents a how much time to wait between two calls to the API
|
||||||
|
type RetryStrategyFunc func(int64) time.Duration
|
||||||
|
|
||||||
|
// IterateItemFunc represents the callback to iterate a list of results, if false stops
|
||||||
|
type IterateItemFunc func(interface{}, error) bool
|
||||||
|
|
||||||
|
// WaitAsyncJobResultFunc represents the callback to wait a results of an async request, if false stops
|
||||||
|
type WaitAsyncJobResultFunc func(*AsyncJobResult, error) bool
|
||||||
|
|
||||||
|
// ClientOpt represents a new Client option.
|
||||||
|
type ClientOpt func(*Client)
|
||||||
|
|
||||||
|
// WithHTTPClient overrides the Client's default HTTP client.
|
||||||
|
func WithHTTPClient(hc *http.Client) ClientOpt {
|
||||||
|
return func(c *Client) { c.HTTPClient = hc }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout overrides the Client's default timeout value (DefaultTimeout).
|
||||||
|
func WithTimeout(d time.Duration) ClientOpt {
|
||||||
|
return func(c *Client) { c.Timeout = d }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTrace enables the Client's HTTP request tracing.
|
||||||
|
func WithTrace() ClientOpt {
|
||||||
|
return func(c *Client) { c.TraceOn() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutV2Client disables implicit v2.Client embedding.
|
||||||
|
func WithoutV2Client() ClientOpt {
|
||||||
|
return func(c *Client) { c.noV2 = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates an Exoscale API client.
|
||||||
|
// Note: unless the WithoutV2Client() ClientOpt is passed, this function
|
||||||
|
// initializes a v2.Client embedded into the returned *Client struct
|
||||||
|
// inheriting the Exoscale API credentials, endpoint and timeout value, but
|
||||||
|
// not the custom http.Client. The 2 clients must not share the same
|
||||||
|
// *http.Client, as it can cause middleware clashes.
|
||||||
|
func NewClient(endpoint, apiKey, apiSecret string, opts ...ClientOpt) *Client {
|
||||||
|
client := &Client{
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: &defaultTransport{next: http.DefaultTransport},
|
||||||
|
},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
APIKey: apiKey,
|
||||||
|
apiSecret: apiSecret,
|
||||||
|
PageSize: 50,
|
||||||
|
Timeout: DefaultTimeout,
|
||||||
|
Expiration: 10 * time.Minute,
|
||||||
|
RetryStrategy: MonotonicRetryStrategyFunc(2),
|
||||||
|
Logger: log.New(ioutil.Discard, "", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, ok := os.LookupEnv("EXOSCALE_TRACE"); ok {
|
||||||
|
client.Logger = log.New(os.Stderr, prefix, log.LstdFlags)
|
||||||
|
client.TraceOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !client.noV2 {
|
||||||
|
v2Client, err := v2.NewClient(
|
||||||
|
client.APIKey,
|
||||||
|
client.apiSecret,
|
||||||
|
v2.ClientOptWithAPIEndpoint(client.Endpoint),
|
||||||
|
v2.ClientOptWithTimeout(client.Timeout),
|
||||||
|
|
||||||
|
// Don't use v2.ClientOptWithHTTPClient() with the root API client's http.Client, as the
|
||||||
|
// v2.Client uses HTTP middleware that can break callers that expect CS-compatible error
|
||||||
|
// responses.
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unable to initialize API V2 client: %s", err))
|
||||||
|
}
|
||||||
|
client.Client = v2Client
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do implemements the v2.HttpRequestDoer interface in order to intercept HTTP response before the
|
||||||
|
// generated code closes its body, giving us a chance to return meaningful error messages from the API.
|
||||||
|
// This is only relevant for API v2 operations.
|
||||||
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// If the request returned a Go error don't bother analyzing the response
|
||||||
|
// body, as there probably won't be any (e.g. connection timeout/refused).
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
|
||||||
|
var res struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading response body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Valid(data) {
|
||||||
|
if err = json.Unmarshal(data, &res); err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshaling response: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.Message = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case resp.StatusCode == http.StatusNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
|
||||||
|
case resp.StatusCode >= 400 && resp.StatusCode < 500:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrInvalidRequest, res.Message)
|
||||||
|
|
||||||
|
case resp.StatusCode >= 500:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrAPIError, res.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get populates the given resource or fails
|
||||||
|
func (c *Client) Get(ls Listable) (interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return c.GetWithContext(ctx, ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWithContext populates the given resource or fails
|
||||||
|
func (c *Client) GetWithContext(ctx context.Context, ls Listable) (interface{}, error) {
|
||||||
|
gs, err := c.ListWithContext(ctx, ls)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(gs) {
|
||||||
|
case 0:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return gs[0], nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, ErrTooManyFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the given resource of fails
|
||||||
|
func (c *Client) Delete(g Deletable) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return c.DeleteWithContext(ctx, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWithContext removes the given resource of fails
|
||||||
|
func (c *Client) DeleteWithContext(ctx context.Context, g Deletable) error {
|
||||||
|
return g.Delete(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists the given resource (and paginate till the end)
|
||||||
|
func (c *Client) List(g Listable) ([]interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return c.ListWithContext(ctx, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWithContext lists the given resources (and paginate till the end)
|
||||||
|
func (c *Client) ListWithContext(ctx context.Context, g Listable) (s []interface{}, err error) {
|
||||||
|
s = make([]interface{}, 0)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
if g == nil || reflect.ValueOf(g).IsNil() {
|
||||||
|
err = fmt.Errorf("g Listable shouldn't be nil, got %#v", g)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
req, e := g.ListRequest()
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.PaginateWithContext(ctx, req, func(item interface{}, e error) bool {
|
||||||
|
if item != nil {
|
||||||
|
s = append(s, item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err = e
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AsyncListWithContext(ctx context.Context, g Listable) (<-chan interface{}, <-chan error) {
|
||||||
|
outChan := make(chan interface{}, c.PageSize)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(outChan)
|
||||||
|
defer close(errChan)
|
||||||
|
|
||||||
|
req, err := g.ListRequest()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.PaginateWithContext(ctx, req, func(item interface{}, e error) bool {
|
||||||
|
if item != nil {
|
||||||
|
outChan <- item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
errChan <- e
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
return outChan, errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paginate runs the ListCommand and paginates
|
||||||
|
func (c *Client) Paginate(g Listable, callback IterateItemFunc) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c.PaginateWithContext(ctx, g, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateWithContext runs the ListCommand as long as the ctx is valid
|
||||||
|
func (c *Client) PaginateWithContext(ctx context.Context, g Listable, callback IterateItemFunc) {
|
||||||
|
req, err := g.ListRequest()
|
||||||
|
if err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSize := c.PageSize
|
||||||
|
|
||||||
|
page := 1
|
||||||
|
|
||||||
|
for {
|
||||||
|
req.SetPage(page)
|
||||||
|
req.SetPageSize(pageSize)
|
||||||
|
resp, err := c.RequestWithContext(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
// in case of 431, the response is knowingly empty
|
||||||
|
if errResponse, ok := err.(*ErrorResponse); ok && page == 1 && errResponse.ErrorCode == ParamError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(nil, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
size := 0
|
||||||
|
didErr := false
|
||||||
|
req.Each(resp, func(element interface{}, err error) bool {
|
||||||
|
// If the context was cancelled, kill it in flight
|
||||||
|
if e := ctx.Err(); e != nil {
|
||||||
|
element = nil
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback(element, err) {
|
||||||
|
size++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
didErr = true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if size < pageSize || didErr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIName returns the name of the given command
|
||||||
|
func (c *Client) APIName(command Command) string {
|
||||||
|
// This is due to a limitation of Go<=1.7
|
||||||
|
_, ok := command.(*AuthorizeSecurityGroupEgress)
|
||||||
|
_, okPtr := command.(AuthorizeSecurityGroupEgress)
|
||||||
|
if ok || okPtr {
|
||||||
|
return "authorizeSecurityGroupEgress"
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := info(command)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return info.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIDescription returns the description of the given command
|
||||||
|
func (c *Client) APIDescription(command Command) string {
|
||||||
|
info, err := info(command)
|
||||||
|
if err != nil {
|
||||||
|
return "*missing description*"
|
||||||
|
}
|
||||||
|
return info.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the response structure of the given command
|
||||||
|
func (c *Client) Response(command Command) interface{} {
|
||||||
|
switch c := command.(type) {
|
||||||
|
case AsyncCommand:
|
||||||
|
return c.AsyncResponse()
|
||||||
|
default:
|
||||||
|
return command.Response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceOn activates the HTTP tracer
|
||||||
|
func (c *Client) TraceOn() {
|
||||||
|
if _, ok := c.HTTPClient.Transport.(*traceTransport); !ok {
|
||||||
|
c.HTTPClient.Transport = &traceTransport{
|
||||||
|
next: c.HTTPClient.Transport,
|
||||||
|
logger: c.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceOff deactivates the HTTP tracer
|
||||||
|
func (c *Client) TraceOff() {
|
||||||
|
if rt, ok := c.HTTPClient.Transport.(*traceTransport); ok {
|
||||||
|
c.HTTPClient.Transport = rt.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultTransport is the default HTTP client transport.
|
||||||
|
type defaultTransport struct {
|
||||||
|
next http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip executes a single HTTP transaction while augmenting requests with custom headers.
|
||||||
|
func (t *defaultTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
|
|
||||||
|
resp, err := t.next.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceTransport is a client HTTP middleware that dumps HTTP requests and responses content to a logger.
|
||||||
|
type traceTransport struct {
|
||||||
|
logger *log.Logger
|
||||||
|
next http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip executes a single HTTP transaction
|
||||||
|
func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
|
|
||||||
|
if dump, err := httputil.DumpRequest(req, true); err == nil {
|
||||||
|
t.logger.Printf("%s", dump)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := t.next.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dump, err := httputil.DumpResponse(resp, true); err == nil {
|
||||||
|
t.logger.Printf("%s", dump)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonotonicRetryStrategyFunc returns a function that waits for n seconds for each iteration
|
||||||
|
func MonotonicRetryStrategyFunc(seconds int) RetryStrategyFunc {
|
||||||
|
return func(iteration int64) time.Duration {
|
||||||
|
return time.Duration(seconds) * time.Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FibonacciRetryStrategy waits for an increasing amount of time following the Fibonacci sequence
|
||||||
|
func FibonacciRetryStrategy(iteration int64) time.Duration {
|
||||||
|
var a, b, i, tmp int64
|
||||||
|
a = 0
|
||||||
|
b = 1
|
||||||
|
for i = 0; i < iteration; i++ {
|
||||||
|
tmp = a + b
|
||||||
|
a = b
|
||||||
|
b = tmp
|
||||||
|
}
|
||||||
|
return time.Duration(a) * time.Second
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Code generated by "stringer -type CSErrorCode"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[CloudRuntimeException-4250]
|
||||||
|
_ = x[ExecutionException-4260]
|
||||||
|
_ = x[HypervisorVersionChangedException-4265]
|
||||||
|
_ = x[CloudException-4275]
|
||||||
|
_ = x[AccountLimitException-4280]
|
||||||
|
_ = x[AgentUnavailableException-4285]
|
||||||
|
_ = x[CloudAuthenticationException-4290]
|
||||||
|
_ = x[ConcurrentOperationException-4300]
|
||||||
|
_ = x[ConflictingNetworkSettingsException-4305]
|
||||||
|
_ = x[DiscoveredWithErrorException-4310]
|
||||||
|
_ = x[HAStateException-4315]
|
||||||
|
_ = x[InsufficientAddressCapacityException-4320]
|
||||||
|
_ = x[InsufficientCapacityException-4325]
|
||||||
|
_ = x[InsufficientNetworkCapacityException-4330]
|
||||||
|
_ = x[InsufficientServerCapacityException-4335]
|
||||||
|
_ = x[InsufficientStorageCapacityException-4340]
|
||||||
|
_ = x[InternalErrorException-4345]
|
||||||
|
_ = x[InvalidParameterValueException-4350]
|
||||||
|
_ = x[ManagementServerException-4355]
|
||||||
|
_ = x[NetworkRuleConflictException-4360]
|
||||||
|
_ = x[PermissionDeniedException-4365]
|
||||||
|
_ = x[ResourceAllocationException-4370]
|
||||||
|
_ = x[ResourceInUseException-4375]
|
||||||
|
_ = x[ResourceUnavailableException-4380]
|
||||||
|
_ = x[StorageUnavailableException-4385]
|
||||||
|
_ = x[UnsupportedServiceException-4390]
|
||||||
|
_ = x[VirtualMachineMigrationException-4395]
|
||||||
|
_ = x[AsyncCommandQueued-4540]
|
||||||
|
_ = x[RequestLimitException-4545]
|
||||||
|
_ = x[ServerAPIException-9999]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CSErrorCode_name = "CloudRuntimeExceptionExecutionExceptionHypervisorVersionChangedExceptionCloudExceptionAccountLimitExceptionAgentUnavailableExceptionCloudAuthenticationExceptionConcurrentOperationExceptionConflictingNetworkSettingsExceptionDiscoveredWithErrorExceptionHAStateExceptionInsufficientAddressCapacityExceptionInsufficientCapacityExceptionInsufficientNetworkCapacityExceptionInsufficientServerCapacityExceptionInsufficientStorageCapacityExceptionInternalErrorExceptionInvalidParameterValueExceptionManagementServerExceptionNetworkRuleConflictExceptionPermissionDeniedExceptionResourceAllocationExceptionResourceInUseExceptionResourceUnavailableExceptionStorageUnavailableExceptionUnsupportedServiceExceptionVirtualMachineMigrationExceptionAsyncCommandQueuedRequestLimitExceptionServerAPIException"
|
||||||
|
|
||||||
|
var _CSErrorCode_map = map[CSErrorCode]string{
|
||||||
|
4250: _CSErrorCode_name[0:21],
|
||||||
|
4260: _CSErrorCode_name[21:39],
|
||||||
|
4265: _CSErrorCode_name[39:72],
|
||||||
|
4275: _CSErrorCode_name[72:86],
|
||||||
|
4280: _CSErrorCode_name[86:107],
|
||||||
|
4285: _CSErrorCode_name[107:132],
|
||||||
|
4290: _CSErrorCode_name[132:160],
|
||||||
|
4300: _CSErrorCode_name[160:188],
|
||||||
|
4305: _CSErrorCode_name[188:223],
|
||||||
|
4310: _CSErrorCode_name[223:251],
|
||||||
|
4315: _CSErrorCode_name[251:267],
|
||||||
|
4320: _CSErrorCode_name[267:303],
|
||||||
|
4325: _CSErrorCode_name[303:332],
|
||||||
|
4330: _CSErrorCode_name[332:368],
|
||||||
|
4335: _CSErrorCode_name[368:403],
|
||||||
|
4340: _CSErrorCode_name[403:439],
|
||||||
|
4345: _CSErrorCode_name[439:461],
|
||||||
|
4350: _CSErrorCode_name[461:491],
|
||||||
|
4355: _CSErrorCode_name[491:516],
|
||||||
|
4360: _CSErrorCode_name[516:544],
|
||||||
|
4365: _CSErrorCode_name[544:569],
|
||||||
|
4370: _CSErrorCode_name[569:596],
|
||||||
|
4375: _CSErrorCode_name[596:618],
|
||||||
|
4380: _CSErrorCode_name[618:646],
|
||||||
|
4385: _CSErrorCode_name[646:673],
|
||||||
|
4390: _CSErrorCode_name[673:700],
|
||||||
|
4395: _CSErrorCode_name[700:732],
|
||||||
|
4540: _CSErrorCode_name[732:750],
|
||||||
|
4545: _CSErrorCode_name[750:771],
|
||||||
|
9999: _CSErrorCode_name[771:789],
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i CSErrorCode) String() string {
|
||||||
|
if str, ok := _CSErrorCode_map[i]; ok {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return "CSErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
|
@ -0,0 +1,364 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSDomain represents a domain
|
||||||
|
type DNSDomain struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UnicodeName string `json:"unicode_name"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
|
Lockable bool `json:"lockable"`
|
||||||
|
AutoRenew bool `json:"auto_renew"`
|
||||||
|
WhoisProtected bool `json:"whois_protected"`
|
||||||
|
RecordCount int64 `json:"record_count"`
|
||||||
|
ServiceCount int64 `json:"service_count"`
|
||||||
|
ExpiresOn string `json:"expires_on,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSDomainResponse represents a domain creation response
|
||||||
|
type DNSDomainResponse struct {
|
||||||
|
Domain *DNSDomain `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSRecord represents a DNS record
|
||||||
|
type DNSRecord struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
DomainID int64 `json:"domain_id,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt string `json:"updated_at,omitempty"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
RecordType string `json:"record_type"`
|
||||||
|
Prio int `json:"prio,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSRecordResponse represents the creation of a DNS record
|
||||||
|
type DNSRecordResponse struct {
|
||||||
|
Record DNSRecord `json:"record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSRecord represents a DNS record
|
||||||
|
type UpdateDNSRecord struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
DomainID int64 `json:"domain_id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt string `json:"updated_at,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
RecordType string `json:"record_type,omitempty"`
|
||||||
|
Prio int `json:"prio,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSRecordResponse represents the creation of a DNS record
|
||||||
|
type UpdateDNSRecordResponse struct {
|
||||||
|
Record UpdateDNSRecord `json:"record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSErrorResponse represents an error in the API
|
||||||
|
type DNSErrorResponse struct {
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Errors map[string][]string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record represent record type
|
||||||
|
type Record int
|
||||||
|
|
||||||
|
//go:generate stringer -type=Record
|
||||||
|
const (
|
||||||
|
// A record type
|
||||||
|
A Record = iota
|
||||||
|
// AAAA record type
|
||||||
|
AAAA
|
||||||
|
// ALIAS record type
|
||||||
|
ALIAS
|
||||||
|
// CNAME record type
|
||||||
|
CNAME
|
||||||
|
// HINFO record type
|
||||||
|
HINFO
|
||||||
|
// MX record type
|
||||||
|
MX
|
||||||
|
// NAPTR record type
|
||||||
|
NAPTR
|
||||||
|
// NS record type
|
||||||
|
NS
|
||||||
|
// POOL record type
|
||||||
|
POOL
|
||||||
|
// SPF record type
|
||||||
|
SPF
|
||||||
|
// SRV record type
|
||||||
|
SRV
|
||||||
|
// SSHFP record type
|
||||||
|
SSHFP
|
||||||
|
// TXT record type
|
||||||
|
TXT
|
||||||
|
// URL record type
|
||||||
|
URL
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error formats the DNSerror into a string
|
||||||
|
func (req *DNSErrorResponse) Error() string {
|
||||||
|
if len(req.Errors) > 0 {
|
||||||
|
errs := []string{}
|
||||||
|
for name, ss := range req.Errors {
|
||||||
|
if len(ss) > 0 {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: %s", name, strings.Join(ss, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("dns error: %s (%s)", req.Message, strings.Join(errs, "; "))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("dns error: %s", req.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDomain creates a DNS domain
|
||||||
|
func (client *Client) CreateDomain(ctx context.Context, name string) (*DNSDomain, error) {
|
||||||
|
m, err := json.Marshal(DNSDomainResponse{
|
||||||
|
Domain: &DNSDomain{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains", nil, string(m), "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *DNSDomainResponse
|
||||||
|
if err := json.Unmarshal(resp, &d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.Domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain gets a DNS domain
|
||||||
|
func (client *Client) GetDomain(ctx context.Context, name string) (*DNSDomain, error) {
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+name, nil, "", "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *DNSDomainResponse
|
||||||
|
if err := json.Unmarshal(resp, &d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.Domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomains gets DNS domains
|
||||||
|
func (client *Client) GetDomains(ctx context.Context) ([]DNSDomain, error) {
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains", nil, "", "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var d []DNSDomainResponse
|
||||||
|
if err := json.Unmarshal(resp, &d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]DNSDomain, len(d))
|
||||||
|
for i := range d {
|
||||||
|
domains[i] = *d[i].Domain
|
||||||
|
}
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomain delets a DNS domain
|
||||||
|
func (client *Client) DeleteDomain(ctx context.Context, name string) error {
|
||||||
|
_, err := client.dnsRequest(ctx, "/v1/domains/"+name, nil, "", "DELETE")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecord returns a DNS record
|
||||||
|
func (client *Client) GetRecord(ctx context.Context, domain string, recordID int64) (*DNSRecord, error) {
|
||||||
|
id := strconv.FormatInt(recordID, 10)
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+domain+"/records/"+id, nil, "", "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r DNSRecordResponse
|
||||||
|
if err = json.Unmarshal(resp, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &(r.Record), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecords returns the DNS records
|
||||||
|
func (client *Client) GetRecords(ctx context.Context, domain string) ([]DNSRecord, error) {
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+domain+"/records", nil, "", "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r []DNSRecordResponse
|
||||||
|
if err = json.Unmarshal(resp, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
records := make([]DNSRecord, 0, len(r))
|
||||||
|
for _, rec := range r {
|
||||||
|
records = append(records, rec.Record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecordsWithFilters returns the DNS records (filters can be empty)
|
||||||
|
func (client *Client) GetRecordsWithFilters(ctx context.Context, domain, name, recordType string) ([]DNSRecord, error) {
|
||||||
|
|
||||||
|
filters := url.Values{}
|
||||||
|
if name != "" {
|
||||||
|
filters.Add("name", name)
|
||||||
|
}
|
||||||
|
if recordType != "" {
|
||||||
|
filters.Add("record_type", recordType)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+domain+"/records", filters, "", "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r []DNSRecordResponse
|
||||||
|
if err = json.Unmarshal(resp, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
records := make([]DNSRecord, 0, len(r))
|
||||||
|
for _, rec := range r {
|
||||||
|
records = append(records, rec.Record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRecord creates a DNS record
|
||||||
|
func (client *Client) CreateRecord(ctx context.Context, name string, rec DNSRecord) (*DNSRecord, error) {
|
||||||
|
body, err := json.Marshal(DNSRecordResponse{
|
||||||
|
Record: rec,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+name+"/records", nil, string(body), "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r DNSRecordResponse
|
||||||
|
if err = json.Unmarshal(resp, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &(r.Record), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRecord updates a DNS record
|
||||||
|
func (client *Client) UpdateRecord(ctx context.Context, name string, rec UpdateDNSRecord) (*DNSRecord, error) {
|
||||||
|
body, err := json.Marshal(UpdateDNSRecordResponse{
|
||||||
|
Record: rec,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := strconv.FormatInt(rec.ID, 10)
|
||||||
|
resp, err := client.dnsRequest(ctx, "/v1/domains/"+name+"/records/"+id, nil, string(body), "PUT")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r DNSRecordResponse
|
||||||
|
if err = json.Unmarshal(resp, &r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &(r.Record), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecord deletes a record
|
||||||
|
func (client *Client) DeleteRecord(ctx context.Context, name string, recordID int64) error {
|
||||||
|
id := strconv.FormatInt(recordID, 10)
|
||||||
|
_, err := client.dnsRequest(ctx, "/v1/domains/"+name+"/records/"+id, nil, "", "DELETE")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) dnsRequest(ctx context.Context, uri string, urlValues url.Values, params, method string) (json.RawMessage, error) {
|
||||||
|
rawURL := client.Endpoint + uri
|
||||||
|
url, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := url.Query()
|
||||||
|
for k, vs := range urlValues {
|
||||||
|
for _, v := range vs {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url.String(), strings.NewReader(params))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hdr = make(http.Header)
|
||||||
|
hdr.Add("X-DNS-TOKEN", client.APIKey+":"+client.apiSecret)
|
||||||
|
hdr.Add("User-Agent", UserAgent)
|
||||||
|
hdr.Add("Accept", "application/json")
|
||||||
|
if params != "" {
|
||||||
|
hdr.Add("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
req.Header = hdr
|
||||||
|
|
||||||
|
resp, err := client.HTTPClient.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
contentType := resp.Header.Get("content-type")
|
||||||
|
if !strings.Contains(contentType, "application/json") {
|
||||||
|
return nil, fmt.Errorf(`response content-type expected to be "application/json", got %q`, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
e := new(DNSErrorResponse)
|
||||||
|
if err := json.Unmarshal(b, e); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package egoscale is a mapping for the Exoscale API (https://community.exoscale.com/api/compute/).
|
||||||
|
|
||||||
|
Requests and Responses
|
||||||
|
|
||||||
|
To build a request, construct the adequate struct. This library expects a pointer for efficiency reasons only. The response is a struct corresponding to the data at stake. E.g. DeployVirtualMachine gives a VirtualMachine, as a pointer as well to avoid big copies.
|
||||||
|
|
||||||
|
Then everything within the struct is not a pointer. Find below some examples of how egoscale may be used. If anything feels odd or unclear, please let us know: https://github.com/exoscale/egoscale/issues
|
||||||
|
|
||||||
|
req := &egoscale.DeployVirtualMachine{
|
||||||
|
Size: 10,
|
||||||
|
ServiceOfferingID: egoscale.MustParseUUID("..."),
|
||||||
|
TemplateID: egoscale.MustParseUUID("..."),
|
||||||
|
ZoneID: egoscale.MastParseUUID("..."),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Deployment started")
|
||||||
|
resp, err := cs.Request(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := resp.(*egoscale.VirtualMachine)
|
||||||
|
fmt.Printf("Virtual Machine ID: %s\n", vm.ID)
|
||||||
|
|
||||||
|
This example deploys a virtual machine while controlling the job status as it goes. It enables a finer control over errors, e.g. HTTP timeout, and eventually a way to kill it of (from the client side).
|
||||||
|
|
||||||
|
req := &egoscale.DeployVirtualMachine{
|
||||||
|
Size: 10,
|
||||||
|
ServiceOfferingID: egoscale.MustParseUUID("..."),
|
||||||
|
TemplateID: egoscale.MustParseUUID("..."),
|
||||||
|
ZoneID: egoscale.MustParseUUID("..."),
|
||||||
|
}
|
||||||
|
vm := &egoscale.VirtualMachine{}
|
||||||
|
|
||||||
|
fmt.Println("Deployment started")
|
||||||
|
cs.AsyncRequest(req, func(jobResult *egoscale.AsyncJobResult, err error) bool {
|
||||||
|
if err != nil {
|
||||||
|
// any kind of error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep waiting
|
||||||
|
if jobResult.JobStatus == egoscale.Pending {
|
||||||
|
fmt.Println("wait...")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the response into the response struct
|
||||||
|
if err := jobResult.Response(vm); err != nil {
|
||||||
|
// JSON unmarshaling error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop waiting
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Printf("Virtual Machine ID: %s\n", vm.ID)
|
||||||
|
|
||||||
|
Debugging and traces
|
||||||
|
|
||||||
|
As this library is mostly an HTTP client, you can reuse all the existing tools around it.
|
||||||
|
|
||||||
|
cs := egoscale.NewClient("https://api.exoscale.com/v1", "EXO...", "...")
|
||||||
|
// sets a logger on stderr
|
||||||
|
cs.Logger = log.New(os.Stderr, "prefix", log.LstdFlags)
|
||||||
|
// activates the HTTP traces
|
||||||
|
cs.TraceOn()
|
||||||
|
|
||||||
|
Nota bene: when running the tests or the egoscale library via another tool, e.g. the exo cli, the environment variable EXOSCALE_TRACE=prefix does the above configuration for you. As a developer using egoscale as a library, you'll find it more convenient to plug your favorite io.Writer as it's a Logger.
|
||||||
|
|
||||||
|
|
||||||
|
APIs
|
||||||
|
|
||||||
|
All the available APIs on the server and provided by the API Discovery plugin.
|
||||||
|
|
||||||
|
cs := egoscale.NewClient("https://api.exoscale.com/v1", "EXO...", "...")
|
||||||
|
|
||||||
|
resp, err := cs.Request(&egoscale.ListAPIs{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, api := range resp.(*egoscale.ListAPIsResponse).API {
|
||||||
|
fmt.Printf("%s %s\n", api.Name, api.Description)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// listNetworks Lists all available networks
|
||||||
|
// ...
|
||||||
|
|
||||||
|
Security Groups
|
||||||
|
|
||||||
|
Security Groups provide a way to isolate traffic to VMs. Rules are added via the two Authorization commands.
|
||||||
|
|
||||||
|
resp, err := cs.Request(&egoscale.CreateSecurityGroup{
|
||||||
|
Name: "Load balancer",
|
||||||
|
Description: "Open HTTP/HTTPS ports from the outside world",
|
||||||
|
})
|
||||||
|
securityGroup := resp.(*egoscale.SecurityGroup)
|
||||||
|
|
||||||
|
resp, err = cs.Request(&egoscale.AuthorizeSecurityGroupIngress{
|
||||||
|
Description: "SSH traffic",
|
||||||
|
SecurityGroupID: securityGroup.ID,
|
||||||
|
CidrList: []CIDR{
|
||||||
|
*egoscale.MustParseCIDR("0.0.0.0/0"),
|
||||||
|
*egoscale.MustParseCIDR("::/0"),
|
||||||
|
},
|
||||||
|
Protocol: "tcp",
|
||||||
|
StartPort: 22,
|
||||||
|
EndPort: 22,
|
||||||
|
})
|
||||||
|
// The modified SecurityGroup is returned
|
||||||
|
securityGroup := resp.(*egoscale.SecurityGroup)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
err = client.BooleanRequest(&egoscale.DeleteSecurityGroup{
|
||||||
|
ID: securityGroup.ID,
|
||||||
|
})
|
||||||
|
// ...
|
||||||
|
|
||||||
|
Security Group also implement the generic List, Get and Delete interfaces (Listable and Deletable).
|
||||||
|
|
||||||
|
// List all Security Groups
|
||||||
|
sgs, _ := cs.List(&egoscale.SecurityGroup{})
|
||||||
|
for _, s := range sgs {
|
||||||
|
sg := s.(egoscale.SecurityGroup)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a Security Group
|
||||||
|
sgQuery := &egoscale.SecurityGroup{Name: "Load balancer"}
|
||||||
|
resp, err := cs.Get(sgQuery); err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
sg := resp.(*egoscale.SecurityGroup)
|
||||||
|
|
||||||
|
if err := cs.Delete(sg); err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
// The SecurityGroup has been deleted
|
||||||
|
|
||||||
|
See: https://community.exoscale.com/documentation/compute/security-groups/
|
||||||
|
|
||||||
|
Zones
|
||||||
|
|
||||||
|
A Zone corresponds to a Data Center. You may list them. Zone implements the Listable interface, which let you perform a list in two different ways. The first exposes the underlying request while the second one hide them and you only manipulate the structs of your interest.
|
||||||
|
|
||||||
|
// Using ListZones request
|
||||||
|
req := &egoscale.ListZones{}
|
||||||
|
resp, err := client.Request(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range resp.(*egoscale.ListZonesResponse) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using client.List
|
||||||
|
zone := &egoscale.Zone{}
|
||||||
|
zones, err := client.List(zone)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, z := range zones {
|
||||||
|
zone := z.(egoscale.Zone)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Elastic IPs
|
||||||
|
|
||||||
|
An Elastic IP is a way to attach an IP address to many Virtual Machines. The API side of the story configures the external environment, like the routing. Some work is required within the machine to properly configure the interfaces.
|
||||||
|
|
||||||
|
See: https://community.exoscale.com/documentation/compute/eip/
|
||||||
|
|
||||||
|
*/
|
||||||
|
package egoscale
|
|
@ -0,0 +1,15 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// ErrNotFound represents an error indicating a non-existent resource.
|
||||||
|
var ErrNotFound = errors.New("resource not found")
|
||||||
|
|
||||||
|
// ErrTooManyFound represents an error indicating multiple results found for a single resource.
|
||||||
|
var ErrTooManyFound = errors.New("multiple resources found")
|
||||||
|
|
||||||
|
// ErrInvalidRequest represents an error indicating that the caller's request is invalid.
|
||||||
|
var ErrInvalidRequest = errors.New("invalid request")
|
||||||
|
|
||||||
|
// ErrAPIError represents an error indicating an API-side issue.
|
||||||
|
var ErrAPIError = errors.New("API error")
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Code generated by "stringer -type ErrorCode"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Unauthorized-401]
|
||||||
|
_ = x[NotFound-404]
|
||||||
|
_ = x[MethodNotAllowed-405]
|
||||||
|
_ = x[UnsupportedActionError-422]
|
||||||
|
_ = x[APILimitExceeded-429]
|
||||||
|
_ = x[MalformedParameterError-430]
|
||||||
|
_ = x[ParamError-431]
|
||||||
|
_ = x[InternalError-530]
|
||||||
|
_ = x[AccountError-531]
|
||||||
|
_ = x[AccountResourceLimitError-532]
|
||||||
|
_ = x[InsufficientCapacityError-533]
|
||||||
|
_ = x[ResourceUnavailableError-534]
|
||||||
|
_ = x[ResourceAllocationError-535]
|
||||||
|
_ = x[ResourceInUseError-536]
|
||||||
|
_ = x[NetworkRuleConflictError-537]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ErrorCode_name_0 = "Unauthorized"
|
||||||
|
_ErrorCode_name_1 = "NotFoundMethodNotAllowed"
|
||||||
|
_ErrorCode_name_2 = "UnsupportedActionError"
|
||||||
|
_ErrorCode_name_3 = "APILimitExceededMalformedParameterErrorParamError"
|
||||||
|
_ErrorCode_name_4 = "InternalErrorAccountErrorAccountResourceLimitErrorInsufficientCapacityErrorResourceUnavailableErrorResourceAllocationErrorResourceInUseErrorNetworkRuleConflictError"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ErrorCode_index_1 = [...]uint8{0, 8, 24}
|
||||||
|
_ErrorCode_index_3 = [...]uint8{0, 16, 39, 49}
|
||||||
|
_ErrorCode_index_4 = [...]uint8{0, 13, 25, 50, 75, 99, 122, 140, 164}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i ErrorCode) String() string {
|
||||||
|
switch {
|
||||||
|
case i == 401:
|
||||||
|
return _ErrorCode_name_0
|
||||||
|
case 404 <= i && i <= 405:
|
||||||
|
i -= 404
|
||||||
|
return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
|
||||||
|
case i == 422:
|
||||||
|
return _ErrorCode_name_2
|
||||||
|
case 429 <= i && i <= 431:
|
||||||
|
i -= 429
|
||||||
|
return _ErrorCode_name_3[_ErrorCode_index_3[i]:_ErrorCode_index_3[i+1]]
|
||||||
|
case 530 <= i && i <= 537:
|
||||||
|
i -= 530
|
||||||
|
return _ErrorCode_name_4[_ErrorCode_index_4[i]:_ErrorCode_index_4[i+1]]
|
||||||
|
default:
|
||||||
|
return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// Event represents an event in the system
|
||||||
|
type Event struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account name for the account that owns the object being acted on in the event (e.g. the owner of the virtual machine, ip address, or security group)"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the date the event was created"`
|
||||||
|
Description string `json:"description,omitempty" doc:"a brief description of the event"`
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the event"`
|
||||||
|
Level string `json:"level,omitempty" doc:"the event level (INFO, WARN, ERROR)"`
|
||||||
|
ParentID *UUID `json:"parentid,omitempty" doc:"whether the event is parented"`
|
||||||
|
State string `json:"state,omitempty" doc:"the state of the event"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the event (see event types)"`
|
||||||
|
UserName string `json:"username,omitempty" doc:"the name of the user who performed the action (can be different from the account if an admin is performing an action for a user, e.g. starting/stopping a user's virtual machine)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListEvents request
|
||||||
|
func (event Event) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListEvents{
|
||||||
|
ID: event.ID,
|
||||||
|
Level: event.Level,
|
||||||
|
Type: event.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventType represent a type of event
|
||||||
|
type EventType struct {
|
||||||
|
Name string `json:"name,omitempty" doc:"Event Type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListEventTypes request
|
||||||
|
func (EventType) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListEventTypes{}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListEvents
|
||||||
|
|
||||||
|
// ListEvents list the events
|
||||||
|
type ListEvents struct {
|
||||||
|
Duration int `json:"duration,omitempty" doc:"the duration of the event"`
|
||||||
|
EndDate string `json:"enddate,omitempty" doc:"the end date range of the list you want to retrieve (use format \"yyyy-MM-dd\" or the new format \"yyyy-MM-dd HH:mm:ss\")"`
|
||||||
|
EntryTime int `json:"entrytime,omitempty" doc:"the time the event was entered"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the event"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Level string `json:"level,omitempty" doc:"the event level (INFO, WARN, ERROR)"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
StartDate string `json:"startdate,omitempty" doc:"the start date range of the list you want to retrieve (use format \"yyyy-MM-dd\" or the new format \"yyyy-MM-dd HH:mm:ss\")"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the event type (see event types)"`
|
||||||
|
_ bool `name:"listEvents" description:"A command to list events."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEventsResponse represents a response of a list query
|
||||||
|
type ListEventsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Event []Event `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListEventTypes
|
||||||
|
|
||||||
|
// ListEventTypes list the event types
|
||||||
|
type ListEventTypes struct {
|
||||||
|
Page int `json:"page,omitempty"` // fake
|
||||||
|
PageSize int `json:"pagesize,omitempty"` // fake
|
||||||
|
_ bool `name:"listEventTypes" description:"List Event Types"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEventTypesResponse represents a response of a list query
|
||||||
|
type ListEventTypesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
EventType []EventType `json:"eventtype"`
|
||||||
|
_ bool `name:"listEventTypes" description:"List Event Types"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListEvents) Response() interface{} {
|
||||||
|
return new(ListEventsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListEvents) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListEvents) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListEvents) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListEvents) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListEventsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListEventsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Event {
|
||||||
|
if !callback(&items.Event[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListEventTypes) Response() interface{} {
|
||||||
|
return new(ListEventTypesResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListEventTypes) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListEventTypes) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListEventTypes) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListEventTypes) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListEventTypesResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListEventTypesResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.EventType {
|
||||||
|
if !callback(&items.EventType[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
module github.com/exoscale/egoscale
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/deepmap/oapi-codegen v1.3.11
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
github.com/jarcoal/httpmock v1.0.6
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/stretchr/objx v0.3.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.14
|
|
@ -0,0 +1,82 @@
|
||||||
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
|
github.com/deepmap/oapi-codegen v1.3.11 h1:Nd3tDQfqgquLmCzyRONHzs5SJEwPPoQcFZxT8MKt1Hs=
|
||||||
|
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||||
|
github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g=
|
||||||
|
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||||
|
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=
|
||||||
|
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
|
||||||
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
|
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||||
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||||
|
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
||||||
|
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
|
||||||
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
||||||
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 h1:wejkGHRTr38uaKRqECZlsCsJ1/TGxIyFbH32x5zUdu4=
|
||||||
|
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
|
@ -0,0 +1,92 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// APIKeyType holds the type of the API key
|
||||||
|
type APIKeyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// APIKeyTypeUnrestricted is unrestricted
|
||||||
|
APIKeyTypeUnrestricted APIKeyType = "unrestricted"
|
||||||
|
// APIKeyTypeRestricted is restricted
|
||||||
|
APIKeyTypeRestricted APIKeyType = "restricted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKey represents an API key
|
||||||
|
type APIKey struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
Operations []string `json:"operations,omitempty"`
|
||||||
|
Resources []string `json:"resources,omitempty"`
|
||||||
|
Type APIKeyType `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAPIKey represents an API key creation
|
||||||
|
type CreateAPIKey struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Operations string `json:"operations,omitempty"`
|
||||||
|
Resources string `json:"resources,omitempty"`
|
||||||
|
_ bool `name:"createApiKey" description:"Create an API key."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateAPIKey) Response() interface{} {
|
||||||
|
return new(APIKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIKeys represents a search for API keys
|
||||||
|
type ListAPIKeys struct {
|
||||||
|
_ bool `name:"listApiKeys" description:"List API keys."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIKeysResponse represents a list of API keys
|
||||||
|
type ListAPIKeysResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
APIKeys []APIKey `json:"apikey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListAPIKeys) Response() interface{} {
|
||||||
|
return new(ListAPIKeysResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIKeyOperations represents a search for operations for the current API key
|
||||||
|
type ListAPIKeyOperations struct {
|
||||||
|
_ bool `name:"listApiKeyOperations" description:"List operations allowed for the current API key."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIKeyOperationsResponse represents a list of operations for the current API key
|
||||||
|
type ListAPIKeyOperationsResponse struct {
|
||||||
|
Operations []string `json:"operations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListAPIKeyOperations) Response() interface{} {
|
||||||
|
return new(ListAPIKeyOperationsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKey get an API key
|
||||||
|
type GetAPIKey struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
_ bool `name:"getApiKey" description:"Get an API key."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (GetAPIKey) Response() interface{} {
|
||||||
|
return new(APIKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAPIKey represents a revocation of an API key
|
||||||
|
type RevokeAPIKey struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
_ bool `name:"revokeApiKey" description:"Revoke an API key."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeAPIKeyResponse represents the response to an API key revocation
|
||||||
|
type RevokeAPIKeyResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RevokeAPIKey) Response() interface{} {
|
||||||
|
return new(RevokeAPIKeyResponse)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// InstanceGroup represents a group of VM
|
||||||
|
type InstanceGroup struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account owning the instance group"`
|
||||||
|
Created string `json:"created,omitempty" doc:"time and date the instance group was created"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the id of the instance group"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the instance group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListInstanceGroups request
|
||||||
|
func (ig InstanceGroup) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListInstanceGroups{
|
||||||
|
ID: ig.ID,
|
||||||
|
Name: ig.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInstanceGroup creates a VM group
|
||||||
|
type CreateInstanceGroup struct {
|
||||||
|
Name string `json:"name" doc:"the name of the instance group"`
|
||||||
|
_ bool `name:"createInstanceGroup" description:"Creates a vm group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateInstanceGroup) Response() interface{} {
|
||||||
|
return new(InstanceGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInstanceGroup updates a VM group
|
||||||
|
type UpdateInstanceGroup struct {
|
||||||
|
ID *UUID `json:"id" doc:"Instance group ID"`
|
||||||
|
Name string `json:"name,omitempty" doc:"new instance group name"`
|
||||||
|
_ bool `name:"updateInstanceGroup" description:"Updates a vm group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (UpdateInstanceGroup) Response() interface{} {
|
||||||
|
return new(InstanceGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteInstanceGroup deletes a VM group
|
||||||
|
type DeleteInstanceGroup struct {
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the instance group"`
|
||||||
|
_ bool `name:"deleteInstanceGroup" description:"Deletes a vm group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteInstanceGroup) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListInstanceGroups
|
||||||
|
|
||||||
|
// ListInstanceGroups lists VM groups
|
||||||
|
type ListInstanceGroups struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List instance groups by ID"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"List instance groups by name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
_ bool `name:"listInstanceGroups" description:"Lists vm groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInstanceGroupsResponse represents a list of instance groups
|
||||||
|
type ListInstanceGroupsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
InstanceGroup []InstanceGroup `json:"instancegroup"`
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// InstancePoolState represents the state of an Instance Pool.
|
||||||
|
type InstancePoolState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InstancePoolCreating creating state.
|
||||||
|
InstancePoolCreating InstancePoolState = "creating"
|
||||||
|
// InstancePoolRunning running state.
|
||||||
|
InstancePoolRunning InstancePoolState = "running"
|
||||||
|
// InstancePoolDestroying destroying state.
|
||||||
|
InstancePoolDestroying InstancePoolState = "destroying"
|
||||||
|
// InstancePoolScalingUp scaling up state.
|
||||||
|
InstancePoolScalingUp InstancePoolState = "scaling-up"
|
||||||
|
// InstancePoolScalingDown scaling down state.
|
||||||
|
InstancePoolScalingDown InstancePoolState = "scaling-down"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstancePool represents an Instance Pool.
|
||||||
|
type InstancePool struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ServiceOfferingID *UUID `json:"serviceofferingid"`
|
||||||
|
TemplateID *UUID `json:"templateid"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
AntiAffinityGroupIDs []UUID `json:"affinitygroupids"`
|
||||||
|
SecurityGroupIDs []UUID `json:"securitygroupids"`
|
||||||
|
NetworkIDs []UUID `json:"networkids"`
|
||||||
|
IPv6 bool `json:"ipv6"`
|
||||||
|
KeyPair string `json:"keypair"`
|
||||||
|
UserData string `json:"userdata"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
RootDiskSize int `json:"rootdisksize"`
|
||||||
|
State InstancePoolState `json:"state"`
|
||||||
|
VirtualMachines []VirtualMachine `json:"virtualmachines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInstancePool represents an Instance Pool creation API request.
|
||||||
|
type CreateInstancePool struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
ServiceOfferingID *UUID `json:"serviceofferingid"`
|
||||||
|
TemplateID *UUID `json:"templateid"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
AntiAffinityGroupIDs []UUID `json:"affinitygroupids,omitempty"`
|
||||||
|
SecurityGroupIDs []UUID `json:"securitygroupids,omitempty"`
|
||||||
|
NetworkIDs []UUID `json:"networkids,omitempty"`
|
||||||
|
IPv6 bool `json:"ipv6,omitempty"`
|
||||||
|
KeyPair string `json:"keypair,omitempty"`
|
||||||
|
UserData string `json:"userdata,omitempty"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
RootDiskSize int `json:"rootdisksize,omitempty"`
|
||||||
|
_ bool `name:"createInstancePool" description:"Create an Instance Pool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInstancePoolResponse represents an Instance Pool creation API response.
|
||||||
|
type CreateInstancePoolResponse struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ServiceOfferingID *UUID `json:"serviceofferingid"`
|
||||||
|
TemplateID *UUID `json:"templateid"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
AntiAffinityGroupIDs []UUID `json:"affinitygroupids"`
|
||||||
|
SecurityGroupIDs []UUID `json:"securitygroupids"`
|
||||||
|
NetworkIDs []UUID `json:"networkids"`
|
||||||
|
IPv6 bool `json:"ipv6"`
|
||||||
|
KeyPair string `json:"keypair"`
|
||||||
|
UserData string `json:"userdata"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
RootDiskSize int `json:"rootdisksize"`
|
||||||
|
State InstancePoolState `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool creation API response into.
|
||||||
|
func (CreateInstancePool) Response() interface{} {
|
||||||
|
return new(CreateInstancePoolResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInstancePool represents an Instance Pool update API request.
|
||||||
|
type UpdateInstancePool struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
TemplateID *UUID `json:"templateid,omitempty"`
|
||||||
|
RootDiskSize int `json:"rootdisksize,omitempty"`
|
||||||
|
UserData string `json:"userdata,omitempty"`
|
||||||
|
IPv6 bool `json:"ipv6,omitempty"`
|
||||||
|
_ bool `name:"updateInstancePool" description:"Update an Instance Pool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool update API response into.
|
||||||
|
func (UpdateInstancePool) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleInstancePool represents an Instance Pool scaling API request.
|
||||||
|
type ScaleInstancePool struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
_ bool `name:"scaleInstancePool" description:"Scale an Instance Pool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool scaling API response into.
|
||||||
|
func (ScaleInstancePool) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyInstancePool represents an Instance Pool destruction API request.
|
||||||
|
type DestroyInstancePool struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
_ bool `name:"destroyInstancePool" description:"Destroy an Instance Pool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool destruction API response into.
|
||||||
|
func (DestroyInstancePool) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstancePool retrieves an Instance Pool's details.
|
||||||
|
type GetInstancePool struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
_ bool `name:"getInstancePool" description:"Get an Instance Pool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstancePoolResponse get Instance Pool API response.
|
||||||
|
type GetInstancePoolResponse struct {
|
||||||
|
Count int
|
||||||
|
InstancePools []InstancePool `json:"instancepool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool get API response into.
|
||||||
|
func (GetInstancePool) Response() interface{} {
|
||||||
|
return new(GetInstancePoolResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInstancePools represents a list Instance Pool API request.
|
||||||
|
type ListInstancePools struct {
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
_ bool `name:"listInstancePools" description:"List Instance Pools"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInstancePoolsResponse represents a list Instance Pool API response.
|
||||||
|
type ListInstancePoolsResponse struct {
|
||||||
|
Count int
|
||||||
|
InstancePools []InstancePool `json:"instancepool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool list API response into.
|
||||||
|
func (ListInstancePools) Response() interface{} {
|
||||||
|
return new(ListInstancePoolsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvictInstancePoolMembers represents an Instance Pool members eviction API request.
|
||||||
|
type EvictInstancePoolMembers struct {
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
ZoneID *UUID `json:"zoneid"`
|
||||||
|
MemberIDs []UUID `json:"memberids"`
|
||||||
|
_ bool `name:"evictInstancePoolMembers" description:"Evict some Instance Pool members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns an empty structure to unmarshal an Instance Pool members eviction API response into.
|
||||||
|
func (EvictInstancePoolMembers) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListInstanceGroups) Response() interface{} {
|
||||||
|
return new(ListInstanceGroupsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListInstanceGroups) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListInstanceGroups) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListInstanceGroups) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListInstanceGroups) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListInstanceGroupsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListInstanceGroupsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.InstanceGroup {
|
||||||
|
if !callback(&items.InstanceGroup[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// ISO represents an attachable ISO disc
|
||||||
|
type ISO Template
|
||||||
|
|
||||||
|
// ResourceType returns the type of the resource
|
||||||
|
func (ISO) ResourceType() string {
|
||||||
|
return "ISO"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest produces the ListIsos command.
|
||||||
|
func (iso ISO) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListISOs{
|
||||||
|
ID: iso.ID,
|
||||||
|
Name: iso.Name,
|
||||||
|
ZoneID: iso.ZoneID,
|
||||||
|
}
|
||||||
|
if iso.Bootable {
|
||||||
|
*req.Bootable = true
|
||||||
|
}
|
||||||
|
if iso.IsFeatured {
|
||||||
|
req.IsoFilter = "featured"
|
||||||
|
}
|
||||||
|
if iso.IsPublic {
|
||||||
|
*req.IsPublic = true
|
||||||
|
}
|
||||||
|
if iso.IsReady {
|
||||||
|
*req.IsReady = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range iso.Tags {
|
||||||
|
req.Tags = append(req.Tags, iso.Tags[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListISOs
|
||||||
|
|
||||||
|
// ListISOs represents the list all available ISO files request
|
||||||
|
type ListISOs struct {
|
||||||
|
_ bool `name:"listIsos" description:"Lists all available ISO files."`
|
||||||
|
Bootable *bool `json:"bootable,omitempty" doc:"True if the ISO is bootable, false otherwise"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List ISO by id"`
|
||||||
|
IsoFilter string `json:"isofilter,omitempty" doc:"Possible values are \"featured\", \"self\", \"selfexecutable\",\"sharedexecutable\",\"executable\", and \"community\". * featured : templates that have been marked as featured and public. * self : templates that have been registered or created by the calling user. * selfexecutable : same as self, but only returns templates that can be used to deploy a new VM. * sharedexecutable : templates ready to be deployed that have been granted to the calling user by another user. * executable : templates that are owned by the calling user, or public templates, that can be used to deploy a VM. * community : templates that have been marked as public but not featured. * all : all templates (only usable by admins)."`
|
||||||
|
IsPublic *bool `json:"ispublic,omitempty" doc:"True if the ISO is publicly available to all users, false otherwise."`
|
||||||
|
IsReady *bool `json:"isready,omitempty" doc:"True if this ISO is ready to be deployed"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"List all isos by name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
ShowRemoved *bool `json:"showremoved,omitempty" doc:"Show removed ISOs as well"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"The ID of the zone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListISOsResponse represents a list of ISO files
|
||||||
|
type ListISOsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
ISO []ISO `json:"iso"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachISO represents the request to attach an ISO to a virtual machine.
|
||||||
|
type AttachISO struct {
|
||||||
|
_ bool `name:"attachIso" description:"Attaches an ISO to a virtual machine."`
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the ISO file"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid" doc:"the ID of the virtual machine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (AttachISO) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (AttachISO) AsyncResponse() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachISO represents the request to detach an ISO to a virtual machine.
|
||||||
|
type DetachISO struct {
|
||||||
|
_ bool `name:"detachIso" description:"Detaches any ISO file (if any) currently attached to a virtual machine."`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid" doc:"The ID of the virtual machine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DetachISO) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DetachISO) AsyncResponse() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListISOs) Response() interface{} {
|
||||||
|
return new(ListISOsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListISOs) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListISOs) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListISOs) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListISOs) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListISOsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListISOsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.ISO {
|
||||||
|
if !callback(&items.ISO[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Code generated by "stringer -type JobStatusType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Pending-0]
|
||||||
|
_ = x[Success-1]
|
||||||
|
_ = x[Failure-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _JobStatusType_name = "PendingSuccessFailure"
|
||||||
|
|
||||||
|
var _JobStatusType_index = [...]uint8{0, 7, 14, 21}
|
||||||
|
|
||||||
|
func (i JobStatusType) String() string {
|
||||||
|
if i < 0 || i >= JobStatusType(len(_JobStatusType_index)-1) {
|
||||||
|
return "JobStatusType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _JobStatusType_name[_JobStatusType_index[i]:_JobStatusType_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MACAddress is a nicely JSON serializable net.HardwareAddr
|
||||||
|
type MACAddress net.HardwareAddr
|
||||||
|
|
||||||
|
// String returns the MAC address in standard format
|
||||||
|
func (mac MACAddress) String() string {
|
||||||
|
return (net.HardwareAddr)(mac).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAC48 builds a MAC-48 MACAddress
|
||||||
|
func MAC48(a, b, c, d, e, f byte) MACAddress {
|
||||||
|
m := make(MACAddress, 6)
|
||||||
|
m[0] = a
|
||||||
|
m[1] = b
|
||||||
|
m[2] = c
|
||||||
|
m[3] = d
|
||||||
|
m[4] = e
|
||||||
|
m[5] = f
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals the raw JSON into the MAC address
|
||||||
|
func (mac *MACAddress) UnmarshalJSON(b []byte) error {
|
||||||
|
var addr string
|
||||||
|
if err := json.Unmarshal(b, &addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hw, err := ParseMAC(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*mac = make(MACAddress, 6)
|
||||||
|
copy(*mac, hw)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts the MAC Address to a string representation
|
||||||
|
func (mac MACAddress) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf("%q", mac.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMAC converts a string into a MACAddress
|
||||||
|
func ParseMAC(s string) (MACAddress, error) {
|
||||||
|
hw, err := net.ParseMAC(s)
|
||||||
|
return (MACAddress)(hw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseMAC acts like ParseMAC but panics if in case of an error
|
||||||
|
func MustParseMAC(s string) MACAddress {
|
||||||
|
mac, err := ParseMAC(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return mac
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Network represents a network
|
||||||
|
//
|
||||||
|
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/latest/networking_and_traffic.html
|
||||||
|
type Network struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the owner of the network"`
|
||||||
|
AccountID *UUID `json:"accountid,omitempty" doc:"the owner ID of the network"`
|
||||||
|
BroadcastDomainType string `json:"broadcastdomaintype,omitempty" doc:"Broadcast domain type of the network"`
|
||||||
|
BroadcastURI string `json:"broadcasturi,omitempty" doc:"broadcast uri of the network."`
|
||||||
|
CanUseForDeploy bool `json:"canusefordeploy,omitempty" doc:"list networks available for vm deployment"`
|
||||||
|
CIDR *CIDR `json:"cidr,omitempty" doc:"Cloudstack managed address space, all CloudStack managed VMs get IP address from CIDR"`
|
||||||
|
DisplayText string `json:"displaytext,omitempty" doc:"the displaytext of the network"`
|
||||||
|
DNS1 net.IP `json:"dns1,omitempty" doc:"the first DNS for the network"`
|
||||||
|
DNS2 net.IP `json:"dns2,omitempty" doc:"the second DNS for the network"`
|
||||||
|
EndIP net.IP `json:"endip,omitempty" doc:"the ending IP address in the network IP range. Required for managed networks."`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty" doc:"the network's gateway"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the id of the network"`
|
||||||
|
IP6CIDR *CIDR `json:"ip6cidr,omitempty" doc:"the cidr of IPv6 network"`
|
||||||
|
IP6Gateway net.IP `json:"ip6gateway,omitempty" doc:"the gateway of IPv6 network"`
|
||||||
|
IsDefault bool `json:"isdefault,omitempty" doc:"true if network is default, false otherwise"`
|
||||||
|
IsPersistent bool `json:"ispersistent,omitempty" doc:"list networks that are persistent"`
|
||||||
|
IsSystem bool `json:"issystem,omitempty" doc:"true if network is system, false otherwise"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the network"`
|
||||||
|
Netmask net.IP `json:"netmask,omitempty" doc:"the network's netmask"`
|
||||||
|
NetworkCIDR *CIDR `json:"networkcidr,omitempty" doc:"the network CIDR of the guest network configured with IP reservation. It is the summation of CIDR and RESERVED_IP_RANGE"`
|
||||||
|
NetworkDomain string `json:"networkdomain,omitempty" doc:"the network domain"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"the physical network id"`
|
||||||
|
Related string `json:"related,omitempty" doc:"related to what other network configuration"`
|
||||||
|
ReservedIPRange string `json:"reservediprange,omitempty" doc:"the network's IP range not to be used by CloudStack guest VMs and can be used for non CloudStack purposes"`
|
||||||
|
RestartRequired bool `json:"restartrequired,omitempty" doc:"true network requires restart"`
|
||||||
|
Service []Service `json:"service,omitempty" doc:"the list of services"`
|
||||||
|
SpecifyIPRanges bool `json:"specifyipranges,omitempty" doc:"true if network supports specifying ip ranges, false otherwise"`
|
||||||
|
StartIP net.IP `json:"startip,omitempty" doc:"the beginning IP address in the network IP range. Required for managed networks."`
|
||||||
|
State string `json:"state,omitempty" doc:"state of the network"`
|
||||||
|
StrechedL2Subnet bool `json:"strechedl2subnet,omitempty" doc:"true if network can span multiple zones"`
|
||||||
|
SubdomainAccess bool `json:"subdomainaccess,omitempty" doc:"true if users from subdomains can access the domain level network"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"the list of resource tags associated with network"`
|
||||||
|
TrafficType string `json:"traffictype,omitempty" doc:"the traffic type of the network"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the network"`
|
||||||
|
Vlan string `json:"vlan,omitempty" doc:"The vlan of the network. This parameter is visible to ROOT admins only"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"zone id of the network"`
|
||||||
|
ZoneName string `json:"zonename,omitempty" doc:"the name of the zone the network belongs to"`
|
||||||
|
ZonesNetworkSpans []Zone `json:"zonesnetworkspans,omitempty" doc:"If a network is enabled for 'streched l2 subnet' then represents zones on which network currently spans"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListNetworks request
|
||||||
|
func (network Network) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListNetworks{
|
||||||
|
ID: network.ID,
|
||||||
|
Keyword: network.Name, // this is a hack as listNetworks doesn't support to search by name.
|
||||||
|
PhysicalNetworkID: network.PhysicalNetworkID,
|
||||||
|
TrafficType: network.TrafficType,
|
||||||
|
Type: network.Type,
|
||||||
|
ZoneID: network.ZoneID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if network.CanUseForDeploy {
|
||||||
|
req.CanUseForDeploy = &network.CanUseForDeploy
|
||||||
|
}
|
||||||
|
if network.RestartRequired {
|
||||||
|
req.RestartRequired = &network.RestartRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceType returns the type of the resource
|
||||||
|
func (Network) ResourceType() string {
|
||||||
|
return "Network"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is a feature of a network
|
||||||
|
type Service struct {
|
||||||
|
Capability []ServiceCapability `json:"capability,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Provider []ServiceProvider `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceCapability represents optional capability of a service
|
||||||
|
type ServiceCapability struct {
|
||||||
|
CanChooseServiceCapability bool `json:"canchooseservicecapability"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceProvider represents the provider of the service
|
||||||
|
type ServiceProvider struct {
|
||||||
|
CanEnableIndividualService bool `json:"canenableindividualservice"`
|
||||||
|
DestinationPhysicalNetworkID *UUID `json:"destinationphysicalnetworkid"`
|
||||||
|
ID *UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid"`
|
||||||
|
ServiceList []string `json:"servicelist,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNetwork creates a network
|
||||||
|
type CreateNetwork struct {
|
||||||
|
DisplayText string `json:"displaytext,omitempty" doc:"the display text of the network"` // This field is required but might be empty
|
||||||
|
EndIP net.IP `json:"endip,omitempty" doc:"the ending IP address in the network IP range. Required for managed networks."`
|
||||||
|
EndIpv6 net.IP `json:"endipv6,omitempty" doc:"the ending IPv6 address in the IPv6 network range"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty" doc:"the gateway of the network. Required for Shared networks and Isolated networks when it belongs to VPC"`
|
||||||
|
IP6CIDR *CIDR `json:"ip6cidr,omitempty" doc:"the CIDR of IPv6 network, must be at least /64"`
|
||||||
|
IP6Gateway net.IP `json:"ip6gateway,omitempty" doc:"the gateway of the IPv6 network. Required for Shared networks and Isolated networks when it belongs to VPC"`
|
||||||
|
IsolatedPVlan string `json:"isolatedpvlan,omitempty" doc:"the isolated private vlan for this network"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the network"` // This field is required but might be empty
|
||||||
|
Netmask net.IP `json:"netmask,omitempty" doc:"the netmask of the network. Required for managed networks."`
|
||||||
|
NetworkDomain string `json:"networkdomain,omitempty" doc:"network domain"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"the Physical Network ID the network belongs to"`
|
||||||
|
StartIP net.IP `json:"startip,omitempty" doc:"the beginning IP address in the network IP range. Required for managed networks."`
|
||||||
|
StartIpv6 net.IP `json:"startipv6,omitempty" doc:"the beginning IPv6 address in the IPv6 network range"`
|
||||||
|
Vlan string `json:"vlan,omitempty" doc:"the ID or VID of the network"`
|
||||||
|
ZoneID *UUID `json:"zoneid" doc:"the Zone ID for the network"`
|
||||||
|
_ bool `name:"createNetwork" description:"Creates a network"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateNetwork) Response() interface{} {
|
||||||
|
return new(Network)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req CreateNetwork) onBeforeSend(params url.Values) error {
|
||||||
|
// Those fields are required but might be empty
|
||||||
|
if req.Name == "" {
|
||||||
|
params.Set("name", "")
|
||||||
|
}
|
||||||
|
if req.DisplayText == "" {
|
||||||
|
params.Set("displaytext", "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNetwork (Async) updates a network
|
||||||
|
type UpdateNetwork struct {
|
||||||
|
_ bool `name:"updateNetwork" description:"Updates a network"`
|
||||||
|
ChangeCIDR *bool `json:"changecidr,omitempty" doc:"Force update even if cidr type is different"`
|
||||||
|
DisplayText string `json:"displaytext,omitempty" doc:"the new display text for the network"`
|
||||||
|
EndIP net.IP `json:"endip,omitempty" doc:"the ending IP address in the network IP range. Required for managed networks."`
|
||||||
|
GuestVMCIDR *CIDR `json:"guestvmcidr,omitempty" doc:"CIDR for Guest VMs,Cloudstack allocates IPs to Guest VMs only from this CIDR"`
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the network"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the new name for the network"`
|
||||||
|
Netmask net.IP `json:"netmask,omitempty" doc:"the netmask of the network. Required for managed networks."`
|
||||||
|
NetworkDomain string `json:"networkdomain,omitempty" doc:"network domain"`
|
||||||
|
StartIP net.IP `json:"startip,omitempty" doc:"the beginning IP address in the network IP range. Required for managed networks."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (UpdateNetwork) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (UpdateNetwork) AsyncResponse() interface{} {
|
||||||
|
return new(Network)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestartNetwork (Async) updates a network
|
||||||
|
type RestartNetwork struct {
|
||||||
|
ID *UUID `json:"id" doc:"The id of the network to restart."`
|
||||||
|
Cleanup *bool `json:"cleanup,omitempty" doc:"If cleanup old network elements"`
|
||||||
|
_ bool `name:"restartNetwork" description:"Restarts the network; includes 1) restarting network elements - virtual routers, dhcp servers 2) reapplying all public ips 3) reapplying loadBalancing/portForwarding rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RestartNetwork) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RestartNetwork) AsyncResponse() interface{} {
|
||||||
|
return new(Network)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNetwork deletes a network
|
||||||
|
type DeleteNetwork struct {
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the network"`
|
||||||
|
Forced *bool `json:"forced,omitempty" doc:"Force delete a network. Network will be marked as 'Destroy' even when commands to shutdown and cleanup to the backend fails."`
|
||||||
|
_ bool `name:"deleteNetwork" description:"Deletes a network"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteNetwork) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DeleteNetwork) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListNetworks
|
||||||
|
|
||||||
|
// ListNetworks represents a query to a network
|
||||||
|
type ListNetworks struct {
|
||||||
|
CanUseForDeploy *bool `json:"canusefordeploy,omitempty" doc:"List networks available for vm deployment"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List networks by id"`
|
||||||
|
IsSystem *bool `json:"issystem,omitempty" doc:"true If network is system, false otherwise"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"List networks by physical network id"`
|
||||||
|
RestartRequired *bool `json:"restartrequired,omitempty" doc:"List networks by restartRequired"`
|
||||||
|
SpecifyIPRanges *bool `json:"specifyipranges,omitempty" doc:"True if need to list only networks which support specifying ip ranges"`
|
||||||
|
SupportedServices []Service `json:"supportedservices,omitempty" doc:"List networks supporting certain services"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
|
||||||
|
TrafficType string `json:"traffictype,omitempty" doc:"Type of the traffic"`
|
||||||
|
Type string `json:"type,omitempty" doc:"The type of the network. Supported values are: Isolated and Shared"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"The Zone ID of the network"`
|
||||||
|
_ bool `name:"listNetworks" description:"Lists all available networks."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNetworksResponse represents the list of networks
|
||||||
|
type ListNetworksResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Network []Network `json:"network"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListNetworks) Response() interface{} {
|
||||||
|
return new(ListNetworksResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListNetworks) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListNetworks) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListNetworks) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListNetworks) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListNetworksResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListNetworksResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Network {
|
||||||
|
if !callback(&items.Network[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nic represents a Network Interface Controller (NIC)
|
||||||
|
//
|
||||||
|
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/latest/networking_and_traffic.html#configuring-multiple-ip-addresses-on-a-single-nic
|
||||||
|
type Nic struct {
|
||||||
|
BroadcastURI string `json:"broadcasturi,omitempty" doc:"the broadcast uri of the nic"`
|
||||||
|
DeviceID *UUID `json:"deviceid,omitempty" doc:"device id for the network when plugged into the virtual machine"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty" doc:"the gateway of the nic"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the nic"`
|
||||||
|
IP6Address net.IP `json:"ip6address,omitempty" doc:"the IPv6 address of network"`
|
||||||
|
IP6CIDR *CIDR `json:"ip6cidr,omitempty" doc:"the cidr of IPv6 network"`
|
||||||
|
IP6Gateway net.IP `json:"ip6gateway,omitempty" doc:"the gateway of IPv6 network"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"the ip address of the nic"`
|
||||||
|
IsDefault bool `json:"isdefault,omitempty" doc:"true if nic is default, false otherwise"`
|
||||||
|
IsolationURI string `json:"isolationuri,omitempty" doc:"the isolation uri of the nic"`
|
||||||
|
MACAddress MACAddress `json:"macaddress,omitempty" doc:"true if nic is default, false otherwise"`
|
||||||
|
Netmask net.IP `json:"netmask,omitempty" doc:"the netmask of the nic"`
|
||||||
|
NetworkID *UUID `json:"networkid,omitempty" doc:"the ID of the corresponding network"`
|
||||||
|
NetworkName string `json:"networkname,omitempty" doc:"the name of the corresponding network"`
|
||||||
|
ReverseDNS []ReverseDNS `json:"reversedns,omitempty" doc:"the list of PTR record(s) associated with the virtual machine"`
|
||||||
|
SecondaryIP []NicSecondaryIP `json:"secondaryip,omitempty" doc:"the Secondary ipv4 addr of nic"`
|
||||||
|
TrafficType string `json:"traffictype,omitempty" doc:"the traffic type of the nic"`
|
||||||
|
Type string `json:"type,omitempty" doc:"the type of the nic"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Id of the vm to which the nic belongs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest build a ListNics request from the given Nic
|
||||||
|
func (nic Nic) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListNics{
|
||||||
|
VirtualMachineID: nic.VirtualMachineID,
|
||||||
|
NicID: nic.ID,
|
||||||
|
NetworkID: nic.NetworkID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NicSecondaryIP represents a link between NicID and IPAddress
|
||||||
|
type NicSecondaryIP struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the secondary private IP addr"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"Secondary IP address"`
|
||||||
|
NetworkID *UUID `json:"networkid,omitempty" doc:"the ID of the network"`
|
||||||
|
NicID *UUID `json:"nicid,omitempty" doc:"the ID of the nic"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"the ID of the vm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListNics
|
||||||
|
|
||||||
|
// ListNics represents the NIC search
|
||||||
|
type ListNics struct {
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
NetworkID *UUID `json:"networkid,omitempty" doc:"list nic of the specific vm's network"`
|
||||||
|
NicID *UUID `json:"nicid,omitempty" doc:"the ID of the nic to to list IPs"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"the ID of the vm"`
|
||||||
|
_ bool `name:"listNics" description:"list the vm nics IP to NIC"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNicsResponse represents a list of templates
|
||||||
|
type ListNicsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Nic []Nic `json:"nic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIPToNic (Async) represents the assignation of a secondary IP
|
||||||
|
type AddIPToNic struct {
|
||||||
|
NicID *UUID `json:"nicid" doc:"the ID of the nic to which you want to assign private IP"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"Secondary IP Address"`
|
||||||
|
_ bool `name:"addIpToNic" description:"Assigns secondary IP to NIC"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (AddIPToNic) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (AddIPToNic) AsyncResponse() interface{} {
|
||||||
|
return new(NicSecondaryIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveIPFromNic (Async) represents a deletion request
|
||||||
|
type RemoveIPFromNic struct {
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the secondary ip address to nic"`
|
||||||
|
_ bool `name:"removeIpFromNic" description:"Removes secondary IP from the NIC."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RemoveIPFromNic) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RemoveIPFromNic) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateIP6 (Async) activates the IP6 on the given NIC
|
||||||
|
//
|
||||||
|
// Exoscale specific API: https://community.exoscale.com/api/compute/#activateip6_GET
|
||||||
|
type ActivateIP6 struct {
|
||||||
|
NicID *UUID `json:"nicid" doc:"the ID of the nic to which you want to assign the IPv6"`
|
||||||
|
_ bool `name:"activateIp6" description:"Activate the IPv6 on the VM's nic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ActivateIP6) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (ActivateIP6) AsyncResponse() interface{} {
|
||||||
|
return new(Nic)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListNics) Response() interface{} {
|
||||||
|
return new(ListNicsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListNics) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListNics) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListNics) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListNics) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListNicsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListNicsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Nic {
|
||||||
|
if !callback(&items.Nic[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListOSCategories) Response() interface{} {
|
||||||
|
return new(ListOSCategoriesResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListOSCategories) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListOSCategories) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListOSCategories) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListOSCategories) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListOSCategoriesResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListOSCategoriesResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.OSCategory {
|
||||||
|
if !callback(&items.OSCategory[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListPublicIPAddresses) Response() interface{} {
|
||||||
|
return new(ListPublicIPAddressesResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListPublicIPAddresses) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListPublicIPAddresses) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListPublicIPAddresses) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListPublicIPAddresses) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListPublicIPAddressesResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListPublicIPAddressesResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.PublicIPAddress {
|
||||||
|
if !callback(&items.PublicIPAddress[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Code generated by "stringer -type=Record"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[A-0]
|
||||||
|
_ = x[AAAA-1]
|
||||||
|
_ = x[ALIAS-2]
|
||||||
|
_ = x[CNAME-3]
|
||||||
|
_ = x[HINFO-4]
|
||||||
|
_ = x[MX-5]
|
||||||
|
_ = x[NAPTR-6]
|
||||||
|
_ = x[NS-7]
|
||||||
|
_ = x[POOL-8]
|
||||||
|
_ = x[SPF-9]
|
||||||
|
_ = x[SRV-10]
|
||||||
|
_ = x[SSHFP-11]
|
||||||
|
_ = x[TXT-12]
|
||||||
|
_ = x[URL-13]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Record_name = "AAAAAALIASCNAMEHINFOMXNAPTRNSPOOLSPFSRVSSHFPTXTURL"
|
||||||
|
|
||||||
|
var _Record_index = [...]uint8{0, 1, 5, 10, 15, 20, 22, 27, 29, 33, 36, 39, 44, 47, 50}
|
||||||
|
|
||||||
|
func (i Record) String() string {
|
||||||
|
if i < 0 || i >= Record(len(_Record_index)-1) {
|
||||||
|
return "Record(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Record_name[_Record_index[i]:_Record_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,404 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error formats a CloudStack error into a standard error
|
||||||
|
func (e ErrorResponse) Error() string {
|
||||||
|
return fmt.Sprintf("API error %s %d (%s %d): %s", e.ErrorCode, e.ErrorCode, e.CSErrorCode, e.CSErrorCode, e.ErrorText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error formats a CloudStack job response into a standard error
|
||||||
|
func (e BooleanResponse) Error() error {
|
||||||
|
if !e.Success {
|
||||||
|
return fmt.Errorf("API error: %s", e.DisplayText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseKey(key string) (string, bool) {
|
||||||
|
// XXX: addIpToNic, activateIp6, restorevmresponse are kind of special
|
||||||
|
var responseKeys = map[string]string{
|
||||||
|
"addiptonicresponse": "addiptovmnicresponse",
|
||||||
|
"activateip6response": "activateip6nicresponse",
|
||||||
|
"restorevirtualmachineresponse": "restorevmresponse",
|
||||||
|
"updatevmaffinitygroupresponse": "updatevirtualmachineresponse",
|
||||||
|
}
|
||||||
|
|
||||||
|
k, ok := responseKeys[key]
|
||||||
|
return k, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) parseResponse(resp *http.Response, apiName string) (json.RawMessage, error) {
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]json.RawMessage{}
|
||||||
|
if err := json.Unmarshal(b, &m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%sresponse", strings.ToLower(apiName))
|
||||||
|
response, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
response, ok = m["errorresponse"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// try again with the special keys
|
||||||
|
value, ok := responseKey(key)
|
||||||
|
if ok {
|
||||||
|
key = value
|
||||||
|
}
|
||||||
|
|
||||||
|
response, ok = m[key]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("malformed JSON response %d, %q was expected.\n%s", resp.StatusCode, key, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
errorResponse := new(ErrorResponse)
|
||||||
|
if e := json.Unmarshal(response, errorResponse); e != nil && errorResponse.ErrorCode <= 0 {
|
||||||
|
return nil, fmt.Errorf("%d %s", resp.StatusCode, b)
|
||||||
|
}
|
||||||
|
return nil, errorResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
n := map[string]json.RawMessage{}
|
||||||
|
if err := json.Unmarshal(response, &n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// list response may contain only one key
|
||||||
|
if len(n) > 1 || strings.HasPrefix(key, "list") {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n) == 1 {
|
||||||
|
for k := range n {
|
||||||
|
// boolean response and asyncjob result may also contain
|
||||||
|
// only one key
|
||||||
|
if k == "success" || k == "jobid" {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return n[k], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncRequest perform an asynchronous job with a context
|
||||||
|
func (client *Client) asyncRequest(ctx context.Context, asyncCommand AsyncCommand) (interface{}, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
resp := asyncCommand.AsyncResponse()
|
||||||
|
client.AsyncRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
asyncCommand,
|
||||||
|
func(j *AsyncJobResult, e error) bool {
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if j.JobStatus != Pending {
|
||||||
|
if r := j.Result(resp); r != nil {
|
||||||
|
err = r
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncRequestWithContext performs a sync request with a context
|
||||||
|
func (client *Client) SyncRequestWithContext(ctx context.Context, command Command) (interface{}, error) {
|
||||||
|
body, err := client.request(ctx, command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := command.Response()
|
||||||
|
b, ok := response.(*BooleanResponse)
|
||||||
|
if ok {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if errUnmarshal := json.Unmarshal(body, &m); errUnmarshal != nil {
|
||||||
|
return nil, errUnmarshal
|
||||||
|
}
|
||||||
|
|
||||||
|
b.DisplayText, _ = m["displaytext"].(string)
|
||||||
|
|
||||||
|
if success, okSuccess := m["success"].(string); okSuccess {
|
||||||
|
b.Success = success == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
if success, okSuccess := m["success"].(bool); okSuccess {
|
||||||
|
b.Success = success
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, response); err != nil {
|
||||||
|
errResponse := new(ErrorResponse)
|
||||||
|
if e := json.Unmarshal(body, errResponse); e == nil && errResponse.ErrorCode > 0 {
|
||||||
|
return errResponse, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BooleanRequest performs the given boolean command
|
||||||
|
func (client *Client) BooleanRequest(command Command) error {
|
||||||
|
resp, err := client.Request(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, ok := resp.(*BooleanResponse); ok {
|
||||||
|
return b.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Errorf("command %q is not a proper boolean response. %#v", client.APIName(command), resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BooleanRequestWithContext performs the given boolean command
|
||||||
|
func (client *Client) BooleanRequestWithContext(ctx context.Context, command Command) error {
|
||||||
|
resp, err := client.RequestWithContext(ctx, command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, ok := resp.(*BooleanResponse); ok {
|
||||||
|
return b.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Errorf("command %q is not a proper boolean response. %#v", client.APIName(command), resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request performs the given command
|
||||||
|
func (client *Client) Request(command Command) (interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return client.RequestWithContext(ctx, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestWithContext preforms a command with a context
|
||||||
|
func (client *Client) RequestWithContext(ctx context.Context, command Command) (interface{}, error) {
|
||||||
|
switch c := command.(type) {
|
||||||
|
case AsyncCommand:
|
||||||
|
return client.asyncRequest(ctx, c)
|
||||||
|
default:
|
||||||
|
return client.SyncRequestWithContext(ctx, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncRequest performs the command as is
|
||||||
|
func (client *Client) SyncRequest(command Command) (interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return client.SyncRequestWithContext(ctx, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncRequest performs the given command
|
||||||
|
func (client *Client) AsyncRequest(asyncCommand AsyncCommand, callback WaitAsyncJobResultFunc) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client.AsyncRequestWithContext(ctx, asyncCommand, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncRequestWithContext preforms a request with a context
|
||||||
|
func (client *Client) AsyncRequestWithContext(ctx context.Context, asyncCommand AsyncCommand, callback WaitAsyncJobResultFunc) {
|
||||||
|
result, err := client.SyncRequestWithContext(ctx, asyncCommand)
|
||||||
|
if err != nil {
|
||||||
|
if !callback(nil, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jobResult, ok := result.(*AsyncJobResult)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, AsyncJobResult was expected instead of %T", result))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successful response
|
||||||
|
if jobResult.JobID == nil || jobResult.JobStatus != Pending {
|
||||||
|
callback(jobResult, nil)
|
||||||
|
// without a JobID, the next requests will only fail
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for iteration := 0; ; iteration++ {
|
||||||
|
time.Sleep(client.RetryStrategy(int64(iteration)))
|
||||||
|
|
||||||
|
req := &QueryAsyncJobResult{JobID: jobResult.JobID}
|
||||||
|
resp, err := client.SyncRequestWithContext(ctx, req)
|
||||||
|
if err != nil && !callback(nil, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := resp.(*AsyncJobResult)
|
||||||
|
if !ok {
|
||||||
|
if !callback(nil, fmt.Errorf("wrong type. AsyncJobResult expected, got %T", resp)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !callback(result, nil) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload builds the HTTP request params from the given command
|
||||||
|
func (client *Client) Payload(command Command) (url.Values, error) {
|
||||||
|
params, err := prepareValues("", command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if hookReq, ok := command.(onBeforeHook); ok {
|
||||||
|
if err := hookReq.onBeforeSend(params); err != nil {
|
||||||
|
return params, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.Set("apikey", client.APIKey)
|
||||||
|
params.Set("command", client.APIName(command))
|
||||||
|
params.Set("response", "json")
|
||||||
|
|
||||||
|
if params.Get("expires") == "" && client.Expiration >= 0 {
|
||||||
|
params.Set("signatureversion", "3")
|
||||||
|
params.Set("expires", time.Now().Add(client.Expiration).Local().Format("2006-01-02T15:04:05-0700"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the HTTP request and returns the signature as as base64 encoding
|
||||||
|
func (client *Client) Sign(params url.Values) (string, error) {
|
||||||
|
query := encodeValues(params)
|
||||||
|
query = strings.ToLower(query)
|
||||||
|
mac := hmac.New(sha1.New, []byte(client.apiSecret))
|
||||||
|
_, err := mac.Write([]byte(query))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// request makes a Request while being close to the metal
|
||||||
|
func (client *Client) request(ctx context.Context, command Command) (json.RawMessage, error) {
|
||||||
|
params, err := client.Payload(command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signature, err := client.Sign(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params.Add("signature", signature)
|
||||||
|
|
||||||
|
method := "GET"
|
||||||
|
query := params.Encode()
|
||||||
|
url := fmt.Sprintf("%s?%s", client.Endpoint, query)
|
||||||
|
|
||||||
|
var body io.Reader
|
||||||
|
// respect Internet Explorer limit of 2048
|
||||||
|
if len(url) > 2048 {
|
||||||
|
url = client.Endpoint
|
||||||
|
method = "POST"
|
||||||
|
body = strings.NewReader(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request = request.WithContext(ctx)
|
||||||
|
|
||||||
|
if method == "POST" {
|
||||||
|
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
request.Header.Add("Content-Length", strconv.Itoa(len(query)))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.HTTPClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
contentType := resp.Header.Get("content-type")
|
||||||
|
|
||||||
|
if !strings.Contains(contentType, "application/json") {
|
||||||
|
return nil, fmt.Errorf(`body content-type response expected "application/json", got %q`, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
text, err := client.parseResponse(resp, client.APIName(command))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeValues(params url.Values) string {
|
||||||
|
// This code is borrowed from net/url/url.go
|
||||||
|
// The way it's encoded by net/url doesn't match
|
||||||
|
// how CloudStack works to determine the signature.
|
||||||
|
//
|
||||||
|
// CloudStack only encodes the values of the query parameters
|
||||||
|
// and furthermore doesn't use '+' for whitespaces. Therefore
|
||||||
|
// after encoding the values all '+' are replaced with '%20'.
|
||||||
|
if params == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
prefix := k + "="
|
||||||
|
for _, v := range params[k] {
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
buf.WriteByte('&')
|
||||||
|
}
|
||||||
|
buf.WriteString(prefix)
|
||||||
|
buf.WriteString(csEncode(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command represents a generic request
|
||||||
|
type Command interface {
|
||||||
|
Response() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncCommand represents a async request
|
||||||
|
type AsyncCommand interface {
|
||||||
|
Command
|
||||||
|
AsyncResponse() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCommand represents a listing request
|
||||||
|
type ListCommand interface {
|
||||||
|
Listable
|
||||||
|
Command
|
||||||
|
// SetPage defines the current pages
|
||||||
|
SetPage(int)
|
||||||
|
// SetPageSize defines the size of the page
|
||||||
|
SetPageSize(int)
|
||||||
|
// Each reads the data from the response and feeds channels, and returns true if we are on the last page
|
||||||
|
Each(interface{}, IterateItemFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// onBeforeHook represents an action to be done on the params before sending them
|
||||||
|
//
|
||||||
|
// This little took helps with issue of relying on JSON serialization logic only.
|
||||||
|
// `omitempty` may make sense in some cases but not all the time.
|
||||||
|
type onBeforeHook interface {
|
||||||
|
onBeforeSend(params url.Values) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandInfo represents the meta data related to a Command
|
||||||
|
type CommandInfo struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
RootOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobStatusType represents the status of a Job
|
||||||
|
type JobStatusType int
|
||||||
|
|
||||||
|
//go:generate stringer -type JobStatusType
|
||||||
|
const (
|
||||||
|
// Pending represents a job in progress
|
||||||
|
Pending JobStatusType = iota
|
||||||
|
// Success represents a successfully completed job
|
||||||
|
Success
|
||||||
|
// Failure represents a job that has failed to complete
|
||||||
|
Failure
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorCode represents the CloudStack ApiErrorCode enum
|
||||||
|
//
|
||||||
|
// See: https://github.com/apache/cloudstack/blob/master/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java
|
||||||
|
type ErrorCode int
|
||||||
|
|
||||||
|
//go:generate stringer -type ErrorCode
|
||||||
|
const (
|
||||||
|
// Unauthorized represents ... (TODO)
|
||||||
|
Unauthorized ErrorCode = 401
|
||||||
|
// NotFound represents ... (TODO)
|
||||||
|
NotFound ErrorCode = 404
|
||||||
|
// MethodNotAllowed represents ... (TODO)
|
||||||
|
MethodNotAllowed ErrorCode = 405
|
||||||
|
// UnsupportedActionError represents ... (TODO)
|
||||||
|
UnsupportedActionError ErrorCode = 422
|
||||||
|
// APILimitExceeded represents ... (TODO)
|
||||||
|
APILimitExceeded ErrorCode = 429
|
||||||
|
// MalformedParameterError represents ... (TODO)
|
||||||
|
MalformedParameterError ErrorCode = 430
|
||||||
|
// ParamError represents ... (TODO)
|
||||||
|
ParamError ErrorCode = 431
|
||||||
|
|
||||||
|
// InternalError represents a server error
|
||||||
|
InternalError ErrorCode = 530
|
||||||
|
// AccountError represents ... (TODO)
|
||||||
|
AccountError ErrorCode = 531
|
||||||
|
// AccountResourceLimitError represents ... (TODO)
|
||||||
|
AccountResourceLimitError ErrorCode = 532
|
||||||
|
// InsufficientCapacityError represents ... (TODO)
|
||||||
|
InsufficientCapacityError ErrorCode = 533
|
||||||
|
// ResourceUnavailableError represents ... (TODO)
|
||||||
|
ResourceUnavailableError ErrorCode = 534
|
||||||
|
// ResourceAllocationError represents ... (TODO)
|
||||||
|
ResourceAllocationError ErrorCode = 535
|
||||||
|
// ResourceInUseError represents ... (TODO)
|
||||||
|
ResourceInUseError ErrorCode = 536
|
||||||
|
// NetworkRuleConflictError represents ... (TODO)
|
||||||
|
NetworkRuleConflictError ErrorCode = 537
|
||||||
|
)
|
||||||
|
|
||||||
|
// CSErrorCode represents the CloudStack CSExceptionErrorCode enum
|
||||||
|
//
|
||||||
|
// See: https://github.com/apache/cloudstack/blob/master/utils/src/main/java/com/cloud/utils/exception/CSExceptionErrorCode.java
|
||||||
|
type CSErrorCode int
|
||||||
|
|
||||||
|
//go:generate stringer -type CSErrorCode
|
||||||
|
const (
|
||||||
|
// CloudRuntimeException ... (TODO)
|
||||||
|
CloudRuntimeException CSErrorCode = 4250
|
||||||
|
// ExecutionException ... (TODO)
|
||||||
|
ExecutionException CSErrorCode = 4260
|
||||||
|
// HypervisorVersionChangedException ... (TODO)
|
||||||
|
HypervisorVersionChangedException CSErrorCode = 4265
|
||||||
|
// CloudException ... (TODO)
|
||||||
|
CloudException CSErrorCode = 4275
|
||||||
|
// AccountLimitException ... (TODO)
|
||||||
|
AccountLimitException CSErrorCode = 4280
|
||||||
|
// AgentUnavailableException ... (TODO)
|
||||||
|
AgentUnavailableException CSErrorCode = 4285
|
||||||
|
// CloudAuthenticationException ... (TODO)
|
||||||
|
CloudAuthenticationException CSErrorCode = 4290
|
||||||
|
// ConcurrentOperationException ... (TODO)
|
||||||
|
ConcurrentOperationException CSErrorCode = 4300
|
||||||
|
// ConflictingNetworksException ... (TODO)
|
||||||
|
ConflictingNetworkSettingsException CSErrorCode = 4305
|
||||||
|
// DiscoveredWithErrorException ... (TODO)
|
||||||
|
DiscoveredWithErrorException CSErrorCode = 4310
|
||||||
|
// HAStateException ... (TODO)
|
||||||
|
HAStateException CSErrorCode = 4315
|
||||||
|
// InsufficientAddressCapacityException ... (TODO)
|
||||||
|
InsufficientAddressCapacityException CSErrorCode = 4320
|
||||||
|
// InsufficientCapacityException ... (TODO)
|
||||||
|
InsufficientCapacityException CSErrorCode = 4325
|
||||||
|
// InsufficientNetworkCapacityException ... (TODO)
|
||||||
|
InsufficientNetworkCapacityException CSErrorCode = 4330
|
||||||
|
// InsufficientServerCapaticyException ... (TODO)
|
||||||
|
InsufficientServerCapacityException CSErrorCode = 4335
|
||||||
|
// InsufficientStorageCapacityException ... (TODO)
|
||||||
|
InsufficientStorageCapacityException CSErrorCode = 4340
|
||||||
|
// InternalErrorException ... (TODO)
|
||||||
|
InternalErrorException CSErrorCode = 4345
|
||||||
|
// InvalidParameterValueException ... (TODO)
|
||||||
|
InvalidParameterValueException CSErrorCode = 4350
|
||||||
|
// ManagementServerException ... (TODO)
|
||||||
|
ManagementServerException CSErrorCode = 4355
|
||||||
|
// NetworkRuleConflictException ... (TODO)
|
||||||
|
NetworkRuleConflictException CSErrorCode = 4360
|
||||||
|
// PermissionDeniedException ... (TODO)
|
||||||
|
PermissionDeniedException CSErrorCode = 4365
|
||||||
|
// ResourceAllocationException ... (TODO)
|
||||||
|
ResourceAllocationException CSErrorCode = 4370
|
||||||
|
// ResourceInUseException ... (TODO)
|
||||||
|
ResourceInUseException CSErrorCode = 4375
|
||||||
|
// ResourceUnavailableException ... (TODO)
|
||||||
|
ResourceUnavailableException CSErrorCode = 4380
|
||||||
|
// StorageUnavailableException ... (TODO)
|
||||||
|
StorageUnavailableException CSErrorCode = 4385
|
||||||
|
// UnsupportedServiceException ... (TODO)
|
||||||
|
UnsupportedServiceException CSErrorCode = 4390
|
||||||
|
// VirtualMachineMigrationException ... (TODO)
|
||||||
|
VirtualMachineMigrationException CSErrorCode = 4395
|
||||||
|
// AsyncCommandQueued ... (TODO)
|
||||||
|
AsyncCommandQueued CSErrorCode = 4540
|
||||||
|
// RequestLimitException ... (TODO)
|
||||||
|
RequestLimitException CSErrorCode = 4545
|
||||||
|
// ServerAPIException ... (TODO)
|
||||||
|
ServerAPIException CSErrorCode = 9999
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorResponse represents the standard error response
|
||||||
|
type ErrorResponse struct {
|
||||||
|
CSErrorCode CSErrorCode `json:"cserrorcode"`
|
||||||
|
ErrorCode ErrorCode `json:"errorcode"`
|
||||||
|
ErrorText string `json:"errortext"`
|
||||||
|
UUIDList []UUIDItem `json:"uuidList,omitempty"` // uuid*L*ist is not a typo
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUIDItem represents an item of the UUIDList part of an ErrorResponse
|
||||||
|
type UUIDItem struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
SerialVersionUID int64 `json:"serialVersionUID,omitempty"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BooleanResponse represents a boolean response (usually after a deletion)
|
||||||
|
type BooleanResponse struct {
|
||||||
|
DisplayText string `json:"displaytext,omitempty"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// https://github.com/apache/cloudstack/blob/master/api/src/main/java/com/cloud/configuration/Resource.java
|
||||||
|
|
||||||
|
// ResourceTypeName represents the name of a resource type (for limits)
|
||||||
|
type ResourceTypeName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VirtualMachineTypeName is the resource type name of a VM
|
||||||
|
VirtualMachineTypeName ResourceTypeName = "user_vm"
|
||||||
|
// IPAddressTypeName is the resource type name of an IP address
|
||||||
|
IPAddressTypeName ResourceTypeName = "public_ip"
|
||||||
|
// VolumeTypeName is the resource type name of a volume
|
||||||
|
VolumeTypeName ResourceTypeName = "volume"
|
||||||
|
// SnapshotTypeName is the resource type name of a snapshot
|
||||||
|
SnapshotTypeName ResourceTypeName = "snapshot"
|
||||||
|
// TemplateTypeName is the resource type name of a template
|
||||||
|
TemplateTypeName ResourceTypeName = "template"
|
||||||
|
// ProjectTypeName is the resource type name of a project
|
||||||
|
ProjectTypeName ResourceTypeName = "project"
|
||||||
|
// NetworkTypeName is the resource type name of a network
|
||||||
|
NetworkTypeName ResourceTypeName = "network"
|
||||||
|
// VPCTypeName is the resource type name of a VPC
|
||||||
|
VPCTypeName ResourceTypeName = "vpc"
|
||||||
|
// CPUTypeName is the resource type name of a CPU
|
||||||
|
CPUTypeName ResourceTypeName = "cpu"
|
||||||
|
// MemoryTypeName is the resource type name of Memory
|
||||||
|
MemoryTypeName ResourceTypeName = "memory"
|
||||||
|
// PrimaryStorageTypeName is the resource type name of primary storage
|
||||||
|
PrimaryStorageTypeName ResourceTypeName = "primary_storage"
|
||||||
|
// SecondaryStorageTypeName is the resource type name of secondary storage
|
||||||
|
SecondaryStorageTypeName ResourceTypeName = "secondary_storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourceType represents the ID of a resource type (for limits)
|
||||||
|
type ResourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VirtualMachineType is the resource type ID of a VM
|
||||||
|
VirtualMachineType ResourceType = "0"
|
||||||
|
// IPAddressType is the resource type ID of an IP address
|
||||||
|
IPAddressType ResourceType = "1"
|
||||||
|
// VolumeType is the resource type ID of a volume
|
||||||
|
VolumeType ResourceType = "2"
|
||||||
|
// SnapshotType is the resource type ID of a snapshot
|
||||||
|
SnapshotType ResourceType = "3"
|
||||||
|
// TemplateType is the resource type ID of a template
|
||||||
|
TemplateType ResourceType = "4"
|
||||||
|
// ProjectType is the resource type ID of a project
|
||||||
|
ProjectType ResourceType = "5"
|
||||||
|
// NetworkType is the resource type ID of a network
|
||||||
|
NetworkType ResourceType = "6"
|
||||||
|
// VPCType is the resource type ID of a VPC
|
||||||
|
VPCType ResourceType = "7"
|
||||||
|
// CPUType is the resource type ID of a CPU
|
||||||
|
CPUType ResourceType = "8"
|
||||||
|
// MemoryType is the resource type ID of Memory
|
||||||
|
MemoryType ResourceType = "9"
|
||||||
|
// PrimaryStorageType is the resource type ID of primary storage
|
||||||
|
PrimaryStorageType ResourceType = "10"
|
||||||
|
// SecondaryStorageType is the resource type ID of secondary storage
|
||||||
|
SecondaryStorageType ResourceType = "11"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourceLimit represents the limit on a particular resource
|
||||||
|
type ResourceLimit struct {
|
||||||
|
Max int64 `json:"max,omitempty" doc:"the maximum number of the resource. A -1 means the resource currently has no limit."`
|
||||||
|
ResourceType ResourceType `json:"resourcetype,omitempty" doc:"resource type. Values include 0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11. See the resourceType parameter for more information on these values."`
|
||||||
|
ResourceTypeName string `json:"resourcetypename,omitempty" doc:"resource type name. Values include user_vm, public_ip, volume, snapshot, template, network, cpu, memory, primary_storage, secondary_storage."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListResourceLimits request
|
||||||
|
func (limit ResourceLimit) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListResourceLimits{
|
||||||
|
ResourceType: limit.ResourceType,
|
||||||
|
ResourceTypeName: limit.ResourceTypeName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListResourceLimits
|
||||||
|
|
||||||
|
// ListResourceLimits lists the resource limits
|
||||||
|
type ListResourceLimits struct {
|
||||||
|
ID int64 `json:"id,omitempty" doc:"Lists resource limits by ID."`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
ResourceType ResourceType `json:"resourcetype,omitempty" doc:"Type of resource. Values are 0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, and 13. 0 - Instance. Number of instances a user can create. 1 - IP. Number of public IP addresses an account can own. 2 - Volume. Number of disk volumes an account can own. 3 - Snapshot. Number of snapshots an account can own. 4 - Template. Number of templates an account can register/create. 6 - Network. Number of networks an account can own. 8 - CPU. Number of CPU an account can allocate for his resources. 9 - Memory. Amount of RAM an account can allocate for his resources. 10 - PrimaryStorage. Total primary storage space (in GiB) a user can use. 11 - SecondaryStorage. Total secondary storage space (in GiB) a user can use. 12 - Elastic IP. Number of public elastic IP addresses an account can own. 13 - SMTP. If the account is allowed SMTP outbound traffic."`
|
||||||
|
ResourceTypeName string `json:"resourcetypename,omitempty" doc:"Type of resource (wins over resourceType if both are provided). Values are: user_vm - Instance. Number of instances a user can create. public_ip - IP. Number of public IP addresses an account can own. volume - Volume. Number of disk volumes an account can own. snapshot - Snapshot. Number of snapshots an account can own. template - Template. Number of templates an account can register/create. network - Network. Number of networks an account can own. cpu - CPU. Number of CPU an account can allocate for his resources. memory - Memory. Amount of RAM an account can allocate for his resources. primary_storage - PrimaryStorage. Total primary storage space (in GiB) a user can use. secondary_storage - SecondaryStorage. Total secondary storage space (in GiB) a user can use. public_elastic_ip - IP. Number of public elastic IP addresses an account can own. smtp - SG. If the account is allowed SMTP outbound traffic."`
|
||||||
|
|
||||||
|
_ bool `name:"listResourceLimits" description:"Lists resource limits."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListResourceLimitsResponse represents a list of resource limits
|
||||||
|
type ListResourceLimitsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
ResourceLimit []ResourceLimit `json:"resourcelimit"`
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ResourceDetail represents extra details
|
||||||
|
type ResourceDetail ResourceTag
|
||||||
|
|
||||||
|
// ListRequest builds the ListResourceDetails request
|
||||||
|
func (detail ResourceDetail) ListRequest() (ListCommand, error) {
|
||||||
|
if detail.ResourceType == "" {
|
||||||
|
return nil, fmt.Errorf("the resourcetype parameter is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &ListResourceDetails{
|
||||||
|
ResourceType: detail.ResourceType,
|
||||||
|
ResourceID: detail.ResourceID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListResourceDetails
|
||||||
|
|
||||||
|
// ListResourceDetails lists the resource tag(s) (but different from listTags...)
|
||||||
|
type ListResourceDetails struct {
|
||||||
|
ResourceType string `json:"resourcetype" doc:"list by resource type"`
|
||||||
|
ForDisplay bool `json:"fordisplay,omitempty" doc:"if set to true, only details marked with display=true, are returned. False by default"`
|
||||||
|
Key string `json:"key,omitempty" doc:"list by key"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
ResourceID *UUID `json:"resourceid,omitempty" doc:"list by resource id"`
|
||||||
|
Value string `json:"value,omitempty" doc:"list by key, value. Needs to be passed only along with key"`
|
||||||
|
_ bool `name:"listResourceDetails" description:"List resource detail(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListResourceDetailsResponse represents a list of resource details
|
||||||
|
type ListResourceDetailsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
ResourceDetail []ResourceTag `json:"resourcedetail"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListResourceDetails) Response() interface{} {
|
||||||
|
return new(ListResourceDetailsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListResourceDetails) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListResourceDetails) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListResourceDetails) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListResourceDetails) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListResourceDetailsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListResourceDetailsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.ResourceDetail {
|
||||||
|
if !callback(&items.ResourceDetail[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListResourceLimits) Response() interface{} {
|
||||||
|
return new(ListResourceLimitsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListResourceLimits) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListResourceLimits) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListResourceLimits) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListResourceLimits) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListResourceLimitsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListResourceLimitsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.ResourceLimit {
|
||||||
|
if !callback(&items.ResourceLimit[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReverseDNS represents the PTR record linked with an IPAddress or IP6Address belonging to a Virtual Machine or a Public IP Address (Elastic IP) instance
|
||||||
|
type ReverseDNS struct {
|
||||||
|
DomainName string `json:"domainname,omitempty" doc:"the domain name of the PTR record"`
|
||||||
|
IP6Address net.IP `json:"ip6address,omitempty" doc:"the IPv6 address linked with the PTR record (mutually exclusive with ipaddress)"`
|
||||||
|
IPAddress net.IP `json:"ipaddress,omitempty" doc:"the IPv4 address linked with the PTR record (mutually exclusive with ip6address)"`
|
||||||
|
NicID *UUID `json:"nicid,omitempty" doc:"the virtual machine default NIC ID"`
|
||||||
|
PublicIPID *UUID `json:"publicipid,omitempty" doc:"the public IP address ID"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"the virtual machine ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteReverseDNSFromPublicIPAddress is a command to create/delete the PTR record of a public IP address
|
||||||
|
type DeleteReverseDNSFromPublicIPAddress struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the public IP address"`
|
||||||
|
_ bool `name:"deleteReverseDnsFromPublicIpAddress" description:"delete the PTR DNS record from the public IP address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*DeleteReverseDNSFromPublicIPAddress) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteReverseDNSFromVirtualMachine is a command to create/delete the PTR record(s) of a virtual machine
|
||||||
|
type DeleteReverseDNSFromVirtualMachine struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the virtual machine"`
|
||||||
|
_ bool `name:"deleteReverseDnsFromVirtualMachine" description:"Delete the PTR DNS record(s) from the virtual machine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*DeleteReverseDNSFromVirtualMachine) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryReverseDNSForPublicIPAddress is a command to create/query the PTR record of a public IP address
|
||||||
|
type QueryReverseDNSForPublicIPAddress struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the public IP address"`
|
||||||
|
_ bool `name:"queryReverseDnsForPublicIpAddress" description:"Query the PTR DNS record for the public IP address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*QueryReverseDNSForPublicIPAddress) Response() interface{} {
|
||||||
|
return new(IPAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryReverseDNSForVirtualMachine is a command to create/query the PTR record(s) of a virtual machine
|
||||||
|
type QueryReverseDNSForVirtualMachine struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the virtual machine"`
|
||||||
|
_ bool `name:"queryReverseDnsForVirtualMachine" description:"Query the PTR DNS record(s) for the virtual machine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*QueryReverseDNSForVirtualMachine) Response() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReverseDNSForPublicIPAddress is a command to create/update the PTR record of a public IP address
|
||||||
|
type UpdateReverseDNSForPublicIPAddress struct {
|
||||||
|
DomainName string `json:"domainname,omitempty" doc:"the domain name for the PTR record. It must have a valid TLD"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the public IP address"`
|
||||||
|
_ bool `name:"updateReverseDnsForPublicIpAddress" description:"Update/create the PTR DNS record for the public IP address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*UpdateReverseDNSForPublicIPAddress) Response() interface{} {
|
||||||
|
return new(IPAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReverseDNSForVirtualMachine is a command to create/update the PTR record(s) of a virtual machine
|
||||||
|
type UpdateReverseDNSForVirtualMachine struct {
|
||||||
|
DomainName string `json:"domainname,omitempty" doc:"the domain name for the PTR record(s). It must have a valid TLD"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the virtual machine"`
|
||||||
|
_ bool `name:"updateReverseDnsForVirtualMachine" description:"Update/create the PTR DNS record(s) for the virtual machine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (*UpdateReverseDNSForVirtualMachine) Response() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusValidationErrorResponse represents an error in the API
|
||||||
|
type RunstatusValidationErrorResponse map[string][]string
|
||||||
|
|
||||||
|
// RunstatusErrorResponse represents the default errors
|
||||||
|
type RunstatusErrorResponse struct {
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// runstatusPagesURL is the only URL that cannot be guessed
|
||||||
|
const runstatusPagesURL = "/pages"
|
||||||
|
|
||||||
|
// Error formats the DNSerror into a string
|
||||||
|
func (req RunstatusErrorResponse) Error() string {
|
||||||
|
return fmt.Sprintf("Runstatus error: %s", req.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error formats the DNSerror into a string
|
||||||
|
func (req RunstatusValidationErrorResponse) Error() string {
|
||||||
|
if len(req) > 0 {
|
||||||
|
errs := []string{}
|
||||||
|
for name, ss := range req {
|
||||||
|
if len(ss) > 0 {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: %s", name, strings.Join(ss, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Runstatus error: %s", strings.Join(errs, "; "))
|
||||||
|
}
|
||||||
|
return "Runstatus error"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) runstatusRequest(ctx context.Context, uri string, structParam interface{}, method string) (json.RawMessage, error) {
|
||||||
|
reqURL, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if reqURL.Scheme == "" {
|
||||||
|
return nil, fmt.Errorf("only absolute URI are considered valid, got %q", uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
var params string
|
||||||
|
if structParam != nil {
|
||||||
|
m, err := json.Marshal(structParam)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params = string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, reqURL.String(), strings.NewReader(params))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
time := time.Now().Local().Format("2006-01-02T15:04:05-0700")
|
||||||
|
|
||||||
|
payload := fmt.Sprintf("%s%s%s", req.URL.String(), time, params)
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, []byte(client.apiSecret))
|
||||||
|
_, err = mac.Write([]byte(payload))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signature := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
var hdr = make(http.Header)
|
||||||
|
|
||||||
|
hdr.Add("Authorization", fmt.Sprintf("Exoscale-HMAC-SHA256 %s:%s", client.APIKey, signature))
|
||||||
|
hdr.Add("Exoscale-Date", time)
|
||||||
|
hdr.Add("Accept", "application/json")
|
||||||
|
if params != "" {
|
||||||
|
hdr.Add("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
req.Header = hdr
|
||||||
|
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
resp, err := client.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
if resp.StatusCode == 204 {
|
||||||
|
if method != "DELETE" {
|
||||||
|
return nil, fmt.Errorf("only DELETE is expected to produce 204, was %q", method)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := resp.Header.Get("content-type")
|
||||||
|
if !strings.Contains(contentType, "application/json") {
|
||||||
|
return nil, fmt.Errorf(`response %d content-type expected to be "application/json", got %q`, resp.StatusCode, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
rerr := new(RunstatusValidationErrorResponse)
|
||||||
|
if err := json.Unmarshal(b, rerr); err == nil {
|
||||||
|
return nil, rerr
|
||||||
|
}
|
||||||
|
rverr := new(RunstatusErrorResponse)
|
||||||
|
if err := json.Unmarshal(b, rverr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, rverr
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusEvent is a runstatus event
|
||||||
|
type RunstatusEvent struct {
|
||||||
|
Created *time.Time `json:"created,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRunstatusIncident create runstatus incident event
|
||||||
|
// Events can be updates or final message with status completed.
|
||||||
|
func (client *Client) UpdateRunstatusIncident(ctx context.Context, incident RunstatusIncident, event RunstatusEvent) error {
|
||||||
|
if incident.EventsURL == "" {
|
||||||
|
return fmt.Errorf("empty Events URL for %#v", incident)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.runstatusRequest(ctx, incident.EventsURL, event, "POST")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRunstatusMaintenance adds a event to a maintenance.
|
||||||
|
// Events can be updates or final message with status completed.
|
||||||
|
func (client *Client) UpdateRunstatusMaintenance(ctx context.Context, maintenance RunstatusMaintenance, event RunstatusEvent) error {
|
||||||
|
if maintenance.EventsURL == "" {
|
||||||
|
return fmt.Errorf("empty Events URL for %#v", maintenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.runstatusRequest(ctx, maintenance.EventsURL, event, "POST")
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusIncident is a runstatus incident
|
||||||
|
type RunstatusIncident struct {
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
Events []RunstatusEvent `json:"events,omitempty"`
|
||||||
|
EventsURL string `json:"events_url,omitempty"`
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
PageURL string `json:"page_url,omitempty"` // fake field
|
||||||
|
PostMortem string `json:"post_mortem,omitempty"`
|
||||||
|
RealTime bool `json:"real_time,omitempty"`
|
||||||
|
Services []string `json:"services"`
|
||||||
|
StartDate *time.Time `json:"start_date,omitempty"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
StatusText string `json:"status_text"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the other incident has got similarities with itself
|
||||||
|
func (incident RunstatusIncident) Match(other RunstatusIncident) bool {
|
||||||
|
if other.Title != "" && incident.Title == other.Title {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.ID > 0 && incident.ID == other.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunstatusIncidentList is a list of incident
|
||||||
|
type RunstatusIncidentList struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Incidents []RunstatusIncident `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunstatusIncident retrieves the details of a specific incident.
|
||||||
|
func (client *Client) GetRunstatusIncident(ctx context.Context, incident RunstatusIncident) (*RunstatusIncident, error) {
|
||||||
|
if incident.URL != "" {
|
||||||
|
return client.getRunstatusIncident(ctx, incident.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if incident.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL for %#v", incident)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.getRunstatusPage(ctx, incident.PageURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range page.Incidents {
|
||||||
|
j := &page.Incidents[i]
|
||||||
|
if j.Match(incident) {
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("incident not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) getRunstatusIncident(ctx context.Context, incidentURL string) (*RunstatusIncident, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, incidentURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := new(RunstatusIncident)
|
||||||
|
if err := json.Unmarshal(resp, i); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunstatusIncidents lists the incidents for a specific page.
|
||||||
|
func (client *Client) ListRunstatusIncidents(ctx context.Context, page RunstatusPage) ([]RunstatusIncident, error) {
|
||||||
|
if page.IncidentsURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Incidents URL for %#v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]RunstatusIncident, 0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.PaginateRunstatusIncidents(ctx, page, func(incident *RunstatusIncident, e error) bool {
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, *incident)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateRunstatusIncidents paginate Incidents
|
||||||
|
func (client *Client) PaginateRunstatusIncidents(ctx context.Context, page RunstatusPage, callback func(*RunstatusIncident, error) bool) {
|
||||||
|
if page.IncidentsURL == "" {
|
||||||
|
callback(nil, fmt.Errorf("empty Incidents URL for %#v", page))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
incidentsURL := page.IncidentsURL
|
||||||
|
for incidentsURL != "" {
|
||||||
|
resp, err := client.runstatusRequest(ctx, incidentsURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var is *RunstatusIncidentList
|
||||||
|
if err := json.Unmarshal(resp, &is); err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range is.Incidents {
|
||||||
|
if cont := callback(&is.Incidents[i], nil); !cont {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
incidentsURL = is.Next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRunstatusIncident create runstatus incident
|
||||||
|
func (client *Client) CreateRunstatusIncident(ctx context.Context, incident RunstatusIncident) (*RunstatusIncident, error) {
|
||||||
|
if incident.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL for %#v", incident)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.getRunstatusPage(ctx, incident.PageURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.IncidentsURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Incidents URL for %#v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.runstatusRequest(ctx, page.IncidentsURL, incident, "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &RunstatusIncident{}
|
||||||
|
if err := json.Unmarshal(resp, &i); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunstatusIncident delete runstatus incident
|
||||||
|
func (client *Client) DeleteRunstatusIncident(ctx context.Context, incident RunstatusIncident) error {
|
||||||
|
if incident.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", incident)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.runstatusRequest(ctx, incident.URL, nil, "DELETE")
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusMaintenance is a runstatus maintenance
|
||||||
|
type RunstatusMaintenance struct {
|
||||||
|
Created *time.Time `json:"created,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
EndDate *time.Time `json:"end_date"`
|
||||||
|
Events []RunstatusEvent `json:"events,omitempty"`
|
||||||
|
EventsURL string `json:"events_url,omitempty"`
|
||||||
|
ID int `json:"id,omitempty"` // missing field
|
||||||
|
PageURL string `json:"page_url,omitempty"` // fake field
|
||||||
|
RealTime bool `json:"real_time,omitempty"`
|
||||||
|
Services []string `json:"services"`
|
||||||
|
StartDate *time.Time `json:"start_date"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the other maintenance has got similarities with itself
|
||||||
|
func (maintenance RunstatusMaintenance) Match(other RunstatusMaintenance) bool {
|
||||||
|
if other.Title != "" && maintenance.Title == other.Title {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.ID > 0 && maintenance.ID == other.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeID fills up the ID field as it's currently missing
|
||||||
|
func (maintenance *RunstatusMaintenance) FakeID() error {
|
||||||
|
if maintenance.ID > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if maintenance.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", maintenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(maintenance.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := path.Base(u.Path)
|
||||||
|
id, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
maintenance.ID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunstatusMaintenanceList is a list of incident
|
||||||
|
type RunstatusMaintenanceList struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Maintenances []RunstatusMaintenance `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunstatusMaintenance retrieves the details of a specific maintenance.
|
||||||
|
func (client *Client) GetRunstatusMaintenance(ctx context.Context, maintenance RunstatusMaintenance) (*RunstatusMaintenance, error) {
|
||||||
|
if maintenance.URL != "" {
|
||||||
|
return client.getRunstatusMaintenance(ctx, maintenance.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maintenance.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL for %#v", maintenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.getRunstatusPage(ctx, maintenance.PageURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range page.Maintenances {
|
||||||
|
m := &page.Maintenances[i]
|
||||||
|
if m.Match(maintenance) {
|
||||||
|
if err := m.FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", m, err)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("maintenance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) getRunstatusMaintenance(ctx context.Context, maintenanceURL string) (*RunstatusMaintenance, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, maintenanceURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(RunstatusMaintenance)
|
||||||
|
if err := json.Unmarshal(resp, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunstatusMaintenances returns the list of maintenances for the page.
|
||||||
|
func (client *Client) ListRunstatusMaintenances(ctx context.Context, page RunstatusPage) ([]RunstatusMaintenance, error) {
|
||||||
|
if page.MaintenancesURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Maintenances URL for %#v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]RunstatusMaintenance, 0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.PaginateRunstatusMaintenances(ctx, page, func(maintenance *RunstatusMaintenance, e error) bool {
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, *maintenance)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateRunstatusMaintenances paginate Maintenances
|
||||||
|
func (client *Client) PaginateRunstatusMaintenances(ctx context.Context, page RunstatusPage, callback func(*RunstatusMaintenance, error) bool) { // nolint: dupl
|
||||||
|
if page.MaintenancesURL == "" {
|
||||||
|
callback(nil, fmt.Errorf("empty Maintenances URL for %#v", page))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maintenancesURL := page.MaintenancesURL
|
||||||
|
for maintenancesURL != "" {
|
||||||
|
resp, err := client.runstatusRequest(ctx, maintenancesURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ms *RunstatusMaintenanceList
|
||||||
|
if err := json.Unmarshal(resp, &ms); err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ms.Maintenances {
|
||||||
|
if err := ms.Maintenances[i].FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", ms.Maintenances[i], err)
|
||||||
|
}
|
||||||
|
if cont := callback(&ms.Maintenances[i], nil); !cont {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maintenancesURL = ms.Next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRunstatusMaintenance create runstatus Maintenance
|
||||||
|
func (client *Client) CreateRunstatusMaintenance(ctx context.Context, maintenance RunstatusMaintenance) (*RunstatusMaintenance, error) {
|
||||||
|
if maintenance.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL for %#v", maintenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.getRunstatusPage(ctx, maintenance.PageURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.runstatusRequest(ctx, page.MaintenancesURL, maintenance, "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &RunstatusMaintenance{}
|
||||||
|
if err := json.Unmarshal(resp, &m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", m, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunstatusMaintenance delete runstatus Maintenance
|
||||||
|
func (client *Client) DeleteRunstatusMaintenance(ctx context.Context, maintenance RunstatusMaintenance) error {
|
||||||
|
if maintenance.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", maintenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.runstatusRequest(ctx, maintenance.URL, nil, "DELETE")
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusPage runstatus page
|
||||||
|
type RunstatusPage struct {
|
||||||
|
Created *time.Time `json:"created,omitempty"`
|
||||||
|
DarkTheme bool `json:"dark_theme,omitempty"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
GradientEnd string `json:"gradient_end,omitempty"`
|
||||||
|
GradientStart string `json:"gradient_start,omitempty"`
|
||||||
|
HeaderBackground string `json:"header_background,omitempty"`
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
Incidents []RunstatusIncident `json:"incidents,omitempty"`
|
||||||
|
IncidentsURL string `json:"incidents_url,omitempty"`
|
||||||
|
Logo string `json:"logo,omitempty"`
|
||||||
|
Maintenances []RunstatusMaintenance `json:"maintenances,omitempty"`
|
||||||
|
MaintenancesURL string `json:"maintenances_url,omitempty"`
|
||||||
|
Name string `json:"name"` // fake field (used to post a new runstatus page)
|
||||||
|
OkText string `json:"ok_text,omitempty"`
|
||||||
|
Plan string `json:"plan,omitempty"`
|
||||||
|
PublicURL string `json:"public_url,omitempty"`
|
||||||
|
Services []RunstatusService `json:"services,omitempty"`
|
||||||
|
ServicesURL string `json:"services_url,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
Subdomain string `json:"subdomain"`
|
||||||
|
SupportEmail string `json:"support_email,omitempty"`
|
||||||
|
TimeZone string `json:"time_zone,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
TitleColor string `json:"title_color,omitempty"`
|
||||||
|
TwitterUsername string `json:"twitter_username,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the other page has got similarities with itself
|
||||||
|
func (page RunstatusPage) Match(other RunstatusPage) bool {
|
||||||
|
if other.Subdomain != "" && page.Subdomain == other.Subdomain {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.ID > 0 && page.ID == other.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunstatusPageList runstatus page list
|
||||||
|
type RunstatusPageList struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Pages []RunstatusPage `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRunstatusPage create runstatus page
|
||||||
|
func (client *Client) CreateRunstatusPage(ctx context.Context, page RunstatusPage) (*RunstatusPage, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, client.Endpoint+runstatusPagesURL, page, "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *RunstatusPage
|
||||||
|
if err := json.Unmarshal(resp, &p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunstatusPage delete runstatus page
|
||||||
|
func (client *Client) DeleteRunstatusPage(ctx context.Context, page RunstatusPage) error {
|
||||||
|
if page.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", page)
|
||||||
|
}
|
||||||
|
_, err := client.runstatusRequest(ctx, page.URL, nil, "DELETE")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunstatusPage fetches the runstatus page
|
||||||
|
func (client *Client) GetRunstatusPage(ctx context.Context, page RunstatusPage) (*RunstatusPage, error) {
|
||||||
|
if page.URL != "" {
|
||||||
|
return client.getRunstatusPage(ctx, page.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := client.ListRunstatusPages(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ps {
|
||||||
|
if ps[i].Match(page) {
|
||||||
|
return client.getRunstatusPage(ctx, ps[i].URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("page not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) getRunstatusPage(ctx context.Context, pageURL string) (*RunstatusPage, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, pageURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := new(RunstatusPage)
|
||||||
|
if err := json.Unmarshal(resp, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: fix the missing IDs
|
||||||
|
for i := range p.Maintenances {
|
||||||
|
if err := p.Maintenances[i].FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", p.Maintenances[i], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range p.Services {
|
||||||
|
if err := p.Services[i].FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", p.Services[i], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunstatusPages list all the runstatus pages
|
||||||
|
func (client *Client) ListRunstatusPages(ctx context.Context) ([]RunstatusPage, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, client.Endpoint+runstatusPagesURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *RunstatusPageList
|
||||||
|
if err := json.Unmarshal(resp, &p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Pages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateRunstatusPages paginate on runstatus pages
|
||||||
|
func (client *Client) PaginateRunstatusPages(ctx context.Context, callback func(pages []RunstatusPage, e error) bool) {
|
||||||
|
pageURL := client.Endpoint + runstatusPagesURL
|
||||||
|
for pageURL != "" {
|
||||||
|
resp, err := client.runstatusRequest(ctx, pageURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *RunstatusPageList
|
||||||
|
if err := json.Unmarshal(resp, &p); err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok := callback(p.Pages, nil); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pageURL = p.Next
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunstatusService is a runstatus service
|
||||||
|
type RunstatusService struct {
|
||||||
|
ID int `json:"id"` // missing field
|
||||||
|
Name string `json:"name"`
|
||||||
|
PageURL string `json:"page_url,omitempty"` // fake field
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeID fills up the ID field as it's currently missing
|
||||||
|
func (service *RunstatusService) FakeID() error {
|
||||||
|
if service.ID > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(service.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := path.Base(u.Path)
|
||||||
|
id, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service.ID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the other service has got similarities with itself
|
||||||
|
func (service RunstatusService) Match(other RunstatusService) bool {
|
||||||
|
if other.Name != "" && service.Name == other.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.ID > 0 && service.ID == other.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunstatusServiceList service list
|
||||||
|
type RunstatusServiceList struct {
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Services []RunstatusService `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunstatusService delete runstatus service
|
||||||
|
func (client *Client) DeleteRunstatusService(ctx context.Context, service RunstatusService) error {
|
||||||
|
if service.URL == "" {
|
||||||
|
return fmt.Errorf("empty URL for %#v", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.runstatusRequest(ctx, service.URL, nil, "DELETE")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRunstatusService create runstatus service
|
||||||
|
func (client *Client) CreateRunstatusService(ctx context.Context, service RunstatusService) (*RunstatusService, error) {
|
||||||
|
if service.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL for %#v", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.GetRunstatusPage(ctx, RunstatusPage{URL: service.PageURL})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.runstatusRequest(ctx, page.ServicesURL, service, "POST")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &RunstatusService{}
|
||||||
|
if err := json.Unmarshal(resp, s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunstatusService displays service detail.
|
||||||
|
func (client *Client) GetRunstatusService(ctx context.Context, service RunstatusService) (*RunstatusService, error) {
|
||||||
|
if service.URL != "" {
|
||||||
|
return client.getRunstatusService(ctx, service.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.PageURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Page URL in %#v", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := client.getRunstatusPage(ctx, service.PageURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range page.Services {
|
||||||
|
s := &page.Services[i]
|
||||||
|
if s.Match(service) {
|
||||||
|
if err := s.FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", s, err)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("service not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) getRunstatusService(ctx context.Context, serviceURL string) (*RunstatusService, error) {
|
||||||
|
resp, err := client.runstatusRequest(ctx, serviceURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &RunstatusService{}
|
||||||
|
if err := json.Unmarshal(resp, &s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunstatusServices displays the list of services.
|
||||||
|
func (client *Client) ListRunstatusServices(ctx context.Context, page RunstatusPage) ([]RunstatusService, error) {
|
||||||
|
if page.ServicesURL == "" {
|
||||||
|
return nil, fmt.Errorf("empty Services URL for %#v", page)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]RunstatusService, 0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.PaginateRunstatusServices(ctx, page, func(service *RunstatusService, e error) bool {
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, *service)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateRunstatusServices paginates Services
|
||||||
|
func (client *Client) PaginateRunstatusServices(ctx context.Context, page RunstatusPage, callback func(*RunstatusService, error) bool) { // nolint: dupl
|
||||||
|
if page.ServicesURL == "" {
|
||||||
|
callback(nil, fmt.Errorf("empty Services URL for %#v", page))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
servicesURL := page.ServicesURL
|
||||||
|
for servicesURL != "" {
|
||||||
|
resp, err := client.runstatusRequest(ctx, servicesURL, nil, "GET")
|
||||||
|
if err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ss *RunstatusServiceList
|
||||||
|
if err := json.Unmarshal(resp, &ss); err != nil {
|
||||||
|
callback(nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ss.Services {
|
||||||
|
if err := ss.Services[i].FakeID(); err != nil {
|
||||||
|
log.Printf("bad fake ID for %#v, %s", ss.Services[i], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cont := callback(&ss.Services[i], nil); !cont {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servicesURL = ss.Next
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityGroup represent a firewalling set of rules
|
||||||
|
type SecurityGroup struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account owning the security group"`
|
||||||
|
Description string `json:"description,omitempty" doc:"the description of the security group"`
|
||||||
|
EgressRule []EgressRule `json:"egressrule,omitempty" doc:"the list of egress rules associated with the security group"`
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the security group"`
|
||||||
|
IngressRule []IngressRule `json:"ingressrule,omitempty" doc:"the list of ingress rules associated with the security group"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserSecurityGroup converts a SecurityGroup to a UserSecurityGroup
|
||||||
|
func (sg SecurityGroup) UserSecurityGroup() UserSecurityGroup {
|
||||||
|
return UserSecurityGroup{
|
||||||
|
Group: sg.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListSecurityGroups request
|
||||||
|
func (sg SecurityGroup) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListSecurityGroups{
|
||||||
|
ID: sg.ID,
|
||||||
|
SecurityGroupName: sg.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the given Security Group
|
||||||
|
func (sg SecurityGroup) Delete(ctx context.Context, client *Client) error {
|
||||||
|
if sg.ID == nil && sg.Name == "" {
|
||||||
|
return fmt.Errorf("a SecurityGroup may only be deleted using ID or Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &DeleteSecurityGroup{}
|
||||||
|
|
||||||
|
if sg.ID != nil {
|
||||||
|
req.ID = sg.ID
|
||||||
|
} else {
|
||||||
|
req.Name = sg.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.BooleanRequestWithContext(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleByID returns IngressRule or EgressRule by a rule ID
|
||||||
|
func (sg SecurityGroup) RuleByID(ruleID UUID) (*IngressRule, *EgressRule) {
|
||||||
|
for i, in := range sg.IngressRule {
|
||||||
|
if in.RuleID.Equal(ruleID) {
|
||||||
|
return &sg.IngressRule[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, out := range sg.EgressRule {
|
||||||
|
if out.RuleID.Equal(ruleID) {
|
||||||
|
return nil, &sg.EgressRule[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IngressRule represents the ingress rule
|
||||||
|
type IngressRule struct {
|
||||||
|
CIDR *CIDR `json:"cidr,omitempty" doc:"the CIDR notation for the base IP address of the security group rule"`
|
||||||
|
Description string `json:"description,omitempty" doc:"description of the security group rule"`
|
||||||
|
EndPort uint16 `json:"endport,omitempty" doc:"the ending port of the security group rule "`
|
||||||
|
IcmpCode uint8 `json:"icmpcode,omitempty" doc:"the code for the ICMP message response"`
|
||||||
|
IcmpType uint8 `json:"icmptype,omitempty" doc:"the type of the ICMP message response"`
|
||||||
|
Protocol string `json:"protocol,omitempty" doc:"the protocol of the security group rule"`
|
||||||
|
RuleID *UUID `json:"ruleid" doc:"the id of the security group rule"`
|
||||||
|
SecurityGroupName string `json:"securitygroupname,omitempty" doc:"security group name"`
|
||||||
|
StartPort uint16 `json:"startport,omitempty" doc:"the starting port of the security group rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EgressRule represents the ingress rule
|
||||||
|
type EgressRule IngressRule
|
||||||
|
|
||||||
|
// UserSecurityGroup represents the traffic of another security group
|
||||||
|
type UserSecurityGroup struct {
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// String gives the UserSecurityGroup name
|
||||||
|
func (usg UserSecurityGroup) String() string {
|
||||||
|
return usg.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSecurityGroup represents a security group creation
|
||||||
|
type CreateSecurityGroup struct {
|
||||||
|
Name string `json:"name" doc:"name of the security group"`
|
||||||
|
Description string `json:"description,omitempty" doc:"the description of the security group"`
|
||||||
|
_ bool `name:"createSecurityGroup" description:"Creates a security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateSecurityGroup) Response() interface{} {
|
||||||
|
return new(SecurityGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecurityGroup represents a security group deletion
|
||||||
|
type DeleteSecurityGroup struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"The ID of the security group. Mutually exclusive with name parameter"`
|
||||||
|
Name string `json:"name,omitempty" doc:"The ID of the security group. Mutually exclusive with id parameter"`
|
||||||
|
_ bool `name:"deleteSecurityGroup" description:"Deletes security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteSecurityGroup) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeSecurityGroupIngress (Async) represents the ingress rule creation
|
||||||
|
type AuthorizeSecurityGroupIngress struct {
|
||||||
|
CIDRList []CIDR `json:"cidrlist,omitempty" doc:"the cidr list associated"`
|
||||||
|
Description string `json:"description,omitempty" doc:"the description of the ingress/egress rule"`
|
||||||
|
EndPort uint16 `json:"endport,omitempty" doc:"end port for this ingress/egress rule"`
|
||||||
|
IcmpCode uint8 `json:"icmpcode,omitempty" doc:"error code for this icmp message"`
|
||||||
|
IcmpType uint8 `json:"icmptype,omitempty" doc:"type of the icmp message being sent"`
|
||||||
|
Protocol string `json:"protocol,omitempty" doc:"TCP is default. UDP, ICMP, ICMPv6, AH, ESP, GRE, IPIP are the other supported protocols"`
|
||||||
|
SecurityGroupID *UUID `json:"securitygroupid,omitempty" doc:"The ID of the security group. Mutually exclusive with securitygroupname parameter"`
|
||||||
|
SecurityGroupName string `json:"securitygroupname,omitempty" doc:"The name of the security group. Mutually exclusive with securitygroupid parameter"`
|
||||||
|
StartPort uint16 `json:"startport,omitempty" doc:"start port for this ingress/egress rule"`
|
||||||
|
UserSecurityGroupList []UserSecurityGroup `json:"usersecuritygrouplist,omitempty" doc:"user to security group mapping"`
|
||||||
|
_ bool `name:"authorizeSecurityGroupIngress" description:"Authorize a particular ingress/egress rule for this security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (AuthorizeSecurityGroupIngress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (AuthorizeSecurityGroupIngress) AsyncResponse() interface{} {
|
||||||
|
return new(SecurityGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AuthorizeSecurityGroupIngress) onBeforeSend(params url.Values) error {
|
||||||
|
// ICMP code and type may be zero but can also be omitted...
|
||||||
|
if strings.HasPrefix(strings.ToLower(req.Protocol), "icmp") {
|
||||||
|
params.Set("icmpcode", strconv.FormatInt(int64(req.IcmpCode), 10))
|
||||||
|
params.Set("icmptype", strconv.FormatInt(int64(req.IcmpType), 10))
|
||||||
|
}
|
||||||
|
// StartPort may be zero but can also be omitted...
|
||||||
|
if req.EndPort != 0 && req.StartPort == 0 {
|
||||||
|
params.Set("startport", "0")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeSecurityGroupEgress (Async) represents the egress rule creation
|
||||||
|
type AuthorizeSecurityGroupEgress AuthorizeSecurityGroupIngress
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (AuthorizeSecurityGroupEgress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (AuthorizeSecurityGroupEgress) AsyncResponse() interface{} {
|
||||||
|
return new(SecurityGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req AuthorizeSecurityGroupEgress) onBeforeSend(params url.Values) error {
|
||||||
|
return (AuthorizeSecurityGroupIngress)(req).onBeforeSend(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeSecurityGroupIngress (Async) represents the ingress/egress rule deletion
|
||||||
|
type RevokeSecurityGroupIngress struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the ingress rule"`
|
||||||
|
_ bool `name:"revokeSecurityGroupIngress" description:"Deletes a particular ingress rule from this security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RevokeSecurityGroupIngress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RevokeSecurityGroupIngress) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeSecurityGroupEgress (Async) represents the ingress/egress rule deletion
|
||||||
|
type RevokeSecurityGroupEgress struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the egress rule"`
|
||||||
|
_ bool `name:"revokeSecurityGroupEgress" description:"Deletes a particular egress rule from this security group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RevokeSecurityGroupEgress) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RevokeSecurityGroupEgress) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListSecurityGroups
|
||||||
|
|
||||||
|
// ListSecurityGroups represents a search for security groups
|
||||||
|
type ListSecurityGroups struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"list the security group by the id provided"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
SecurityGroupName string `json:"securitygroupname,omitempty" doc:"lists security groups by name"`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"lists security groups by virtual machine id"`
|
||||||
|
_ bool `name:"listSecurityGroups" description:"Lists security groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSecurityGroupsResponse represents a list of security groups
|
||||||
|
type ListSecurityGroupsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
SecurityGroup []SecurityGroup `json:"securitygroup"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListSecurityGroups) Response() interface{} {
|
||||||
|
return new(ListSecurityGroupsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListSecurityGroups) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListSecurityGroups) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListSecurityGroups) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListSecurityGroups) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListSecurityGroupsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListSecurityGroupsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.SecurityGroup {
|
||||||
|
if !callback(&items.SecurityGroup[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,404 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func csQuotePlus(s string) string {
|
||||||
|
s = strings.ReplaceAll(s, "+", "%20")
|
||||||
|
// This line is used to safeguard the "*" when producing the signature
|
||||||
|
s = strings.ReplaceAll(s, "%2A", "*")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func csEncode(s string) string {
|
||||||
|
return csQuotePlus(url.QueryEscape(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// info returns the meta info of a command
|
||||||
|
//
|
||||||
|
// command is not a Command so it's easier to Test
|
||||||
|
func info(command interface{}) (*CommandInfo, error) {
|
||||||
|
typeof := reflect.TypeOf(command)
|
||||||
|
|
||||||
|
// Going up the pointer chain to find the underlying struct
|
||||||
|
for typeof.Kind() == reflect.Ptr {
|
||||||
|
typeof = typeof.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
field, ok := typeof.FieldByName("_")
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf(`missing meta ("_") field in %#v`, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, nameOk := field.Tag.Lookup("name")
|
||||||
|
description, _ := field.Tag.Lookup("description")
|
||||||
|
|
||||||
|
if !nameOk {
|
||||||
|
return nil, fmt.Errorf(`missing "name" key in the tag string of %#v`, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &CommandInfo{
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareValues uses a command to build a POST request
|
||||||
|
//
|
||||||
|
// command is not a Command so it's easier to Test
|
||||||
|
func prepareValues(prefix string, command interface{}) (url.Values, error) {
|
||||||
|
params := url.Values{}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(command)
|
||||||
|
typeof := reflect.TypeOf(command)
|
||||||
|
|
||||||
|
// Going up the pointer chain to find the underlying struct
|
||||||
|
for typeof.Kind() == reflect.Ptr {
|
||||||
|
typeof = typeof.Elem()
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking for nil commands
|
||||||
|
if !value.IsValid() {
|
||||||
|
return nil, fmt.Errorf("cannot serialize the invalid value %#v", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < typeof.NumField(); i++ {
|
||||||
|
field := typeof.Field(i)
|
||||||
|
if field.Name == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := value.Field(i)
|
||||||
|
tag := field.Tag
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var name string
|
||||||
|
var value interface{}
|
||||||
|
|
||||||
|
if json, ok := tag.Lookup("json"); ok {
|
||||||
|
n, required := ExtractJSONTag(field.Name, json)
|
||||||
|
name = prefix + n
|
||||||
|
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
value, err = prepareInt(val.Int(), required)
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
value, err = prepareUint(val.Uint(), required)
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
value, err = prepareFloat(val.Float(), required)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
value, err = prepareString(val.String(), required)
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
value, err = prepareBool(val.Bool(), required)
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
if required {
|
||||||
|
err = fmt.Errorf("field is required, got empty map")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value, err = prepareMap(name, val.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
value, err = preparePtr(field.Type.Elem().Kind(), val, required)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
value, err = prepareSlice(name, field.Type, val, required)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
value, err = prepareStruct(val.Interface(), required)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if required {
|
||||||
|
err = fmt.Errorf("unsupported type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
value, err = prepareEmbedStruct(val.Interface())
|
||||||
|
default:
|
||||||
|
log.Printf("[SKIP] %s.%s no json label found", typeof.Name(), field.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s.%s (%v) %s", typeof.Name(), field.Name, val.Kind(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case *string:
|
||||||
|
if name != "" && v != nil {
|
||||||
|
params.Set(name, *v)
|
||||||
|
}
|
||||||
|
case url.Values:
|
||||||
|
for k, xs := range v {
|
||||||
|
for _, x := range xs {
|
||||||
|
params.Add(k, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareInt(v int64, required bool) (*string, error) {
|
||||||
|
if v == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got %d", v)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
value := strconv.FormatInt(v, 10)
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUint(v uint64, required bool) (*string, error) {
|
||||||
|
if v == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got %d", v)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value := strconv.FormatUint(v, 10)
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareFloat(v float64, required bool) (*string, error) {
|
||||||
|
if v == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got %f", v)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
value := strconv.FormatFloat(v, 'f', -1, 64)
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareString(v string, required bool) (*string, error) {
|
||||||
|
if v == "" {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got %q", v)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareBool(v bool, required bool) (*string, error) {
|
||||||
|
value := strconv.FormatBool(v)
|
||||||
|
if !v {
|
||||||
|
if required {
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareList(prefix string, slice interface{}) (url.Values, error) {
|
||||||
|
params := url.Values{}
|
||||||
|
value := reflect.ValueOf(slice)
|
||||||
|
|
||||||
|
for i := 0; i < value.Len(); i++ {
|
||||||
|
ps, err := prepareValues(fmt.Sprintf("%s[%d].", prefix, i), value.Index(i).Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, xs := range ps {
|
||||||
|
for _, x := range xs {
|
||||||
|
params.Add(k, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMap(prefix string, m interface{}) (url.Values, error) {
|
||||||
|
value := url.Values{}
|
||||||
|
v := reflect.ValueOf(m)
|
||||||
|
|
||||||
|
for i, key := range v.MapKeys() {
|
||||||
|
var keyName string
|
||||||
|
var keyValue string
|
||||||
|
|
||||||
|
switch key.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
keyName = key.String()
|
||||||
|
default:
|
||||||
|
return value, fmt.Errorf("only map[string]string are supported (XXX)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val := v.MapIndex(key)
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
keyValue = val.String()
|
||||||
|
default:
|
||||||
|
return value, fmt.Errorf("only map[string]string are supported (XXX)")
|
||||||
|
}
|
||||||
|
|
||||||
|
value.Set(fmt.Sprintf("%s[%d].%s", prefix, i, keyName), keyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func preparePtr(kind reflect.Kind, val reflect.Value, required bool) (*string, error) {
|
||||||
|
if val.IsNil() {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty ptr")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return prepareBool(val.Elem().Bool(), true)
|
||||||
|
case reflect.Struct:
|
||||||
|
return prepareStruct(val.Interface(), required)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("kind %v is not supported as a ptr", kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareSlice(name string, fieldType reflect.Type, val reflect.Value, required bool) (interface{}, error) {
|
||||||
|
switch fieldType.Elem().Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
switch fieldType {
|
||||||
|
case reflect.TypeOf(net.IPv4zero):
|
||||||
|
ip := (net.IP)(val.Bytes())
|
||||||
|
if ip == nil || ip.Equal(net.IP{}) {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got zero IPv4 address")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value := ip.String()
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.TypeOf(MAC48(0, 0, 0, 0, 0, 0)):
|
||||||
|
mac := val.Interface().(MACAddress)
|
||||||
|
s := mac.String()
|
||||||
|
if s == "" {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty MAC address")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty slice")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value := base64.StdEncoding.EncodeToString(val.Bytes())
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty slice")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elems := make([]string, 0, val.Len())
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
// XXX what if the value contains a comma? Double encode?
|
||||||
|
s := val.Index(i).String()
|
||||||
|
elems = append(elems, s)
|
||||||
|
}
|
||||||
|
value := strings.Join(elems, ",")
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
switch fieldType.Elem() {
|
||||||
|
case reflect.TypeOf(CIDR{}), reflect.TypeOf(UUID{}):
|
||||||
|
if val.Len() == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty slice")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v := reflect.ValueOf(val.Interface())
|
||||||
|
ss := make([]string, val.Len())
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
e := v.Index(i).Interface()
|
||||||
|
s, ok := e.(fmt.Stringer)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not a String, %T", e)
|
||||||
|
}
|
||||||
|
ss[i] = s.String()
|
||||||
|
}
|
||||||
|
value := strings.Join(ss, ",")
|
||||||
|
return &value, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got empty slice")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return prepareList(name, val.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareStruct(i interface{}, required bool) (*string, error) {
|
||||||
|
s, ok := i.(fmt.Stringer)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("struct field not a Stringer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("field is required, got %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prepareString(s.String(), required)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareEmbedStruct(i interface{}) (url.Values, error) {
|
||||||
|
return prepareValues("", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractJSONTag returns the variable name or defaultName as well as if the field is required (!omitempty)
|
||||||
|
func ExtractJSONTag(defaultName, jsonTag string) (string, bool) {
|
||||||
|
tags := strings.Split(jsonTag, ",")
|
||||||
|
name := tags[0]
|
||||||
|
required := true
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag == "omitempty" {
|
||||||
|
required = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" || name == "omitempty" {
|
||||||
|
name = defaultName
|
||||||
|
}
|
||||||
|
return name, required
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// ServiceOffering corresponds to the Compute Offerings
|
||||||
|
//
|
||||||
|
// A service offering correspond to some hardware features (CPU, RAM).
|
||||||
|
//
|
||||||
|
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/latest/service_offerings.html
|
||||||
|
type ServiceOffering struct {
|
||||||
|
Authorized bool `json:"authorized,omitempty" doc:"is the account/domain authorized to use this service offering"`
|
||||||
|
CPUNumber int `json:"cpunumber,omitempty" doc:"the number of CPU"`
|
||||||
|
CPUSpeed int `json:"cpuspeed,omitempty" doc:"the clock rate CPU speed in Mhz"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the date this service offering was created"`
|
||||||
|
DefaultUse bool `json:"defaultuse,omitempty" doc:"is this a default system vm offering"`
|
||||||
|
DeploymentPlanner string `json:"deploymentplanner,omitempty" doc:"deployment strategy used to deploy VM."`
|
||||||
|
DiskBytesReadRate int64 `json:"diskBytesReadRate,omitempty" doc:"bytes read rate of the service offering"`
|
||||||
|
DiskBytesWriteRate int64 `json:"diskBytesWriteRate,omitempty" doc:"bytes write rate of the service offering"`
|
||||||
|
DiskIopsReadRate int64 `json:"diskIopsReadRate,omitempty" doc:"io requests read rate of the service offering"`
|
||||||
|
DiskIopsWriteRate int64 `json:"diskIopsWriteRate,omitempty" doc:"io requests write rate of the service offering"`
|
||||||
|
Displaytext string `json:"displaytext,omitempty" doc:"an alternate display text of the service offering."`
|
||||||
|
HostTags string `json:"hosttags,omitempty" doc:"the host tag for the service offering"`
|
||||||
|
HypervisorSnapshotReserve int `json:"hypervisorsnapshotreserve,omitempty" doc:"Hypervisor snapshot reserve space as a percent of a volume (for managed storage using Xen or VMware)"`
|
||||||
|
ID *UUID `json:"id" doc:"the id of the service offering"`
|
||||||
|
IsCustomized bool `json:"iscustomized,omitempty" doc:"is true if the offering is customized"`
|
||||||
|
IsCustomizedIops bool `json:"iscustomizediops,omitempty" doc:"true if disk offering uses custom iops, false otherwise"`
|
||||||
|
IsSystem bool `json:"issystem,omitempty" doc:"is this a system vm offering"`
|
||||||
|
IsVolatile bool `json:"isvolatile,omitempty" doc:"true if the vm needs to be volatile, i.e., on every reboot of vm from API root disk is discarded and creates a new root disk"`
|
||||||
|
LimitCPUUse bool `json:"limitcpuuse,omitempty" doc:"restrict the CPU usage to committed service offering"`
|
||||||
|
MaxIops int64 `json:"maxiops,omitempty" doc:"the max iops of the disk offering"`
|
||||||
|
Memory int `json:"memory,omitempty" doc:"the memory in MB"`
|
||||||
|
MinIops int64 `json:"miniops,omitempty" doc:"the min iops of the disk offering"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the service offering"`
|
||||||
|
NetworkRate int `json:"networkrate,omitempty" doc:"data transfer rate in megabits per second allowed."`
|
||||||
|
OfferHA bool `json:"offerha,omitempty" doc:"the ha support in the service offering"`
|
||||||
|
Restricted bool `json:"restricted,omitempty" doc:"is this offering restricted"`
|
||||||
|
ServiceOfferingDetails map[string]string `json:"serviceofferingdetails,omitempty" doc:"additional key/value details tied with this service offering"`
|
||||||
|
StorageType string `json:"storagetype,omitempty" doc:"the storage type for this service offering"`
|
||||||
|
SystemVMType string `json:"systemvmtype,omitempty" doc:"is this a the systemvm type for system vm offering"`
|
||||||
|
Tags string `json:"tags,omitempty" doc:"the tags for the service offering"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListSecurityGroups request
|
||||||
|
func (so ServiceOffering) ListRequest() (ListCommand, error) {
|
||||||
|
// Restricted cannot be applied here because it really has three states
|
||||||
|
req := &ListServiceOfferings{
|
||||||
|
ID: so.ID,
|
||||||
|
Name: so.Name,
|
||||||
|
SystemVMType: so.SystemVMType,
|
||||||
|
}
|
||||||
|
|
||||||
|
if so.IsSystem {
|
||||||
|
req.IsSystem = &so.IsSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListServiceOfferings
|
||||||
|
|
||||||
|
// ListServiceOfferings represents a query for service offerings
|
||||||
|
type ListServiceOfferings struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"ID of the service offering"`
|
||||||
|
IsSystem *bool `json:"issystem,omitempty" doc:"is this a system vm offering"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"name of the service offering"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
Restricted *bool `json:"restricted,omitempty" doc:"filter by the restriction flag: true to list only the restricted service offerings, false to list non-restricted service offerings, or nothing for all."`
|
||||||
|
SystemVMType string `json:"systemvmtype,omitempty" doc:"the system VM type. Possible types are \"consoleproxy\", \"secondarystoragevm\" or \"domainrouter\"."`
|
||||||
|
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"the ID of the virtual machine. Pass this in if you want to see the available service offering that a virtual machine can be changed to."`
|
||||||
|
_ bool `name:"listServiceOfferings" description:"Lists all available service offerings."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServiceOfferingsResponse represents a list of service offerings
|
||||||
|
type ListServiceOfferingsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
ServiceOffering []ServiceOffering `json:"serviceoffering"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListServiceOfferings) Response() interface{} {
|
||||||
|
return new(ListServiceOfferingsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListServiceOfferings) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListServiceOfferings) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListServiceOfferings) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListServiceOfferings) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListServiceOfferingsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListServiceOfferingsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.ServiceOffering {
|
||||||
|
if !callback(&items.ServiceOffering[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// SnapshotState represents the Snapshot.State enum
|
||||||
|
//
|
||||||
|
// See: https://github.com/apache/cloudstack/blob/master/api/src/main/java/com/cloud/storage/Snapshot.java
|
||||||
|
type SnapshotState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Allocated ... (TODO)
|
||||||
|
Allocated SnapshotState = "Allocated"
|
||||||
|
// Creating ... (TODO)
|
||||||
|
Creating SnapshotState = "Creating"
|
||||||
|
// CreatedOnPrimary ... (TODO)
|
||||||
|
CreatedOnPrimary SnapshotState = "CreatedOnPrimary"
|
||||||
|
// BackingUp ... (TODO)
|
||||||
|
BackingUp SnapshotState = "BackingUp"
|
||||||
|
// BackedUp ... (TODO)
|
||||||
|
BackedUp SnapshotState = "BackedUp"
|
||||||
|
// Copying ... (TODO)
|
||||||
|
Copying SnapshotState = "Copying"
|
||||||
|
// Destroying ... (TODO)
|
||||||
|
Destroying SnapshotState = "Destroying"
|
||||||
|
// Destroyed ... (TODO)
|
||||||
|
Destroyed SnapshotState = "Destroyed"
|
||||||
|
// Error is a state where the user can't see the snapshot while the snapshot may still exist on the storage
|
||||||
|
Error SnapshotState = "Error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Snapshot represents a volume snapshot
|
||||||
|
type Snapshot struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account associated with the snapshot"`
|
||||||
|
AccountID *UUID `json:"accountid,omitempty" doc:"the account ID associated with the snapshot"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the date the snapshot was created"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"ID of the snapshot"`
|
||||||
|
IntervalType string `json:"intervaltype,omitempty" doc:"valid types are hourly, daily, weekly, monthy, template, and none."`
|
||||||
|
Name string `json:"name,omitempty" doc:"name of the snapshot"`
|
||||||
|
PhysicalSize int64 `json:"physicalsize,omitempty" doc:"physical size of the snapshot on image store"`
|
||||||
|
Revertable *bool `json:"revertable,omitempty" doc:"indicates whether the underlying storage supports reverting the volume to this snapshot"`
|
||||||
|
Size int64 `json:"size,omitempty" doc:"the size of original volume"`
|
||||||
|
SnapshotType string `json:"snapshottype,omitempty" doc:"the type of the snapshot"`
|
||||||
|
State string `json:"state,omitempty" doc:"the state of the snapshot. BackedUp means that snapshot is ready to be used; Creating - the snapshot is being allocated on the primary storage; BackingUp - the snapshot is being backed up on secondary storage"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"the list of resource tags associated with snapshot"`
|
||||||
|
VolumeID *UUID `json:"volumeid,omitempty" doc:"ID of the disk volume"`
|
||||||
|
VolumeName string `json:"volumename,omitempty" doc:"name of the disk volume"`
|
||||||
|
VolumeType string `json:"volumetype,omitempty" doc:"type of the disk volume"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"id of the availability zone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceType returns the type of the resource
|
||||||
|
func (Snapshot) ResourceType() string {
|
||||||
|
return "Snapshot"
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSnapshot (Async) creates an instant snapshot of a volume
|
||||||
|
type CreateSnapshot struct {
|
||||||
|
VolumeID *UUID `json:"volumeid" doc:"The ID of the disk volume"`
|
||||||
|
QuiesceVM *bool `json:"quiescevm,omitempty" doc:"quiesce vm if true"`
|
||||||
|
_ bool `name:"createSnapshot" description:"Creates an instant snapshot of a volume."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateSnapshot) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (CreateSnapshot) AsyncResponse() interface{} {
|
||||||
|
return new(Snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListSnapshot request
|
||||||
|
func (ss Snapshot) ListRequest() (ListCommand, error) {
|
||||||
|
// Restricted cannot be applied here because it really has three states
|
||||||
|
req := &ListSnapshots{
|
||||||
|
ID: ss.ID,
|
||||||
|
Name: ss.Name,
|
||||||
|
VolumeID: ss.VolumeID,
|
||||||
|
SnapshotType: ss.SnapshotType,
|
||||||
|
ZoneID: ss.ZoneID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListSnapshots
|
||||||
|
|
||||||
|
// ListSnapshots lists the volume snapshots
|
||||||
|
type ListSnapshots struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"lists snapshot by snapshot ID"`
|
||||||
|
IntervalType string `json:"intervaltype,omitempty" doc:"valid values are HOURLY, DAILY, WEEKLY, and MONTHLY."`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"lists snapshot by snapshot name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
SnapshotType string `json:"snapshottype,omitempty" doc:"valid values are MANUAL or RECURRING."`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
|
||||||
|
VolumeID *UUID `json:"volumeid,omitempty" doc:"the ID of the disk volume"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"list snapshots by zone id"`
|
||||||
|
_ bool `name:"listSnapshots" description:"Lists all available snapshots for the account."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSnapshotsResponse represents a list of volume snapshots
|
||||||
|
type ListSnapshotsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Snapshot []Snapshot `json:"snapshot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSnapshot (Async) deletes a snapshot of a disk volume
|
||||||
|
type DeleteSnapshot struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the snapshot"`
|
||||||
|
_ bool `name:"deleteSnapshot" description:"Deletes a snapshot of a disk volume."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteSnapshot) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DeleteSnapshot) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevertSnapshot (Async) reverts a volume snapshot
|
||||||
|
type RevertSnapshot struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the snapshot"`
|
||||||
|
_ bool `name:"revertSnapshot" description:"revert a volume snapshot."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RevertSnapshot) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RevertSnapshot) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSnapshot (Async) exports a volume snapshot
|
||||||
|
type ExportSnapshot struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the snapshot"`
|
||||||
|
_ bool `name:"exportSnapshot" description:"Exports an instant snapshot of a volume."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSnapshotResponse represents the response of a snapshot export operation
|
||||||
|
type ExportSnapshotResponse struct {
|
||||||
|
PresignedURL string `json:"presignedurl"`
|
||||||
|
MD5sum string `json:"md5sum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ExportSnapshot) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (ExportSnapshot) AsyncResponse() interface{} {
|
||||||
|
return new(ExportSnapshotResponse)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListSnapshots) Response() interface{} {
|
||||||
|
return new(ListSnapshotsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListSnapshots) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListSnapshots) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListSnapshots) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListSnapshots) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListSnapshotsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListSnapshotsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Snapshot {
|
||||||
|
if !callback(&items.Snapshot[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// BucketUsage represents the usage (in bytes) for a bucket
|
||||||
|
type BucketUsage struct {
|
||||||
|
Created string `json:"created"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
Usage int64 `json:"usage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBucketsUsage represents a listBucketsUsage API request
|
||||||
|
type ListBucketsUsage struct {
|
||||||
|
_ bool `name:"listBucketsUsage" description:"List"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBucketsUsageResponse represents a listBucketsUsage API response
|
||||||
|
type ListBucketsUsageResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
BucketsUsage []BucketUsage `json:"bucketsusage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListBucketsUsage) Response() interface{} {
|
||||||
|
return new(ListBucketsUsageResponse)
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHKeyPair represents an SSH key pair
|
||||||
|
//
|
||||||
|
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/stable/virtual_machines.html#creating-the-ssh-keypair
|
||||||
|
type SSHKeyPair struct {
|
||||||
|
Fingerprint string `json:"fingerprint,omitempty" doc:"Fingerprint of the public key"`
|
||||||
|
Name string `json:"name,omitempty" doc:"Name of the keypair"`
|
||||||
|
PrivateKey string `json:"privatekey,omitempty" doc:"Private key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the given SSH key, by Name
|
||||||
|
func (ssh SSHKeyPair) Delete(ctx context.Context, client *Client) error {
|
||||||
|
if ssh.Name == "" {
|
||||||
|
return fmt.Errorf("an SSH Key Pair may only be deleted using Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.BooleanRequestWithContext(ctx, &DeleteSSHKeyPair{
|
||||||
|
Name: ssh.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListSSHKeyPairs request
|
||||||
|
func (ssh SSHKeyPair) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListSSHKeyPairs{
|
||||||
|
Fingerprint: ssh.Fingerprint,
|
||||||
|
Name: ssh.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSSHKeyPair represents a new keypair to be created
|
||||||
|
type CreateSSHKeyPair struct {
|
||||||
|
Name string `json:"name" doc:"Name of the keypair"`
|
||||||
|
_ bool `name:"createSSHKeyPair" description:"Create a new keypair and returns the private key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateSSHKeyPair) Response() interface{} {
|
||||||
|
return new(SSHKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSSHKeyPair represents a new keypair to be created
|
||||||
|
type DeleteSSHKeyPair struct {
|
||||||
|
Name string `json:"name" doc:"Name of the keypair"`
|
||||||
|
_ bool `name:"deleteSSHKeyPair" description:"Deletes a keypair by name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteSSHKeyPair) Response() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSSHKeyPair represents a new registration of a public key in a keypair
|
||||||
|
type RegisterSSHKeyPair struct {
|
||||||
|
Name string `json:"name" doc:"Name of the keypair"`
|
||||||
|
PublicKey string `json:"publickey" doc:"Public key material of the keypair"`
|
||||||
|
_ bool `name:"registerSSHKeyPair" description:"Register a public key in a keypair under a certain name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RegisterSSHKeyPair) Response() interface{} {
|
||||||
|
return new(SSHKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListSSHKeyPairs
|
||||||
|
|
||||||
|
// ListSSHKeyPairs represents a query for a list of SSH KeyPairs
|
||||||
|
type ListSSHKeyPairs struct {
|
||||||
|
Fingerprint string `json:"fingerprint,omitempty" doc:"A public key fingerprint to look for"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"A key pair name to look for"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
_ bool `name:"listSSHKeyPairs" description:"List registered keypairs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSSHKeyPairsResponse represents a list of SSH key pairs
|
||||||
|
type ListSSHKeyPairsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
SSHKeyPair []SSHKeyPair `json:"sshkeypair"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSSHKeyForVirtualMachine (Async) represents a change for the key pairs
|
||||||
|
type ResetSSHKeyForVirtualMachine struct {
|
||||||
|
ID *UUID `json:"id" doc:"The ID of the virtual machine"`
|
||||||
|
KeyPair string `json:"keypair" doc:"Name of the ssh key pair used to login to the virtual machine"`
|
||||||
|
_ bool `name:"resetSSHKeyForVirtualMachine" description:"Resets the SSH Key for virtual machine. The virtual machine must be in a \"Stopped\" state."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ResetSSHKeyForVirtualMachine) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (ResetSSHKeyForVirtualMachine) AsyncResponse() interface{} {
|
||||||
|
return new(VirtualMachine)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListSSHKeyPairs) Response() interface{} {
|
||||||
|
return new(ListSSHKeyPairsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListSSHKeyPairs) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListSSHKeyPairs) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListSSHKeyPairs) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListSSHKeyPairs) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListSSHKeyPairsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListSSHKeyPairsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.SSHKeyPair {
|
||||||
|
if !callback(&items.SSHKeyPair[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// ResourceTag is a tag associated with a resource
|
||||||
|
//
|
||||||
|
// https://community.exoscale.com/documentation/compute/instance-tags/
|
||||||
|
type ResourceTag struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account associated with the tag"`
|
||||||
|
Customer string `json:"customer,omitempty" doc:"customer associated with the tag"`
|
||||||
|
Key string `json:"key,omitempty" doc:"tag key name"`
|
||||||
|
ResourceID *UUID `json:"resourceid,omitempty" doc:"id of the resource"`
|
||||||
|
ResourceType string `json:"resourcetype,omitempty" doc:"resource type"`
|
||||||
|
Value string `json:"value,omitempty" doc:"tag value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListZones request
|
||||||
|
func (tag ResourceTag) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListTags{
|
||||||
|
Customer: tag.Customer,
|
||||||
|
Key: tag.Key,
|
||||||
|
ResourceID: tag.ResourceID,
|
||||||
|
ResourceType: tag.ResourceType,
|
||||||
|
Value: tag.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTags (Async) creates resource tag(s)
|
||||||
|
type CreateTags struct {
|
||||||
|
ResourceIDs []UUID `json:"resourceids" doc:"list of resources to create the tags for"`
|
||||||
|
ResourceType string `json:"resourcetype" doc:"type of the resource"`
|
||||||
|
Tags []ResourceTag `json:"tags" doc:"Map of tags (key/value pairs)"`
|
||||||
|
Customer string `json:"customer,omitempty" doc:"identifies client specific tag. When the value is not null, the tag can't be used by cloudStack code internally"`
|
||||||
|
_ bool `name:"createTags" description:"Creates resource tag(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (CreateTags) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (CreateTags) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTags (Async) deletes the resource tag(s)
|
||||||
|
type DeleteTags struct {
|
||||||
|
ResourceIDs []UUID `json:"resourceids" doc:"Delete tags for resource id(s)"`
|
||||||
|
ResourceType string `json:"resourcetype" doc:"Delete tag by resource type"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"Delete tags matching key/value pairs"`
|
||||||
|
_ bool `name:"deleteTags" description:"Deleting resource tag(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteTags) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DeleteTags) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListTags
|
||||||
|
|
||||||
|
// ListTags list resource tag(s)
|
||||||
|
type ListTags struct {
|
||||||
|
Customer string `json:"customer,omitempty" doc:"list by customer name"`
|
||||||
|
Key string `json:"key,omitempty" doc:"list by key"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
ResourceID *UUID `json:"resourceid,omitempty" doc:"list by resource id"`
|
||||||
|
ResourceType string `json:"resourcetype,omitempty" doc:"list by resource type"`
|
||||||
|
Value string `json:"value,omitempty" doc:"list by value"`
|
||||||
|
_ bool `name:"listTags" description:"List resource tag(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTagsResponse represents a list of resource tags
|
||||||
|
type ListTagsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Tag []ResourceTag `json:"tag"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListTags) Response() interface{} {
|
||||||
|
return new(ListTagsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListTags) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListTags) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListTags) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListTags) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListTagsResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListTagsResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Tag {
|
||||||
|
if !callback(&items.Tag[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// Template represents a machine to be deployed.
|
||||||
|
type Template struct {
|
||||||
|
Account string `json:"account,omitempty" doc:"the account name to which the template belongs"`
|
||||||
|
AccountID *UUID `json:"accountid,omitempty" doc:"the account id to which the template belongs"`
|
||||||
|
Bootable bool `json:"bootable,omitempty" doc:"true if the ISO is bootable, false otherwise"`
|
||||||
|
BootMode string `json:"bootmode" doc:"the template boot mode (legacy/uefi)"`
|
||||||
|
Checksum string `json:"checksum,omitempty" doc:"checksum of the template"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the date this template was created"`
|
||||||
|
CrossZones bool `json:"crossZones,omitempty" doc:"true if the template is managed across all Zones, false otherwise"`
|
||||||
|
Details map[string]string `json:"details,omitempty" doc:"additional key/value details tied with template"`
|
||||||
|
DisplayText string `json:"displaytext,omitempty" doc:"the template display text"`
|
||||||
|
Format string `json:"format,omitempty" doc:"the format of the template."`
|
||||||
|
HostID *UUID `json:"hostid,omitempty" doc:"the ID of the secondary storage host for the template"`
|
||||||
|
HostName string `json:"hostname,omitempty" doc:"the name of the secondary storage host for the template"`
|
||||||
|
Hypervisor string `json:"hypervisor,omitempty" doc:"the target hypervisor for the template"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the template ID"`
|
||||||
|
IsDynamicallyScalable bool `json:"isdynamicallyscalable,omitempty" doc:"true if template contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory"`
|
||||||
|
IsExtractable bool `json:"isextractable,omitempty" doc:"true if the template is extractable, false otherwise"`
|
||||||
|
IsFeatured bool `json:"isfeatured,omitempty" doc:"true if this template is a featured template, false otherwise"`
|
||||||
|
IsPublic bool `json:"ispublic,omitempty" doc:"true if this template is a public template, false otherwise"`
|
||||||
|
IsReady bool `json:"isready,omitempty" doc:"true if the template is ready to be deployed from, false otherwise."`
|
||||||
|
Name string `json:"name,omitempty" doc:"the template name"`
|
||||||
|
OsCategoryID *UUID `json:"oscategoryid,omitempty" doc:"the ID of the OS category for this template"`
|
||||||
|
OsCategoryName string `json:"oscategoryname,omitempty" doc:"the name of the OS category for this template"`
|
||||||
|
OsTypeID *UUID `json:"ostypeid,omitempty" doc:"the ID of the OS type for this template"`
|
||||||
|
OsTypeName string `json:"ostypename,omitempty" doc:"the name of the OS type for this template"`
|
||||||
|
PasswordEnabled bool `json:"passwordenabled,omitempty" doc:"true if the reset password feature is enabled, false otherwise"`
|
||||||
|
Removed string `json:"removed,omitempty" doc:"the date this template was removed"`
|
||||||
|
Size int64 `json:"size,omitempty" doc:"the size of the template"`
|
||||||
|
SourceTemplateID *UUID `json:"sourcetemplateid,omitempty" doc:"the template ID of the parent template if present"`
|
||||||
|
SSHKeyEnabled bool `json:"sshkeyenabled,omitempty" doc:"true if template is sshkey enabled, false otherwise"`
|
||||||
|
Status string `json:"status,omitempty" doc:"the status of the template"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"the list of resource tags associated with tempate"`
|
||||||
|
TemplateDirectory string `json:"templatedirectory,omitempty" doc:"Template directory"`
|
||||||
|
TemplateTag string `json:"templatetag,omitempty" doc:"the tag of this template"`
|
||||||
|
TemplateType string `json:"templatetype,omitempty" doc:"the type of the template"`
|
||||||
|
URL string `json:"url,omitempty" doc:"Original URL of the template where it was downloaded"`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"the ID of the zone for this template"`
|
||||||
|
ZoneName string `json:"zonename,omitempty" doc:"the name of the zone for this template"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceType returns the type of the resource
|
||||||
|
func (Template) ResourceType() string {
|
||||||
|
return "Template"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListTemplates request
|
||||||
|
func (template Template) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListTemplates{
|
||||||
|
ID: template.ID,
|
||||||
|
Name: template.Name,
|
||||||
|
ZoneID: template.ZoneID,
|
||||||
|
}
|
||||||
|
if template.IsFeatured {
|
||||||
|
req.TemplateFilter = "featured"
|
||||||
|
}
|
||||||
|
if template.Removed != "" {
|
||||||
|
*req.ShowRemoved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range template.Tags {
|
||||||
|
req.Tags = append(req.Tags, template.Tags[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListTemplates
|
||||||
|
|
||||||
|
// ListTemplates represents a template query filter
|
||||||
|
type ListTemplates struct {
|
||||||
|
TemplateFilter string `json:"templatefilter,omitempty" doc:"Possible values are \"featured\", \"self\", \"selfexecutable\",\"sharedexecutable\",\"executable\", and \"community\". * featured : templates that have been marked as featured and public. * self : templates that have been registered or created by the calling user. * selfexecutable : same as self, but only returns templates that can be used to deploy a new VM. * sharedexecutable : templates ready to be deployed that have been granted to the calling user by another user. * executable : templates that are owned by the calling user, or public templates, that can be used to deploy a VM. * community : templates that have been marked as public but not featured."`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the template ID"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the template name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
ShowRemoved *bool `json:"showremoved,omitempty" doc:"Show removed templates as well"`
|
||||||
|
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
|
||||||
|
ZoneID *UUID `json:"zoneid,omitempty" doc:"list templates by zoneid"`
|
||||||
|
_ bool `name:"listTemplates" description:"List all public, private, and privileged templates."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTemplatesResponse represents a list of templates
|
||||||
|
type ListTemplatesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Template []Template `json:"template"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSCategory represents an OS category
|
||||||
|
type OSCategory struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the ID of the OS category"`
|
||||||
|
Name string `json:"name,omitempty" doc:"the name of the OS category"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListOSCategories request
|
||||||
|
func (osCat OSCategory) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListOSCategories{
|
||||||
|
Name: osCat.Name,
|
||||||
|
ID: osCat.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListOSCategories
|
||||||
|
|
||||||
|
// ListOSCategories lists the OS categories
|
||||||
|
type ListOSCategories struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"list Os category by id"`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Name string `json:"name,omitempty" doc:"list os category by name"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
_ bool `name:"listOsCategories" description:"Lists all supported OS categories for this cloud."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOSCategoriesResponse represents a list of OS categories
|
||||||
|
type ListOSCategoriesResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
OSCategory []OSCategory `json:"oscategory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTemplate deletes a template by ID
|
||||||
|
type DeleteTemplate struct {
|
||||||
|
_ bool `name:"deleteTemplate" description:"Deletes a template"`
|
||||||
|
ID *UUID `json:"id" doc:"the ID of the template"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (DeleteTemplate) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (DeleteTemplate) AsyncResponse() interface{} {
|
||||||
|
return new(BooleanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCustomTemplate registers a new template
|
||||||
|
type RegisterCustomTemplate struct {
|
||||||
|
_ bool `name:"registerCustomTemplate" description:"Register a new template."`
|
||||||
|
BootMode string `json:"bootmode" doc:"the template boot mode (legacy/uefi)"`
|
||||||
|
Checksum string `json:"checksum" doc:"the MD5 checksum value of this template"`
|
||||||
|
Details map[string]string `json:"details,omitempty" doc:"Template details in key/value pairs"`
|
||||||
|
Displaytext string `json:"displaytext,omitempty" doc:"the display text of the template"`
|
||||||
|
Name string `json:"name" doc:"the name of the template"`
|
||||||
|
PasswordEnabled *bool `json:"passwordenabled,omitempty" doc:"true if the template supports the password reset feature; default is false"`
|
||||||
|
SSHKeyEnabled *bool `json:"sshkeyenabled,omitempty" doc:"true if the template supports the sshkey upload feature; default is false"`
|
||||||
|
TemplateTag string `json:"templatetag,omitempty" doc:"the tag for this template"`
|
||||||
|
URL string `json:"url" doc:"the URL of where the template is hosted"`
|
||||||
|
ZoneID *UUID `json:"zoneid" doc:"the ID of the zone the template is to be hosted on"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RegisterCustomTemplate) Response() interface{} {
|
||||||
|
return new(AsyncJobResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncResponse returns the struct to unmarshal the async job
|
||||||
|
func (RegisterCustomTemplate) AsyncResponse() interface{} {
|
||||||
|
return new([]Template)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListTemplates) Response() interface{} {
|
||||||
|
return new(ListTemplatesResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListTemplates) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListTemplates) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListTemplates) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListTemplates) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListTemplatesResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListTemplatesResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.Template {
|
||||||
|
if !callback(&items.Template[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
// User represents a User
|
||||||
|
type User struct {
|
||||||
|
APIKey string `json:"apikey,omitempty" doc:"the api key of the user"`
|
||||||
|
Account string `json:"account,omitempty" doc:"the account name of the user"`
|
||||||
|
AccountID *UUID `json:"accountid,omitempty" doc:"the account ID of the user"`
|
||||||
|
Created string `json:"created,omitempty" doc:"the date and time the user account was created"`
|
||||||
|
Email string `json:"email,omitempty" doc:"the user email address"`
|
||||||
|
FirstName string `json:"firstname,omitempty" doc:"the user firstname"`
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"the user ID"`
|
||||||
|
IsDefault bool `json:"isdefault,omitempty" doc:"true if user is default, false otherwise"`
|
||||||
|
LastName string `json:"lastname,omitempty" doc:"the user lastname"`
|
||||||
|
RoleID *UUID `json:"roleid,omitempty" doc:"the ID of the role"`
|
||||||
|
RoleName string `json:"rolename,omitempty" doc:"the name of the role"`
|
||||||
|
RoleType string `json:"roletype,omitempty" doc:"the type of the role"`
|
||||||
|
SecretKey string `json:"secretkey,omitempty" doc:"the secret key of the user"`
|
||||||
|
State string `json:"state,omitempty" doc:"the user state"`
|
||||||
|
Timezone string `json:"timezone,omitempty" doc:"the timezone user was created in"`
|
||||||
|
UserName string `json:"username,omitempty" doc:"the user name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest builds the ListUsers request
|
||||||
|
func (user User) ListRequest() (ListCommand, error) {
|
||||||
|
req := &ListUsers{
|
||||||
|
ID: user.ID,
|
||||||
|
UserName: user.UserName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUserKeys registers a new set of key of the given user
|
||||||
|
//
|
||||||
|
// NB: only the APIKey and SecretKey will be filled
|
||||||
|
type RegisterUserKeys struct {
|
||||||
|
ID *UUID `json:"id" doc:"User id"`
|
||||||
|
_ bool `name:"registerUserKeys" description:"This command allows a user to register for the developer API, returning a secret key and an API key. This request is made through the integration API port, so it is a privileged command and must be made on behalf of a user. It is up to the implementer just how the username and password are entered, and then how that translates to an integration API request. Both secret key and API key should be returned to the user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (RegisterUserKeys) Response() interface{} {
|
||||||
|
return new(User)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run generate/main.go -interface=Listable ListUsers
|
||||||
|
|
||||||
|
// ListUsers represents the search for Users
|
||||||
|
type ListUsers struct {
|
||||||
|
ID *UUID `json:"id,omitempty" doc:"List user by ID."`
|
||||||
|
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
|
State string `json:"state,omitempty" doc:"List users by state of the user account."`
|
||||||
|
UserName string `json:"username,omitempty" doc:"List user by the username"`
|
||||||
|
_ bool `name:"listUsers" description:"Lists user accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsersResponse represents a list of users
|
||||||
|
type ListUsersResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
User []User `json:"user"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// code generated; DO NOT EDIT.
|
||||||
|
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Response returns the struct to unmarshal
|
||||||
|
func (ListUsers) Response() interface{} {
|
||||||
|
return new(ListUsersResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequest returns itself
|
||||||
|
func (ls *ListUsers) ListRequest() (ListCommand, error) {
|
||||||
|
if ls == nil {
|
||||||
|
return nil, fmt.Errorf("%T cannot be nil", ls)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPage sets the current apge
|
||||||
|
func (ls *ListUsers) SetPage(page int) {
|
||||||
|
ls.Page = page
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPageSize sets the page size
|
||||||
|
func (ls *ListUsers) SetPageSize(pageSize int) {
|
||||||
|
ls.PageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each triggers the callback for each, valid answer or any non 404 issue
|
||||||
|
func (ListUsers) Each(resp interface{}, callback IterateItemFunc) {
|
||||||
|
items, ok := resp.(*ListUsersResponse)
|
||||||
|
if !ok {
|
||||||
|
callback(nil, fmt.Errorf("wrong type, ListUsersResponse was expected, got %T", resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range items.User {
|
||||||
|
if !callback(&items.User[i], nil) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package egoscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
uuid "github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UUID holds a UUID v4
|
||||||
|
type UUID struct {
|
||||||
|
uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy create a true copy of the receiver.
|
||||||
|
func (u *UUID) DeepCopy() *UUID {
|
||||||
|
if u == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := [uuid.Size]byte{}
|
||||||
|
copy(out[:], u.Bytes())
|
||||||
|
|
||||||
|
return &UUID{
|
||||||
|
(uuid.UUID)(out),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto copies the receiver into out.
|
||||||
|
//
|
||||||
|
// In must be non nil.
|
||||||
|
func (u *UUID) DeepCopyInto(out *UUID) {
|
||||||
|
o := [uuid.Size]byte{}
|
||||||
|
copy(o[:], u.Bytes())
|
||||||
|
|
||||||
|
out.UUID = (uuid.UUID)(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if itself is equal to other.
|
||||||
|
func (u UUID) Equal(other UUID) bool {
|
||||||
|
return u == other
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals the raw JSON into the UUID.
|
||||||
|
func (u *UUID) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
new, err := ParseUUID(s)
|
||||||
|
if err == nil {
|
||||||
|
u.UUID = new.UUID
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts the UUID to a string representation.
|
||||||
|
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf("%q", u.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseUUID parses a string into a UUID.
|
||||||
|
func ParseUUID(s string) (*UUID, error) {
|
||||||
|
u, err := uuid.FromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UUID{u}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseUUID acts like ParseUUID but panic in case of a failure.
|
||||||
|
func MustParseUUID(s string) *UUID {
|
||||||
|
u, e := ParseUUID(s)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Package api implements low-level primitives for interacting with the
|
||||||
|
// Exoscale API.
|
||||||
|
package api
|
|
@ -0,0 +1,14 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNotFound represents an error indicating a non-existent resource.
|
||||||
|
ErrNotFound = errors.New("resource not found")
|
||||||
|
|
||||||
|
// ErrInvalidRequest represents an error indicating that the caller's request is invalid.
|
||||||
|
ErrInvalidRequest = errors.New("invalid request")
|
||||||
|
|
||||||
|
// ErrAPIError represents an error indicating an API-side issue.
|
||||||
|
ErrAPIError = errors.New("API error")
|
||||||
|
)
|
|
@ -0,0 +1,67 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Middleware interface {
|
||||||
|
http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandlerMiddleware is an Exoscale API HTTP client middleware that
|
||||||
|
// returns concrete Go errors according to API response errors.
|
||||||
|
type ErrorHandlerMiddleware struct {
|
||||||
|
next http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIErrorHandlerMiddleware(next http.RoundTripper) Middleware {
|
||||||
|
if next == nil {
|
||||||
|
next = http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ErrorHandlerMiddleware{next: next}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ErrorHandlerMiddleware) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := m.next.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
// If the request returned a Go error don't bother analyzing the response
|
||||||
|
// body, as there probably won't be any (e.g. connection timeout/refused).
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
|
||||||
|
var res struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading response body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Valid(data) {
|
||||||
|
if err = json.Unmarshal(data, &res); err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshaling response: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.Message = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case resp.StatusCode == http.StatusNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
|
||||||
|
case resp.StatusCode >= 400 && resp.StatusCode < 500:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrInvalidRequest, res.Message)
|
||||||
|
|
||||||
|
case resp.StatusCode >= 500:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrAPIError, res.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EndpointURL = "https://api.exoscale.com/"
|
||||||
|
Prefix = "v2.alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultReqEndpointEnv = "api"
|
||||||
|
|
||||||
|
// ReqEndpoint represents an Exoscale API request endpoint.
|
||||||
|
type ReqEndpoint struct {
|
||||||
|
env string
|
||||||
|
zone string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReqEndpoint returns a new Exoscale API request endpoint from an environment and zone.
|
||||||
|
func NewReqEndpoint(env, zone string) ReqEndpoint {
|
||||||
|
re := ReqEndpoint{
|
||||||
|
env: env,
|
||||||
|
zone: zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
if re.env == "" {
|
||||||
|
re.env = defaultReqEndpointEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
return re
|
||||||
|
}
|
||||||
|
|
||||||
|
// Env returns the Exoscale API endpoint environment.
|
||||||
|
func (r *ReqEndpoint) Env() string {
|
||||||
|
return r.env
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone returns the Exoscale API endpoint zone.
|
||||||
|
func (r *ReqEndpoint) Zone() string {
|
||||||
|
return r.zone
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host returns the Exoscale API endpoint host FQDN.
|
||||||
|
func (r *ReqEndpoint) Host() string {
|
||||||
|
return fmt.Sprintf("%s-%s.exoscale.com", r.env, r.zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEndpoint returns an augmented context instance containing the Exoscale endpoint to send
|
||||||
|
// the request to.
|
||||||
|
func WithEndpoint(ctx context.Context, endpoint ReqEndpoint) context.Context {
|
||||||
|
return context.WithValue(ctx, ReqEndpoint{}, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithZone is a shorthand to WithEndpoint where only the endpoint zone has to be specified.
|
||||||
|
// If a request endpoint is already set in the specified context instance, the value currently
|
||||||
|
// set for the environment will be reused.
|
||||||
|
func WithZone(ctx context.Context, zone string) context.Context {
|
||||||
|
var env string
|
||||||
|
|
||||||
|
if v, ok := ctx.Value(ReqEndpoint{}).(ReqEndpoint); ok {
|
||||||
|
env = v.Env()
|
||||||
|
}
|
||||||
|
|
||||||
|
return WithEndpoint(ctx, NewReqEndpoint(env, zone))
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityProviderExoscale represents an Exoscale public API security
|
||||||
|
// provider.
|
||||||
|
type SecurityProviderExoscale struct {
|
||||||
|
// ReqExpire represents the request expiration duration.
|
||||||
|
ReqExpire time.Duration
|
||||||
|
|
||||||
|
apiKey string
|
||||||
|
apiSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecurityProvider returns a new Exoscale public API security
|
||||||
|
// provider to sign API requests using the specified API key/secret.
|
||||||
|
func NewSecurityProvider(apiKey, apiSecret string) (*SecurityProviderExoscale, error) {
|
||||||
|
if apiKey == "" {
|
||||||
|
return nil, errors.New("missing API key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiSecret == "" {
|
||||||
|
return nil, errors.New("missing API secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SecurityProviderExoscale{
|
||||||
|
ReqExpire: 10 * time.Minute,
|
||||||
|
apiKey: apiKey,
|
||||||
|
apiSecret: apiSecret,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept is an HTTP middleware that intercepts and signs client requests
|
||||||
|
// before sending them to the API endpoint.
|
||||||
|
func (s *SecurityProviderExoscale) Intercept(_ context.Context, req *http.Request) error {
|
||||||
|
return s.signRequest(req, time.Now().UTC().Add(s.ReqExpire))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecurityProviderExoscale) signRequest(req *http.Request, expiration time.Time) error {
|
||||||
|
var (
|
||||||
|
sigParts []string
|
||||||
|
headerParts []string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request method/URL path
|
||||||
|
sigParts = append(sigParts, fmt.Sprintf("%s %s", req.Method, req.URL.Path))
|
||||||
|
headerParts = append(headerParts, "EXO2-HMAC-SHA256 credential="+s.apiKey)
|
||||||
|
|
||||||
|
// Request body if present
|
||||||
|
body := ""
|
||||||
|
if req.Body != nil {
|
||||||
|
data, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = req.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body = string(data)
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(data))
|
||||||
|
}
|
||||||
|
sigParts = append(sigParts, body)
|
||||||
|
|
||||||
|
// Request query string parameters
|
||||||
|
// Important: this is order-sensitive, we have to have to sort parameters alphabetically to ensure signed
|
||||||
|
// values match the names listed in the "signed-query-args=" signature pragma.
|
||||||
|
signedParams, paramsValues := extractRequestParameters(req)
|
||||||
|
sigParts = append(sigParts, paramsValues)
|
||||||
|
if len(signedParams) > 0 {
|
||||||
|
headerParts = append(headerParts, "signed-query-args="+strings.Join(signedParams, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request headers -- none at the moment
|
||||||
|
// Note: the same order-sensitive caution for query string parameters applies to headers.
|
||||||
|
sigParts = append(sigParts, "")
|
||||||
|
|
||||||
|
// Request expiration date (UNIX timestamp, no line return)
|
||||||
|
sigParts = append(sigParts, fmt.Sprint(expiration.Unix()))
|
||||||
|
headerParts = append(headerParts, "expires="+fmt.Sprint(expiration.Unix()))
|
||||||
|
|
||||||
|
h := hmac.New(sha256.New, []byte(s.apiSecret))
|
||||||
|
if _, err := h.Write([]byte(strings.Join(sigParts, "\n"))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
headerParts = append(headerParts, "signature="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", strings.Join(headerParts, ","))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractRequestParameters returns the list of request URL parameters names
|
||||||
|
// and a strings concatenating the values of the parameters.
|
||||||
|
func extractRequestParameters(req *http.Request) ([]string, string) {
|
||||||
|
var (
|
||||||
|
names []string
|
||||||
|
values string
|
||||||
|
)
|
||||||
|
|
||||||
|
for param, values := range req.URL.Query() {
|
||||||
|
// Keep only parameters that hold exactly 1 value (i.e. no empty or multi-valued parameters)
|
||||||
|
if len(values) == 1 {
|
||||||
|
names = append(names, param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
for _, param := range names {
|
||||||
|
values += req.URL.Query().Get(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, values
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/exoscale/egoscale/v2/api"
|
||||||
|
papi "github.com/exoscale/egoscale/v2/internal/public-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
// ClientOpt represents a function setting Exoscale API client option.
|
||||||
|
type ClientOpt func(*Client) error
|
||||||
|
|
||||||
|
// ClientOptWithAPIEndpoint returns a ClientOpt overriding the default Exoscale API endpoint.
|
||||||
|
func ClientOptWithAPIEndpoint(v string) ClientOpt {
|
||||||
|
return func(c *Client) error {
|
||||||
|
endpointURL, err := url.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointURL = endpointURL.ResolveReference(&url.URL{Path: api.Prefix})
|
||||||
|
c.apiEndpoint = endpointURL.String()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOptWithTimeout returns a ClientOpt overriding the default client timeout.
|
||||||
|
func ClientOptWithTimeout(v time.Duration) ClientOpt {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.timeout = v
|
||||||
|
|
||||||
|
if v <= 0 {
|
||||||
|
return errors.New("timeout value must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOptWithHTTPClient returns a ClientOpt overriding the default http.Client.
|
||||||
|
// Note: the Exoscale API client will chain additional middleware
|
||||||
|
// (http.RoundTripper) on the HTTP client internally, which can alter the HTTP
|
||||||
|
// requests and responses. If you don't want any other middleware than the ones
|
||||||
|
// currently set to your HTTP client, you should duplicate it and pass a copy
|
||||||
|
// instead.
|
||||||
|
func ClientOptWithHTTPClient(v *http.Client) ClientOpt {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.httpClient = v
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents an Exoscale API client.
|
||||||
|
type Client struct {
|
||||||
|
apiKey string
|
||||||
|
apiSecret string
|
||||||
|
apiEndpoint string
|
||||||
|
timeout time.Duration
|
||||||
|
httpClient *http.Client
|
||||||
|
|
||||||
|
*papi.ClientWithResponses
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new Exoscale API client, or an error if one couldn't be initialized.
|
||||||
|
func NewClient(apiKey, apiSecret string, opts ...ClientOpt) (*Client, error) {
|
||||||
|
client := Client{
|
||||||
|
apiKey: apiKey,
|
||||||
|
apiSecret: apiSecret,
|
||||||
|
apiEndpoint: api.EndpointURL,
|
||||||
|
httpClient: http.DefaultClient,
|
||||||
|
timeout: defaultTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.apiKey == "" || client.apiSecret == "" {
|
||||||
|
return nil, fmt.Errorf("%w: missing or incomplete API credentials", ErrClientConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&client); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrClientConfig, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSecurityProvider, err := api.NewSecurityProvider(client.apiKey, client.apiSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize API security provider: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL, err := url.Parse(client.apiEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize API client: %s", err)
|
||||||
|
}
|
||||||
|
apiURL = apiURL.ResolveReference(&url.URL{Path: api.Prefix})
|
||||||
|
|
||||||
|
client.httpClient.Transport = api.NewAPIErrorHandlerMiddleware(client.httpClient.Transport)
|
||||||
|
|
||||||
|
papiOpts := []papi.ClientOption{
|
||||||
|
papi.WithHTTPClient(client.httpClient),
|
||||||
|
papi.WithRequestEditorFn(
|
||||||
|
papi.MultiRequestsEditor(
|
||||||
|
apiSecurityProvider.Intercept,
|
||||||
|
setEndpointFromContext,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.ClientWithResponses, err = papi.NewClientWithResponses(apiURL.String(), papiOpts...); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize API client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setEndpointFromContext is an HTTP client request interceptor that overrides the "Host" header
|
||||||
|
// with information from a request endpoint optionally set in the context instance. If none is
|
||||||
|
// found, the request is left untouched.
|
||||||
|
func setEndpointFromContext(ctx context.Context, req *http.Request) error {
|
||||||
|
if v, ok := ctx.Value(api.ReqEndpoint{}).(api.ReqEndpoint); ok {
|
||||||
|
req.Host = v.Host()
|
||||||
|
req.URL.Host = v.Host()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrClientConfig = errors.New("client configuration error")
|
123
vendor/github.com/exoscale/egoscale/v2/internal/public-api/async.go
generated
vendored
Normal file
123
vendor/github.com/exoscale/egoscale/v2/internal/public-api/async.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v2 "github.com/exoscale/egoscale/v2/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
operationStatePending = "pending"
|
||||||
|
operationStateSuccess = "success"
|
||||||
|
operationStateFailure = "failure"
|
||||||
|
operationStateTimeout = "timeout"
|
||||||
|
|
||||||
|
defaultPollingInterval = 3 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// PollFunc represents a function invoked periodically in a polling loop. It returns a boolean flag
|
||||||
|
// true if the job is completed or false if polling must continue, and any error that occurred
|
||||||
|
// during the polling (which interrupts the polling regardless of the boolean flag value).
|
||||||
|
// Upon successful completion, an interface descring an opaque operation can be returned to the
|
||||||
|
// caller, which will have to perform type assertion depending on the PollFunc implementation.
|
||||||
|
type PollFunc func(ctx context.Context) (bool, interface{}, error)
|
||||||
|
|
||||||
|
// Poller represents a poller instance.
|
||||||
|
type Poller struct {
|
||||||
|
interval time.Duration
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPoller returns a Poller instance.
|
||||||
|
func NewPoller() *Poller {
|
||||||
|
return &Poller{
|
||||||
|
interval: defaultPollingInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInterval sets the interval at which the polling function will be executed (default: 3s).
|
||||||
|
func (p *Poller) WithInterval(interval time.Duration) *Poller {
|
||||||
|
if interval > 0 {
|
||||||
|
p.interval = interval
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout sets the time out value after which the polling routine will be cancelled
|
||||||
|
// (default: no time out).
|
||||||
|
func (p *Poller) WithTimeout(timeout time.Duration) *Poller {
|
||||||
|
if timeout > 0 {
|
||||||
|
p.timeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll starts the polling routine, executing the provided polling function at the configured
|
||||||
|
// polling interval. Upon successful polling, an opaque operation is returned to the caller, which
|
||||||
|
// actual type has to asserted depending on the PollFunc executed.
|
||||||
|
func (p *Poller) Poll(ctx context.Context, pf PollFunc) (interface{}, error) {
|
||||||
|
if p.timeout > 0 {
|
||||||
|
ctxWithTimeout, cancel := context.WithTimeout(ctx, p.timeout)
|
||||||
|
defer cancel()
|
||||||
|
ctx = ctxWithTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(p.interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
done, res, err := pf(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperationPoller returns a PollFunc function which queries the state of the specified job.
|
||||||
|
// Upon successful job completion, the type of the interface{} returned by the PollFunc is a
|
||||||
|
// pointer to a Resource object (*Resource).
|
||||||
|
func (c *ClientWithResponses) OperationPoller(zone string, jobID string) PollFunc {
|
||||||
|
return func(ctx context.Context) (bool, interface{}, error) {
|
||||||
|
resp, err := c.GetOperationWithResponse(v2.WithZone(ctx, zone), jobID)
|
||||||
|
if err != nil {
|
||||||
|
return true, nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return true, nil, fmt.Errorf("unexpected response from API: %s", resp.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *resp.JSON200.State {
|
||||||
|
case operationStatePending:
|
||||||
|
return false, nil, nil
|
||||||
|
|
||||||
|
case operationStateSuccess:
|
||||||
|
return true, resp.JSON200.Reference, nil
|
||||||
|
|
||||||
|
case operationStateFailure:
|
||||||
|
return true, nil, errors.New("job failed")
|
||||||
|
|
||||||
|
case operationStateTimeout:
|
||||||
|
return true, nil, errors.New("job timed out")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true, nil, fmt.Errorf("unknown job state: %s", *resp.JSON200.State)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
vendor/github.com/exoscale/egoscale/v2/internal/public-api/loadbalancer.go
generated
vendored
Normal file
70
vendor/github.com/exoscale/egoscale/v2/internal/public-api/loadbalancer.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals a LoadBalancer structure into a temporary structure whose "CreatedAt" field of type
|
||||||
|
// string to be able to parse the original timestamp (ISO 8601) into a time.Time object, since json.Unmarshal()
|
||||||
|
// only supports RFC 3339 format.
|
||||||
|
func (lb *LoadBalancer) UnmarshalJSON(data []byte) error {
|
||||||
|
raw := struct {
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
Ip *string `json:"ip,omitempty"` // nolint:golint
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Services *[]LoadBalancerService `json:"services,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.CreatedAt != nil {
|
||||||
|
createdAt, err := time.Parse(iso8601Format, *raw.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lb.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
lb.Description = raw.Description
|
||||||
|
lb.Id = raw.Id
|
||||||
|
lb.Ip = raw.Ip
|
||||||
|
lb.Name = raw.Name
|
||||||
|
lb.Services = raw.Services
|
||||||
|
lb.State = raw.State
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of a LoadBalancer structure after having formatted the CreatedAt field
|
||||||
|
// in the original timestamp (ISO 8601), since time.MarshalJSON() only supports RFC 3339 format.
|
||||||
|
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
|
||||||
|
raw := struct {
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
Ip *string `json:"ip,omitempty"` // nolint:golint
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Services *[]LoadBalancerService `json:"services,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if lb.CreatedAt != nil {
|
||||||
|
createdAt := lb.CreatedAt.Format(iso8601Format)
|
||||||
|
raw.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.Description = lb.Description
|
||||||
|
raw.Id = lb.Id
|
||||||
|
raw.Ip = lb.Ip
|
||||||
|
raw.Name = lb.Name
|
||||||
|
raw.Services = lb.Services
|
||||||
|
raw.State = lb.State
|
||||||
|
|
||||||
|
return json.Marshal(raw)
|
||||||
|
}
|
28
vendor/github.com/exoscale/egoscale/v2/internal/public-api/mock.go
generated
vendored
Normal file
28
vendor/github.com/exoscale/egoscale/v2/internal/public-api/mock.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockClient struct {
|
||||||
|
mock.Mock
|
||||||
|
*httpmock.MockTransport
|
||||||
|
ClientWithResponsesInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockClient() *MockClient {
|
||||||
|
var c MockClient
|
||||||
|
|
||||||
|
c.MockTransport = httpmock.NewMockTransport()
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
hc := http.Client{Transport: c.MockTransport}
|
||||||
|
|
||||||
|
return hc.Do(req)
|
||||||
|
}
|
9554
vendor/github.com/exoscale/egoscale/v2/internal/public-api/public-api.gen.go
generated
vendored
Normal file
9554
vendor/github.com/exoscale/egoscale/v2/internal/public-api/public-api.gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24
vendor/github.com/exoscale/egoscale/v2/internal/public-api/public-api.go
generated
vendored
Normal file
24
vendor/github.com/exoscale/egoscale/v2/internal/public-api/public-api.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Package publicapi is an internal package containing code generated from the
|
||||||
|
// Exoscale API OpenAPI specs, as well as helpers and transition types exposed
|
||||||
|
// in the public-facing package.
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
//go:generate oapi-codegen -generate types,client -package publicapi -o public-api.gen.go ../../../public-api.json
|
||||||
|
|
||||||
|
// OptionalString returns the dereferenced string value of v if not nil, otherwise an empty string.
|
||||||
|
func OptionalString(v *string) string {
|
||||||
|
if v != nil {
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionalInt64 returns the dereferenced int64 value of v if not nil, otherwise 0.
|
||||||
|
func OptionalInt64(v *int64) int64 {
|
||||||
|
if v != nil {
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
20
vendor/github.com/exoscale/egoscale/v2/internal/public-api/request.go
generated
vendored
Normal file
20
vendor/github.com/exoscale/egoscale/v2/internal/public-api/request.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiRequestsEditor is an oapi-codegen compatible RequestEditorFn function that executes multiple
|
||||||
|
// RequestEditorFn functions sequentially.
|
||||||
|
func MultiRequestsEditor(fns ...RequestEditorFn) RequestEditorFn {
|
||||||
|
return func(ctx context.Context, req *http.Request) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(ctx, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
86
vendor/github.com/exoscale/egoscale/v2/internal/public-api/sks_cluster.go
generated
vendored
Normal file
86
vendor/github.com/exoscale/egoscale/v2/internal/public-api/sks_cluster.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals a SksCluster structure into a temporary structure whose "CreatedAt" field of type
|
||||||
|
// string to be able to parse the original timestamp (ISO 8601) into a time.Time object, since json.Unmarshal()
|
||||||
|
// only supports RFC 3339 format.
|
||||||
|
func (c *SksCluster) UnmarshalJSON(data []byte) error {
|
||||||
|
raw := struct {
|
||||||
|
Addons *[]string `json:"addons,omitempty"`
|
||||||
|
Cni *string `json:"cni,omitempty"`
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Endpoint *string `json:"endpoint,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
Level *string `json:"level,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Nodepools *[]SksNodepool `json:"nodepools,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
Version *string `json:"version,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.CreatedAt != nil {
|
||||||
|
createdAt, err := time.Parse(iso8601Format, *raw.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Addons = raw.Addons
|
||||||
|
c.Cni = raw.Cni
|
||||||
|
c.Description = raw.Description
|
||||||
|
c.Endpoint = raw.Endpoint
|
||||||
|
c.Id = raw.Id
|
||||||
|
c.Level = raw.Level
|
||||||
|
c.Name = raw.Name
|
||||||
|
c.Nodepools = raw.Nodepools
|
||||||
|
c.State = raw.State
|
||||||
|
c.Version = raw.Version
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of a SksCluster structure after having formatted the CreatedAt field
|
||||||
|
// in the original timestamp (ISO 8601), since time.MarshalJSON() only supports RFC 3339 format.
|
||||||
|
func (c *SksCluster) MarshalJSON() ([]byte, error) {
|
||||||
|
raw := struct {
|
||||||
|
Addons *[]string `json:"addons,omitempty"`
|
||||||
|
Cni *string `json:"cni,omitempty"`
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Endpoint *string `json:"endpoint,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
Level *string `json:"level,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Nodepools *[]SksNodepool `json:"nodepools,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
Version *string `json:"version,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if c.CreatedAt != nil {
|
||||||
|
createdAt := c.CreatedAt.Format(iso8601Format)
|
||||||
|
raw.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.Addons = c.Addons
|
||||||
|
raw.Cni = c.Cni
|
||||||
|
raw.Description = c.Description
|
||||||
|
raw.Endpoint = c.Endpoint
|
||||||
|
raw.Id = c.Id
|
||||||
|
raw.Level = c.Level
|
||||||
|
raw.Name = c.Name
|
||||||
|
raw.Nodepools = c.Nodepools
|
||||||
|
raw.State = c.State
|
||||||
|
raw.Version = c.Version
|
||||||
|
|
||||||
|
return json.Marshal(raw)
|
||||||
|
}
|
94
vendor/github.com/exoscale/egoscale/v2/internal/public-api/sks_nodepool.go
generated
vendored
Normal file
94
vendor/github.com/exoscale/egoscale/v2/internal/public-api/sks_nodepool.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package publicapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals a SksNodepool structure into a temporary structure whose "CreatedAt" field of type
|
||||||
|
// string to be able to parse the original timestamp (ISO 8601) into a time.Time object, since json.Unmarshal()
|
||||||
|
// only supports RFC 3339 format.
|
||||||
|
func (n *SksNodepool) UnmarshalJSON(data []byte) error {
|
||||||
|
raw := struct {
|
||||||
|
AntiAffinityGroups *[]AntiAffinityGroup `json:"anti-affinity-groups,omitempty"`
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
DiskSize *int64 `json:"disk-size,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
InstancePool *InstancePool `json:"instance-pool,omitempty"`
|
||||||
|
InstanceType *InstanceType `json:"instance-type,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
SecurityGroups *[]SecurityGroup `json:"security-groups,omitempty"`
|
||||||
|
Size *int64 `json:"size,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
Template *Template `json:"template,omitempty"`
|
||||||
|
Version *string `json:"version,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.CreatedAt != nil {
|
||||||
|
createdAt, err := time.Parse(iso8601Format, *raw.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
n.AntiAffinityGroups = raw.AntiAffinityGroups
|
||||||
|
n.Description = raw.Description
|
||||||
|
n.DiskSize = raw.DiskSize
|
||||||
|
n.Id = raw.Id
|
||||||
|
n.InstancePool = raw.InstancePool
|
||||||
|
n.InstanceType = raw.InstanceType
|
||||||
|
n.Name = raw.Name
|
||||||
|
n.SecurityGroups = raw.SecurityGroups
|
||||||
|
n.Size = raw.Size
|
||||||
|
n.State = raw.State
|
||||||
|
n.Template = raw.Template
|
||||||
|
n.Version = raw.Version
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of a SksNodepool structure after having formatted the CreatedAt field
|
||||||
|
// in the original timestamp (ISO 8601), since time.MarshalJSON() only supports RFC 3339 format.
|
||||||
|
func (n *SksNodepool) MarshalJSON() ([]byte, error) {
|
||||||
|
raw := struct {
|
||||||
|
AntiAffinityGroups *[]AntiAffinityGroup `json:"anti-affinity-groups,omitempty"`
|
||||||
|
CreatedAt *string `json:"created-at,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
DiskSize *int64 `json:"disk-size,omitempty"`
|
||||||
|
Id *string `json:"id,omitempty"` // nolint:golint
|
||||||
|
InstancePool *InstancePool `json:"instance-pool,omitempty"`
|
||||||
|
InstanceType *InstanceType `json:"instance-type,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
SecurityGroups *[]SecurityGroup `json:"security-groups,omitempty"`
|
||||||
|
Size *int64 `json:"size,omitempty"`
|
||||||
|
State *string `json:"state,omitempty"`
|
||||||
|
Template *Template `json:"template,omitempty"`
|
||||||
|
Version *string `json:"version,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if n.CreatedAt != nil {
|
||||||
|
createdAt := n.CreatedAt.Format(iso8601Format)
|
||||||
|
raw.CreatedAt = &createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.AntiAffinityGroups = n.AntiAffinityGroups
|
||||||
|
raw.Description = n.Description
|
||||||
|
raw.DiskSize = n.DiskSize
|
||||||
|
raw.Id = n.Id
|
||||||
|
raw.InstancePool = n.InstancePool
|
||||||
|
raw.InstanceType = n.InstanceType
|
||||||
|
raw.Name = n.Name
|
||||||
|
raw.SecurityGroups = n.SecurityGroups
|
||||||
|
raw.Size = n.Size
|
||||||
|
raw.State = n.State
|
||||||
|
raw.Template = n.Template
|
||||||
|
raw.Version = n.Version
|
||||||
|
|
||||||
|
return json.Marshal(raw)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue