diff --git a/selvpcclient/resell/v2/subnets/doc.go b/selvpcclient/resell/v2/subnets/doc.go index 6c3bf64..0dae5a8 100644 --- a/selvpcclient/resell/v2/subnets/doc.go +++ b/selvpcclient/resell/v2/subnets/doc.go @@ -19,5 +19,25 @@ Example of getting all subnets for _, subnet := range allSubnet { fmt.Println(subnet) } + +Example of creating subnets + + createOpts := subnets.SubnetOpts{ + Subnets: []subnets.SubnetOpt{ + { + Region: "ru-3", + Type: selvpcclient.IPv4, + PrefixLength: 29, + Quantity: 1, + }, + }, + } + newSubnets, _, err := subnets.Create(ctx, resellClient, projectID, createOpts) + if err != nil { + log.Fatal(err) + } + for _, newSubnet := range newSubnets { + fmt.Printf("%v\n", newSubnet) + } */ package subnets diff --git a/selvpcclient/resell/v2/subnets/request_opts.go b/selvpcclient/resell/v2/subnets/request_opts.go new file mode 100644 index 0000000..0ce00a7 --- /dev/null +++ b/selvpcclient/resell/v2/subnets/request_opts.go @@ -0,0 +1,24 @@ +package subnets + +import "github.com/selectel/go-selvpcclient/selvpcclient" + +// SubnetOpts represents options for the subnets Create request. +type SubnetOpts struct { + // Subnets represents options for all subnets. + Subnets []SubnetOpt `json:"subnets"` +} + +// SubnetOpt represents options for the single subnet. +type SubnetOpt struct { + // Region represents a region of where the subnet should reside. + Region string `json:"region"` + + // Quantity represents how many subnets do we need to create. + Quantity int `json:"quantity"` + + // Type represents ip version type. + Type selvpcclient.IPVersion `json:"type"` + + // PrefixLength represents length of the subnet prefix. + PrefixLength int `json:"prefix_length"` +} diff --git a/selvpcclient/resell/v2/subnets/requests.go b/selvpcclient/resell/v2/subnets/requests.go index 3330646..4ec5e4b 100644 --- a/selvpcclient/resell/v2/subnets/requests.go +++ b/selvpcclient/resell/v2/subnets/requests.go @@ -1,7 +1,9 @@ package subnets import ( + "bytes" "context" + "encoding/json" "strings" "github.com/selectel/go-selvpcclient/selvpcclient" @@ -54,3 +56,32 @@ func List(ctx context.Context, client *selvpcclient.ServiceClient) ([]*Subnet, * return result.Subnets, responseResult, nil } + +// Create requests a creation of the subnets in the specified project. +func Create(ctx context.Context, client *selvpcclient.ServiceClient, projectID string, createOpts SubnetOpts) ([]*Subnet, *selvpcclient.ResponseResult, error) { + createSubnetsOpts := &createOpts + requestBody, err := json.Marshal(createSubnetsOpts) + if err != nil { + return nil, nil, err + } + + url := strings.Join([]string{client.Endpoint, resourceURL, "projects", projectID}, "/") + responseResult, err := client.DoRequest(ctx, "POST", url, bytes.NewReader(requestBody)) + if err != nil { + return nil, nil, err + } + if responseResult.Err != nil { + return nil, responseResult, responseResult.Err + } + + // Extract subnets from the response body. + var result struct { + Subnets []*Subnet `json:"subnets"` + } + err = responseResult.ExtractResult(&result) + if err != nil { + return nil, responseResult, err + } + + return result.Subnets, responseResult, nil +} diff --git a/selvpcclient/resell/v2/subnets/testing/fixtures.go b/selvpcclient/resell/v2/subnets/testing/fixtures.go index 0fe3b82..fc4390f 100644 --- a/selvpcclient/resell/v2/subnets/testing/fixtures.go +++ b/selvpcclient/resell/v2/subnets/testing/fixtures.go @@ -3,6 +3,7 @@ package testing import ( "time" + "github.com/selectel/go-selvpcclient/selvpcclient" "github.com/selectel/go-selvpcclient/selvpcclient/resell/v2/servers" "github.com/selectel/go-selvpcclient/selvpcclient/resell/v2/subnets" ) @@ -98,3 +99,81 @@ var TestListSubnetsSingleResponse = []*subnets.Subnet{ Status: "ACTIVE", }, } + +// TestCreateSubnetsOptsRaw represents marshalled options for the Create request. +const TestCreateSubnetsOptsRaw = ` +{ + "subnets": [ + { + "region": "ru-2", + "type": "ipv4", + "quantity": 1, + "prefix_length": 29 + }, + { + "region": "ru-1", + "type": "ipv4", + "quantity": 1, + "prefix_length": 29 + } + ] +} +` + +// TestCreateSubnetsOpts represent options for the Create request. +var TestCreateSubnetsOpts = subnets.SubnetOpts{ + Subnets: []subnets.SubnetOpt{ + { + Type: selvpcclient.IPv4, + PrefixLength: 29, + Region: "ru-2", + Quantity: 1, + }, + { + Type: selvpcclient.IPv4, + PrefixLength: 29, + Region: "ru-1", + Quantity: 1, + }, + }, +} + +// TestCreateSubnetsResponseRaw represents a raw response from the Create request. +const TestCreateSubnetsResponseRaw = ` +{ + "subnets": [ + { + "cidr": "203.0.113.0/29", + "id": 112233, + "project_id": "9c97bdc75295493096cf5edcb8c37933", + "region": "ru-2", + "status": "DOWN" + }, + { + "cidr": "198.51.100.0/29", + "id": 112234, + "project_id": "9c97bdc75295493096cf5edcb8c37933", + "region": "ru-1", + "status": "DOWN" + } + ] +} +` + +// TestCreateSubnetResponse represents the unmarshalled TestCreateSubnetsResponseRaw response. +var TestCreateSubnetResponse = []*subnets.Subnet{ + { + CIDR: "203.0.113.0/29", + ID: 112233, + ProjectID: "9c97bdc75295493096cf5edcb8c37933", + Region: "ru-2", + Status: "DOWN", + }, + { + CIDR: "198.51.100.0/29", + ID: 112234, + ProjectID: "9c97bdc75295493096cf5edcb8c37933", + Region: "ru-1", + Status: "DOWN", + }, +} diff --git a/selvpcclient/resell/v2/subnets/testing/request_test.go b/selvpcclient/resell/v2/subnets/testing/request_test.go index bf0863e..20a3669 100644 --- a/selvpcclient/resell/v2/subnets/testing/request_test.go +++ b/selvpcclient/resell/v2/subnets/testing/request_test.go @@ -2,7 +2,9 @@ package testing import ( "context" + "encoding/json" "fmt" + "io/ioutil" "net/http" "reflect" "testing" @@ -93,3 +95,51 @@ func TestListSubnetsSingle(t *testing.T) { t.Fatalf("expected %#v, but got %#v", expected, actual) } } + +func TestCreateSubnets(t *testing.T) { + testEnv := testutils.SetupTestEnv() + defer testEnv.TearDownTestEnv() + testEnv.NewTestResellV2Client() + testEnv.Mux.HandleFunc("/resell/v2/subnets/projects/9c97bdc75295493096cf5edcb8c37933", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, TestCreateSubnetsResponseRaw) + + if r.Method != http.MethodPost { + t.Fatalf("expected %s method but got %s", http.MethodPost, r.Method) + } + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("unable to read the request body: %v", err) + } + + var actualRequest interface{} + err = json.Unmarshal(b, &actualRequest) + if err != nil { + t.Errorf("unable to unmarshal the request body: %v", err) + } + + var expectedRequest interface{} + err = json.Unmarshal([]byte(TestCreateSubnetsOptsRaw), &expectedRequest) + if err != nil { + t.Errorf("unable to unmarshal expected raw response: %v", err) + } + + if !reflect.DeepEqual(actualRequest, expectedRequest) { + t.Fatalf("expected %#v create options, but got %#v", expectedRequest, actualRequest) + } + }) + + ctx := context.Background() + createOpts := TestCreateSubnetsOpts + actualResponse, _, err := subnets.Create(ctx, testEnv.Client, "9c97bdc75295493096cf5edcb8c37933", createOpts) + if err != nil { + t.Fatal(err) + } + + expectedResponse := TestCreateSubnetResponse + + if !reflect.DeepEqual(actualResponse, expectedResponse) { + t.Fatalf("expected %#v, but got %#v", actualResponse, expectedResponse) + } +} diff --git a/selvpcclient/selvpc.go b/selvpcclient/selvpc.go index 122ade7..dcd1b46 100644 --- a/selvpcclient/selvpc.go +++ b/selvpcclient/selvpc.go @@ -18,8 +18,17 @@ const ( // DefaultUserAgent contains basic user agent that will be used in queries. DefaultUserAgent = "selvpcclient/" + AppVersion + + // IPv4 represents IP version 4. + IPv4 IPVersion = "ipv4" + + // IPv6 represents IP version 6. + IPv6 IPVersion = "ipv6" ) +// IPVersion represents a type for the IP versions of the Resell API. +type IPVersion string + // ServiceClient stores details that are needed to work with different Selectel VPC APIs. type ServiceClient struct { // HTTPClient represents an initialized HTTP client that will be used to do requests.