text:
package godo

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestDropletActions_Shutdown(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "shutdown",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.Shutdown(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.Shutdown returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Shutdown returned %+v, expected %+v", action, expected)
	}
}

func TestDropletActions_ShutdownByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "shutdown",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.ShutdownByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.ShutdownByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.ShutdownByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.ShutdownByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerOff(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_off",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.PowerOff(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.PowerOff returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Poweroff returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerOffByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_off",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.PowerOffByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.PowerOffByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.PowerOffByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PoweroffByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerOn(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_on",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.PowerOn(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.PowerOn returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PowerOn returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerOnByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_on",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.PowerOnByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.PowerOnByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.PowerOnByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PowerOnByTag returned %+v, expected %+v", action, expected)
	}
}
func TestDropletAction_Reboot(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "reboot",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)

	})

	action, _, err := client.DropletActions.Reboot(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.Reboot returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Reboot returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_Restore(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type":  "restore",
		"image": float64(1),
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)

	})

	action, _, err := client.DropletActions.Restore(ctx, 1, 1)
	if err != nil {
		t.Errorf("DropletActions.Restore returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Restore returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_Resize(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "resize",
		"size": "1024mb",
		"disk": true,
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)

	})

	action, _, err := client.DropletActions.Resize(ctx, 1, "1024mb", true)
	if err != nil {
		t.Errorf("DropletActions.Resize returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Resize returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_Rename(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "rename",
		"name": "Droplet-Name",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.Rename(ctx, 1, "Droplet-Name")
	if err != nil {
		t.Errorf("DropletActions.Rename returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Rename returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerCycle(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_cycle",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)

	})

	action, _, err := client.DropletActions.PowerCycle(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.PowerCycle returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PowerCycle returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PowerCycleByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "power_cycle",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.PowerCycleByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.PowerCycleByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.PowerCycleByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PowerCycleByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_Snapshot(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "snapshot",
		"name": "Image-Name",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.Snapshot(ctx, 1, "Image-Name")
	if err != nil {
		t.Errorf("DropletActions.Snapshot returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Snapshot returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_SnapshotByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "snapshot",
		"name": "Image-Name",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.SnapshotByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.SnapshotByTag(ctx, "testing-1", "Image-Name")
	if err != nil {
		t.Errorf("DropletActions.SnapshotByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.SnapshotByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnableBackups(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_backups",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.EnableBackups(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.EnableBackups returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnableBackups returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnableBackupsByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_backups",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.EnableBackupByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.EnableBackupsByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.EnableBackupsByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnableBackupsByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_DisableBackups(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "disable_backups",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.DisableBackups(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.DisableBackups returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.DisableBackups returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_DisableBackupsByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "disable_backups",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.DisableBackupsByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.DisableBackupsByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.DisableBackupsByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.DisableBackupsByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_PasswordReset(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "password_reset",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.PasswordReset(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.PasswordReset returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.PasswordReset returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_RebuildByImageID(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type":  "rebuild",
		"image": float64(2),
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = \n%#v, expected \n%#v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.RebuildByImageID(ctx, 1, 2)
	if err != nil {
		t.Errorf("DropletActions.RebuildByImageID returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.RebuildByImageID returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_RebuildByImageSlug(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type":  "rebuild",
		"image": "Image-Name",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.RebuildByImageSlug(ctx, 1, "Image-Name")
	if err != nil {
		t.Errorf("DropletActions.RebuildByImageSlug returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.RebuildByImageSlug returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_ChangeKernel(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type":   "change_kernel",
		"kernel": float64(2),
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.ChangeKernel(ctx, 1, 2)
	if err != nil {
		t.Errorf("DropletActions.ChangeKernel returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.ChangeKernel returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnableIPv6(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_ipv6",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.EnableIPv6(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.EnableIPv6 returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnableIPv6 returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnableIPv6ByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_ipv6",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.EnableIPv6ByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.EnableIPv6ByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.EnableIPv6ByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnableIPv6byTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnablePrivateNetworking(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_private_networking",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.EnablePrivateNetworking(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.EnablePrivateNetworking returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnablePrivateNetworking returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_EnablePrivateNetworkingByTag(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "enable_private_networking",
	}

	mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("tag_name") != "testing-1" {
			t.Errorf("DropletActions.EnablePrivateNetworkingByTag did not request with a tag parameter")
		}

		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`)
	})

	action, _, err := client.DropletActions.EnablePrivateNetworkingByTag(ctx, "testing-1")
	if err != nil {
		t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned %+v, expected %+v", action, expected)
	}
}

func TestDropletAction_Upgrade(t *testing.T) {
	setup()
	defer teardown()

	request := &ActionRequest{
		"type": "upgrade",
	}

	mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)

		if !reflect.DeepEqual(v, request) {
			t.Errorf("Request body = %+v, expected %+v", v, request)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.Upgrade(ctx, 1)
	if err != nil {
		t.Errorf("DropletActions.Upgrade returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Upgrade returned %+v, expected %+v", action, expected)
	}
}

func TestDropletActions_Get(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/droplets/123/actions/456", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.DropletActions.Get(ctx, 123, 456)
	if err != nil {
		t.Errorf("DropletActions.Get returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("DropletActions.Get returned %+v, expected %+v", action, expected)
	}
}


text:
package godo

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path"
	"testing"

	"github.com/stretchr/testify/assert"
)

var certJSONResponse = `
{
	"certificate": {
		"id": "892071a0-bb95-49bc-8021-3afd67a210bf",
		"name": "web-cert-01",
		"not_after": "2017-02-22T00:23:00Z",
		"sha1_fingerprint": "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
		"created_at": "2017-02-08T16:02:37Z"
	}
}
`

var certsJSONResponse = `
{
  	"certificates": [
    	{
      		"id": "892071a0-bb95-49bc-8021-3afd67a210bf",
      		"name": "web-cert-01",
      		"not_after": "2017-02-22T00:23:00Z",
      		"sha1_fingerprint": "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
      		"created_at": "2017-02-08T16:02:37Z"
    	},
    	{
      		"id": "992071a0-bb95-49bc-8021-3afd67a210bf",
      		"name": "web-cert-02",
      		"not_after": "2017-02-22T00:23:00Z",
      		"sha1_fingerprint": "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
      		"created_at": "2017-02-08T16:02:37Z"
    	}
  	],
  	"links": {},
  	"meta": {
    	"total": 1
  	}
}
`

func TestCertificates_Get(t *testing.T) {
	setup()
	defer teardown()

	urlStr := "/v2/certificates"
	cID := "892071a0-bb95-49bc-8021-3afd67a210bf"
	urlStr = path.Join(urlStr, cID)
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, certJSONResponse)
	})

	certificate, _, err := client.Certificates.Get(ctx, cID)
	if err != nil {
		t.Errorf("Certificates.Get returned error: %v", err)
	}

	expected := &Certificate{
		ID:              "892071a0-bb95-49bc-8021-3afd67a210bf",
		Name:            "web-cert-01",
		NotAfter:        "2017-02-22T00:23:00Z",
		SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
		Created:         "2017-02-08T16:02:37Z",
	}

	assert.Equal(t, expected, certificate)
}

func TestCertificates_List(t *testing.T) {
	setup()
	defer teardown()

	urlStr := "/v2/certificates"
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, certsJSONResponse)
	})

	certificates, _, err := client.Certificates.List(ctx, nil)

	if err != nil {
		t.Errorf("Certificates.List returned error: %v", err)
	}

	expected := []Certificate{
		{
			ID:              "892071a0-bb95-49bc-8021-3afd67a210bf",
			Name:            "web-cert-01",
			NotAfter:        "2017-02-22T00:23:00Z",
			SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
			Created:         "2017-02-08T16:02:37Z",
		},
		{
			ID:              "992071a0-bb95-49bc-8021-3afd67a210bf",
			Name:            "web-cert-02",
			NotAfter:        "2017-02-22T00:23:00Z",
			SHA1Fingerprint: "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
			Created:         "2017-02-08T16:02:37Z",
		},
	}

	assert.Equal(t, expected, certificates)
}

func TestCertificates_Create(t *testing.T) {
	setup()
	defer teardown()

	createRequest := &CertificateRequest{
		Name:             "web-cert-01",
		PrivateKey:       "-----BEGIN PRIVATE KEY-----",
		LeafCertificate:  "-----BEGIN CERTIFICATE-----",
		CertificateChain: "-----BEGIN CERTIFICATE-----",
	}

	urlStr := "/v2/certificates"
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(CertificateRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		assert.Equal(t, createRequest, v)

		fmt.Fprint(w, certJSONResponse)
	})

	certificate, _, err := client.Certificates.Create(ctx, createRequest)
	if err != nil {
		t.Errorf("Certificates.Create returned error: %v", err)
	}

	expected := &Certificate{
		ID:              "892071a0-bb95-49bc-8021-3afd67a210bf",
		Name:            "web-cert-01",
		NotAfter:        "2017-02-22T00:23:00Z",
		SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
		Created:         "2017-02-08T16:02:37Z",
	}

	assert.Equal(t, expected, certificate)
}

func TestCertificates_Delete(t *testing.T) {
	setup()
	defer teardown()

	cID := "892071a0-bb95-49bc-8021-3afd67a210bf"
	urlStr := "/v2/certificates"
	urlStr = path.Join(urlStr, cID)
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Certificates.Delete(ctx, cID)

	if err != nil {
		t.Errorf("Certificates.Delete returned error: %v", err)
	}
}

repo_name: dan-v/dosxvpn

repo_language: Go

mime_type: text/x-shellscript


text:
package godo

import (
	"fmt"
	"net/http"

	"github.com/digitalocean/godo/context"
)

// ImageActionsService is an interface for interfacing with the image actions
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#image-actions
type ImageActionsService interface {
	Get(context.Context, int, int) (*Action, *Response, error)
	Transfer(context.Context, int, *ActionRequest) (*Action, *Response, error)
	Convert(context.Context, int) (*Action, *Response, error)
}

// ImageActionsServiceOp handles communition with the image action related methods of the
// DigitalOcean API.
type ImageActionsServiceOp struct {
	client *Client
}

var _ ImageActionsService = &ImageActionsServiceOp{}

// Transfer an image
func (i *ImageActionsServiceOp) Transfer(ctx context.Context, imageID int, transferRequest *ActionRequest) (*Action, *Response, error) {
	if imageID < 1 {
		return nil, nil, NewArgError("imageID", "cannot be less than 1")
	}

	if transferRequest == nil {
		return nil, nil, NewArgError("transferRequest", "cannot be nil")
	}

	path := fmt.Sprintf("v2/images/%d/actions", imageID)

	req, err := i.client.NewRequest(ctx, http.MethodPost, path, transferRequest)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionRoot)
	resp, err := i.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Event, resp, err
}

// Convert an image to a snapshot
func (i *ImageActionsServiceOp) Convert(ctx context.Context, imageID int) (*Action, *Response, error) {
	if imageID < 1 {
		return nil, nil, NewArgError("imageID", "cannont be less than 1")
	}

	path := fmt.Sprintf("v2/images/%d/actions", imageID)

	convertRequest := &ActionRequest{
		"type": "convert",
	}

	req, err := i.client.NewRequest(ctx, http.MethodPost, path, convertRequest)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionRoot)
	resp, err := i.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Event, resp, err
}

// Get an action for a particular image by id.
func (i *ImageActionsServiceOp) Get(ctx context.Context, imageID, actionID int) (*Action, *Response, error) {
	if imageID < 1 {
		return nil, nil, NewArgError("imageID", "cannot be less than 1")
	}

	if actionID < 1 {
		return nil, nil, NewArgError("actionID", "cannot be less than 1")
	}

	path := fmt.Sprintf("v2/images/%d/actions/%d", imageID, actionID)

	req, err := i.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionRoot)
	resp, err := i.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Event, resp, err
}


text:
package godo

import (
	"fmt"
	"net/http"

	"github.com/digitalocean/godo/context"
)

const tagsBasePath = "v2/tags"

// TagsService is an interface for interfacing with the tags
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#tags
type TagsService interface {
	List(context.Context, *ListOptions) ([]Tag, *Response, error)
	Get(context.Context, string) (*Tag, *Response, error)
	Create(context.Context, *TagCreateRequest) (*Tag, *Response, error)
	Delete(context.Context, string) (*Response, error)

	TagResources(context.Context, string, *TagResourcesRequest) (*Response, error)
	UntagResources(context.Context, string, *UntagResourcesRequest) (*Response, error)
}

// TagsServiceOp handles communication with tag related method of the
// DigitalOcean API.
type TagsServiceOp struct {
	client *Client
}

var _ TagsService = &TagsServiceOp{}

// ResourceType represents a class of resource, currently only droplet are supported
type ResourceType string

const (
	//DropletResourceType holds the string representing our ResourceType of Droplet.
	DropletResourceType ResourceType = "droplet"
)

// Resource represent a single resource for associating/disassociating with tags
type Resource struct {
	ID   string       `json:"resource_id,omit_empty"`
	Type ResourceType `json:"resource_type,omit_empty"`
}

// TaggedResources represent the set of resources a tag is attached to
type TaggedResources struct {
	Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
}

// TaggedDropletsResources represent the droplet resources a tag is attached to
type TaggedDropletsResources struct {
	Count      int      `json:"count,float64,omitempty"`
	LastTagged *Droplet `json:"last_tagged,omitempty"`
}

// Tag represent DigitalOcean tag
type Tag struct {
	Name      string           `json:"name,omitempty"`
	Resources *TaggedResources `json:"resources,omitempty"`
}

//TagCreateRequest represents the JSON structure of a request of that type.
type TagCreateRequest struct {
	Name string `json:"name"`
}

// TagResourcesRequest represents the JSON structure of a request of that type.
type TagResourcesRequest struct {
	Resources []Resource `json:"resources"`
}

// UntagResourcesRequest represents the JSON structure of a request of that type.
type UntagResourcesRequest struct {
	Resources []Resource `json:"resources"`
}

type tagsRoot struct {
	Tags  []Tag  `json:"tags"`
	Links *Links `json:"links"`
}

type tagRoot struct {
	Tag *Tag `json:"tag"`
}

// List all tags
func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Response, error) {
	path := tagsBasePath
	path, err := addOptions(path, opt)

	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(tagsRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	if l := root.Links; l != nil {
		resp.Links = l
	}

	return root.Tags, resp, err
}

// Get a single tag
func (s *TagsServiceOp) Get(ctx context.Context, name string) (*Tag, *Response, error) {
	path := fmt.Sprintf("%s/%s", tagsBasePath, name)

	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(tagRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Tag, resp, err
}

// Create a new tag
func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequest) (*Tag, *Response, error) {
	if createRequest == nil {
		return nil, nil, NewArgError("createRequest", "cannot be nil")
	}

	req, err := s.client.NewRequest(ctx, http.MethodPost, tagsBasePath, createRequest)
	if err != nil {
		return nil, nil, err
	}

	root := new(tagRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Tag, resp, err
}

// Delete an existing tag
func (s *TagsServiceOp) Delete(ctx context.Context, name string) (*Response, error) {
	if name == "" {
		return nil, NewArgError("name", "cannot be empty")
	}

	path := fmt.Sprintf("%s/%s", tagsBasePath, name)
	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(ctx, req, nil)

	return resp, err
}

// TagResources associates resources with a given Tag.
func (s *TagsServiceOp) TagResources(ctx context.Context, name string, tagRequest *TagResourcesRequest) (*Response, error) {
	if name == "" {
		return nil, NewArgError("name", "cannot be empty")
	}

	if tagRequest == nil {
		return nil, NewArgError("tagRequest", "cannot be nil")
	}

	path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
	req, err := s.client.NewRequest(ctx, http.MethodPost, path, tagRequest)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(ctx, req, nil)

	return resp, err
}

// UntagResources dissociates resources with a given Tag.
func (s *TagsServiceOp) UntagResources(ctx context.Context, name string, untagRequest *UntagResourcesRequest) (*Response, error) {
	if name == "" {
		return nil, NewArgError("name", "cannot be empty")
	}

	if untagRequest == nil {
		return nil, NewArgError("tagRequest", "cannot be nil")
	}

	path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
	req, err := s.client.NewRequest(ctx, http.MethodDelete, path, untagRequest)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(ctx, req, nil)

	return resp, err
}


text:
# Change Log

## [v1.1.3] - 2018-03-07

- #156 Handle non-json errors from the API - @aknuds1
- #158 Update droplet example to use latest instance type - @dan-v

## [v1.1.2] - 2018-03-06

- #157 storage: list volumes should handle only name or only region params - @andrewsykim
- #154 docs: replace first example with fully-runnable example - @xmudrii
- #152 Handle flags & tag properties of domain record - @jaymecd

## [v1.1.1] - 2017-09-29

- #151 Following user agent field recommendations - @joonas
- #148 AsRequest method to create load balancers requests - @lukegb

## [v1.1.0] - 2017-06-06

### Added
- #145 Add FirewallsService for managing Firewalls with the DigitalOcean API. - @viola
- #139 Add TTL field to the Domains. - @xmudrii

### Fixed
- #143 Fix oauth2.NoContext depreciation. - @jbowens
- #141 Fix DropletActions on tagged resources. - @xmudrii

## [v1.0.0] - 2017-03-10

### Added
- #130 Add Convert to ImageActionsService. - @xmudrii
- #126 Add CertificatesService for managing certificates with the DigitalOcean API. - @viola
- #125 Add LoadBalancersService for managing load balancers with the DigitalOcean API. - @viola
- #122 Add GetVolumeByName to StorageService. - @protochron
- #113 Add context.Context to all calls. - @aybabtme


text:
package godo

import (
	"net/url"
	"strconv"

	"github.com/digitalocean/godo/context"
)

// Links manages links that are returned along with a List
type Links struct {
	Pages   *Pages       `json:"pages,omitempty"`
	Actions []LinkAction `json:"actions,omitempty"`
}

// Pages are pages specified in Links
type Pages struct {
	First string `json:"first,omitempty"`
	Prev  string `json:"prev,omitempty"`
	Last  string `json:"last,omitempty"`
	Next  string `json:"next,omitempty"`
}

// LinkAction is a pointer to an action
type LinkAction struct {
	ID   int    `json:"id,omitempty"`
	Rel  string `json:"rel,omitempty"`
	HREF string `json:"href,omitempty"`
}

// CurrentPage is current page of the list
func (l *Links) CurrentPage() (int, error) {
	return l.Pages.current()
}

func (p *Pages) current() (int, error) {
	switch {
	case p == nil:
		return 1, nil
	case p.Prev == "" && p.Next != "":
		return 1, nil
	case p.Prev != "":
		prevPage, err := pageForURL(p.Prev)
		if err != nil {
			return 0, err
		}

		return prevPage + 1, nil
	}

	return 0, nil
}

// IsLastPage returns true if the current page is the last
func (l *Links) IsLastPage() bool {
	if l.Pages == nil {
		return true
	}
	return l.Pages.isLast()
}

func (p *Pages) isLast() bool {
	return p.Last == ""
}

func pageForURL(urlText string) (int, error) {
	u, err := url.ParseRequestURI(urlText)
	if err != nil {
		return 0, err
	}

	pageStr := u.Query().Get("page")
	page, err := strconv.Atoi(pageStr)
	if err != nil {
		return 0, err
	}

	return page, nil
}

// Get a link action by id.
func (la *LinkAction) Get(ctx context.Context, client *Client) (*Action, *Response, error) {
	return client.Actions.Get(ctx, la.ID)
}


text:
package godo

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path"
	"reflect"
	"testing"
)

var (
	firewallCreateJSONBody = `
{
  "name": "f-i-r-e-w-a-l-l",
  "inbound_rules": [
    {
      "protocol": "icmp",
      "sources": {
        "addresses": ["0.0.0.0/0"],
        "tags": ["frontend"],
        "droplet_ids": [123, 456],
        "load_balancer_uids": ["lb-uid"]
      }
    },
    {
      "protocol": "tcp",
      "ports": "8000-9000",
      "sources": {
        "addresses": ["0.0.0.0/0"]
      }
    }
  ],
  "outbound_rules": [
    {
      "protocol": "icmp",
      "destinations": {
        "tags": ["frontend"]
      }
    },
    {
      "protocol": "tcp",
      "ports": "8000-9000",
      "destinations": {
        "addresses": ["::/1"]
      }
    }
  ],
  "droplet_ids": [123],
  "tags": ["frontend"]
}
`
	firewallRulesJSONBody = `
{
  "inbound_rules": [
    {
      "protocol": "tcp",
      "ports": "22",
      "sources": {
        "addresses": ["0.0.0.0/0"]
      }
    }
  ],
  "outbound_rules": [
    {
      "protocol": "tcp",
      "ports": "443",
      "destinations": {
        "addresses": ["0.0.0.0/0"]
      }
    }
  ]
}
`
	firewallUpdateJSONBody = `
{
  "name": "f-i-r-e-w-a-l-l",
  "inbound_rules": [
    {
      "protocol": "tcp",
      "ports": "443",
      "sources": {
        "addresses": ["10.0.0.0/8"]
      }
    }
  ],
  "droplet_ids": [123],
  "tags": []
}
`
	firewallUpdateJSONResponse = `
{
  "firewall": {
    "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
    "name": "f-i-r-e-w-a-l-l",
    "inbound_rules": [
      {
        "protocol": "tcp",
        "ports": "443",
        "sources": {
          "addresses": ["10.0.0.0/8"]
        }
      }
    ],
    "outbound_rules": [],
    "created_at": "2017-04-06T13:07:27Z",
    "droplet_ids": [
      123
    ],
    "tags": []
  }
}
`
	firewallJSONResponse = `
{
  "firewall": {
    "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
    "name": "f-i-r-e-w-a-l-l",
    "status": "waiting",
    "inbound_rules": [
      {
        "protocol": "icmp",
        "ports": "0",
        "sources": {
          "tags": ["frontend"]
        }
      },
      {
        "protocol": "tcp",
        "ports": "8000-9000",
        "sources": {
          "addresses": ["0.0.0.0/0"]
        }
      }
    ],
    "outbound_rules": [
      {
        "protocol": "icmp",
        "ports": "0"
      },
      {
        "protocol": "tcp",
        "ports": "8000-9000",
        "destinations": {
          "addresses": ["::/1"]
        }
      }
    ],
    "created_at": "2017-04-06T13:07:27Z",
    "droplet_ids": [
      123
    ],
    "tags": [
      "frontend"
    ],
    "pending_changes": [
      {
        "droplet_id": 123,
        "removing": false,
        "status": "waiting"
      }
    ]
  }
}
`
	firewallListJSONResponse = `
{
  "firewalls": [
    {
      "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
      "name": "f-i-r-e-w-a-l-l",
      "inbound_rules": [
        {
          "protocol": "icmp",
          "ports": "0",
          "sources": {
            "tags": ["frontend"]
          }
        },
        {
          "protocol": "tcp",
          "ports": "8000-9000",
          "sources": {
            "addresses": ["0.0.0.0/0"]
          }
        }
      ],
      "outbound_rules": [
        {
          "protocol": "icmp",
          "ports": "0"
        },
        {
          "protocol": "tcp",
          "ports": "8000-9000",
          "destinations": {
            "addresses": ["::/1"]
          }
        }
      ],
      "created_at": "2017-04-06T13:07:27Z",
      "droplet_ids": [
        123
      ],
      "tags": [
        "frontend"
      ]
    }
  ],
  "links": {},
  "meta": {
    "total": 1
  }
}
`
)

func TestFirewalls_Get(t *testing.T) {
	setup()
	defer teardown()

	urlStr := "/v2/firewalls"
	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr = path.Join(urlStr, fID)

	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, firewallJSONResponse)
	})

	actualFirewall, _, err := client.Firewalls.Get(ctx, fID)
	if err != nil {
		t.Errorf("Firewalls.Get returned error: %v", err)
	}

	expectedFirewall := &Firewall{
		ID:     "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
		Name:   "f-i-r-e-w-a-l-l",
		Status: "waiting",
		InboundRules: []InboundRule{
			{
				Protocol:  "icmp",
				PortRange: "0",
				Sources: &Sources{
					Tags: []string{"frontend"},
				},
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Sources: &Sources{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
		OutboundRules: []OutboundRule{
			{
				Protocol:  "icmp",
				PortRange: "0",
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Destinations: &Destinations{
					Addresses: []string{"::/1"},
				},
			},
		},
		Created:    "2017-04-06T13:07:27Z",
		DropletIDs: []int{123},
		Tags:       []string{"frontend"},
		PendingChanges: []PendingChange{
			{
				DropletID: 123,
				Removing:  false,
				Status:    "waiting",
			},
		},
	}

	if !reflect.DeepEqual(actualFirewall, expectedFirewall) {
		t.Errorf("Firewalls.Get returned %+v, expected %+v", actualFirewall, expectedFirewall)
	}
}

func TestFirewalls_Create(t *testing.T) {
	setup()
	defer teardown()

	expectedFirewallRequest := &FirewallRequest{
		Name: "f-i-r-e-w-a-l-l",
		InboundRules: []InboundRule{
			{
				Protocol: "icmp",
				Sources: &Sources{
					Addresses:        []string{"0.0.0.0/0"},
					Tags:             []string{"frontend"},
					DropletIDs:       []int{123, 456},
					LoadBalancerUIDs: []string{"lb-uid"},
				},
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Sources: &Sources{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
		OutboundRules: []OutboundRule{
			{
				Protocol: "icmp",
				Destinations: &Destinations{
					Tags: []string{"frontend"},
				},
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Destinations: &Destinations{
					Addresses: []string{"::/1"},
				},
			},
		},
		DropletIDs: []int{123},
		Tags:       []string{"frontend"},
	}

	mux.HandleFunc("/v2/firewalls", func(w http.ResponseWriter, r *http.Request) {
		v := new(FirewallRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, expectedFirewallRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, expectedFirewallRequest)
		}

		var actualFirewallRequest *FirewallRequest
		json.Unmarshal([]byte(firewallCreateJSONBody), &actualFirewallRequest)
		if !reflect.DeepEqual(actualFirewallRequest, expectedFirewallRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualFirewallRequest, expectedFirewallRequest)
		}

		fmt.Fprint(w, firewallJSONResponse)
	})

	actualFirewall, _, err := client.Firewalls.Create(ctx, expectedFirewallRequest)
	if err != nil {
		t.Errorf("Firewalls.Create returned error: %v", err)
	}

	expectedFirewall := &Firewall{
		ID:     "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
		Name:   "f-i-r-e-w-a-l-l",
		Status: "waiting",
		InboundRules: []InboundRule{
			{
				Protocol:  "icmp",
				PortRange: "0",
				Sources: &Sources{
					Tags: []string{"frontend"},
				},
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Sources: &Sources{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
		OutboundRules: []OutboundRule{
			{
				Protocol:  "icmp",
				PortRange: "0",
			},
			{
				Protocol:  "tcp",
				PortRange: "8000-9000",
				Destinations: &Destinations{
					Addresses: []string{"::/1"},
				},
			},
		},
		Created:    "2017-04-06T13:07:27Z",
		DropletIDs: []int{123},
		Tags:       []string{"frontend"},
		PendingChanges: []PendingChange{
			{
				DropletID: 123,
				Removing:  false,
				Status:    "waiting",
			},
		},
	}

	if !reflect.DeepEqual(actualFirewall, expectedFirewall) {
		t.Errorf("Firewalls.Create returned %+v, expected %+v", actualFirewall, expectedFirewall)
	}
}

func TestFirewalls_Update(t *testing.T) {
	setup()
	defer teardown()

	expectedFirewallRequest := &FirewallRequest{
		Name: "f-i-r-e-w-a-l-l",
		InboundRules: []InboundRule{
			{
				Protocol:  "tcp",
				PortRange: "443",
				Sources: &Sources{
					Addresses: []string{"10.0.0.0/8"},
				},
			},
		},
		DropletIDs: []int{123},
		Tags:       []string{},
	}

	urlStr := "/v2/firewalls"
	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr = path.Join(urlStr, fID)
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(FirewallRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, "PUT")
		if !reflect.DeepEqual(v, expectedFirewallRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, expectedFirewallRequest)
		}

		var actualFirewallRequest *FirewallRequest
		json.Unmarshal([]byte(firewallUpdateJSONBody), &actualFirewallRequest)
		if !reflect.DeepEqual(actualFirewallRequest, expectedFirewallRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualFirewallRequest, expectedFirewallRequest)
		}

		fmt.Fprint(w, firewallUpdateJSONResponse)
	})

	actualFirewall, _, err := client.Firewalls.Update(ctx, fID, expectedFirewallRequest)
	if err != nil {
		t.Errorf("Firewalls.Update returned error: %v", err)
	}

	expectedFirewall := &Firewall{
		ID:   "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
		Name: "f-i-r-e-w-a-l-l",
		InboundRules: []InboundRule{
			{
				Protocol:  "tcp",
				PortRange: "443",
				Sources: &Sources{
					Addresses: []string{"10.0.0.0/8"},
				},
			},
		},
		OutboundRules: []OutboundRule{},
		Created:       "2017-04-06T13:07:27Z",
		DropletIDs:    []int{123},
		Tags:          []string{},
	}

	if !reflect.DeepEqual(actualFirewall, expectedFirewall) {
		t.Errorf("Firewalls.Update returned %+v, expected %+v", actualFirewall, expectedFirewall)
	}
}

func TestFirewalls_Delete(t *testing.T) {
	setup()
	defer teardown()

	urlStr := "/v2/firewalls"
	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr = path.Join(urlStr, fID)
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Firewalls.Delete(ctx, fID)

	if err != nil {
		t.Errorf("Firewalls.Delete returned error: %v", err)
	}
}

func TestFirewalls_List(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/firewalls", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, firewallListJSONResponse)
	})

	actualFirewalls, _, err := client.Firewalls.List(ctx, nil)

	if err != nil {
		t.Errorf("Firewalls.List returned error: %v", err)
	}

	expectedFirewalls := makeExpectedFirewalls()
	if !reflect.DeepEqual(actualFirewalls, expectedFirewalls) {
		t.Errorf("Firewalls.List returned %+v, expected %+v", actualFirewalls, expectedFirewalls)
	}
}

func TestFirewalls_ListByDroplet(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/droplets/123/firewalls", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, firewallListJSONResponse)
	})

	actualFirewalls, _, err := client.Firewalls.ListByDroplet(ctx, 123, nil)

	if err != nil {
		t.Errorf("Firewalls.List returned error: %v", err)
	}

	expectedFirewalls := makeExpectedFirewalls()
	if !reflect.DeepEqual(actualFirewalls, expectedFirewalls) {
		t.Errorf("Firewalls.List returned %+v, expected %+v", actualFirewalls, expectedFirewalls)
	}
}

func TestFirewalls_AddDroplets(t *testing.T) {
	setup()
	defer teardown()

	dRequest := &dropletsRequest{
		IDs: []int{123},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "droplets")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(dropletsRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, dRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, dRequest)
		}

		expectedJSONBody := `{"droplet_ids": [123]}`
		var actualDropletsRequest *dropletsRequest
		json.Unmarshal([]byte(expectedJSONBody), &actualDropletsRequest)
		if !reflect.DeepEqual(actualDropletsRequest, dRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualDropletsRequest, dRequest)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.AddDroplets(ctx, fID, dRequest.IDs...)

	if err != nil {
		t.Errorf("Firewalls.AddDroplets returned error: %v", err)
	}
}

func TestFirewalls_RemoveDroplets(t *testing.T) {
	setup()
	defer teardown()

	dRequest := &dropletsRequest{
		IDs: []int{123, 345},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "droplets")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(dropletsRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodDelete)
		if !reflect.DeepEqual(v, dRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, dRequest)
		}

		expectedJSONBody := `{"droplet_ids": [123, 345]}`
		var actualDropletsRequest *dropletsRequest
		json.Unmarshal([]byte(expectedJSONBody), &actualDropletsRequest)
		if !reflect.DeepEqual(actualDropletsRequest, dRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualDropletsRequest, dRequest)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.RemoveDroplets(ctx, fID, dRequest.IDs...)

	if err != nil {
		t.Errorf("Firewalls.RemoveDroplets returned error: %v", err)
	}
}

func TestFirewalls_AddTags(t *testing.T) {
	setup()
	defer teardown()

	tRequest := &tagsRequest{
		Tags: []string{"frontend"},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "tags")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(tagsRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, tRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, tRequest)
		}

		var actualTagsRequest *tagsRequest
		json.Unmarshal([]byte(`{"tags": ["frontend"]}`), &actualTagsRequest)
		if !reflect.DeepEqual(actualTagsRequest, tRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualTagsRequest, tRequest)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.AddTags(ctx, fID, tRequest.Tags...)

	if err != nil {
		t.Errorf("Firewalls.AddTags returned error: %v", err)
	}
}

func TestFirewalls_RemoveTags(t *testing.T) {
	setup()
	defer teardown()

	tRequest := &tagsRequest{
		Tags: []string{"frontend", "backend"},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "tags")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(tagsRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodDelete)
		if !reflect.DeepEqual(v, tRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, tRequest)
		}

		var actualTagsRequest *tagsRequest
		json.Unmarshal([]byte(`{"tags": ["frontend", "backend"]}`), &actualTagsRequest)
		if !reflect.DeepEqual(actualTagsRequest, tRequest) {
			t.Errorf("Request body = %+v, expected %+v", actualTagsRequest, tRequest)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.RemoveTags(ctx, fID, tRequest.Tags...)

	if err != nil {
		t.Errorf("Firewalls.RemoveTags returned error: %v", err)
	}
}

func TestFirewalls_AddRules(t *testing.T) {
	setup()
	defer teardown()

	rr := &FirewallRulesRequest{
		InboundRules: []InboundRule{
			{
				Protocol:  "tcp",
				PortRange: "22",
				Sources: &Sources{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
		OutboundRules: []OutboundRule{
			{
				Protocol:  "tcp",
				PortRange: "443",
				Destinations: &Destinations{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "rules")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(FirewallRulesRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, rr) {
			t.Errorf("Request body = %+v, expected %+v", v, rr)
		}

		var actualFirewallRulesRequest *FirewallRulesRequest
		json.Unmarshal([]byte(firewallRulesJSONBody), &actualFirewallRulesRequest)
		if !reflect.DeepEqual(actualFirewallRulesRequest, rr) {
			t.Errorf("Request body = %+v, expected %+v", actualFirewallRulesRequest, rr)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.AddRules(ctx, fID, rr)

	if err != nil {
		t.Errorf("Firewalls.AddRules returned error: %v", err)
	}
}

func TestFirewalls_RemoveRules(t *testing.T) {
	setup()
	defer teardown()

	rr := &FirewallRulesRequest{
		InboundRules: []InboundRule{
			{
				Protocol:  "tcp",
				PortRange: "22",
				Sources: &Sources{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
		OutboundRules: []OutboundRule{
			{
				Protocol:  "tcp",
				PortRange: "443",
				Destinations: &Destinations{
					Addresses: []string{"0.0.0.0/0"},
				},
			},
		},
	}

	fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0"
	urlStr := path.Join("/v2/firewalls", fID, "rules")
	mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
		v := new(FirewallRulesRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodDelete)
		if !reflect.DeepEqual(v, rr) {
			t.Errorf("Request body = %+v, expected %+v", v, rr)
		}

		var actualFirewallRulesRequest *FirewallRulesRequest
		json.Unmarshal([]byte(firewallRulesJSONBody), &actualFirewallRulesRequest)
		if !reflect.DeepEqual(actualFirewallRulesRequest, rr) {
			t.Errorf("Request body = %+v, expected %+v", actualFirewallRulesRequest, rr)
		}

		fmt.Fprint(w, nil)
	})

	_, err := client.Firewalls.RemoveRules(ctx, fID, rr)

	if err != nil {
		t.Errorf("Firewalls.RemoveRules returned error: %v", err)
	}
}

func makeExpectedFirewalls() []Firewall {
	return []Firewall{
		Firewall{
			ID:   "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0",
			Name: "f-i-r-e-w-a-l-l",
			InboundRules: []InboundRule{
				{
					Protocol:  "icmp",
					PortRange: "0",
					Sources: &Sources{
						Tags: []string{"frontend"},
					},
				},
				{
					Protocol:  "tcp",
					PortRange: "8000-9000",
					Sources: &Sources{
						Addresses: []string{"0.0.0.0/0"},
					},
				},
			},
			OutboundRules: []OutboundRule{
				{
					Protocol:  "icmp",
					PortRange: "0",
				},
				{
					Protocol:  "tcp",
					PortRange: "8000-9000",
					Destinations: &Destinations{
						Addresses: []string{"::/1"},
					},
				},
			},
			DropletIDs: []int{123},
			Tags:       []string{"frontend"},
			Created:    "2017-04-06T13:07:27Z",
		},
	}
}


text:
# Contributing

If you submit a pull request, please keep the following guidelines in mind:

1. Code should be `go fmt` compliant.
2. Types, structs and funcs should be documented.
3. Tests pass.

## Getting set up

Assuming your `$GOPATH` is set up according to your desires, run:

```sh
go get github.com/digitalocean/godo
```

## Running tests

When working on code in this repository, tests can be run via:

```sh
go test .
```


text:
package godo

import (
	"fmt"
	"time"

	"net/http"

	"github.com/digitalocean/godo/context"
)

const (
	storageBasePath  = "v2"
	storageAllocPath = storageBasePath + "/volumes"
	storageSnapPath  = storageBasePath + "/snapshots"
)

// StorageService is an interface for interfacing with the storage
// endpoints of the Digital Ocean API.
// See: https://developers.digitalocean.com/documentation/v2#storage
type StorageService interface {
	ListVolumes(context.Context, *ListVolumeParams) ([]Volume, *Response, error)
	GetVolume(context.Context, string) (*Volume, *Response, error)
	CreateVolume(context.Context, *VolumeCreateRequest) (*Volume, *Response, error)
	DeleteVolume(context.Context, string) (*Response, error)
	ListSnapshots(ctx context.Context, volumeID string, opts *ListOptions) ([]Snapshot, *Response, error)
	GetSnapshot(context.Context, string) (*Snapshot, *Response, error)
	CreateSnapshot(context.Context, *SnapshotCreateRequest) (*Snapshot, *Response, error)
	DeleteSnapshot(context.Context, string) (*Response, error)
}

// StorageServiceOp handles communication with the storage volumes related methods of the
// DigitalOcean API.
type StorageServiceOp struct {
	client *Client
}

// ListVolumeParams stores the options you can set for a ListVolumeCall
type ListVolumeParams struct {
	Region      string       `json:"region"`
	Name        string       `json:"name"`
	ListOptions *ListOptions `json:"list_options,omitempty"`
}

var _ StorageService = &StorageServiceOp{}

// Volume represents a Digital Ocean block store volume.
type Volume struct {
	ID            string    `json:"id"`
	Region        *Region   `json:"region"`
	Name          string    `json:"name"`
	SizeGigaBytes int64     `json:"size_gigabytes"`
	Description   string    `json:"description"`
	DropletIDs    []int     `json:"droplet_ids"`
	CreatedAt     time.Time `json:"created_at"`
}

func (f Volume) String() string {
	return Stringify(f)
}

type storageVolumesRoot struct {
	Volumes []Volume `json:"volumes"`
	Links   *Links   `json:"links"`
}

type storageVolumeRoot struct {
	Volume *Volume `json:"volume"`
	Links  *Links  `json:"links,omitempty"`
}

// VolumeCreateRequest represents a request to create a block store
// volume.
type VolumeCreateRequest struct {
	Region        string `json:"region"`
	Name          string `json:"name"`
	Description   string `json:"description"`
	SizeGigaBytes int64  `json:"size_gigabytes"`
	SnapshotID    string `json:"snapshot_id"`
}

// ListVolumes lists all storage volumes.
func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolumeParams) ([]Volume, *Response, error) {
	path := storageAllocPath
	if params != nil {
		if params.Region != "" && params.Name != "" {
			path = fmt.Sprintf("%s?name=%s&region=%s", path, params.Name, params.Region)
		} else if params.Region != "" {
			path = fmt.Sprintf("%s?region=%s", path, params.Region)
		} else if params.Name != "" {
			path = fmt.Sprintf("%s?name=%s", path, params.Name)
		}

		if params.ListOptions != nil {
			var err error
			path, err = addOptions(path, params.ListOptions)
			if err != nil {
				return nil, nil, err
			}
		}
	}

	req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(storageVolumesRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	if l := root.Links; l != nil {
		resp.Links = l
	}

	return root.Volumes, resp, nil
}

// CreateVolume creates a storage volume. The name must be unique.
func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) {
	path := storageAllocPath

	req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
	if err != nil {
		return nil, nil, err
	}

	root := new(storageVolumeRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Volume, resp, nil
}

// GetVolume retrieves an individual storage volume.
func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, *Response, error) {
	path := fmt.Sprintf("%s/%s", storageAllocPath, id)

	req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(storageVolumeRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Volume, resp, nil
}

// DeleteVolume deletes a storage volume.
func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Response, error) {
	path := fmt.Sprintf("%s/%s", storageAllocPath, id)

	req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}
	return svc.client.Do(ctx, req, nil)
}

// SnapshotCreateRequest represents a request to create a block store
// volume.
type SnapshotCreateRequest struct {
	VolumeID    string `json:"volume_id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

// ListSnapshots lists all snapshots related to a storage volume.
func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string, opt *ListOptions) ([]Snapshot, *Response, error) {
	path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, volumeID)
	path, err := addOptions(path, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(snapshotsRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	if l := root.Links; l != nil {
		resp.Links = l
	}

	return root.Snapshots, resp, nil
}

// CreateSnapshot creates a snapshot of a storage volume.
func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) {
	path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID)

	req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
	if err != nil {
		return nil, nil, err
	}

	root := new(snapshotRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	return root.Snapshot, resp, nil
}

// GetSnapshot retrieves an individual snapshot.
func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snapshot, *Response, error) {
	path := fmt.Sprintf("%s/%s", storageSnapPath, id)

	req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(snapshotRoot)
	resp, err := svc.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Snapshot, resp, nil
}

// DeleteSnapshot deletes a snapshot.
func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Response, error) {
	path := fmt.Sprintf("%s/%s", storageSnapPath, id)

	req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}
	return svc.client.Do(ctx, req, nil)
}


text:
package godo

import (
	"fmt"
	"net/http"

	"github.com/digitalocean/godo/context"
)

const loadBalancersBasePath = "/v2/load_balancers"
const forwardingRulesPath = "forwarding_rules"

const dropletsPath = "droplets"

// LoadBalancersService is an interface for managing load balancers with the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2#load-balancers
type LoadBalancersService interface {
	Get(context.Context, string) (*LoadBalancer, *Response, error)
	List(context.Context, *ListOptions) ([]LoadBalancer, *Response, error)
	Create(context.Context, *LoadBalancerRequest) (*LoadBalancer, *Response, error)
	Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error)
	Delete(ctx context.Context, lbID string) (*Response, error)
	AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error)
	RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error)
	AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error)
	RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error)
}

// LoadBalancer represents a DigitalOcean load balancer configuration.
type LoadBalancer struct {
	ID                  string           `json:"id,omitempty"`
	Name                string           `json:"name,omitempty"`
	IP                  string           `json:"ip,omitempty"`
	Algorithm           string           `json:"algorithm,omitempty"`
	Status              string           `json:"status,omitempty"`
	Created             string           `json:"created_at,omitempty"`
	ForwardingRules     []ForwardingRule `json:"forwarding_rules,omitempty"`
	HealthCheck         *HealthCheck     `json:"health_check,omitempty"`
	StickySessions      *StickySessions  `json:"sticky_sessions,omitempty"`
	Region              *Region          `json:"region,omitempty"`
	DropletIDs          []int            `json:"droplet_ids,omitempty"`
	Tag                 string           `json:"tag,omitempty"`
	RedirectHttpToHttps bool             `json:"redirect_http_to_https,omitempty"`
}

// String creates a human-readable description of a LoadBalancer.
func (l LoadBalancer) String() string {
	return Stringify(l)
}

// AsRequest creates a LoadBalancerRequest that can be submitted to Update with the current values of the LoadBalancer.
// Modifying the returned LoadBalancerRequest will not modify the original LoadBalancer.
func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
	r := LoadBalancerRequest{
		Name:                l.Name,
		Algorithm:           l.Algorithm,
		ForwardingRules:     append([]ForwardingRule(nil), l.ForwardingRules...),
		DropletIDs:          append([]int(nil), l.DropletIDs...),
		Tag:                 l.Tag,
		RedirectHttpToHttps: l.RedirectHttpToHttps,
		HealthCheck:         l.HealthCheck,
	}
	if l.HealthCheck != nil {
		r.HealthCheck = &HealthCheck{}
		*r.HealthCheck = *l.HealthCheck
	}
	if l.StickySessions != nil {
		r.StickySessions = &StickySessions{}
		*r.StickySessions = *l.StickySessions
	}
	if l.Region != nil {
		r.Region = l.Region.Slug
	}
	return &r
}

// ForwardingRule represents load balancer forwarding rules.
type ForwardingRule struct {
	EntryProtocol  string `json:"entry_protocol,omitempty"`
	EntryPort      int    `json:"entry_port,omitempty"`
	TargetProtocol string `json:"target_protocol,omitempty"`
	TargetPort     int    `json:"target_port,omitempty"`
	CertificateID  string `json:"certificate_id,omitempty"`
	TlsPassthrough bool   `json:"tls_passthrough,omitempty"`
}

// String creates a human-readable description of a ForwardingRule.
func (f ForwardingRule) String() string {
	return Stringify(f)
}

// HealthCheck represents optional load balancer health check rules.
type HealthCheck struct {
	Protocol               string `json:"protocol,omitempty"`
	Port                   int    `json:"port,omitempty"`
	Path                   string `json:"path,omitempty"`
	CheckIntervalSeconds   int    `json:"check_interval_seconds,omitempty"`
	ResponseTimeoutSeconds int    `json:"response_timeout_seconds,omitempty"`
	HealthyThreshold       int    `json:"healthy_threshold,omitempty"`
	UnhealthyThreshold     int    `json:"unhealthy_threshold,omitempty"`
}

// String creates a human-readable description of a HealthCheck.
func (h HealthCheck) String() string {
	return Stringify(h)
}

// StickySessions represents optional load balancer session affinity rules.
type StickySessions struct {
	Type             string `json:"type,omitempty"`
	CookieName       string `json:"cookie_name,omitempty"`
	CookieTtlSeconds int    `json:"cookie_ttl_seconds,omitempty"`
}

// String creates a human-readable description of a StickySessions instance.
func (s StickySessions) String() string {
	return Stringify(s)
}

// LoadBalancerRequest represents the configuration to be applied to an existing or a new load balancer.
type LoadBalancerRequest struct {
	Name                string           `json:"name,omitempty"`
	Algorithm           string           `json:"algorithm,omitempty"`
	Region              string           `json:"region,omitempty"`
	ForwardingRules     []ForwardingRule `json:"forwarding_rules,omitempty"`
	HealthCheck         *HealthCheck     `json:"health_check,omitempty"`
	StickySessions      *StickySessions  `json:"sticky_sessions,omitempty"`
	DropletIDs          []int            `json:"droplet_ids,omitempty"`
	Tag                 string           `json:"tag,omitempty"`
	RedirectHttpToHttps bool             `json:"redirect_http_to_https,omitempty"`
}

// String creates a human-readable description of a LoadBalancerRequest.
func (l LoadBalancerRequest) String() string {
	return Stringify(l)
}

type forwardingRulesRequest struct {
	Rules []ForwardingRule `json:"forwarding_rules,omitempty"`
}

func (l forwardingRulesRequest) String() string {
	return Stringify(l)
}

type dropletIDsRequest struct {
	IDs []int `json:"droplet_ids,omitempty"`
}

func (l dropletIDsRequest) String() string {
	return Stringify(l)
}

type loadBalancersRoot struct {
	LoadBalancers []LoadBalancer `json:"load_balancers"`
	Links         *Links         `json:"links"`
}

type loadBalancerRoot struct {
	LoadBalancer *LoadBalancer `json:"load_balancer"`
}

// LoadBalancersServiceOp handles communication with load balancer-related methods of the DigitalOcean API.
type LoadBalancersServiceOp struct {
	client *Client
}

var _ LoadBalancersService = &LoadBalancersServiceOp{}

// Get an existing load balancer by its identifier.
func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBalancer, *Response, error) {
	path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)

	req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(loadBalancerRoot)
	resp, err := l.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.LoadBalancer, resp, err
}

// List load balancers, with optional pagination.
func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]LoadBalancer, *Response, error) {
	path, err := addOptions(loadBalancersBasePath, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(loadBalancersRoot)
	resp, err := l.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}
	if l := root.Links; l != nil {
		resp.Links = l
	}

	return root.LoadBalancers, resp, err
}

// Create a new load balancer with a given configuration.
func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
	req, err := l.client.NewRequest(ctx, http.MethodPost, loadBalancersBasePath, lbr)
	if err != nil {
		return nil, nil, err
	}

	root := new(loadBalancerRoot)
	resp, err := l.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.LoadBalancer, resp, err
}

// Update an existing load balancer with new configuration.
func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
	path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)

	req, err := l.client.NewRequest(ctx, "PUT", path, lbr)
	if err != nil {
		return nil, nil, err
	}

	root := new(loadBalancerRoot)
	resp, err := l.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.LoadBalancer, resp, err
}

// Delete a load balancer by its identifier.
func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Response, error) {
	path := fmt.Sprintf("%s/%s", loadBalancersBasePath, ldID)

	req, err := l.client.NewRequest(ctx, http.MethodDelete, path, nil)
	if err != nil {
		return nil, err
	}

	return l.client.Do(ctx, req, nil)
}

// AddDroplets adds droplets to a load balancer.
func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
	path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)

	req, err := l.client.NewRequest(ctx, http.MethodPost, path, &dropletIDsRequest{IDs: dropletIDs})
	if err != nil {
		return nil, err
	}

	return l.client.Do(ctx, req, nil)
}

// RemoveDroplets removes droplets from a load balancer.
func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
	path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)

	req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &dropletIDsRequest{IDs: dropletIDs})
	if err != nil {
		return nil, err
	}

	return l.client.Do(ctx, req, nil)
}

// AddForwardingRules adds forwarding rules to a load balancer.
func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
	path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)

	req, err := l.client.NewRequest(ctx, http.MethodPost, path, &forwardingRulesRequest{Rules: rules})
	if err != nil {
		return nil, err
	}

	return l.client.Do(ctx, req, nil)
}

// RemoveForwardingRules removes forwarding rules from a load balancer.
func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
	path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)

	req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &forwardingRulesRequest{Rules: rules})
	if err != nil {
		return nil, err
	}

	return l.client.Do(ctx, req, nil)
}


text:
package godo

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestFloatingIPsActions_Assign(t *testing.T) {
	setup()
	defer teardown()
	dropletID := 12345
	assignRequest := &ActionRequest{
		"droplet_id": float64(dropletID), // encoding/json decodes numbers as floats
		"type":       "assign",
	}

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, assignRequest) {
			t.Errorf("Request body = %#v, expected %#v", v, assignRequest)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)

	})

	assign, _, err := client.FloatingIPActions.Assign(ctx, "192.168.0.1", 12345)
	if err != nil {
		t.Errorf("FloatingIPsActions.Assign returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(assign, expected) {
		t.Errorf("FloatingIPsActions.Assign returned %+v, expected %+v", assign, expected)
	}
}

func TestFloatingIPsActions_Unassign(t *testing.T) {
	setup()
	defer teardown()

	unassignRequest := &ActionRequest{
		"type": "unassign",
	}

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
		v := new(ActionRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatalf("decode json: %v", err)
		}

		testMethod(t, r, http.MethodPost)
		if !reflect.DeepEqual(v, unassignRequest) {
			t.Errorf("Request body = %+v, expected %+v", v, unassignRequest)
		}

		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.FloatingIPActions.Unassign(ctx, "192.168.0.1")
	if err != nil {
		t.Errorf("FloatingIPsActions.Get returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("FloatingIPsActions.Get returned %+v, expected %+v", action, expected)
	}
}

func TestFloatingIPsActions_Get(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions/456", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
	})

	action, _, err := client.FloatingIPActions.Get(ctx, "192.168.0.1", 456)
	if err != nil {
		t.Errorf("FloatingIPsActions.Get returned error: %v", err)
	}

	expected := &Action{Status: "in-progress"}
	if !reflect.DeepEqual(action, expected) {
		t.Errorf("FloatingIPsActions.Get returned %+v, expected %+v", action, expected)
	}
}

func TestFloatingIPsActions_List(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `{"actions":[{"status":"in-progress"}]}`)
	})

	actions, _, err := client.FloatingIPActions.List(ctx, "192.168.0.1", nil)
	if err != nil {
		t.Errorf("FloatingIPsActions.List returned error: %v", err)
	}

	expected := []Action{{Status: "in-progress"}}
	if !reflect.DeepEqual(actions, expected) {
		t.Errorf("FloatingIPsActions.List returned %+v, expected %+v", actions, expected)
	}
}

func TestFloatingIPsActions_ListMultiplePages(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, `{"actions":[{"status":"in-progress"}], "links":{"pages":{"next":"http://example.com/v2/floating_ips/192.168.0.1/actions?page=2"}}}`)
	})

	_, resp, err := client.FloatingIPActions.List(ctx, "192.168.0.1", nil)
	if err != nil {
		t.Errorf("FloatingIPsActions.List returned error: %v", err)
	}

	checkCurrentPage(t, resp, 1)
}

func TestFloatingIPsActions_ListPageByNumber(t *testing.T) {
	setup()
	defer teardown()

	jBlob := `
	{
		"actions":[{"status":"in-progress"}],
		"links":{
			"pages":{
				"next":"http://example.com/v2/regions/?page=3",
				"prev":"http://example.com/v2/regions/?page=1",
				"last":"http://example.com/v2/regions/?page=3",
				"first":"http://example.com/v2/regions/?page=1"
			}
		}
	}`

	mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, jBlob)
	})

	opt := &ListOptions{Page: 2}
	_, resp, err := client.FloatingIPActions.List(ctx, "192.168.0.1", opt)
	if err != nil {
		t.Errorf("FloatingIPsActions.List returned error: %v", err)
	}

	checkCurrentPage(t, resp, 2)
}


text:
package godo

import (
	"fmt"
	"net/http"
	"net/url"

	"github.com/digitalocean/godo/context"
)

// ActionRequest reprents DigitalOcean Action Request
type ActionRequest map[string]interface{}

// DropletActionsService is an interface for interfacing with the Droplet actions
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#droplet-actions
type DropletActionsService interface {
	Shutdown(context.Context, int) (*Action, *Response, error)
	ShutdownByTag(context.Context, string) ([]Action, *Response, error)
	PowerOff(context.Context, int) (*Action, *Response, error)
	PowerOffByTag(context.Context, string) ([]Action, *Response, error)
	PowerOn(context.Context, int) (*Action, *Response, error)
	PowerOnByTag(context.Context, string) ([]Action, *Response, error)
	PowerCycle(context.Context, int) (*Action, *Response, error)
	PowerCycleByTag(context.Context, string) ([]Action, *Response, error)
	Reboot(context.Context, int) (*Action, *Response, error)
	Restore(context.Context, int, int) (*Action, *Response, error)
	Resize(context.Context, int, string, bool) (*Action, *Response, error)
	Rename(context.Context, int, string) (*Action, *Response, error)
	Snapshot(context.Context, int, string) (*Action, *Response, error)
	SnapshotByTag(context.Context, string, string) ([]Action, *Response, error)
	EnableBackups(context.Context, int) (*Action, *Response, error)
	EnableBackupsByTag(context.Context, string) ([]Action, *Response, error)
	DisableBackups(context.Context, int) (*Action, *Response, error)
	DisableBackupsByTag(context.Context, string) ([]Action, *Response, error)
	PasswordReset(context.Context, int) (*Action, *Response, error)
	RebuildByImageID(context.Context, int, int) (*Action, *Response, error)
	RebuildByImageSlug(context.Context, int, string) (*Action, *Response, error)
	ChangeKernel(context.Context, int, int) (*Action, *Response, error)
	EnableIPv6(context.Context, int) (*Action, *Response, error)
	EnableIPv6ByTag(context.Context, string) ([]Action, *Response, error)
	EnablePrivateNetworking(context.Context, int) (*Action, *Response, error)
	EnablePrivateNetworkingByTag(context.Context, string) ([]Action, *Response, error)
	Upgrade(context.Context, int) (*Action, *Response, error)
	Get(context.Context, int, int) (*Action, *Response, error)
	GetByURI(context.Context, string) (*Action, *Response, error)
}

// DropletActionsServiceOp handles communication with the Droplet action related
// methods of the DigitalOcean API.
type DropletActionsServiceOp struct {
	client *Client
}

var _ DropletActionsService = &DropletActionsServiceOp{}

// Shutdown a Droplet
func (s *DropletActionsServiceOp) Shutdown(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "shutdown"}
	return s.doAction(ctx, id, request)
}

// ShutdownByTag shuts down Droplets matched by a Tag.
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "shutdown"}
	return s.doActionByTag(ctx, tag, request)
}

// PowerOff a Droplet
func (s *DropletActionsServiceOp) PowerOff(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "power_off"}
	return s.doAction(ctx, id, request)
}

// PowerOffByTag powers off Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "power_off"}
	return s.doActionByTag(ctx, tag, request)
}

// PowerOn a Droplet
func (s *DropletActionsServiceOp) PowerOn(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "power_on"}
	return s.doAction(ctx, id, request)
}

// PowerOnByTag powers on Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "power_on"}
	return s.doActionByTag(ctx, tag, request)
}

// PowerCycle a Droplet
func (s *DropletActionsServiceOp) PowerCycle(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "power_cycle"}
	return s.doAction(ctx, id, request)
}

// PowerCycleByTag power cycles Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "power_cycle"}
	return s.doActionByTag(ctx, tag, request)
}

// Reboot a Droplet
func (s *DropletActionsServiceOp) Reboot(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "reboot"}
	return s.doAction(ctx, id, request)
}

// Restore an image to a Droplet
func (s *DropletActionsServiceOp) Restore(ctx context.Context, id, imageID int) (*Action, *Response, error) {
	requestType := "restore"
	request := &ActionRequest{
		"type":  requestType,
		"image": float64(imageID),
	}
	return s.doAction(ctx, id, request)
}

// Resize a Droplet
func (s *DropletActionsServiceOp) Resize(ctx context.Context, id int, sizeSlug string, resizeDisk bool) (*Action, *Response, error) {
	requestType := "resize"
	request := &ActionRequest{
		"type": requestType,
		"size": sizeSlug,
		"disk": resizeDisk,
	}
	return s.doAction(ctx, id, request)
}

// Rename a Droplet
func (s *DropletActionsServiceOp) Rename(ctx context.Context, id int, name string) (*Action, *Response, error) {
	requestType := "rename"
	request := &ActionRequest{
		"type": requestType,
		"name": name,
	}
	return s.doAction(ctx, id, request)
}

// Snapshot a Droplet.
func (s *DropletActionsServiceOp) Snapshot(ctx context.Context, id int, name string) (*Action, *Response, error) {
	requestType := "snapshot"
	request := &ActionRequest{
		"type": requestType,
		"name": name,
	}
	return s.doAction(ctx, id, request)
}

// SnapshotByTag snapshots Droplets matched by a Tag.
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) ([]Action, *Response, error) {
	requestType := "snapshot"
	request := &ActionRequest{
		"type": requestType,
		"name": name,
	}
	return s.doActionByTag(ctx, tag, request)
}

// EnableBackups enables backups for a Droplet.
func (s *DropletActionsServiceOp) EnableBackups(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "enable_backups"}
	return s.doAction(ctx, id, request)
}

// EnableBackupsByTag enables backups for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "enable_backups"}
	return s.doActionByTag(ctx, tag, request)
}

// DisableBackups disables backups for a Droplet.
func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "disable_backups"}
	return s.doAction(ctx, id, request)
}

// DisableBackupsByTag disables backups for Droplet matched by a Tag.
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "disable_backups"}
	return s.doActionByTag(ctx, tag, request)
}

// PasswordReset resets the password for a Droplet.
func (s *DropletActionsServiceOp) PasswordReset(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "password_reset"}
	return s.doAction(ctx, id, request)
}

// RebuildByImageID rebuilds a Droplet from an image with a given id.
func (s *DropletActionsServiceOp) RebuildByImageID(ctx context.Context, id, imageID int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "rebuild", "image": imageID}
	return s.doAction(ctx, id, request)
}

// RebuildByImageSlug rebuilds a Droplet from an Image matched by a given Slug.
func (s *DropletActionsServiceOp) RebuildByImageSlug(ctx context.Context, id int, slug string) (*Action, *Response, error) {
	request := &ActionRequest{"type": "rebuild", "image": slug}
	return s.doAction(ctx, id, request)
}

// ChangeKernel changes the kernel for a Droplet.
func (s *DropletActionsServiceOp) ChangeKernel(ctx context.Context, id, kernelID int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "change_kernel", "kernel": kernelID}
	return s.doAction(ctx, id, request)
}

// EnableIPv6 enables IPv6 for a Droplet.
func (s *DropletActionsServiceOp) EnableIPv6(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "enable_ipv6"}
	return s.doAction(ctx, id, request)
}

// EnableIPv6ByTag enables IPv6 for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "enable_ipv6"}
	return s.doActionByTag(ctx, tag, request)
}

// EnablePrivateNetworking enables private networking for a Droplet.
func (s *DropletActionsServiceOp) EnablePrivateNetworking(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "enable_private_networking"}
	return s.doAction(ctx, id, request)
}

// EnablePrivateNetworkingByTag enables private networking for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
	request := &ActionRequest{"type": "enable_private_networking"}
	return s.doActionByTag(ctx, tag, request)
}

// Upgrade a Droplet.
func (s *DropletActionsServiceOp) Upgrade(ctx context.Context, id int) (*Action, *Response, error) {
	request := &ActionRequest{"type": "upgrade"}
	return s.doAction(ctx, id, request)
}

func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request *ActionRequest) (*Action, *Response, error) {
	if id < 1 {
		return nil, nil, NewArgError("id", "cannot be less than 1")
	}

	if request == nil {
		return nil, nil, NewArgError("request", "request can't be nil")
	}

	path := dropletActionPath(id)

	req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Event, resp, err
}

func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) ([]Action, *Response, error) {
	if tag == "" {
		return nil, nil, NewArgError("tag", "cannot be empty")
	}

	if request == nil {
		return nil, nil, NewArgError("request", "request can't be nil")
	}

	path := dropletActionPathByTag(tag)

	req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionsRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Actions, resp, err
}

// Get an action for a particular Droplet by id.
func (s *DropletActionsServiceOp) Get(ctx context.Context, dropletID, actionID int) (*Action, *Response, error) {
	if dropletID < 1 {
		return nil, nil, NewArgError("dropletID", "cannot be less than 1")
	}

	if actionID < 1 {
		return nil, nil, NewArgError("actionID", "cannot be less than 1")
	}

	path := fmt.Sprintf("%s/%d", dropletActionPath(dropletID), actionID)
	return s.get(ctx, path)
}

// GetByURI gets an action for a particular Droplet by id.
func (s *DropletActionsServiceOp) GetByURI(ctx context.Context, rawurl string) (*Action, *Response, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, nil, err
	}

	return s.get(ctx, u.Path)

}

func (s *DropletActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
	req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
	if err != nil {
		return nil, nil, err
	}

	root := new(actionRoot)
	resp, err := s.client.Do(ctx, req, root)
	if err != nil {
		return nil, resp, err
	}

	return root.Event, resp, err

}

func dropletActionPath(dropletID int) string {
	return fmt.Sprintf("v2/droplets/%d/actions", dropletID)
}

func dropletActionPathByTag(tag string) string {
	return fmt.Sprintf("v2/droplets/actions?tag_name=%s", tag)
}


text:
package godo

import "testing"

func TestArgError(t *testing.T) {
	expected := "foo is invalid because bar"
	err := NewArgError("foo", "bar")
	if got := err.Error(); got != expected {
		t.Errorf("ArgError().Error() = %q; expected %q", got, expected)
	}
}