Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

droplet create: assign to project after create #1499

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions commands/droplets.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ func Droplet() *Command {
aliasOpt("b"), displayerType(&displayers.Image{}))
cmdDropletBackups.Example = `The following example retrieves a list of backups for a Droplet with the ID ` + "`" + `386734086` + "`" + `: doctl compute droplet backups 386734086`

dropletCreateLongDesc := `Creates a new Droplet on your account. The command requires values for the ` + "`" + `--size` + "`" + `, and ` + "`" + `--image` + "`" + ` flags.
dropletCreateLongDesc := `Creates a new Droplet on your account. The command requires values for the ` + "`" + `--size` + "`" + `, and ` + "`" + `--image` + "`" + ` flags.
To retrieve a list of size slugs, use the ` + "`" + `doctl compute size list` + "`" + ` command. To retrieve a list of image slugs, use the ` + "`" + `doctl compute image list` + "`" + ` command.
To retrieve a list of size slugs, use the ` + "`" + `doctl compute size list` + "`" + ` command. To retrieve a list of image slugs, use the ` + "`" + `doctl compute image list` + "`" + ` command.
If you do not specify a region, the Droplet is created in the default region for your account. If you do not specify any SSH keys, we email a temporary password to your account's email address.`

Expand All @@ -88,6 +88,7 @@ If you do not specify a region, the Droplet is created in the default region for
requiredOpt())
AddStringFlag(cmdDropletCreate, doctl.ArgTagName, "", "", "Applies a tag to the Droplet")
AddStringFlag(cmdDropletCreate, doctl.ArgVPCUUID, "", "", "The UUID of a non-default VPC to create the Droplet in")
AddStringFlag(cmdDropletCreate, doctl.ArgProjectID, "", "", "The UUID of the project to assign the Droplet to")
AddStringSliceFlag(cmdDropletCreate, doctl.ArgTagNames, "", []string{}, "Applies a list of tags to the Droplet")
AddBoolFlag(cmdDropletCreate, doctl.ArgDropletAgent, "", false, "Specifies whether or not the Droplet monitoring agent should be installed. By default, the agent is installed on new Droplets but installation errors are ignored. Set `--droplet-agent=false` to prevent installation. Set to `true` to make installation errors fatal.")
AddStringSliceFlag(cmdDropletCreate, doctl.ArgVolumeList, "", []string{}, "A list of block storage volume IDs to attach to the Droplet")
Expand Down Expand Up @@ -230,6 +231,11 @@ func RunDropletCreate(c *CmdConfig) error {
return err
}

projectUUID, err := c.Doit.GetString(c.NS, doctl.ArgProjectID)
if err != nil {
return err
}

tagNames, err := c.Doit.GetStringSlice(c.NS, doctl.ArgTagNames)
if err != nil {
return err
Expand Down Expand Up @@ -329,6 +335,16 @@ func RunDropletCreate(c *CmdConfig) error {
}
}

if projectUUID != "" {
dropletURNs := make([]string, 0, len(createdList))
for _, createdDroplet := range createdList {
dropletURNs = append(dropletURNs, createdDroplet.URN())
}
if _, err := c.Projects().AssignResources(projectUUID, dropletURNs); err != nil {
return err
}
}

return c.Display(item)
}

Expand Down Expand Up @@ -775,7 +791,7 @@ func dropletOneClicks() *Command {
}

cmdDropletOneClickList := CmdBuilder(cmd, RunDropletOneClickList, "list", "Retrieve a list of Droplet 1-Click applications", `Retrieves a list of Droplet 1-Click application slugs.
You can use 1-click slugs to create Droplets by using them as the argument for the `+"`"+`--image`+"`"+` flag in the `+"`"+`doctl compute droplet create`+"`"+` command. For example, the following command creates a Droplet with an Openblocks installation on it: `+"`"+`doctl compute droplet create example-droplet --image openblocks --size s-2vcpu-2gb --region nyc1`+"`"+``, Writer,
aliasOpt("ls"), displayerType(&displayers.OneClick{}))
cmdDropletOneClickList.Example = `The following example retrieves a list of 1-clicks for Droplets: doctl compute droplet 1-click list`
Expand Down
27 changes: 27 additions & 0 deletions commands/droplets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,33 @@ coreos:
})
}

func TestDropletCreateWithProjectID(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
projectUUID := "00000000-0000-4000-8000-000000000000"

dcr := &godo.DropletCreateRequest{
Name: "droplet",
Region: "dev0",
Size: "1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "image"},
SSHKeys: []godo.DropletCreateSSHKey{},
}
tm.droplets.EXPECT().Create(dcr, false).Return(&testDroplet, nil)
tm.projects.EXPECT().
AssignResources(projectUUID, []string{testDroplet.URN()}).
Return(do.ProjectResources{}, nil)

config.Args = append(config.Args, "droplet")
config.Doit.Set(config.NS, doctl.ArgRegionSlug, "dev0")
config.Doit.Set(config.NS, doctl.ArgSizeSlug, "1gb")
config.Doit.Set(config.NS, doctl.ArgImage, "image")
config.Doit.Set(config.NS, doctl.ArgProjectID, projectUUID)

err := RunDropletCreate(config)
assert.NoError(t, err)
})
}

func TestDropletDelete(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
tm.droplets.EXPECT().Delete(1).Return(nil)
Expand Down
85 changes: 82 additions & 3 deletions integration/droplet_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ type dropletRequest struct {
Name string `json:"name"`
}

type assignResourcesRequest struct {
Resources []string `json:"resources"`
}

var _ = suite("compute/droplet/create", func(t *testing.T, when spec.G, it spec.S) {
var (
expect *require.Assertions
server *httptest.Server
reqBody []byte
expect *require.Assertions
server *httptest.Server
reqBody []byte
assignResourcesReqBody []byte
)

it.Before(func() {
Expand Down Expand Up @@ -64,6 +69,30 @@ var _ = suite("compute/droplet/create", func(t *testing.T, when spec.G, it spec.
// since we've successfully tested all the behavior
// at this point
w.Write([]byte(dropletCreateResponse))
case "/v2/projects/00000000-0000-4000-8000-000000000000/resources":
auth := req.Header.Get("Authorization")
if auth != "Bearer some-magic-token" {
w.WriteHeader(http.StatusUnauthorized)
return
}

if req.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

var err error
assignResourcesReqBody, err = io.ReadAll(req.Body)
expect.NoError(err)

var arr assignResourcesRequest
err = json.Unmarshal(assignResourcesReqBody, &arr)
expect.NoError(err)

expect.Greater(len(arr.Resources), 0)
expect.Equal(arr.Resources[0], "do:droplet:1111")

w.Write([]byte(assignResourcesResponse))
default:
dump, err := httputil.DumpRequest(req, true)
if err != nil {
Expand Down Expand Up @@ -133,6 +162,42 @@ var _ = suite("compute/droplet/create", func(t *testing.T, when spec.G, it spec.
})
})

when("a project id is passed", func() {
it("creates a droplet and moves it to that project", func() {
cmd := exec.Command(builtBinaryPath,
"-t", "some-magic-token",
"-u", server.URL,
"compute",
"droplet",
"create",
"some-droplet-name",
"--image", "a-test-image",
"--region", "a-test-region",
"--size", "a-test-size",
"--project-id", "00000000-0000-4000-8000-000000000000",
)

output, err := cmd.CombinedOutput()
expect.NoError(err, fmt.Sprintf("received error output: %s", output))
expect.Equal(strings.TrimSpace(dropletCreateOutput), strings.TrimSpace(string(output)))

request := &struct {
Name string
Image string
Region string
Size string
}{}

err = json.Unmarshal(reqBody, request)
expect.NoError(err)

expect.Equal("some-droplet-name", request.Name)
expect.Equal("a-test-image", request.Image)
expect.Equal("a-test-region", request.Region)
expect.Equal("a-test-size", request.Size)
})
})

when("missing required arguments", func() {
base := []string{
"-t", "some-magic-token",
Expand Down Expand Up @@ -208,6 +273,20 @@ const (
actionCompletedResponse = `
{"action": "id": 1, "status": "completed"}
`
assignResourcesResponse = `{
"resources": [
{
"urn": "do:droplet:1111",
"assigned_at": "2024-01-01T00:00:00Z",
"links": {
"self": "https://api.digitalocean.com/v2/droplets/1111"
},
"status": "ok"
}
]
}
`

dropletCreateOutput = `
ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
1111 some-droplet-name 1.2.3.4 7.7.7.7 12 13 15 some-region-slug some-distro some-image-name 00000000-0000-4000-8000-000000000000 active yes remotes some-volume-id
Expand Down
Loading