Skip to content

Commit

Permalink
Add ability to delete using ip address
Browse files Browse the repository at this point in the history
Added lookup functions to allow the use of the ip address instead
of the id when deleting exit nodes

Signed-off-by: Adam Johnson <[email protected]>
  • Loading branch information
adamjohnson01 committed Jan 22, 2020
1 parent 5e219c2 commit 585d0f3
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 102 deletions.
52 changes: 34 additions & 18 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package cmd
import (
"fmt"

"github.com/inlets/inletsctl/pkg/provision"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
Expand All @@ -14,15 +15,19 @@ func init() {
inletsCmd.AddCommand(deleteCmd)
deleteCmd.Flags().StringP("provider", "p", "digitalocean", "The cloud provider - digitalocean, gce, ec2, packet, scaleway, or civo")
deleteCmd.Flags().StringP("region", "r", "lon1", "The region for your cloud provider")
deleteCmd.Flags().StringP("zone", "z", "us-central1-a", "The zone for the exit node (Google Compute Engine)")

deleteCmd.Flags().StringP("access-token", "a", "", "The access token for your cloud")
deleteCmd.Flags().StringP("access-token-file", "f", "", "Read this file for the access token for your cloud")

deleteCmd.Flags().StringP("id", "i", "", "Host ID")
deleteCmd.Flags().String("ip", "", "Host IP")

deleteCmd.Flags().String("secret-key", "", "The access token for your cloud (Scaleway, EC2)")
deleteCmd.Flags().String("secret-key-file", "", "Read this file for the access token for your cloud (Scaleway, EC2)")
deleteCmd.Flags().String("organisation-id", "", "Organisation ID (Scaleway)")
deleteCmd.Flags().String("project-id", "", "Project ID (Packet.com, Google Compute Engine)")

}

// deleteCmd represents the client sub command
Expand All @@ -49,7 +54,7 @@ func runDelete(cmd *cobra.Command, _ []string) error {

var region string
if cmd.Flags().Changed("region") {
if regionVal, err := cmd.Flags().GetString("region"); len(regionVal) > 0 {
if regionVal, err := cmd.Flags().GetString("region"); isSet(regionVal) {
if err != nil {
return errors.Wrap(err, "failed to get 'region' value.")
}
Expand All @@ -62,19 +67,6 @@ func runDelete(cmd *cobra.Command, _ []string) error {
region = "eu-west-1"
}

inletsToken, err := cmd.Flags().GetString("inlets-token")
if err != nil {
return errors.Wrap(err, "failed to get 'inlets-token' value.")
}
if len(inletsToken) == 0 {
var passwordErr error
inletsToken, passwordErr = generateAuth()

if passwordErr != nil {
return passwordErr
}
}

accessToken, err := getFileOrString(cmd.Flags(), "access-token-file", "access-token", true)
if err != nil {
return err
Expand Down Expand Up @@ -104,17 +96,41 @@ func runDelete(cmd *cobra.Command, _ []string) error {
}

hostID, _ := cmd.Flags().GetString("id")
hostIP, _ := cmd.Flags().GetString("ip")
projectID, _ := cmd.Flags().GetString("project-id")
zone, _ := cmd.Flags().GetString("zone")

if isNotSet(hostID) && isNotSet(hostIP) {
return fmt.Errorf("give a valid --id or --ip for your host")
}

if provider == "gce" && isSet(hostIP) {
if isNotSet(projectID) {
return fmt.Errorf("--ip requires --project-id to be set for provider")
}
}

if len(hostID) == 0 {
return fmt.Errorf("give a valid --id for your host")
deleteRequest := provision.HostDeleteRequest{
ID: hostID,
IP: hostIP,
ProjectID: projectID,
Zone: zone,
}

fmt.Printf("Deleting host: %s from %s\n", hostID, provider)
fmt.Printf("Deleting host: %s%s from %s\n", hostID, hostIP, provider)

err = provisioner.Delete(hostID)
err = provisioner.Delete(deleteRequest)
if err != nil {
return err
}

return err
}

func isNotSet(s string) bool {
return len(s) == 0
}

func isSet(s string) bool {
return len(s) > 0
}
128 changes: 86 additions & 42 deletions pkg/provision/civo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package provision
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
Expand All @@ -22,12 +23,12 @@ type CivoProvisioner struct {

// NewCivoProvisioner with an accessKey
func NewCivoProvisioner(accessKey string) (*CivoProvisioner, error) {

return &CivoProvisioner{
APIKey: accessKey,
}, nil
}

// Status gets the status of the exit node
func (p *CivoProvisioner) Status(id string) (*ProvisionedHost, error) {
host := &ProvisionedHost{}

Expand All @@ -40,7 +41,7 @@ func (p *CivoProvisioner) Status(id string) (*ProvisionedHost, error) {
addAuth(req, p.APIKey)

req.Header.Add("Accept", "application/json")
instance := CreatedInstance{}
instance := createdInstance{}

res, err := http.DefaultClient.Do(req)
if err != nil {
Expand Down Expand Up @@ -69,41 +70,28 @@ func (p *CivoProvisioner) Status(id string) (*ProvisionedHost, error) {
}, nil
}

func (p *CivoProvisioner) Delete(id string) error {

apiURL := fmt.Sprint("https://api.civo.com/v2/instances/", id)

req, err := http.NewRequest(http.MethodDelete, apiURL, nil)
if err != nil {
return err
// Delete terminates the exit node
func (p *CivoProvisioner) Delete(request HostDeleteRequest) error {
var id string
var err error
if len(request.ID) > 0 {
id = request.ID
} else {
id, err = p.lookupID(request)
if err != nil {
return err
}
}
addAuth(req, p.APIKey)

req.Header.Add("Accept", "application/json")
instance := CreatedInstance{}

res, err := http.DefaultClient.Do(req)
apiURL := fmt.Sprint("https://api.civo.com/v2/instances/", id)
_, err = apiCall(p.APIKey, http.MethodDelete, apiURL, nil)
if err != nil {
return err
}

var body []byte
if res.Body != nil {
defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
}

if res.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected HTTP code: %d\n%q", res.StatusCode, string(body))
}

unmarshalErr := json.Unmarshal(body, &instance)
if unmarshalErr != nil {
return unmarshalErr
}
return nil
}

// Provision creates a new exit node
func (p *CivoProvisioner) Provision(host BasicHost) (*ProvisionedHost, error) {

log.Printf("Provisioning host with Civo\n")
Expand All @@ -123,8 +111,47 @@ func (p *CivoProvisioner) Provision(host BasicHost) (*ProvisionedHost, error) {
}, nil
}

func provisionCivoInstance(host BasicHost, key string) (CreatedInstance, error) {
instance := CreatedInstance{}
// List returns a list of exit nodes
func (p *CivoProvisioner) List(filter ListFilter) ([]*ProvisionedHost, error) {
var inlets []*ProvisionedHost
apiURL := fmt.Sprintf("https://api.civo.com/v2/instances/?tags=%s", filter.Filter)
body, err := apiCall(p.APIKey, http.MethodGet, apiURL, nil)
if err != nil {
return inlets, err
}

var resp apiResponse
unmarshalErr := json.Unmarshal(body, &resp)
if unmarshalErr != nil {
return inlets, unmarshalErr
}

for _, instance := range resp.Items {
host := &ProvisionedHost{
IP: instance.PublicIP,
ID: instance.ID,
Status: instance.Status,
}
inlets = append(inlets, host)
}
return inlets, nil
}

func (p *CivoProvisioner) lookupID(request HostDeleteRequest) (string, error) {
inlets, err := p.List(ListFilter{Filter: "inlets"})
if err != nil {
return "", err
}
for _, inlet := range inlets {
if inlet.IP == request.IP {
return inlet.ID, nil
}
}
return "", fmt.Errorf("no host with ip: %s", request.IP)
}

func provisionCivoInstance(host BasicHost, key string) (createdInstance, error) {
instance := createdInstance{}

apiURL := "https://api.civo.com/v2/instances"

Expand All @@ -137,40 +164,57 @@ func provisionCivoInstance(host BasicHost, key string) (CreatedInstance, error)
values.Add("script", host.UserData)
values.Add("tags", "inlets")

req, err := http.NewRequest(http.MethodPost, apiURL, strings.NewReader(values.Encode()))
body, err := apiCall(key, http.MethodPost, apiURL, strings.NewReader(values.Encode()))
if err != nil {
return instance, err
}

unmarshalErr := json.Unmarshal(body, &instance)
if unmarshalErr != nil {
return instance, unmarshalErr
}

fmt.Printf("Instance ID: %s\n", instance.ID)
return instance, nil
}

func apiCall(key, method, url string, requestBody io.Reader) ([]byte, error) {

req, err := http.NewRequest(method, url, requestBody)
if err != nil {
return nil, err
}
addAuth(req, key)

req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

res, err := http.DefaultClient.Do(req)
if err != nil {
return instance, err
return nil, err
}

var body []byte
if res.Body != nil {
defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
}

if res.StatusCode != http.StatusOK {
return instance, fmt.Errorf("unexpected HTTP code: %d\n%q", res.StatusCode, string(body))
return nil, fmt.Errorf("unexpected HTTP code: %d\n%q", res.StatusCode, string(body))
}

unmarshalErr := json.Unmarshal(body, &instance)
if unmarshalErr != nil {
return instance, unmarshalErr
}
return body, nil
}

fmt.Printf("Instance ID: %s\n", instance.ID)
return instance, nil
type apiResponse struct {
Items []createdInstance `json:"items"`
}

type CreatedInstance struct {
type createdInstance struct {
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
PublicIP string `json:"public_ip"`
Expand Down
Loading

0 comments on commit 585d0f3

Please sign in to comment.