diff --git a/go.mod b/go.mod index 993ac876..ea71dd79 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 - github.com/vultr/govultr/v3 v3.2.0 + github.com/vultr/govultr/v3 v3.3.0 golang.org/x/oauth2 v0.10.0 ) diff --git a/go.sum b/go.sum index 2dc48d1a..92ee254e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= @@ -7,6 +10,8 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= @@ -20,6 +25,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -81,8 +87,10 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -102,8 +110,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -117,11 +127,15 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -141,9 +155,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vultr/govultr/v3 v3.2.0 h1:gvplbfQOXYKUbYH/yG+8PJ7IAwRER/qqreh9EI2NlD0= github.com/vultr/govultr/v3 v3.2.0/go.mod h1:7NjuHeQv5vgUWR2H1sPc9D+xffrT5ql+kNi6R3yuwzo= +github.com/vultr/govultr/v3 v3.3.0 h1:bGbbeHB9ftzrlGgwRKnWzAHYHi4sOD/SpA13nK+1aSc= +github.com/vultr/govultr/v3 v3.3.0/go.mod h1:7NjuHeQv5vgUWR2H1sPc9D+xffrT5ql+kNi6R3yuwzo= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= @@ -196,6 +213,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/vultr/govultr/v3/CHANGELOG.md b/vendor/github.com/vultr/govultr/v3/CHANGELOG.md index 046fd660..bf01ebf7 100644 --- a/vendor/github.com/vultr/govultr/v3/CHANGELOG.md +++ b/vendor/github.com/vultr/govultr/v3/CHANGELOG.md @@ -2,6 +2,14 @@ ## GoVultr v1 changelog is located [here](https://github.com/vultr/govultr/blob/v1/CHANGELOG.md) +## [v3.3.0](https://github.com/vultr/govultr/compare/v3.2.0...v3.3.0) (2023-08-10) +### Enhancements +* Add VPC2 [PR 261](https://github.com/vultr/govultr/pull/261) +* Bare Metal/Instances: Add support for VPC 2.0 [PR 261](https://github.com/vultr/govultr/pull/261) + +### New Contributors +* @ogawa0071 made their first contribution in [PR 261](https://github.com/vultr/govultr/pull/261) + ## [v3.2.0](https://github.com/vultr/govultr/compare/v3.1.0...v3.2.0) (2023-07-24) ### Enhancements * Database: add support for DBaaS VPC networks [PR 255](https://github.com/vultr/govultr/pull/255) diff --git a/vendor/github.com/vultr/govultr/v3/bare_metal_server.go b/vendor/github.com/vultr/govultr/v3/bare_metal_server.go index ea847c83..8ae73b85 100644 --- a/vendor/github.com/vultr/govultr/v3/bare_metal_server.go +++ b/vendor/github.com/vultr/govultr/v3/bare_metal_server.go @@ -36,6 +36,10 @@ type BareMetalServerService interface { MassReboot(ctx context.Context, serverList []string) error GetUpgrades(ctx context.Context, serverID string) (*Upgrades, *http.Response, error) + + ListVPC2Info(ctx context.Context, serverID string) ([]VPC2Info, *http.Response, error) + AttachVPC2(ctx context.Context, serverID string, vpc2Req *AttachVPC2Req) error + DetachVPC2(ctx context.Context, serverID, vpcID string) error } // BareMetalServerServiceHandler handles interaction with the Bare Metal methods for the Vultr API @@ -92,6 +96,9 @@ type BareMetalCreate struct { ReservedIPv4 string `json:"reserved_ipv4,omitempty"` PersistentPxe *bool `json:"persistent_pxe,omitempty"` Tags []string `json:"tags"` + AttachVPC2 []string `json:"attach_vpc2,omitempty"` + DetachVPC2 []string `json:"detach_vpc2,omitempty"` + EnableVPC2 *bool `json:"enable_vpc2,omitempty"` } // BareMetalUpdate represents the optional parameters that can be set when updating a Bare Metal server @@ -103,8 +110,11 @@ type BareMetalUpdate struct { ImageID string `json:"image_id,omitempty"` UserData string `json:"user_data,omitempty"` // Deprecated: Tag should no longer be used. Instead, use Tags. - Tag *string `json:"tag,omitempty"` - Tags []string `json:"tags"` + Tag *string `json:"tag,omitempty"` + Tags []string `json:"tags"` + AttachVPC2 []string `json:"attach_vpc2,omitempty"` + DetachVPC2 []string `json:"detach_vpc2,omitempty"` + EnableVPC2 *bool `json:"enable_vpc2,omitempty"` } // BareMetalServerBandwidth represents bandwidth information for a Bare Metal server @@ -434,3 +444,47 @@ func (b *BareMetalServerServiceHandler) GetUpgrades(ctx context.Context, serverI return upgrades.Upgrades, resp, nil } + +// ListVPC2Info currently attached to a Bare Metal server. +func (b *BareMetalServerServiceHandler) ListVPC2Info(ctx context.Context, serverID string) ([]VPC2Info, *http.Response, error) { + uri := fmt.Sprintf("%s/%s/vpc2", bmPath, serverID) + req, err := b.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, err + } + + vpcs := new(vpc2InfoBase) + resp, err := b.client.DoWithContext(ctx, req, vpcs) + if err != nil { + return nil, resp, err + } + + return vpcs.VPCs, resp, nil +} + +// AttachVPC2 to a Bare Metal server. +func (b *BareMetalServerServiceHandler) AttachVPC2(ctx context.Context, serverID string, vpc2Req *AttachVPC2Req) error { + uri := fmt.Sprintf("%s/%s/vpc2/attach", bmPath, serverID) + + req, err := b.client.NewRequest(ctx, http.MethodPost, uri, vpc2Req) + if err != nil { + return err + } + + _, err = b.client.DoWithContext(ctx, req, nil) + return err +} + +// DetachVPC2 from a Bare Metal server. +func (b *BareMetalServerServiceHandler) DetachVPC2(ctx context.Context, serverID, vpcID string) error { + uri := fmt.Sprintf("%s/%s/vpc2/detach", bmPath, serverID) + body := RequestBody{"vpc_id": vpcID} + + req, err := b.client.NewRequest(ctx, http.MethodPost, uri, body) + if err != nil { + return err + } + + _, err = b.client.DoWithContext(ctx, req, nil) + return err +} diff --git a/vendor/github.com/vultr/govultr/v3/govultr.go b/vendor/github.com/vultr/govultr/v3/govultr.go index 3cb538b9..6776a79e 100644 --- a/vendor/github.com/vultr/govultr/v3/govultr.go +++ b/vendor/github.com/vultr/govultr/v3/govultr.go @@ -17,7 +17,7 @@ import ( ) const ( - version = "3.2.0" + version = "3.3.0" defaultBase = "https://api.vultr.com" userAgent = "govultr/" + version rateLimit = 500 * time.Millisecond @@ -66,6 +66,7 @@ type Client struct { StartupScript StartupScriptService User UserService VPC VPCService + VPC2 VPC2Service // Optional function called after every successful request made to the Vultr API onRequestCompleted RequestCompletionCallback @@ -135,6 +136,7 @@ func NewClient(httpClient *http.Client) *Client { client.StartupScript = &StartupScriptServiceHandler{client} client.User = &UserServiceHandler{client} client.VPC = &VPCServiceHandler{client} + client.VPC2 = &VPC2ServiceHandler{client} return client } diff --git a/vendor/github.com/vultr/govultr/v3/instance.go b/vendor/github.com/vultr/govultr/v3/instance.go index d4ae9d0a..acaadf3e 100644 --- a/vendor/github.com/vultr/govultr/v3/instance.go +++ b/vendor/github.com/vultr/govultr/v3/instance.go @@ -44,6 +44,10 @@ type InstanceService interface { AttachVPC(ctx context.Context, instanceID, vpcID string) error DetachVPC(ctx context.Context, instanceID, vpcID string) error + ListVPC2Info(ctx context.Context, instanceID string, options *ListOptions) ([]VPC2Info, *Meta, *http.Response, error) + AttachVPC2(ctx context.Context, instanceID string, vpc2Req *AttachVPC2Req) error + DetachVPC2(ctx context.Context, instanceID, vpcID string) error + ISOStatus(ctx context.Context, instanceID string) (*Iso, *http.Response, error) AttachISO(ctx context.Context, instanceID, isoID string) (*http.Response, error) DetachISO(ctx context.Context, instanceID string) (*http.Response, error) @@ -159,6 +163,23 @@ type VPCInfo struct { IPAddress string `json:"ip_address"` } +type vpc2InfoBase struct { + VPCs []VPC2Info `json:"vpcs"` + Meta *Meta `json:"meta"` +} + +// VPC2Info information for a given instance. +type VPC2Info struct { + ID string `json:"id"` + MacAddress string `json:"mac_address"` + IPAddress string `json:"ip_address"` +} + +type AttachVPC2Req struct { + VPCID string `json:"vpc_id,omitempty"` + IPAddress *string `json:"ip_address,omitempty"` +} + type isoStatusBase struct { IsoStatus *Iso `json:"iso_status"` } @@ -253,6 +274,8 @@ type InstanceCreateReq struct { AttachPrivateNetwork []string `json:"attach_private_network,omitempty"` EnableVPC *bool `json:"enable_vpc,omitempty"` AttachVPC []string `json:"attach_vpc,omitempty"` + EnableVPC2 *bool `json:"enable_vpc2,omitempty"` + AttachVPC2 []string `json:"attach_vpc2,omitempty"` SSHKeys []string `json:"sshkey_id,omitempty"` Backups string `json:"backups,omitempty"` DDOSProtection *bool `json:"ddos_protection,omitempty"` @@ -281,6 +304,9 @@ type InstanceUpdateReq struct { EnableVPC *bool `json:"enable_vpc,omitempty"` AttachVPC []string `json:"attach_vpc,omitempty"` DetachVPC []string `json:"detach_vpc,omitempty"` + EnableVPC2 *bool `json:"enable_vpc2,omitempty"` + AttachVPC2 []string `json:"attach_vpc2,omitempty"` + DetachVPC2 []string `json:"detach_vpc2,omitempty"` Backups string `json:"backups,omitempty"` DDOSProtection *bool `json:"ddos_protection"` UserData string `json:"user_data,omitempty"` @@ -624,6 +650,57 @@ func (i *InstanceServiceHandler) DetachVPC(ctx context.Context, instanceID, vpcI return err } +// ListVPC2Info currently attached to an instance. +func (i *InstanceServiceHandler) ListVPC2Info(ctx context.Context, instanceID string, options *ListOptions) ([]VPC2Info, *Meta, *http.Response, error) { //nolint:lll,dupl + uri := fmt.Sprintf("%s/%s/vpc2", instancePath, instanceID) + req, err := i.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + vpcs := new(vpc2InfoBase) + resp, err := i.client.DoWithContext(ctx, req, vpcs) + if err != nil { + return nil, nil, resp, err + } + + return vpcs.VPCs, vpcs.Meta, resp, nil +} + +// AttachVPC2 to an instance +func (i *InstanceServiceHandler) AttachVPC2(ctx context.Context, instanceID string, vpc2Req *AttachVPC2Req) error { + uri := fmt.Sprintf("%s/%s/vpc2/attach", instancePath, instanceID) + + req, err := i.client.NewRequest(ctx, http.MethodPost, uri, vpc2Req) + if err != nil { + return err + } + + _, err = i.client.DoWithContext(ctx, req, nil) + return err +} + +// DetachVPC2 from an instance. +func (i *InstanceServiceHandler) DetachVPC2(ctx context.Context, instanceID, vpcID string) error { + uri := fmt.Sprintf("%s/%s/vpc2/detach", instancePath, instanceID) + body := RequestBody{"vpc_id": vpcID} + + req, err := i.client.NewRequest(ctx, http.MethodPost, uri, body) + if err != nil { + return err + } + + _, err = i.client.DoWithContext(ctx, req, nil) + return err +} + // ISOStatus retrieves the current ISO state for a given VPS. // The returned state may be one of: ready | isomounting | isomounted. func (i *InstanceServiceHandler) ISOStatus(ctx context.Context, instanceID string) (*Iso, *http.Response, error) { diff --git a/vendor/github.com/vultr/govultr/v3/network.go b/vendor/github.com/vultr/govultr/v3/network.go index 1b9a146a..cb6284a7 100644 --- a/vendor/github.com/vultr/govultr/v3/network.go +++ b/vendor/github.com/vultr/govultr/v3/network.go @@ -13,7 +13,7 @@ const netPath = "/v2/private-networks" // NetworkService is the interface to interact with the network endpoints on the Vultr API // Link : https://www.vultr.com/api/#tag/private-Networks // Deprecated: NetworkService should no longer be used. Instead, use VPCService. -type NetworkService interface { +type NetworkService interface { //nolint:dupl // Deprecated: NetworkService Create should no longer be used. Instead, use VPCService Create. Create(ctx context.Context, createReq *NetworkReq) (*Network, *http.Response, error) // Deprecated: NetworkService Get should no longer be used. Instead, use VPCService Get. diff --git a/vendor/github.com/vultr/govultr/v3/vpc.go b/vendor/github.com/vultr/govultr/v3/vpc.go index 1de69f80..83852cc0 100644 --- a/vendor/github.com/vultr/govultr/v3/vpc.go +++ b/vendor/github.com/vultr/govultr/v3/vpc.go @@ -12,7 +12,7 @@ const vpcPath = "/v2/vpcs" // VPCService is the interface to interact with the VPC endpoints on the Vultr API // Link : https://www.vultr.com/api/#tag/vpcs -type VPCService interface { +type VPCService interface { //nolint:dupl Create(ctx context.Context, createReq *VPCReq) (*VPC, *http.Response, error) Get(ctx context.Context, vpcID string) (*VPC, *http.Response, error) Update(ctx context.Context, vpcID string, description string) error diff --git a/vendor/github.com/vultr/govultr/v3/vpc2.go b/vendor/github.com/vultr/govultr/v3/vpc2.go new file mode 100644 index 00000000..670215ab --- /dev/null +++ b/vendor/github.com/vultr/govultr/v3/vpc2.go @@ -0,0 +1,135 @@ +package govultr + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +const vpc2Path = "/v2/vpc2" + +// VPC2Service is the interface to interact with the VPC 2.0 endpoints on the Vultr API +// Link : https://www.vultr.com/api/#tag/vpc2 +type VPC2Service interface { //nolint:dupl + Create(ctx context.Context, createReq *VPC2Req) (*VPC2, *http.Response, error) + Get(ctx context.Context, vpcID string) (*VPC2, *http.Response, error) + Update(ctx context.Context, vpcID string, description string) error + Delete(ctx context.Context, vpcID string) error + List(ctx context.Context, options *ListOptions) ([]VPC2, *Meta, *http.Response, error) +} + +// VPC2ServiceHandler handles interaction with the VPC 2.0 methods for the Vultr API +type VPC2ServiceHandler struct { + client *Client +} + +// VPC2 represents a Vultr VPC 2.0 +type VPC2 struct { + ID string `json:"id"` + Region string `json:"region"` + Description string `json:"description"` + IPBlock string `json:"ip_block"` + PrefixLength int `json:"prefix_length"` + DateCreated string `json:"date_created"` +} + +// VPC2Req represents parameters to create or update a VPC 2.0 resource +type VPC2Req struct { + Region string `json:"region"` + Description string `json:"description"` + IPType string `json:"ip_type"` + IPBlock string `json:"ip_block"` + PrefixLength int `json:"prefix_length"` +} + +type vpc2sBase struct { + VPCs []VPC2 `json:"vpcs"` + Meta *Meta `json:"meta"` +} + +type vpc2Base struct { + VPC *VPC2 `json:"vpc"` +} + +// Create creates a new VPC 2.0. A VPC 2.0 can only be used at the location for which it was created. +func (n *VPC2ServiceHandler) Create(ctx context.Context, createReq *VPC2Req) (*VPC2, *http.Response, error) { + req, err := n.client.NewRequest(ctx, http.MethodPost, vpc2Path, createReq) + if err != nil { + return nil, nil, err + } + + vpc := new(vpc2Base) + resp, err := n.client.DoWithContext(ctx, req, vpc) + if err != nil { + return nil, resp, err + } + + return vpc.VPC, resp, nil +} + +// Get gets the VPC 2.0 of the requested ID +func (n *VPC2ServiceHandler) Get(ctx context.Context, vpcID string) (*VPC2, *http.Response, error) { + uri := fmt.Sprintf("%s/%s", vpc2Path, vpcID) + req, err := n.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, err + } + + vpc := new(vpc2Base) + resp, err := n.client.DoWithContext(ctx, req, vpc) + if err != nil { + return nil, resp, err + } + + return vpc.VPC, resp, nil +} + +// Update updates a VPC 2.0 +func (n *VPC2ServiceHandler) Update(ctx context.Context, vpcID, description string) error { + uri := fmt.Sprintf("%s/%s", vpc2Path, vpcID) + + vpcReq := RequestBody{"description": description} + req, err := n.client.NewRequest(ctx, http.MethodPut, uri, vpcReq) + if err != nil { + return err + } + + _, err = n.client.DoWithContext(ctx, req, nil) + return err +} + +// Delete deletes a VPC 2.0. Before deleting, a VPC 2.0 must be disabled from all instances +func (n *VPC2ServiceHandler) Delete(ctx context.Context, vpcID string) error { + uri := fmt.Sprintf("%s/%s", vpc2Path, vpcID) + req, err := n.client.NewRequest(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + _, err = n.client.DoWithContext(ctx, req, nil) + return err +} + +// List lists all VPCs 2.0 on the current account +func (n *VPC2ServiceHandler) List(ctx context.Context, options *ListOptions) ([]VPC2, *Meta, *http.Response, error) { //nolint:dupl + req, err := n.client.NewRequest(ctx, http.MethodGet, vpc2Path, nil) + if err != nil { + return nil, nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + vpcs := new(vpc2sBase) + resp, err := n.client.DoWithContext(ctx, req, vpcs) + if err != nil { + return nil, nil, resp, err + } + + return vpcs.VPCs, vpcs.Meta, resp, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 414a2e24..4e9175e0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -227,7 +227,7 @@ github.com/vmihailenco/msgpack/v5/msgpcode github.com/vmihailenco/tagparser/v2 github.com/vmihailenco/tagparser/v2/internal github.com/vmihailenco/tagparser/v2/internal/parser -# github.com/vultr/govultr/v3 v3.2.0 +# github.com/vultr/govultr/v3 v3.3.0 ## explicit; go 1.20 github.com/vultr/govultr/v3 # github.com/zclconf/go-cty v1.13.2 diff --git a/vultr/data_source_vultr_bare_metal_server.go b/vultr/data_source_vultr_bare_metal_server.go index 776c4d94..f582ce1e 100644 --- a/vultr/data_source_vultr_bare_metal_server.go +++ b/vultr/data_source_vultr_bare_metal_server.go @@ -99,6 +99,11 @@ func dataSourceVultrBareMetalServer() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "vpc2_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -215,5 +220,14 @@ func dataSourceVultrBareMetalServerRead(ctx context.Context, d *schema.ResourceD return diag.Errorf("unable to set bare_metal_server `features` read value: %v", err) } + vpc2s, err := getBareMetalServerVPC2s(client, d.Id()) + if err != nil { + return diag.Errorf(err.Error()) + } + + if err := d.Set("vpc2_ids", vpc2s); err != nil { + return diag.Errorf("unable to set instance `vpc2_ids` read value: %v", err) + } + return nil } diff --git a/vultr/data_source_vultr_instance.go b/vultr/data_source_vultr_instance.go index 5100cfec..a27c53f3 100644 --- a/vultr/data_source_vultr_instance.go +++ b/vultr/data_source_vultr_instance.go @@ -146,6 +146,11 @@ func dataSourceVultrInstance() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "vpc2_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } @@ -303,12 +308,20 @@ func dataSourceVultrInstanceRead(ctx context.Context, d *schema.ResourceData, me return diag.Errorf(err.Error()) } + vpc2s, err := getVPC2s(client, d.Id()) + if err != nil { + return diag.Errorf(err.Error()) + } + if err := d.Set("private_network_ids", vpcs); err != nil { return diag.Errorf("unable to set instance `private_network_ids` read value: %v", err) } if err := d.Set("vpc_ids", vpcs); err != nil { return diag.Errorf("unable to set instance `vpc_ids` read value: %v", err) } + if err := d.Set("vpc2_ids", vpc2s); err != nil { + return diag.Errorf("unable to set instance `vpc2_ids` read value: %v", err) + } return nil } diff --git a/vultr/data_source_vultr_vpc2.go b/vultr/data_source_vultr_vpc2.go new file mode 100644 index 00000000..89432b8f --- /dev/null +++ b/vultr/data_source_vultr_vpc2.go @@ -0,0 +1,106 @@ +package vultr + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vultr/govultr/v3" +) + +func dataSourceVultrVPC2() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceVultrVPC2Read, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "region": { + Type: schema.TypeString, + Computed: true, + }, + "ip_block": { + Type: schema.TypeString, + Computed: true, + }, + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "date_created": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceVultrVPC2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Client).govultrClient() + + filters, filtersOk := d.GetOk("filter") + + if !filtersOk { + return diag.Errorf("issue with filter: %v", filtersOk) + } + + var vpcList []govultr.VPC2 + f := buildVultrDataSourceFilter(filters.(*schema.Set)) + options := &govultr.ListOptions{} + + for { + vpcs, meta, _, err := client.VPC2.List(ctx, options) + if err != nil { + return diag.Errorf("error getting VPCs 2.0: %v", err) + } + + for _, n := range vpcs { + // we need convert the a struct INTO a map so we can easily manipulate the data here + sm, err := structToMap(n) + + if err != nil { + return diag.FromErr(err) + } + + if filterLoop(f, sm) { + vpcList = append(vpcList, n) + } + } + + if meta.Links.Next == "" { + break + } else { + options.Cursor = meta.Links.Next + continue + } + } + + if len(vpcList) > 1 { + return diag.Errorf("your search returned too many results. Please refine your search to be more specific") + } + + if len(vpcList) < 1 { + return diag.Errorf("no results were found") + } + + d.SetId(vpcList[0].ID) + if err := d.Set("region", vpcList[0].Region); err != nil { + return diag.Errorf("unable to set vpc2 `region` read value: %v", err) + } + if err := d.Set("description", vpcList[0].Description); err != nil { + return diag.Errorf("unable to set vpc2 `description` read value: %v", err) + } + if err := d.Set("date_created", vpcList[0].DateCreated); err != nil { + return diag.Errorf("unable to set vpc2 `date_created` read value: %v", err) + } + if err := d.Set("ip_block", vpcList[0].IPBlock); err != nil { + return diag.Errorf("unable to set vpc2 `ip_block` read value: %v", err) + } + if err := d.Set("prefix_length", vpcList[0].PrefixLength); err != nil { + return diag.Errorf("unable to set vpc2 `prefix_length` read value: %v", err) + } + + return nil +} diff --git a/vultr/data_source_vultr_vpc2_test.go b/vultr/data_source_vultr_vpc2_test.go new file mode 100644 index 00000000..791f1184 --- /dev/null +++ b/vultr/data_source_vultr_vpc2_test.go @@ -0,0 +1,46 @@ +package vultr + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceVultrVPC2(t *testing.T) { + rDesc := acctest.RandomWithPrefix("tf-vpc2-ds") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckVultrVPC2Destroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVultrVPC2Config(rDesc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.vultr_vpc2.my_vpc2", "description", rDesc), + resource.TestCheckResourceAttrSet("data.vultr_vpc2.my_vpc2", "date_created"), + resource.TestCheckResourceAttrSet("data.vultr_vpc2.my_vpc2", "region"), + resource.TestCheckResourceAttrSet("data.vultr_vpc2.my_vpc2", "ip_block"), + resource.TestCheckResourceAttrSet("data.vultr_vpc2.my_vpc2", "prefix_length"), + ), + }, + }, + }) +} + +func testAccDataSourceVultrVPC2Config(description string) string { + return fmt.Sprintf(` + resource "vultr_vpc2" "foo" { + region = "ewr" + description = "%s" + } + + data "vultr_vpc2" "my_vpc2" { + filter { + name = "description" + values = ["${vultr_vpc2.foo.description}"] + } + }`, description) +} diff --git a/vultr/instances.go b/vultr/instances.go index b6efa38f..0afdd1b6 100644 --- a/vultr/instances.go +++ b/vultr/instances.go @@ -31,3 +31,43 @@ func getVPCs(client *govultr.Client, instanceID string) ([]string, error) { } return vpcs, nil } + +func getVPC2s(client *govultr.Client, instanceID string) ([]string, error) { + options := &govultr.ListOptions{} + var vpcs []string + for { + vpcInfo, meta, _, err := client.Instance.ListVPC2Info(context.Background(), instanceID, options) + if err != nil { + return nil, fmt.Errorf("error getting list of attached VPCs 2.0: %v", err) + } + + if len(vpcInfo) == 0 { + break + } + + for _, v := range vpcInfo { + vpcs = append(vpcs, v.ID) + } + + if meta.Links.Next == "" { + break + } + options.Cursor = meta.Links.Next + } + return vpcs, nil +} + +func getBareMetalServerVPC2s(client *govultr.Client, serverID string) ([]string, error) { + var vpcs []string + + vpcInfo, _, err := client.BareMetalServer.ListVPC2Info(context.Background(), serverID) + if err != nil { + return nil, fmt.Errorf("error getting list of attached VPCs 2.0: %v", err) + } + + for _, v := range vpcInfo { + vpcs = append(vpcs, v.ID) + } + + return vpcs, nil +} diff --git a/vultr/provider.go b/vultr/provider.go index 7de0d975..704691fc 100644 --- a/vultr/provider.go +++ b/vultr/provider.go @@ -56,6 +56,7 @@ func Provider() *schema.Provider { "vultr_startup_script": dataSourceVultrStartupScript(), "vultr_user": dataSourceVultrUser(), "vultr_vpc": dataSourceVultrVPC(), + "vultr_vpc2": dataSourceVultrVPC2(), }, ResourcesMap: map[string]*schema.Resource{ @@ -87,6 +88,7 @@ func Provider() *schema.Provider { "vultr_startup_script": resourceVultrStartupScript(), "vultr_user": resourceVultrUsers(), "vultr_vpc": resourceVultrVPC(), + "vultr_vpc2": resourceVultrVPC2(), }, ConfigureFunc: providerConfigure, diff --git a/vultr/resource_vultr_bare_metal_server.go b/vultr/resource_vultr_bare_metal_server.go index ce17e536..3195bc11 100644 --- a/vultr/resource_vultr_bare_metal_server.go +++ b/vultr/resource_vultr_bare_metal_server.go @@ -64,6 +64,11 @@ func resourceVultrBareMetalServer() *schema.Resource { Optional: true, ForceNew: true, }, + "vpc2_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "ssh_key_ids": { Type: schema.TypeList, Optional: true, @@ -222,6 +227,12 @@ func resourceVultrBareMetalServerCreate(ctx context.Context, d *schema.ResourceD } } + if vpcIDs, vpcOK := d.GetOk("vpc2_ids"); vpcOK { + for _, v := range vpcIDs.(*schema.Set).List() { + req.AttachVPC2 = append(req.AttachVPC2, v.(string)) + } + } + client := meta.(*Client).govultrClient() bm, _, err := client.BareMetalServer.Create(ctx, req) @@ -317,6 +328,17 @@ func resourceVultrBareMetalServerRead(ctx context.Context, d *schema.ResourceDat return diag.Errorf("unable to set resource bare_metal_server `v6_network_size` read value: %v", err) } + vpc2s, err := getBareMetalServerVPC2s(client, d.Id()) + if err != nil { + return diag.Errorf(err.Error()) + } + + if _, vpcUpdate := d.GetOk("vpc2_ids"); vpcUpdate { + if err := d.Set("vpc2_ids", vpc2s); err != nil { + return diag.Errorf("unable to set resource instance `vpc2_ids` read value: %v", err) + } + } + return nil } @@ -345,6 +367,24 @@ func resourceVultrBareMetalServerUpdate(ctx context.Context, d *schema.ResourceD req.OsID = osID } + if d.HasChange("vpc2_ids") { + log.Printf("[INFO] Updating vpc2_ids") + oldVPC, newVPC := d.GetChange("vpc2_ids") + + var oldIDs []string + for _, v := range oldVPC.(*schema.Set).List() { + oldIDs = append(oldIDs, v.(string)) + } + + var newIDs []string + for _, v := range newVPC.(*schema.Set).List() { + newIDs = append(newIDs, v.(string)) + } + + req.AttachVPC2 = append(req.AttachVPC2, diffSlice(oldIDs, newIDs)...) + req.DetachVPC2 = append(req.DetachVPC2, diffSlice(newIDs, oldIDs)...) + } + if d.HasChange("tags") { _, newTags := tfChangeToSlices("tags", d) req.Tags = newTags @@ -361,6 +401,18 @@ func resourceVultrBareMetalServerDelete(ctx context.Context, d *schema.ResourceD client := meta.(*Client).govultrClient() log.Printf("[INFO] Deleting bare metal server: %s", d.Id()) + + if vpcIDs, vpcOK := d.GetOk("vpc2_ids"); vpcOK { + detach := &govultr.InstanceUpdateReq{} + for _, v := range vpcIDs.(*schema.Set).List() { + detach.DetachVPC2 = append(detach.DetachVPC2, v.(string)) + } + + if _, _, err := client.Instance.Update(ctx, d.Id(), detach); err != nil { + return diag.Errorf("error detaching VPCs 2.0 prior to deleting instance %s : %v", d.Id(), err) + } + } + if err := client.BareMetalServer.Delete(ctx, d.Id()); err != nil { return diag.Errorf("error deleting bare metal server (%s): %v", d.Id(), err) } diff --git a/vultr/resource_vultr_bare_metal_server_test.go b/vultr/resource_vultr_bare_metal_server_test.go index 99118007..65083158 100644 --- a/vultr/resource_vultr_bare_metal_server_test.go +++ b/vultr/resource_vultr_bare_metal_server_test.go @@ -65,6 +65,7 @@ func TestAccVultrBareMetalServerBasic(t *testing.T) { resource.TestCheckResourceAttrSet("vultr_bare_metal_server.foo", "plan"), resource.TestCheckResourceAttrSet("vultr_bare_metal_server.foo", "label"), resource.TestCheckResourceAttr("vultr_bare_metal_server.foo", "tags.#", "2"), + resource.TestCheckResourceAttr("vultr_bare_metal_server.foo", "vpc2_ids.#", "1"), resource.TestCheckResourceAttrSet("vultr_bare_metal_server.foo", "os_id"), resource.TestCheckResourceAttrSet("vultr_bare_metal_server.foo", "app_id"), ), @@ -139,6 +140,13 @@ func testAccVultrBareMetalServerConfigBasic(rInt int, rSSH, rName string) string func testAccVultrBareMetalServerConfigUpdate(rInt int, rSSH, rName string) string { return testAccVultrSSHKeyConfigBasic(rInt, rSSH) + testAccVultrStartupScriptConfigBasic(rName) + fmt.Sprintf(` + resource "vultr_vpc2" "foo" { + region = "ewr" + description = "foo" + ip_block = "10.0.0.0" + prefix_length = "24" + } + resource "vultr_bare_metal_server" "foo" { region = "ewr" os_id = 1946 @@ -150,6 +158,7 @@ func testAccVultrBareMetalServerConfigUpdate(rInt int, rSSH, rName string) strin label = "%s-update" hostname = "%s" tags = [ "test tag", "another tag" ] + vpc2_ids = ["${vultr_vpc2.foo.id}"] } `, rName, rName) } diff --git a/vultr/resource_vultr_instance.go b/vultr/resource_vultr_instance.go index 3ebc9ddc..1a2eb702 100644 --- a/vultr/resource_vultr_instance.go +++ b/vultr/resource_vultr_instance.go @@ -85,6 +85,11 @@ func resourceVultrInstance() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "vpc2_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "label": { Type: schema.TypeString, Computed: true, @@ -346,6 +351,12 @@ func resourceVultrInstanceCreate(ctx context.Context, d *schema.ResourceData, me } } + if vpcIDs, vpcOK := d.GetOk("vpc2_ids"); vpcOK { + for _, v := range vpcIDs.(*schema.Set).List() { + req.AttachVPC2 = append(req.AttachVPC2, v.(string)) + } + } + if sshKeyIDs, sshKeyOK := d.GetOk("ssh_key_ids"); sshKeyOK { for _, v := range sshKeyIDs.([]interface{}) { req.SSHKeys = append(req.SSHKeys, v.(string)) @@ -520,6 +531,11 @@ func resourceVultrInstanceRead(ctx context.Context, d *schema.ResourceData, meta return diag.Errorf(err.Error()) } + vpc2s, err := getVPC2s(client, d.Id()) + if err != nil { + return diag.Errorf(err.Error()) + } + // Manipulate the read state so that, depending on which value was passed, // only one of these values is populated when a VPC or PN is defined for // the instance @@ -543,6 +559,12 @@ func resourceVultrInstanceRead(ctx context.Context, d *schema.ResourceData, meta } } + if _, vpcUpdate := d.GetOk("vpc2_ids"); vpcUpdate { + if err := d.Set("vpc2_ids", vpc2s); err != nil { + return diag.Errorf("unable to set resource instance `vpc2_ids` read value: %v", err) + } + } + return nil } func resourceVultrInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -622,6 +644,24 @@ func resourceVultrInstanceUpdate(ctx context.Context, d *schema.ResourceData, me req.DetachVPC = append(req.DetachVPC, diffSlice(newIDs, oldIDs)...) } + if d.HasChange("vpc2_ids") { + log.Printf("[INFO] Updating vpc2_ids") + oldVPC, newVPC := d.GetChange("vpc2_ids") + + var oldIDs []string + for _, v := range oldVPC.(*schema.Set).List() { + oldIDs = append(oldIDs, v.(string)) + } + + var newIDs []string + for _, v := range newVPC.(*schema.Set).List() { + newIDs = append(newIDs, v.(string)) + } + + req.AttachVPC2 = append(req.AttachVPC2, diffSlice(oldIDs, newIDs)...) + req.DetachVPC2 = append(req.DetachVPC2, diffSlice(newIDs, oldIDs)...) + } + if d.HasChange("tags") { _, newTags := tfChangeToSlices("tags", d) req.Tags = newTags @@ -698,6 +738,17 @@ func resourceVultrInstanceDelete(ctx context.Context, d *schema.ResourceData, me } } + if vpcIDs, vpcOK := d.GetOk("vpc2_ids"); vpcOK { + detach := &govultr.InstanceUpdateReq{} + for _, v := range vpcIDs.(*schema.Set).List() { + detach.DetachVPC2 = append(detach.DetachVPC2, v.(string)) + } + + if _, _, err := client.Instance.Update(ctx, d.Id(), detach); err != nil { + return diag.Errorf("error detaching VPCs 2.0 prior to deleting instance %s : %v", d.Id(), err) + } + } + if _, isoOK := d.GetOk("iso_id"); isoOK { if _, err := client.Instance.DetachISO(ctx, d.Id()); err != nil { return diag.Errorf("error detaching ISO prior to deleting instance %s : %v", d.Id(), err) diff --git a/vultr/resource_vultr_instance_test.go b/vultr/resource_vultr_instance_test.go index 322d35af..550cacf3 100644 --- a/vultr/resource_vultr_instance_test.go +++ b/vultr/resource_vultr_instance_test.go @@ -168,6 +168,45 @@ func TestAccVultrInstanceUpdateVPCIDs(t *testing.T) { }) } +func TestAccVultrInstanceUpdateVPC2IDs(t *testing.T) { + t.Parallel() + rName := acctest.RandomWithPrefix("tf-vps-rs-upnid") + + name := "vultr_instance.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVultrInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVultrInstanceBase(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "label", rName), + resource.TestCheckResourceAttr(name, "os", "CentOS 7 x64"), + resource.TestCheckResourceAttr(name, "os_id", "167"), + resource.TestCheckResourceAttr(name, "status", "active"), + resource.TestCheckResourceAttr(name, "power_status", "running"), + resource.TestCheckResourceAttr(name, "region", "sea"), + resource.TestCheckResourceAttr(name, "tags.#", "2"), + ), + }, + { + Config: testAccVultrInstanceBaseUpdateVPC2IDs(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "label", rName), + resource.TestCheckResourceAttr(name, "os", "CentOS 7 x64"), + resource.TestCheckResourceAttr(name, "os_id", "167"), + resource.TestCheckResourceAttr(name, "status", "active"), + resource.TestCheckResourceAttr(name, "power_status", "running"), + resource.TestCheckResourceAttr(name, "region", "sea"), + resource.TestCheckResourceAttr(name, "tags.#", "2"), + resource.TestCheckResourceAttr(name, "vpc2_ids.#", "2"), + ), + }, + }, + }) +} + func TestAccVultrInstanceUpdateTags(t *testing.T) { t.Parallel() rName := acctest.RandomWithPrefix("tf-vps-rs-upnid") @@ -299,6 +338,37 @@ func testAccVultrInstanceBaseUpdateVPCIDs(name string) string { `, name) } +func testAccVultrInstanceBaseUpdateVPC2IDs(name string) string { + return fmt.Sprintf(` + resource "vultr_vpc2" "foo" { + region = "sea" + description = "foo" + ip_block = "10.0.0.0" + prefix_length = "24" + } + + resource "vultr_vpc2" "bar" { + region = "sea" + description = "bar" + ip_block = "10.0.0.0" + prefix_length = "24" + } + + resource "vultr_instance" "test" { + plan = "vc2-1c-2gb" + region = "sea" + os_id = 167 + label = "%s" + hostname = "testing-the-hostname" + enable_ipv6 = true + activation_email = false + ddos_protection = true + tags = [ "test tag", "another test" ] + vpc2_ids = ["${vultr_vpc2.foo.id}","${vultr_vpc2.bar.id}"] + } + `, name) +} + func testAccVultrInstanceBaseUpdatedRegion(name string) string { return fmt.Sprintf(` resource "vultr_instance" "test" { diff --git a/vultr/resource_vultr_vpc2.go b/vultr/resource_vultr_vpc2.go new file mode 100644 index 00000000..28f34bb4 --- /dev/null +++ b/vultr/resource_vultr_vpc2.go @@ -0,0 +1,151 @@ +package vultr + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vultr/govultr/v3" +) + +func resourceVultrVPC2() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceVultrVPC2Create, + ReadContext: resourceVultrVPC2Read, + UpdateContext: resourceVultrVPC2Update, + DeleteContext: resourceVultrVPC2Delete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: IgnoreCase, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "ip_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "ip_block": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsIPv4Address, + }, + "prefix_length": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "date_created": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceVultrVPC2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Client).govultrClient() + + vpcReq := &govultr.VPC2Req{ + Region: d.Get("region").(string), + Description: d.Get("description").(string), + IPType: d.Get("ip_type").(string), + IPBlock: d.Get("ip_block").(string), + PrefixLength: d.Get("prefix_length").(int), + } + + vpc, _, err := client.VPC2.Create(ctx, vpcReq) + if err != nil { + return diag.Errorf("error creating VPC 2.0: %v", err) + } + + d.SetId(vpc.ID) + log.Printf("[INFO] VPC 2.0 ID: %s", d.Id()) + + return resourceVultrVPC2Read(ctx, d, meta) +} + +func resourceVultrVPC2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Client).govultrClient() + + vpc, _, err := client.VPC2.Get(ctx, d.Id()) + if err != nil { + if strings.Contains(err.Error(), "Invalid VPC 2.0 ID") { + log.Printf("[WARN] Vultr VPC 2.0 (%s) not found", d.Id()) + d.SetId("") + return nil + } + return diag.Errorf("error getting VPC 2.0: %v", err) + } + + if err := d.Set("region", vpc.Region); err != nil { + return diag.Errorf("unable to set resource vpc2 `region` read value: %v", err) + } + if err := d.Set("description", vpc.Description); err != nil { + return diag.Errorf("unable to set resource vpc2 `description` read value: %v", err) + } + if err := d.Set("ip_block", vpc.IPBlock); err != nil { + return diag.Errorf("unable to set resource vpc2 `ip_block` read value: %v", err) + } + if err := d.Set("prefix_length", vpc.PrefixLength); err != nil { + return diag.Errorf("unable to set resource vpc2 `prefix_length` read value: %v", err) + } + if err := d.Set("date_created", vpc.DateCreated); err != nil { + return diag.Errorf("unable to set resource vpc2 `date_created` read value: %v", err) + } + + return nil +} + +func resourceVultrVPC2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Client).govultrClient() + + if err := client.VPC2.Update(ctx, d.Id(), d.Get("description").(string)); err != nil { + return diag.Errorf("error updating VPC 2.0: %v", err) + } + + return resourceVultrVPC2Read(ctx, d, meta) +} + +func resourceVultrVPC2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Client).govultrClient() + + log.Printf("[INFO] Deleting VPC 2.0: %s", d.Id()) + + retryErr := retry.RetryContext(ctx, d.Timeout(schema.TimeoutDelete)-time.Minute, func() *retry.RetryError { + err := client.VPC2.Delete(ctx, d.Id()) + + if err == nil { + return nil + } + + if strings.Contains(err.Error(), "VPC 2.0 is attached") { + return retry.RetryableError(fmt.Errorf("cannot remove attached VPC 2.0: %s", err.Error())) + } + + return retry.NonRetryableError(err) + }) + + if retryErr != nil { + return diag.Errorf("error destroying VPC 2.0 (%s): %v", d.Id(), retryErr) + } + + return nil +} diff --git a/vultr/resource_vultr_vpc2_test.go b/vultr/resource_vultr_vpc2_test.go new file mode 100644 index 00000000..919397a5 --- /dev/null +++ b/vultr/resource_vultr_vpc2_test.go @@ -0,0 +1,114 @@ +package vultr + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVultrVPC2(t *testing.T) { + rDesc := acctest.RandomWithPrefix("tf-vpc2-rs-nocdir") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckVultrVPC2Destroy, + Steps: []resource.TestStep{ + { + Config: testAccVultrVPC2ConfigBase(rDesc), + Check: resource.ComposeTestCheckFunc( + testAccCheckVultrVPC2Exists("vultr_vpc2.foo"), + resource.TestCheckResourceAttr("vultr_vpc2.foo", "description", rDesc), + resource.TestCheckResourceAttrSet("vultr_vpc2.foo", "date_created"), + resource.TestCheckResourceAttrSet("vultr_vpc2.foo", "ip_block"), + ), + }, + }, + }) +} + +func TestAccVultrVPC2WithSubnet(t *testing.T) { + rDesc := acctest.RandomWithPrefix("tf-vpc2-rs-subnet") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckVultrVPC2Destroy, + Steps: []resource.TestStep{ + { + Config: testAccVultrVPC2ConfigWithSubnet(rDesc), + Check: resource.ComposeTestCheckFunc( + testAccCheckVultrVPC2Exists("vultr_vpc2.foo"), + resource.TestCheckResourceAttr("vultr_vpc2.foo", "description", rDesc), + resource.TestCheckResourceAttr("vultr_vpc2.foo", "ip_block", "10.0.0.0"), + resource.TestCheckResourceAttrSet("vultr_vpc2.foo", "date_created"), + ), + }, + }, + }) +} + +func testAccCheckVultrVPC2Destroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "vultr_vpc2" { + continue + } + + vpc2ID := rs.Primary.ID + client := testAccProvider.Meta().(*Client).govultrClient() + + _, _, err := client.VPC2.Get(context.Background(), vpc2ID) + if err == nil { + return fmt.Errorf("vpc 2.0 still exists: %s", vpc2ID) + } + } + return nil +} + +func testAccCheckVultrVPC2Exists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("VPC 2.0 ID is not set") + } + + vpc2ID := rs.Primary.ID + client := testAccProvider.Meta().(*Client).govultrClient() + + _, _, err := client.VPC2.Get(context.Background(), vpc2ID) + if err != nil { + return fmt.Errorf("VPC 2.0 does not exist: %s", vpc2ID) + } + + return nil + } +} + +func testAccVultrVPC2ConfigBase(rDesc string) string { + return fmt.Sprintf(` + resource "vultr_vpc2" "foo" { + region = "atl" + description = "%s" + } + `, rDesc) +} + +func testAccVultrVPC2ConfigWithSubnet(rDesc string) string { + return fmt.Sprintf(` + resource "vultr_vpc2" "foo" { + region = "atl" + description = "%s" + ip_type = "v4" + ip_block = "10.0.0.0" + prefix_length = 24 + } + `, rDesc) +} diff --git a/website/docs/d/bare_metal_server.html.markdown b/website/docs/d/bare_metal_server.html.markdown index b84d4ab9..757521ea 100644 --- a/website/docs/d/bare_metal_server.html.markdown +++ b/website/docs/d/bare_metal_server.html.markdown @@ -58,3 +58,4 @@ The following attributes are exported: * `os_id` - The server's operating system ID. * `app_id` - The server's application ID. * `image_id` - The Marketplace ID for this application. +* `vpc2_ids` - A list of VPC 2.0 IDs attached to the server. diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index 9ce4d7ef..285e87db 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -67,3 +67,4 @@ The following attributes are exported: * `features` - Array of which features are enabled. * `backups_schedule` - The current configuration for backups * `hostname` - The hostname assigned to the server. +* `vpc2_ids` - A list of VPC 2.0 IDs attached to the server. diff --git a/website/docs/d/vpc2.html.markdown b/website/docs/d/vpc2.html.markdown new file mode 100644 index 00000000..9bce5b9a --- /dev/null +++ b/website/docs/d/vpc2.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "vultr" +page_title: "Vultr: vultr_vpc2" +sidebar_current: "docs-vultr-datasource-vpc2" +description: |- + Get information about a Vultr VPC 2.0. +--- + +# vultr_vpc2 + +Get information about a Vultr VPC 2.0. + +## Example Usage + +Get the information for a VPC 2.0 by `description`: + +```hcl +data "vultr_vpc2" "my_vpc2" { + filter { + name = "description" + values = ["my-vpc2-description"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `filter` - (Required) Query parameters for finding VPCs 2.0. + +The `filter` block supports the following: + +* `name` - Attribute name to filter with. +* `values` - One or more values filter with. + +## Attributes Reference + +The following attributes are exported: + +* `region` - The ID of the region that the VPC 2.0 is in. +* `ip_block` - The IPv4 network address. For example: 10.1.1.0. +* `prefix_length` - The number of bits for the netmask in CIDR notation. Example: 20 +* `description` - The VPC 2.0's description. +* `date_created` - The date the VPC 2.0 was added to your Vultr account. diff --git a/website/docs/r/bare_metal_server.html.markdown b/website/docs/r/bare_metal_server.html.markdown index bf4b18fc..cfdde149 100644 --- a/website/docs/r/bare_metal_server.html.markdown +++ b/website/docs/r/bare_metal_server.html.markdown @@ -49,6 +49,7 @@ The following arguments are supported: * `image_id` - (Optional) The ID of the Vultr marketplace application to be installed on the server. [See List Applications](https://www.vultr.com/api/#operation/list-applications) Note marketplace applications are denoted by type: `marketplace` and you must use the `image_id` not the id. * `snapshot_id` - (Optional) The ID of the Vultr snapshot that the server will restore for the initial installation. [See List Snapshots](https://www.vultr.com/api/#operation/list-snapshots) * `script_id` - (Optional) The ID of the startup script you want added to the server. +* `vpc2_ids` - (Optional) A list of VPC 2.0 IDs to be attached to the server. * `ssh_key_ids` - (Optional) A list of SSH key IDs to apply to the server on install (only valid for Linux/FreeBSD). * `user_data` - (Optional) Generic data store, which some provisioning tools and cloud operating systems use as a configuration file. It is generally consumed only once after an instance has been launched, but individual needs may vary. * `enable_ipv6` - (Optional) Whether the server has IPv6 networking activated. @@ -84,6 +85,7 @@ The following attributes are exported: * `app_id` - The ID of the Vultr marketplace application installed on the server. * `snapshot_id` - The ID of the Vultr snapshot that the server was restored from. * `script_id` - The ID of the startup script that was added to the server. +* `vpc2_ids` - A list of VPC 2.0 IDs to be attached to the server. * `ssh_key_ids` - A list of SSH key IDs applied to the server on install. * `user_data` - Generic data store, which some provisioning tools and cloud operating systems use as a configuration file. It is generally consumed only once after an instance has been launched, but individual needs may vary. * `enable_ipv6` - Whether the server has IPv6 networking activated. diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index 40cce343..3450140e 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -60,6 +60,7 @@ The following arguments are supported: * `firewall_group_id` - (Optional) The ID of the firewall group to assign to the server. * `private_network_ids` - (Optional) (Deprecated: use `vpc_ids` instead) A list of private network IDs to be attached to the server. * `vpc_ids` - (Optional) A list of VPC IDs to be attached to the server. +* `vpc2_ids` - (Optional) A list of VPC 2.0 IDs to be attached to the server. * `ssh_key_ids` - (Optional) A list of SSH key IDs to apply to the server on install (only valid for Linux/FreeBSD). * `user_data` - (Optional) Generic data store, which some provisioning tools and cloud operating systems use as a configuration file. It is generally consumed only once after an instance has been launched, but individual needs may vary. * `backups` - (Optional) Whether automatic backups will be enabled for this server (these have an extra charge associated with them). Values can be enabled or disabled. @@ -114,6 +115,7 @@ The following attributes are exported: * `firewall_group_id` - The ID of the firewall group assigned to the server. * `private_network_ids` - (Deprecated: Use `vpc_ids` instead) A list of private network IDs attached to the server. * `vpc_ids` - A list of VPC IDs attached to the server. +* `vpc2_ids` - A list of VPC 2.0 IDs attached to the server. * `ssh_key_ids` - A list of SSH key IDs applied to the server on install. * `user_data` - Generic data store, which some provisioning tools and cloud operating systems use as a configuration file. It is generally consumed only once after an instance has been launched, but individual needs may vary. * `backups` - Whether automatic backups are enabled for this server. diff --git a/website/docs/r/vpc2.html.markdown b/website/docs/r/vpc2.html.markdown new file mode 100644 index 00000000..cd1cb023 --- /dev/null +++ b/website/docs/r/vpc2.html.markdown @@ -0,0 +1,62 @@ +--- +layout: "vultr" +page_title: "Vultr: vultr_vpc2" +sidebar_current: "docs-vultr-resource-vpc2" +description: |- + Provides a Vultr VPC 2.0 resource. This can be used to create, read, and delete VPCs 2.0 on your Vultr account. +--- + +# vultr_vpc2 + +Provides a Vultr VPC 2.0 resource. This can be used to create, read, and delete VPCs 2.0 on your Vultr account. + +## Example Usage + +Create a new VPC 2.0 with an automatically generated CIDR block: + +```hcl +resource "vultr_vpc2" "my_vpc2" { + description = "my vpc2" + region = "ewr" +} +``` + +Create a new VPC 2.0 with a specified CIDR block: + +```hcl +resource "vultr_vpc2" "my_vpc2" { + description = "my private vpc2" + region = "ewr" + ip_block = "10.0.0.0" + prefix_length = 24 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region ID that you want the VPC 2.0 to be created in. +* `description` - (Optional) The description you want to give your VPC 2.0. +* `ip_type` - (Optional) Accepted values: `v4`. +* `ip_block` - (Optional) The IPv4 subnet to be used when attaching instances to this VPC 2.0. +* `prefix_length` - The number of bits for the netmask in CIDR notation. Example: 32 + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID of the VPC 2.0. +* `region` - The region ID that the VPC 2.0 operates in. +* `description` - The description of the VPC 2.0. +* `ip_block` - The IPv4 subnet used when attaching instances to this VPC 2.0. +* `prefix_length` - The number of bits for the netmask in CIDR notation. Example: 32 +* `date_created` - The date that the VPC 2.0 was added to your Vultr account. + +## Import + +VPCs 2.0 can be imported using the VPC 2.0 `ID`, e.g. + +``` +terraform import vultr_vpc2.my_vpc2 0e04f918-575e-41cb-86f6-d729b354a5a1 +``` diff --git a/website/vultr.erb b/website/vultr.erb index dab5de6d..5a3badd3 100644 --- a/website/vultr.erb +++ b/website/vultr.erb @@ -58,6 +58,9 @@ > vultr_vpc + > + vultr_vpc2 + > vultr_object_storage @@ -139,6 +142,9 @@ > vultr_vpc + > + vultr_vpc2 + > vultr_object_storage