Skip to content
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
5 changes: 2 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Lint
on: [push, pull_request, workflow_dispatch]
on: [ push, pull_request, workflow_dispatch ]

jobs:
golangci-lint:
Expand All @@ -9,10 +9,9 @@ jobs:
- uses: actions/setup-go@v2
with:
go-version: "1.16"
- name: download dependencies
run: make download
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
skip-go-installation: true
version: v1.41.1
args: --timeout=10m
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@
dist/
coverage/
config.yaml
.vscode
.air.toml
.idea/
tmp/

6 changes: 3 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ builds:
binary: entropy
flags: [-a]
ldflags:
- -X github.com/odpf/entropy/version.Version={{.Tag}}
- -X github.com/odpf/entropy/version.Commit={{.FullCommit}}
- -X github.com/odpf/entropy/version.BuildTime={{.Date}}
- -X github.com/odpf/entropy/pkg/version.Version={{.Tag}}
- -X github.com/odpf/entropy/pkg/version.Commit={{.FullCommit}}
- -X github.com/odpf/entropy/pkg/version.BuildTime={{.Date}}
goos:
- darwin
- linux
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ all: clean test build

build:
mkdir -p ${BUILD_DIR}
CGO_ENABLED=0 go build -ldflags '-X "${NAME}/version.Version=${VERSION}" -X "${NAME}/version.Commit=${COMMIT}" -X "${NAME}/version.BuildTime=${BUILD_TIME}"' -o ${BUILD_DIR}/${EXE}
CGO_ENABLED=0 go build -ldflags '-X "${NAME}/pkg/version.Version=${VERSION}" -X "${NAME}/pkg/version.Commit=${COMMIT}" -X "${NAME}/pkg/version.BuildTime=${BUILD_TIME}"' -o ${BUILD_DIR}/${EXE}

clean:
rm -rf ${COVERAGE_DIR} ${BUILD_DIR}
Expand Down
91 changes: 87 additions & 4 deletions api/handlers/v1/resource.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,98 @@
package handlersv1

import (
"github.com/odpf/entropy/service"
"context"
"errors"
"github.com/odpf/entropy/domain"
"github.com/odpf/entropy/pkg/resource"
"github.com/odpf/entropy/store"
entropyv1beta1 "go.buf.build/odpf/gwv/odpf/proton/odpf/entropy/v1beta1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)

type APIServer struct {
container *service.Container
entropyv1beta1.UnimplementedResourceServiceServer
resourceService resource.ServiceInterface
}

func NewApiServer(container *service.Container) *APIServer {
func NewApiServer(resourceService resource.ServiceInterface) *APIServer {
return &APIServer{
container: container,
resourceService: resourceService,
}
}

func (server APIServer) CreateResource(ctx context.Context, request *entropyv1beta1.CreateResourceRequest) (*entropyv1beta1.CreateResourceResponse, error) {
res := resourceFromProto(request.Resource)
createdResource, err := server.resourceService.CreateResource(ctx, res)
if err != nil {
if errors.Is(err, store.ResourceAlreadyExistsError) {
return nil, status.Error(codes.AlreadyExists, "resource already exists")
}
return nil, status.Error(codes.Internal, "failed to create resource in db")
}
createdResponse, err := resourceToProto(createdResource)
if err != nil {
return nil, status.Error(codes.Internal, "failed to serialize resource")
}
response := entropyv1beta1.CreateResourceResponse{
Resource: createdResponse,
}
return &response, nil
}

func (server APIServer) UpdateResource(ctx context.Context, request *entropyv1beta1.UpdateResourceRequest) (*entropyv1beta1.UpdateResourceResponse, error) {
updatedResource, err := server.resourceService.UpdateResource(ctx, request.GetUrn(), request.GetConfigs().GetStructValue().AsMap())
if err != nil {
if errors.Is(err, store.ResourceNotFoundError) {
return nil, status.Error(codes.NotFound, "could not find resource with given urn")
}
return nil, status.Error(codes.Internal, "failed to update resource in db")
}
updatedResponse, err := resourceToProto(updatedResource)
if err != nil {
return nil, status.Error(codes.Internal, "failed to serialize resource")
}
response := entropyv1beta1.UpdateResourceResponse{
Resource: updatedResponse,
}
return &response, nil
}

func resourceToProto(res *domain.Resource) (*entropyv1beta1.Resource, error) {
conf, err := structpb.NewValue(res.Configs)
if err != nil {
return nil, err
}
return &entropyv1beta1.Resource{
Urn: res.Urn,
Name: res.Name,
Parent: res.Parent,
Kind: res.Kind,
Configs: conf,
Labels: res.Labels,
Status: resourceStatusToProto(string(res.Status)),
CreatedAt: timestamppb.New(res.CreatedAt),
UpdatedAt: timestamppb.New(res.UpdatedAt),
}, nil
}

func resourceStatusToProto(status string) entropyv1beta1.Resource_Status {
if resourceStatus, ok := entropyv1beta1.Resource_Status_value[status]; ok {
return entropyv1beta1.Resource_Status(resourceStatus)
}
return entropyv1beta1.Resource_STATUS_UNSPECIFIED
}

func resourceFromProto(res *entropyv1beta1.Resource) *domain.Resource {
return &domain.Resource{
Urn: res.GetUrn(),
Name: res.GetName(),
Parent: res.GetParent(),
Kind: res.GetKind(),
Configs: res.GetConfigs().GetStructValue().AsMap(),
Labels: res.GetLabels(),
}
}
207 changes: 207 additions & 0 deletions api/handlers/v1/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package handlersv1

import (
"context"
"errors"
"github.com/odpf/entropy/domain"
"github.com/odpf/entropy/mocks"
"github.com/odpf/entropy/store"
"github.com/stretchr/testify/mock"
entropyv1beta1 "go.buf.build/odpf/gwv/odpf/proton/odpf/entropy/v1beta1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"reflect"
"testing"
"time"
)

func TestAPIServer_CreateResource(t *testing.T) {
t.Run("test create new resource", func(t *testing.T) {
createdAt := time.Now()
updatedAt := createdAt
configsStructValue, _ := structpb.NewValue(map[string]interface{}{
"replicas": "10",
})
want := &entropyv1beta1.CreateResourceResponse{
Resource: &entropyv1beta1.Resource{
Urn: "p-testdata-gl-testname-firehose",
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: configsStructValue,
Labels: nil,
Status: entropyv1beta1.Resource_STATUS_PENDING,
CreatedAt: timestamppb.New(createdAt),
UpdatedAt: timestamppb.New(updatedAt),
},
}
wantErr := error(nil)

ctx := context.Background()
request := &entropyv1beta1.CreateResourceRequest{
Resource: &entropyv1beta1.Resource{
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: configsStructValue,
Labels: nil,
},
}

resourceService := mocks.ResourceService{}

resourceService.EXPECT().CreateResource(mock.Anything, mock.Anything).Return(&domain.Resource{
Urn: "p-testdata-gl-testname-firehose",
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: map[string]interface{}{
"replicas": "10",
},
Labels: nil,
Status: domain.ResourceStatusPending,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}, nil).Once()

server := NewApiServer(&resourceService)
got, err := server.CreateResource(ctx, request)
if !errors.Is(err, wantErr) {
t.Errorf("CreateResource() error = %v, wantErr %v", err, wantErr)
return
}
if !reflect.DeepEqual(got, want) {
t.Errorf("CreateResource() got = %v, want %v", got, want)
}
})

t.Run("test create duplicate resource", func(t *testing.T) {
configsStructValue, _ := structpb.NewValue(map[string]interface{}{
"replicas": "10",
})
want := (*entropyv1beta1.CreateResourceResponse)(nil)
wantErr := status.Error(codes.AlreadyExists, "resource already exists")

ctx := context.Background()
request := &entropyv1beta1.CreateResourceRequest{
Resource: &entropyv1beta1.Resource{
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: configsStructValue,
Labels: nil,
},
}

resourceService := mocks.ResourceService{}

resourceService.EXPECT().
CreateResource(mock.Anything, mock.Anything).
Return(nil, store.ResourceAlreadyExistsError).
Once()

server := NewApiServer(&resourceService)
got, err := server.CreateResource(ctx, request)
if !errors.Is(err, wantErr) {
t.Errorf("CreateResource() error = %v, wantErr %v", err, wantErr)
return
}
if !reflect.DeepEqual(got, want) {
t.Errorf("CreateResource() got = %v, want %v", got, want)
}
})
}

func TestAPIServer_UpdateResource(t *testing.T) {
t.Run("test update existing resource", func(t *testing.T) {
createdAt := time.Now()
updatedAt := createdAt.Add(time.Minute)
configsStructValue, _ := structpb.NewValue(map[string]interface{}{
"replicas": "10",
})
want := &entropyv1beta1.UpdateResourceResponse{
Resource: &entropyv1beta1.Resource{
Urn: "p-testdata-gl-testname-firehose",
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: configsStructValue,
Labels: nil,
Status: entropyv1beta1.Resource_STATUS_PENDING,
CreatedAt: timestamppb.New(createdAt),
UpdatedAt: timestamppb.New(updatedAt),
},
}
wantErr := error(nil)

ctx := context.Background()
request := &entropyv1beta1.UpdateResourceRequest{
Urn: "p-testdata-gl-testname-firehose",
Configs: configsStructValue,
}

resourceService := mocks.ResourceService{}

resourceService.EXPECT().
UpdateResource(mock.Anything, "p-testdata-gl-testname-firehose", map[string]interface{}{
"replicas": "10",
}).
Return(&domain.Resource{
Urn: "p-testdata-gl-testname-firehose",
Name: "testname",
Parent: "p-testdata-gl",
Kind: "firehose",
Configs: map[string]interface{}{
"replicas": "10",
},
Labels: nil,
Status: domain.ResourceStatusPending,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}, nil).Once()

server := NewApiServer(&resourceService)
got, err := server.UpdateResource(ctx, request)
if !errors.Is(err, wantErr) {
t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr)
return
}
if !reflect.DeepEqual(got, want) {
t.Errorf("UpdateResource() got = %v, want %v", got, want)
}
})

t.Run("test update non-existing resource", func(t *testing.T) {
configsStructValue, _ := structpb.NewValue(map[string]interface{}{
"replicas": "10",
})
want := (*entropyv1beta1.UpdateResourceResponse)(nil)
wantErr := status.Error(codes.NotFound, "could not find resource with given urn")

ctx := context.Background()
request := &entropyv1beta1.UpdateResourceRequest{
Urn: "p-testdata-gl-testname-firehose",
Configs: configsStructValue,
}

resourceService := mocks.ResourceService{}

resourceService.EXPECT().
UpdateResource(mock.Anything, "p-testdata-gl-testname-firehose", map[string]interface{}{
"replicas": "10",
}).
Return(nil, store.ResourceNotFoundError).Once()

server := NewApiServer(&resourceService)
got, err := server.UpdateResource(ctx, request)
if !errors.Is(err, wantErr) {
t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr)
return
}
if !reflect.DeepEqual(got, want) {
t.Errorf("UpdateResource() got = %v, want %v", got, want)
}
})
}
Loading