Skip to content

Commit

Permalink
add v1 and v2 client for diff sdks, break out client
Browse files Browse the repository at this point in the history
  • Loading branch information
nickfiggins committed Nov 26, 2023
1 parent c22a259 commit 4ac20bf
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 83 deletions.
92 changes: 92 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package elasticspot

import (
"context"
"errors"
"fmt"
"os"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)

// Client will create a new client for version 1 of the AWS SDK.
type Client struct {
EC2API EC2API
}

type EC2API interface {
DescribeAddresses(input *ec2.DescribeAddressesInput) (*ec2.DescribeAddressesOutput, error)
AssociateAddress(input *ec2.AssociateAddressInput) (*ec2.AssociateAddressOutput, error)
DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
}

func (c *Client) AssociateIP(ctx context.Context, ip, instanceID string) (*AssociateResponse, error) {
instance, err := c.getInstanceById(instanceID)
if err != nil {
return nil, fmt.Errorf("error fetching instance: %w", err)
}

if *instance.PublicIpAddress == ip {
return &AssociateResponse{AlreadyAssociated: true}, nil
}

address, err := c.getAddressForIp(ip)
if err != nil {
return nil, fmt.Errorf("error fetching elastic ip address: %w", err)
}

assocRes, err := c.EC2API.AssociateAddress(&ec2.AssociateAddressInput{
AllocationId: address.AllocationId,
InstanceId: aws.String(instanceID),
})
if err != nil {
return nil, fmt.Errorf("error associating elastic ip address with %q %w", instanceID, err)
}

return &AssociateResponse{
AllocationID: *address.AllocationId,
AssociationID: *assocRes.AssociationId,
}, nil
}

func (a *Client) getInstanceById(id string) (*ec2.Instance, error) {
instances, err := a.EC2API.DescribeInstances(&ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("instance-id"),
Values: aws.StringSlice([]string{id}),
},
},
})
if err != nil {
return nil, fmt.Errorf("DescribeInstances failed: %w", err)
}

if instances == nil || len(instances.Reservations) == 0 || len(instances.Reservations[0].Instances) == 0 {
return nil, errors.New("no instance found for the given id")
}

return instances.Reservations[0].Instances[0], nil
}

func (a *Client) getAddressForIp(ip string) (*ec2.Address, error) {
result, err := a.EC2API.DescribeAddresses(&ec2.DescribeAddressesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("domain"),
Values: aws.StringSlice([]string{"vpc"}),
},
},
PublicIps: []*string{aws.String(ip)},
})
if err != nil {
return nil, err
}

if len(result.Addresses) == 0 {
return nil, fmt.Errorf("elastic ip address not found in region %q", os.Getenv("AWS_REGION"))
}

return result.Addresses[0], nil
}
99 changes: 99 additions & 0 deletions client_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package elasticspot

import (
"context"
"errors"
"fmt"
"os"

ec2v2 "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go/aws"
)

type AssociateResponse struct {
AlreadyAssociated bool
AllocationID string
AssociationID string
}

// ClientV2 will create a new client for version 2 of the AWS SDK.
type ClientV2 struct {
EC2API EC2APIV2
}

type EC2APIV2 interface {
DescribeAddresses(ctx context.Context, params *ec2v2.DescribeAddressesInput, optFns ...func(*ec2v2.Options)) (*ec2v2.DescribeAddressesOutput, error)
AssociateAddress(ctx context.Context, params *ec2v2.AssociateAddressInput, optFns ...func(*ec2v2.Options)) (*ec2v2.AssociateAddressOutput, error)
DescribeInstances(ctx context.Context, params *ec2v2.DescribeInstancesInput, optFns ...func(*ec2v2.Options)) (*ec2v2.DescribeInstancesOutput, error)
}

func (a *ClientV2) AssociateIP(ctx context.Context, ip string, instanceID string) (*AssociateResponse, error) {
instance, err := a.getInstanceById(ctx, instanceID)
if err != nil {
return nil, fmt.Errorf("error fetching instance: %w", err)
}

if *instance.PublicIpAddress == ip {
return &AssociateResponse{AlreadyAssociated: true}, nil
}

address, err := a.getAddressForIp(ctx, ip)
if err != nil {
return nil, fmt.Errorf("error fetching elastic ip address: %w", err)
}

assocRes, err := a.EC2API.AssociateAddress(ctx, &ec2v2.AssociateAddressInput{
AllocationId: address.AllocationId,
InstanceId: aws.String(instanceID),
})
if err != nil {
return nil, fmt.Errorf("error associating elastic ip address with %q %w", instanceID, err)
}

return &AssociateResponse{
AllocationID: *address.AllocationId,
AssociationID: *assocRes.AssociationId,
}, nil
}

func (a *ClientV2) getInstanceById(ctx context.Context, id string) (*types.Instance, error) {
instances, err := a.EC2API.DescribeInstances(ctx, &ec2v2.DescribeInstancesInput{
Filters: []types.Filter{
{
Name: aws.String("instance-id"),
Values: []string{id},
},
},
})
if err != nil {
return nil, fmt.Errorf("DescribeInstances failed: %w", err)
}

if instances == nil || len(instances.Reservations) == 0 || len(instances.Reservations[0].Instances) == 0 {
return nil, errors.New("no instance found for the given id")
}

return &instances.Reservations[0].Instances[0], nil
}

func (c *ClientV2) getAddressForIp(ctx context.Context, ip string) (*types.Address, error) {
result, err := c.EC2API.DescribeAddresses(ctx, &ec2v2.DescribeAddressesInput{
Filters: []types.Filter{
{
Name: aws.String("domain"),
Values: []string{"vpc"},
},
},
PublicIps: []string{ip},
})
if err != nil {
return nil, err
}

if len(result.Addresses) == 0 {
return nil, fmt.Errorf("elastic ip address not found in region %q", os.Getenv("AWS_REGION"))
}

return &result.Addresses[0], nil
}
2 changes: 1 addition & 1 deletion cmd/main.go → cmd/v1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/nickfiggins/elasticspot"
)

var handler *elasticspot.V1Handler
var handler *elasticspot.Handler

func init() {
cfg := &aws.Config{Region: aws.String(os.Getenv("AWS_REGION"))}
Expand Down
33 changes: 33 additions & 0 deletions cmd/v2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"context"
"fmt"
"os"

"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/nickfiggins/elasticspot"
)

var handler elasticspot.HandleFunc

func init() {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
panic(fmt.Sprintf("error loading default config: %v", err))
}

client := ec2.NewFromConfig(cfg)
elasticIP := os.Getenv("ELASTIC_IP")
if len(elasticIP) == 0 {
panic("Elastic IP has not been set.")
}
handler = elasticspot.HandleV2(client, elasticIP)
}

func main() {
lambda.Start(handler)
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ go 1.16
require (
github.com/aws/aws-lambda-go v1.28.0
github.com/aws/aws-sdk-go v1.43.12
github.com/stretchr/testify v1.7.0
github.com/aws/aws-sdk-go-v2/config v1.25.5
github.com/aws/aws-sdk-go-v2/service/ec2 v1.137.1
github.com/stretchr/testify v1.7.0 // indirect
)
31 changes: 30 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,41 @@ github.com/aws/aws-lambda-go v1.28.0 h1:fZiik1PZqW2IyAN4rj+Y0UBaO1IDFlsNo9Zz/XnA
github.com/aws/aws-lambda-go v1.28.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
github.com/aws/aws-sdk-go v1.43.12 h1:wOdx6+reSDpUBFEuJDA6edCrojzy8rOtMzhS2rD9+7M=
github.com/aws/aws-sdk-go v1.43.12/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI=
github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
github.com/aws/aws-sdk-go-v2/config v1.25.5 h1:UGKm9hpQS2hoK8CEJ1BzAW8NbUpvwDJJ4lyqXSzu8bk=
github.com/aws/aws-sdk-go-v2/config v1.25.5/go.mod h1:Bf4gDvy4ZcFIK0rqDu1wp9wrubNba2DojiPB2rt6nvI=
github.com/aws/aws-sdk-go-v2/credentials v1.16.4 h1:i7UQYYDSJrtc30RSwJwfBKwLFNnBTiICqAJ0pPdum8E=
github.com/aws/aws-sdk-go-v2/credentials v1.16.4/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.137.1 h1:J/N4ydefXQZIwKBDPtvrhxrIuP/vaaYKnAsy3bKVIvU=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.137.1/go.mod h1:hrBzQzlQQRmiaeYRQPr0SdSx6fdqP+5YcGhb97LCt8M=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY=
github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI=
github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
Expand All @@ -28,7 +58,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
Expand Down
Loading

0 comments on commit 4ac20bf

Please sign in to comment.