Skip to content

Commit

Permalink
Merge pull request #19 from nduyphuong/add-localstack-test
Browse files Browse the repository at this point in the history
add suppport for testing with localstack
  • Loading branch information
jacobnguyenn committed Aug 23, 2023
2 parents 9ca5b86 + 5acc66f commit 54b0838
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,5 @@ sketch
# Build folder
build
app
volume
.env
6 changes: 5 additions & 1 deletion cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func newServerCommand() *cobra.Command {
//process result from channel
go func() {
for task := range taskProcessResultChan {
log.Infof("popped item %v", task)
var elem worker.QueueElem
err := json.Unmarshal([]byte(task), &elem)
if err != nil {
Expand All @@ -89,10 +90,13 @@ func newServerCommand() *cobra.Command {
return
}
req.Header.Set("Content-Type", "application/json")
_, err = http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Errorf("making request %v", err)
}
if resp.StatusCode != http.StatusOK {
log.Errorf("change state request failed with status code: %v", resp.StatusCode)
}
}
}()
//periodically check if there is action to be done
Expand Down
17 changes: 16 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ services:
GORYA_DB_TYPE: mysql
GORYA_REDIS_ADDR: redis:6379
GORYA_QUEUE_NAME: gorya
AWS_ENDPOINT: http://localstack:4566
depends_on:
redis:
condition: service_healthy
Expand All @@ -69,7 +70,21 @@ services:
- gorya-backend
networks:
- gorya

localstack:
container_name: "${LOCALSTACK_DOCKER_NAME-gorya_localstack}"
image: localstack/localstack-pro
ports:
- "127.0.0.1:4566:4566" # LocalStack Gateway
- "127.0.0.1:4510-4559:4510-4559" # external services port range
environment:
- DEBUG=${DEBUG-}
- DOCKER_HOST=unix:///var/run/docker.sock
- LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY-}
volumes:
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
- gorya
networks:
gorya:
driver: bridge
Expand Down
18 changes: 12 additions & 6 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package api

import (
"context"
"net"
"net/http"
"time"

"github.com/nduyphuong/gorya/internal/api/config"
"github.com/nduyphuong/gorya/internal/api/handler"
"github.com/nduyphuong/gorya/internal/logging"
Expand All @@ -12,12 +16,10 @@ import (
"github.com/nduyphuong/gorya/internal/worker"
svcv1alpha1 "github.com/nduyphuong/gorya/pkg/api/service/v1alpha1"
"github.com/nduyphuong/gorya/pkg/aws"
awsOptions "github.com/nduyphuong/gorya/pkg/aws/options"
"github.com/pkg/errors"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"net"
"net/http"
"time"
)

type Server interface {
Expand All @@ -43,15 +45,19 @@ func (s *server) Serve(ctx context.Context, l net.Listener) error {
log := logging.LoggerFromContext(ctx)
log.Infof("Server is listening on %q", l.Addr().String())
mux := http.NewServeMux()
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Ok"))
})
s.sc, err = store.GetOnce()
if err != nil {
return err
}
awsRegion := os.GetEnv("AWS_REGION", "ap-southeast-1")
s.aws, err = aws.New(ctx, awsRegion)
awsEndpoint := os.GetEnv("AWS_ENDPOINT", "")
s.aws, err = aws.New(ctx,
awsOptions.WithRegion(awsRegion),
awsOptions.WithEndpoint(awsEndpoint),
)
if err != nil {
return err
}
Expand Down
8 changes: 5 additions & 3 deletions internal/api/handler/change_state_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ package handler
import (
"context"
"encoding/json"
"net/http"

svcv1alpha1 "github.com/nduyphuong/gorya/pkg/api/service/v1alpha1"
"github.com/nduyphuong/gorya/pkg/aws"
"net/http"
pkgerrors "github.com/pkg/errors"
)

func ChangeStateV1alpha1(ctx context.Context, awsClient aws.Interface) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
compute := awsClient.EC2()
m := svcv1alpha1.ChangeStateRequest{}
if err := json.NewDecoder(req.Body).Decode(&m); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, pkgerrors.Wrap(err, "decode state change request body").Error(), http.StatusBadRequest)
return
}
if err := compute.ChangeStatus(ctx, m.Action, m.TagKey, m.TagValue); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, pkgerrors.Wrap(err, "change compute status").Error(), http.StatusInternalServerError)
return
}
}
Expand Down
40 changes: 34 additions & 6 deletions pkg/aws/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@ import (
"context"
"sync"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/nduyphuong/gorya/pkg/aws/ec2"
"github.com/nduyphuong/gorya/pkg/aws/options"
)

//go:generate mockery --name Interface
type Interface interface {
EC2() ec2.Interface
}

type client struct {
ec2 ec2.Interface
ec2 ec2.Interface
opts options.Options
}

func New(ctx context.Context, region string, opts ...options.Option) (Interface, error) {
return getOnce(ctx, region, opts...)
func New(ctx context.Context, opts ...options.Option) (Interface, error) {
return getOnce(ctx, opts...)
}

var (
awsClient *client
muAwsClient sync.Mutex
)

func getOnce(ctx context.Context, region string, opts ...options.Option) (*client, error) {
func getOnce(ctx context.Context, opts ...options.Option) (*client, error) {
muAwsClient.Lock()
defer func() {
muAwsClient.Unlock()
Expand All @@ -34,8 +38,32 @@ func getOnce(ctx context.Context, region string, opts ...options.Option) (*clien
return awsClient, nil
}
var c client
var err error
if c.ec2, err = ec2.New(ctx, region, opts...); err != nil {
for _, o := range opts {
o.Apply(&c.opts)
}
awsEndpoint := c.opts.AwsEndpoint
awsRegion := c.opts.AwsRegion
// custom resolver so we can testing locally with localstack
customResolverWithOptions := aws.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
if awsEndpoint != "" {
return aws.Endpoint{
PartitionID: "aws",
URL: awsEndpoint,
SigningRegion: awsRegion,
}, nil
}
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
},
)
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(c.opts.AwsEndpoint),
config.WithEndpointResolverWithOptions(customResolverWithOptions),
)
if err != nil {
return nil, err
}
if c.ec2, err = ec2.NewFromConfig(cfg); err != nil {
return nil, err
}
return &c, nil
Expand Down
23 changes: 23 additions & 0 deletions pkg/aws/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package aws

import (
"context"
"testing"

"github.com/nduyphuong/gorya/internal/os"
awsOptions "github.com/nduyphuong/gorya/pkg/aws/options"
"github.com/stretchr/testify/assert"
)

func TestSmoke(t *testing.T) {
ctx := context.TODO()
awsRegion := os.GetEnv("AWS_REGION", "ap-southeast-1")
awsEndpoint := os.GetEnv("AWS_ENDPOINT", "")
c, err := New(ctx,
awsOptions.WithRegion(awsRegion),
awsOptions.WithEndpoint(awsEndpoint),
)
assert.NoError(t, err)
err = c.EC2().ChangeStatus(ctx, 0, "foo", "bar")
assert.NoError(t, err)
}
32 changes: 12 additions & 20 deletions pkg/aws/ec2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (
"errors"
"fmt"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/nduyphuong/gorya/pkg/aws/options"
)

//go:generate mockery --name Interface
Expand All @@ -17,27 +16,16 @@ type Interface interface {
}

type Client struct {
ec2 *ec2.Client
opts options.Options
ec2 *ec2.Client
}

func New(ctx context.Context, region string, opts ...options.Option) (*Client, error) {
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(region),
)
if err != nil {
return nil, err
}
func NewFromConfig(cfg aws.Config) (*Client, error) {
c := &Client{}
for _, o := range opts {
o.Apply(&c.opts)
}
c.ec2 = ec2.NewFromConfig(cfg)
return c, nil
}

func (c *Client) ChangeStatus(ctx context.Context, to int, tagKey string, tagValue string) (err error) {
print(to)
if to != 0 && to != 1 {
return errors.New("to must have value of 0 or 1")
}
Expand All @@ -55,13 +43,17 @@ func (c *Client) ChangeStatus(ctx context.Context, to int, tagKey string, tagVal
return err
}
var instancesIds []string
if describeInstancesOutput != nil {
for _, r := range describeInstancesOutput.Reservations {
for _, i := range r.Instances {
instancesIds = append(instancesIds, *i.InstanceId)
}
if describeInstancesOutput == nil {
return nil
}
for _, r := range describeInstancesOutput.Reservations {
for _, i := range r.Instances {
instancesIds = append(instancesIds, *i.InstanceId)
}
}
if len(instancesIds) == 0 {
return nil
}
switch to {
case 0:
if _, err = c.ec2.StopInstances(ctx, &ec2.StopInstancesInput{
Expand Down
9 changes: 7 additions & 2 deletions pkg/aws/ec2/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package ec2

import (
"context"
"github.com/stretchr/testify/assert"
"testing"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/stretchr/testify/assert"
)

func TestClient_ChangeStatus(t *testing.T) {
var err error
awsRegion := "ap-southeast-1"
ctx := context.TODO()
client, err := New(ctx, "ap-southeast-1")
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion))
assert.NoError(t, err)
client, err := NewFromConfig(cfg)
assert.NoError(t, err)
err = client.ChangeStatus(ctx, 1, "phuong", "test")
assert.NoError(t, err)
Expand Down
78 changes: 78 additions & 0 deletions pkg/aws/mock_Interface_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 54b0838

Please sign in to comment.