Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add v1 and v2 client for diff sdks, break out client #2

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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