diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3273e85d..cf42d8ce 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,5 @@ name: Lint -on: [push, pull_request, workflow_dispatch] +on: [ push, pull_request, workflow_dispatch ] jobs: golangci-lint: @@ -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 diff --git a/.gitignore b/.gitignore index 78e13569..836579aa 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ dist/ coverage/ config.yaml +.vscode +.air.toml +.idea/ +tmp/ + diff --git a/.goreleaser.yml b/.goreleaser.yml index 8bf6de93..3dd1d776 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -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 diff --git a/Makefile b/Makefile index 15f01298..cb64522d 100644 --- a/Makefile +++ b/Makefile @@ -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} diff --git a/api/handlers/v1/resource.go b/api/handlers/v1/resource.go index 4365736a..7dd02809 100644 --- a/api/handlers/v1/resource.go +++ b/api/handlers/v1/resource.go @@ -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(), } } diff --git a/api/handlers/v1/resource_test.go b/api/handlers/v1/resource_test.go new file mode 100644 index 00000000..848b8231 --- /dev/null +++ b/api/handlers/v1/resource_test.go @@ -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) + } + }) +} diff --git a/app/server.go b/app/app.go similarity index 52% rename from app/server.go rename to app/app.go index aa886c87..369c476d 100644 --- a/app/server.go +++ b/app/app.go @@ -3,6 +3,9 @@ package app import ( "context" "fmt" + "github.com/odpf/entropy/pkg/resource" + "github.com/odpf/entropy/store" + "github.com/odpf/entropy/store/mongodb" "net/http" "time" @@ -15,22 +18,35 @@ import ( "github.com/odpf/salt/common" commonv1 "go.buf.build/odpf/gw/odpf/proton/odpf/common/v1" + entropyv1beta1 "go.buf.build/odpf/gwv/odpf/proton/odpf/entropy/v1beta1" handlersv1 "github.com/odpf/entropy/api/handlers/v1" - "github.com/odpf/entropy/domain" - "github.com/odpf/entropy/logger" - "github.com/odpf/entropy/metric" - "github.com/odpf/entropy/service" - "github.com/odpf/entropy/store" - "github.com/odpf/entropy/version" + "github.com/odpf/entropy/pkg/logger" + "github.com/odpf/entropy/pkg/metric" + "github.com/odpf/entropy/pkg/version" "github.com/odpf/salt/server" "go.uber.org/zap" "google.golang.org/grpc" ) +// Config contains the application configuration +type Config struct { + Service ServiceConfig `mapstructure:"service"` + DB mongodb.DBConfig `mapstructure:"db"` + NewRelic metric.NewRelicConfig `mapstructure:"newrelic"` + Log logger.LogConfig `mapstructure:"log"` +} + +type ServiceConfig struct { + Port int `mapstructure:"port" default:"8080"` + Host string `mapstructure:"host" default:""` +} + // RunServer runs the application server -func RunServer(c *domain.Config) error { - ctx, cancelFunc := context.WithCancel(server.HandleSignals(context.Background())) +func RunServer(c *Config) error { + ctx, cancelFunc := context.WithCancel( + server.HandleSignals(context.Background()), + ) defer cancelFunc() nr, err := metric.New(&c.NewRelic) @@ -43,17 +59,16 @@ func RunServer(c *domain.Config) error { return err } - store, err := store.New(&c.DB) + mongoStore, err := mongodb.New(&c.DB) if err != nil { return err } - serviceContainer, err := service.Init(store) - if err != nil { - return err - } + resourceRepository := mongodb.NewResourceRepository( + mongoStore.Collection(store.ResourceRepositoryName), + ) - _ = handlersv1.NewApiServer(serviceContainer) + resourceService := resource.NewService(resourceRepository) muxServer, err := server.NewMux(server.Config{ Port: c.Service.Port, @@ -78,43 +93,55 @@ func RunServer(c *domain.Config) error { if err != nil { return err } - muxServer.SetGateway("/api", gw) - muxServer.RegisterHandler("/ping", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "pong") - })) + err = gw.RegisterHandler(ctx, entropyv1beta1.RegisterResourceServiceHandlerFromEndpoint) + if err != nil { + return err + } + + muxServer.SetGateway("/api", gw) muxServer.RegisterService( &commonv1.CommonService_ServiceDesc, common.New(version.GetVersionAndBuildInfo()), ) + muxServer.RegisterService( + &entropyv1beta1.ResourceService_ServiceDesc, + handlersv1.NewApiServer(resourceService), + ) + + muxServer.RegisterHandler("/ping", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "pong") + })) logger.Info("starting server", zap.String("host", c.Service.Host), zap.Int("port", c.Service.Port)) - errorChan := make(chan error) + serverErrorChan := make(chan error) go func() { - errorChan <- muxServer.Serve() + serverErrorChan <- muxServer.Serve() }() - <-ctx.Done() - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second*30) - defer shutdownCancel() - - muxServer.Shutdown(shutdownCtx) - return <-errorChan + select { + case <-ctx.Done(): + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second*30) + defer shutdownCancel() + muxServer.Shutdown(shutdownCtx) + case serverError := <-serverErrorChan: + return serverError + } + return nil } -func RunMigrations(c *domain.Config) error { - store, err := store.New(&c.DB) +func RunMigrations(c *Config) error { + mongoStore, err := mongodb.New(&c.DB) if err != nil { return err } - services, err := service.Init(store) - if err != nil { - return err - } + resourceRepository := mongodb.NewResourceRepository( + mongoStore.Collection(store.ResourceRepositoryName), + ) - return services.MigrateAll(store) + return resourceRepository.Migrate() } diff --git a/cmd/migrate.go b/cmd/migrate.go index b9c81a4a..95602de4 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -2,7 +2,6 @@ package cmd import ( "github.com/odpf/entropy/app" - "github.com/odpf/entropy/domain" "github.com/odpf/salt/config" "github.com/spf13/cobra" ) @@ -16,7 +15,7 @@ func init() { } func migrate(cmd *cobra.Command, args []string) error { - var c domain.Config + var c app.Config l := config.NewLoader(config.WithPath("./")) err := l.Load(&c) if err != nil { diff --git a/cmd/serve.go b/cmd/serve.go index 19e0eedb..3d7efd3c 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -2,7 +2,6 @@ package cmd import ( "github.com/odpf/entropy/app" - "github.com/odpf/entropy/domain" "github.com/odpf/salt/config" "github.com/spf13/cobra" ) @@ -16,7 +15,8 @@ func init() { } func serve(cmd *cobra.Command, args []string) error { - var c domain.Config + var c app.Config + //TODO: load config from path using flag. l := config.NewLoader(config.WithPath("./")) err := l.Load(&c) if err != nil { diff --git a/cmd/version.go b/cmd/version.go index f3ef6a31..152c2451 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,7 +1,7 @@ package cmd import ( - v "github.com/odpf/entropy/version" + v "github.com/odpf/entropy/pkg/version" "github.com/spf13/cobra" ) diff --git a/domain/config.go b/domain/config.go deleted file mode 100644 index 617502ea..00000000 --- a/domain/config.go +++ /dev/null @@ -1,32 +0,0 @@ -package domain - -// DBConfig contains the database configuration -type DBConfig struct { - Host string `mapstructure:"host" default:"localhost"` - Port string `mapstructure:"port" default:"27017"` - Name string `mapstructure:"name" default:"entropy"` -} - -// NewRelic contains the New Relic go-agent configuration -type NewRelicConfig struct { - Enabled bool `mapstructure:"enabled" default:"false"` - AppName string `mapstructure:"appname" default:"entropy-dev"` - License string `mapstructure:"license"` -} - -type LogConfig struct { - Level string `mapstructure:"level" default:"info"` -} - -type ServiceConfig struct { - Port int `mapstructure:"port" default:"8080"` - Host string `mapstructure:"host" default:""` -} - -// Config contains the application configuration -type Config struct { - Service ServiceConfig `mapstructure:"service"` - DB DBConfig `mapstructure:"db"` - NewRelic NewRelicConfig `mapstructure:"newrelic"` - Log LogConfig `mapstructure:"log"` -} diff --git a/domain/resource.go b/domain/resource.go new file mode 100644 index 00000000..242e0377 --- /dev/null +++ b/domain/resource.go @@ -0,0 +1,41 @@ +package domain + +import ( + "strings" + "time" +) + +type ResourceStatus string + +const ( + ResourceStatusUnspecified ResourceStatus = "STATUS_UNSPECIFIED" + ResourceStatusPending ResourceStatus = "STATUS_PENDING" + ResourceStatusError ResourceStatus = "STATUS_ERROR" + ResourceStatusRunning ResourceStatus = "STATUS_RUNNING" + ResourceStatusStopped ResourceStatus = "STATUS_STOPPED" + ResourceStatusCompleted ResourceStatus = "STATUS_COMPLETED" +) + +type Resource struct { + Urn string `bson:"urn"` + Name string `bson:"name"` + Parent string `bson:"parent"` + Kind string `bson:"kind"` + Configs map[string]interface{} `bson:"configs"` + Labels map[string]string `bson:"labels"` + Status ResourceStatus `bson:"status"` + CreatedAt time.Time `bson:"created_at"` + UpdatedAt time.Time `bson:"updated_at"` +} + +func GenerateResourceUrn(res *Resource) string { + return strings.Join([]string{ + sanitizeString(res.Parent), + sanitizeString(res.Name), + sanitizeString(res.Kind), + }, "-") +} + +func sanitizeString(s string) string { + return strings.Replace(s, " ", "_", -1) +} diff --git a/go.mod b/go.mod index 6ad3242b..2cfe5673 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,21 @@ go 1.16 require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/klauspost/compress v1.11.13 // indirect + github.com/kr/text v0.2.0 // indirect github.com/newrelic/go-agent/v3 v3.12.0 github.com/newrelic/go-agent/v3/integrations/nrgrpc v1.3.1 github.com/odpf/salt v0.0.0-20210929215807-5e1f68b4ec91 github.com/spf13/cobra v1.2.1 + github.com/stretchr/testify v1.7.0 go.buf.build/odpf/gw/odpf/proton v1.1.9 + go.buf.build/odpf/gwv/odpf/proton v1.1.56 go.mongodb.org/mongo-driver v1.5.1 go.uber.org/zap v1.19.0 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect google.golang.org/grpc v1.40.0 google.golang.org/protobuf v1.27.1 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 3bce9999..0803be3f 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,7 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 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= @@ -262,17 +263,21 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -314,7 +319,6 @@ github.com/newrelic/go-agent/v3 v3.12.0 h1:tcDo0Q8qRWAJqb9uykfmM8pxGSbv0HqSS3q1+ github.com/newrelic/go-agent/v3 v3.12.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= github.com/newrelic/go-agent/v3/integrations/nrgrpc v1.3.1 h1:/ar1Omo9luapTJYWXt86oQGBpWwpWF92x+UuYU9v/7o= github.com/newrelic/go-agent/v3/integrations/nrgrpc v1.3.1/go.mod h1:2q0u6qkNJ4ClDt920A4r+NpcO370lFze1NF4OPJjAks= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/odpf/salt v0.0.0-20210929215807-5e1f68b4ec91 h1:UTUhSKe9J+xJHGSJAiGh6zNvZUEKkGkPtRcPvsGKenc= github.com/odpf/salt v0.0.0-20210929215807-5e1f68b4ec91/go.mod h1:2RzZihKtzPWS5/kMB+h4c/4rbm3hqGKmyuCIXcHFW2I= @@ -386,6 +390,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -417,6 +422,10 @@ go.buf.build/odpf/gw/grpc-ecosystem/grpc-gateway v1.1.35 h1:d5dMyaXBowF98Oylwhyr go.buf.build/odpf/gw/grpc-ecosystem/grpc-gateway v1.1.35/go.mod h1:/LuddrGPi0fwj7ay6Orutt8oFfPz8Y3c8qdBkacJq1A= go.buf.build/odpf/gw/odpf/proton v1.1.9 h1:iEdRUVVc/HwOqB7WRXjhXjR2pza2gyUbQ74G2sBlWHU= go.buf.build/odpf/gw/odpf/proton v1.1.9/go.mod h1:I9E8CF7w/690vRNWqBU6qDcUbi3Pi2THdn1yycBVTDQ= +go.buf.build/odpf/gwv/envoyproxy/protoc-gen-validate v1.1.3/go.mod h1:2Tg6rYIoDhpl39Zd2+WBOF9uG4XxAOs0bK2Z2/bwTOc= +go.buf.build/odpf/gwv/grpc-ecosystem/grpc-gateway v1.1.37/go.mod h1:UrBCdmHgaY/pLapYUMOq01c1yuzwT8AEBTsgpmzq2zo= +go.buf.build/odpf/gwv/odpf/proton v1.1.56 h1:rfwXAGTUL1yTh5zoSG+NzkqLNFno9B4oyjdGynb/q+I= +go.buf.build/odpf/gwv/odpf/proton v1.1.56/go.mod h1:3VlD6BkZ7cmhyEb+pOdyjier/UO5QNT5rfBIgHyWzLM= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -451,8 +460,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -528,8 +538,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -609,9 +620,11 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -619,8 +632,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -801,8 +815,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/metric/metric.go b/metric/metric.go deleted file mode 100644 index c9d93921..00000000 --- a/metric/metric.go +++ /dev/null @@ -1,15 +0,0 @@ -package metric - -import ( - "github.com/newrelic/go-agent/v3/newrelic" - "github.com/odpf/entropy/domain" -) - -// RunServer runs the application server -func New(c *domain.NewRelicConfig) (*newrelic.Application, error) { - return newrelic.NewApplication( - newrelic.ConfigAppName(c.AppName), - newrelic.ConfigEnabled(c.Enabled), - newrelic.ConfigLicense(c.License), - ) -} diff --git a/mocks/resource_repository.go b/mocks/resource_repository.go new file mode 100644 index 00000000..e4792047 --- /dev/null +++ b/mocks/resource_repository.go @@ -0,0 +1,177 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + domain "github.com/odpf/entropy/domain" + mock "github.com/stretchr/testify/mock" +) + +// ResourceRepository is an autogenerated mock type for the ResourceRepository type +type ResourceRepository struct { + mock.Mock +} + +type ResourceRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *ResourceRepository) EXPECT() *ResourceRepository_Expecter { + return &ResourceRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: r +func (_m *ResourceRepository) Create(r *domain.Resource) error { + ret := _m.Called(r) + + var r0 error + if rf, ok := ret.Get(0).(func(*domain.Resource) error); ok { + r0 = rf(r) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResourceRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type ResourceRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - r *domain.Resource +func (_e *ResourceRepository_Expecter) Create(r interface{}) *ResourceRepository_Create_Call { + return &ResourceRepository_Create_Call{Call: _e.mock.On("Create", r)} +} + +func (_c *ResourceRepository_Create_Call) Run(run func(r *domain.Resource)) *ResourceRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*domain.Resource)) + }) + return _c +} + +func (_c *ResourceRepository_Create_Call) Return(_a0 error) *ResourceRepository_Create_Call { + _c.Call.Return(_a0) + return _c +} + +// GetByURN provides a mock function with given fields: urn +func (_m *ResourceRepository) GetByURN(urn string) (*domain.Resource, error) { + ret := _m.Called(urn) + + var r0 *domain.Resource + if rf, ok := ret.Get(0).(func(string) *domain.Resource); ok { + r0 = rf(urn) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Resource) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(urn) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResourceRepository_GetByURN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByURN' +type ResourceRepository_GetByURN_Call struct { + *mock.Call +} + +// GetByURN is a helper method to define mock.On call +// - urn string +func (_e *ResourceRepository_Expecter) GetByURN(urn interface{}) *ResourceRepository_GetByURN_Call { + return &ResourceRepository_GetByURN_Call{Call: _e.mock.On("GetByURN", urn)} +} + +func (_c *ResourceRepository_GetByURN_Call) Run(run func(urn string)) *ResourceRepository_GetByURN_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *ResourceRepository_GetByURN_Call) Return(_a0 *domain.Resource, _a1 error) *ResourceRepository_GetByURN_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// Migrate provides a mock function with given fields: +func (_m *ResourceRepository) Migrate() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResourceRepository_Migrate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Migrate' +type ResourceRepository_Migrate_Call struct { + *mock.Call +} + +// Migrate is a helper method to define mock.On call +func (_e *ResourceRepository_Expecter) Migrate() *ResourceRepository_Migrate_Call { + return &ResourceRepository_Migrate_Call{Call: _e.mock.On("Migrate")} +} + +func (_c *ResourceRepository_Migrate_Call) Run(run func()) *ResourceRepository_Migrate_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ResourceRepository_Migrate_Call) Return(_a0 error) *ResourceRepository_Migrate_Call { + _c.Call.Return(_a0) + return _c +} + +// Update provides a mock function with given fields: r +func (_m *ResourceRepository) Update(r *domain.Resource) error { + ret := _m.Called(r) + + var r0 error + if rf, ok := ret.Get(0).(func(*domain.Resource) error); ok { + r0 = rf(r) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResourceRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type ResourceRepository_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - r *domain.Resource +func (_e *ResourceRepository_Expecter) Update(r interface{}) *ResourceRepository_Update_Call { + return &ResourceRepository_Update_Call{Call: _e.mock.On("Update", r)} +} + +func (_c *ResourceRepository_Update_Call) Run(run func(r *domain.Resource)) *ResourceRepository_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*domain.Resource)) + }) + return _c +} + +func (_c *ResourceRepository_Update_Call) Return(_a0 error) *ResourceRepository_Update_Call { + _c.Call.Return(_a0) + return _c +} diff --git a/mocks/service_interface.go b/mocks/service_interface.go new file mode 100644 index 00000000..ac218fde --- /dev/null +++ b/mocks/service_interface.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + domain "github.com/odpf/entropy/domain" + mock "github.com/stretchr/testify/mock" +) + +// ResourceService is an autogenerated mock type for the ServiceInterface type +type ResourceService struct { + mock.Mock +} + +type ResourceService_Expecter struct { + mock *mock.Mock +} + +func (_m *ResourceService) EXPECT() *ResourceService_Expecter { + return &ResourceService_Expecter{mock: &_m.Mock} +} + +// CreateResource provides a mock function with given fields: ctx, res +func (_m *ResourceService) CreateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { + ret := _m.Called(ctx, res) + + var r0 *domain.Resource + if rf, ok := ret.Get(0).(func(context.Context, *domain.Resource) *domain.Resource); ok { + r0 = rf(ctx, res) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Resource) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *domain.Resource) error); ok { + r1 = rf(ctx, res) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResourceService_CreateResource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateResource' +type ResourceService_CreateResource_Call struct { + *mock.Call +} + +// CreateResource is a helper method to define mock.On call +// - ctx context.Context +// - res *domain.Resource +func (_e *ResourceService_Expecter) CreateResource(ctx interface{}, res interface{}) *ResourceService_CreateResource_Call { + return &ResourceService_CreateResource_Call{Call: _e.mock.On("CreateResource", ctx, res)} +} + +func (_c *ResourceService_CreateResource_Call) Run(run func(ctx context.Context, res *domain.Resource)) *ResourceService_CreateResource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.Resource)) + }) + return _c +} + +func (_c *ResourceService_CreateResource_Call) Return(_a0 *domain.Resource, _a1 error) *ResourceService_CreateResource_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// UpdateResource provides a mock function with given fields: ctx, urn, configs +func (_m *ResourceService) UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) { + ret := _m.Called(ctx, urn, configs) + + var r0 *domain.Resource + if rf, ok := ret.Get(0).(func(context.Context, string, map[string]interface{}) *domain.Resource); ok { + r0 = rf(ctx, urn, configs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Resource) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, map[string]interface{}) error); ok { + r1 = rf(ctx, urn, configs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResourceService_UpdateResource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateResource' +type ResourceService_UpdateResource_Call struct { + *mock.Call +} + +// UpdateResource is a helper method to define mock.On call +// - ctx context.Context +// - urn string +// - configs map[string]interface{} +func (_e *ResourceService_Expecter) UpdateResource(ctx interface{}, urn interface{}, configs interface{}) *ResourceService_UpdateResource_Call { + return &ResourceService_UpdateResource_Call{Call: _e.mock.On("UpdateResource", ctx, urn, configs)} +} + +func (_c *ResourceService_UpdateResource_Call) Run(run func(ctx context.Context, urn string, configs map[string]interface{})) *ResourceService_UpdateResource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(map[string]interface{})) + }) + return _c +} + +func (_c *ResourceService_UpdateResource_Call) Return(_a0 *domain.Resource, _a1 error) *ResourceService_UpdateResource_Call { + _c.Call.Return(_a0, _a1) + return _c +} diff --git a/logger/logger.go b/pkg/logger/logger.go similarity index 83% rename from logger/logger.go rename to pkg/logger/logger.go index ac1a2beb..ca58c9bb 100644 --- a/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,12 +1,15 @@ package logger import ( - "github.com/odpf/entropy/domain" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) -func New(config *domain.LogConfig) (*zap.Logger, error) { +type LogConfig struct { + Level string `mapstructure:"level" default:"info"` +} + +func New(config *LogConfig) (*zap.Logger, error) { defaultConfig := zap.NewProductionConfig() defaultConfig.Level = zap.NewAtomicLevelAt(getZapLogLevelFromString(config.Level)) logger, err := zap.NewProductionConfig().Build() diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go new file mode 100644 index 00000000..15cd8c12 --- /dev/null +++ b/pkg/metric/metric.go @@ -0,0 +1,21 @@ +package metric + +import ( + "github.com/newrelic/go-agent/v3/newrelic" +) + +// NewRelic contains the New Relic go-agent configuration +type NewRelicConfig struct { + Enabled bool `mapstructure:"enabled" default:"false"` + AppName string `mapstructure:"appname" default:"entropy-dev"` + License string `mapstructure:"license"` +} + +// RunServer runs the application server +func New(c *NewRelicConfig) (*newrelic.Application, error) { + return newrelic.NewApplication( + newrelic.ConfigAppName(c.AppName), + newrelic.ConfigEnabled(c.Enabled), + newrelic.ConfigLicense(c.License), + ) +} diff --git a/pkg/resource/service.go b/pkg/resource/service.go new file mode 100644 index 00000000..99006277 --- /dev/null +++ b/pkg/resource/service.go @@ -0,0 +1,53 @@ +package resource + +import ( + "context" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/store" +) + +type ServiceInterface interface { + CreateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) + UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) +} + +type Service struct { + resourceRepository store.ResourceRepository +} + +func NewService(repository store.ResourceRepository) *Service { + return &Service{ + resourceRepository: repository, + } +} + +func (s *Service) CreateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { + res.Urn = domain.GenerateResourceUrn(res) + res.Status = domain.ResourceStatusPending + err := s.resourceRepository.Create(res) + if err != nil { + return nil, err + } + createdResource, err := s.resourceRepository.GetByURN(res.Urn) + if err != nil { + return nil, err + } + return createdResource, nil +} + +func (s *Service) UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) { + res, err := s.resourceRepository.GetByURN(urn) + if err != nil { + return nil, err + } + res.Configs = configs + err = s.resourceRepository.Update(res) + if err != nil { + return nil, err + } + updatedRes, err := s.resourceRepository.GetByURN(urn) + if err != nil { + return nil, err + } + return updatedRes, nil +} diff --git a/pkg/resource/service_test.go b/pkg/resource/service_test.go new file mode 100644 index 00000000..5ba0837d --- /dev/null +++ b/pkg/resource/service_test.go @@ -0,0 +1,186 @@ +package resource + +import ( + "context" + "errors" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/mocks" + "github.com/odpf/entropy/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "reflect" + "testing" + "time" +) + +func TestService_CreateResource(t *testing.T) { + t.Run("test create new resource", func(t *testing.T) { + mockRepo := &mocks.ResourceRepository{} + argResource := &domain.Resource{ + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + } + currentTime := time.Now() + want := &domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } + wantErr := error(nil) + mockRepo.EXPECT().Create(mock.Anything).Run(func(r *domain.Resource) { + assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) + assert.Equal(t, domain.ResourceStatusPending, r.Status) + }).Return(nil).Once() + + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }, nil).Once() + + s := NewService(mockRepo) + got, err := s.CreateResource(context.Background(), argResource) + 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) { + mockRepo := &mocks.ResourceRepository{} + argResource := &domain.Resource{ + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + } + want := (*domain.Resource)(nil) + wantErr := store.ResourceAlreadyExistsError + mockRepo.EXPECT().Create(mock.Anything).Run(func(r *domain.Resource) { + assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) + assert.Equal(t, domain.ResourceStatusPending, r.Status) + }).Return(store.ResourceAlreadyExistsError).Once() + + s := NewService(mockRepo) + got, err := s.CreateResource(context.Background(), argResource) + 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 TestService_UpdateResource(t *testing.T) { + t.Run("test update existing resource", func(t *testing.T) { + mockRepo := &mocks.ResourceRepository{} + argUrn := "p-testdata-gl-testname-firehose" + argConfigs := map[string]interface{}{ + "replicas": "10", + } + currentTime := time.Now() + updatedTime := time.Now() + want := &domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: updatedTime, + } + wantErr := error(nil) + + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }, nil).Once() + + mockRepo.EXPECT().Update(mock.Anything).Run(func(r *domain.Resource) { + assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) + assert.Equal(t, domain.ResourceStatusPending, r.Status) + assert.Equal(t, currentTime, r.CreatedAt) + }).Return(nil) + + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: updatedTime, + }, nil).Once() + + s := NewService(mockRepo) + got, err := s.UpdateResource(context.Background(), argUrn, argConfigs) + 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-existent resource", func(t *testing.T) { + mockRepo := &mocks.ResourceRepository{} + argUrn := "p-testdata-gl-testname-firehose" + argConfigs := map[string]interface{}{ + "replicas": "10", + } + want := (*domain.Resource)(nil) + wantErr := store.ResourceNotFoundError + + mockRepo.EXPECT(). + GetByURN("p-testdata-gl-testname-firehose"). + Return(nil, store.ResourceNotFoundError). + Once() + + s := NewService(mockRepo) + got, err := s.UpdateResource(context.Background(), argUrn, argConfigs) + 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) + } + }) +} diff --git a/version/version.go b/pkg/version/version.go similarity index 100% rename from version/version.go rename to pkg/version/version.go diff --git a/service/container.go b/service/container.go deleted file mode 100644 index 56e46bf4..00000000 --- a/service/container.go +++ /dev/null @@ -1,15 +0,0 @@ -package service - -import ( - "go.mongodb.org/mongo-driver/mongo" -) - -type Container struct{} - -func Init(db *mongo.Database) (*Container, error) { - return &Container{}, nil -} - -func (container *Container) MigrateAll(db *mongo.Database) error { - return nil -} diff --git a/store/mongodb/db.go b/store/mongodb/db.go new file mode 100644 index 00000000..742071a2 --- /dev/null +++ b/store/mongodb/db.go @@ -0,0 +1,43 @@ +package mongodb + +import ( + "context" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// DBConfig contains the database configuration +type DBConfig struct { + Host string `mapstructure:"host" default:"localhost"` + Port string `mapstructure:"port" default:"27017"` + Name string `mapstructure:"name" default:"entropy"` +} + +// New returns the database instance +func New(config *DBConfig) (*mongo.Database, error) { + uri := fmt.Sprintf( + "mongodb://%s:%s/%s", + config.Host, + config.Port, + config.Name, + ) + + client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) + if err != nil { + return nil, err + } + + pingCtx, pingCancel := context.WithTimeout(context.TODO(), time.Second*5) + defer pingCancel() + + err = client.Ping(pingCtx, nil) + + if err != nil { + return nil, err + } + + return client.Database(config.Name), nil +} diff --git a/store/mongodb/resource_repository.go b/store/mongodb/resource_repository.go new file mode 100644 index 00000000..325063b1 --- /dev/null +++ b/store/mongodb/resource_repository.go @@ -0,0 +1,80 @@ +package mongodb + +import ( + "context" + "fmt" + "github.com/odpf/entropy/store" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" + "time" + + "github.com/odpf/entropy/domain" + "go.mongodb.org/mongo-driver/mongo" +) + +type ResourceRepository struct { + collection *mongo.Collection +} + +func NewResourceRepository(collection *mongo.Collection) *ResourceRepository { + return &ResourceRepository{ + collection: collection, + } +} + +func (rc *ResourceRepository) Migrate() error { + return createUniqueIndex(rc.collection, "urn", 1) +} + +func createUniqueIndex(collection *mongo.Collection, key string, order int) error { + _, err := collection.Indexes().CreateOne( + context.TODO(), + mongo.IndexModel{ + Keys: bson.D{{Key: key, Value: order}}, + Options: options.Index().SetUnique(true), + }, + ) + return err +} + +func (rc *ResourceRepository) Create(resource *domain.Resource) error { + resource.CreatedAt = time.Now() + resource.UpdatedAt = time.Now() + + _, err := rc.collection.InsertOne(context.TODO(), resource) + if err != nil { + if mongo.IsDuplicateKeyError(err) { + return fmt.Errorf("%w: %s", store.ResourceAlreadyExistsError, err) + } + return err + } + return nil +} + +func (rc *ResourceRepository) Update(r *domain.Resource) error { + r.UpdatedAt = time.Now() + singleResult := rc.collection.FindOneAndUpdate(context.TODO(), + map[string]interface{}{"urn": r.Urn}, + map[string]interface{}{"$set": r}, + ) + err := singleResult.Err() + if err != nil { + if err == mongo.ErrNoDocuments { + return fmt.Errorf("%w: urn = %s", store.ResourceNotFoundError, r.Urn) + } + return err + } + return nil +} + +func (rc *ResourceRepository) GetByURN(urn string) (*domain.Resource, error) { + res := &domain.Resource{} + err := rc.collection.FindOne(context.TODO(), map[string]interface{}{"urn": urn}).Decode(res) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, fmt.Errorf("%w: %s", store.ResourceNotFoundError, err) + } + return nil, err + } + return res, nil +} diff --git a/store/mongodb/resource_repository_test.go b/store/mongodb/resource_repository_test.go new file mode 100644 index 00000000..68ba3dea --- /dev/null +++ b/store/mongodb/resource_repository_test.go @@ -0,0 +1,172 @@ +package mongodb + +import ( + "errors" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/store" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/integration/mtest" + "reflect" + "testing" + "time" +) + +func TestNewResourceRepository(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + defer mt.Close() + type args struct { + collection *mongo.Collection + } + test := struct { + name string + args func(mt *mtest.T) args + want func(mt *mtest.T) *ResourceRepository + }{ + name: "test creating new resource repository", + args: func(mt *mtest.T) args { return args{mt.Coll} }, + want: func(mt *mtest.T) *ResourceRepository { return &ResourceRepository{mt.Coll} }, + } + mt.Run(test.name, func(mt *mtest.T) { + if got, want := NewResourceRepository(test.args(mt).collection), test.want(mt); !reflect.DeepEqual(got, want) { + t.Errorf("NewResourceRepository() = %v, want %v", got, want) + } + }) +} + +func TestResourceRepository_Create(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + defer mt.Close() + type fields struct { + collection *mongo.Collection + } + type args struct { + resource *domain.Resource + } + tests := []struct { + name string + setup func(mt *mtest.T) + fields func(mt *mtest.T) fields + args func(mt *mtest.T) args + wantErr error + }{ + { + name: "test create success", + setup: func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) }, + fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, + args: func(mt *mtest.T) args { + return args{&domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }} + }, + wantErr: nil, + }, + { + name: "test create duplicate", + setup: func(mt *mtest.T) { + mt.AddMockResponses(mtest.CreateWriteErrorsResponse(mtest.WriteError{ + Index: 1, + Code: 11000, + Message: "duplicate key error", + })) + }, + fields: func(mt *mtest.T) fields { + return fields{mt.Coll} + }, + args: func(mt *mtest.T) args { + return args{&domain.Resource{ + Urn: "p-testdata-gl-testname-firehose", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "firehose", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }} + }, + wantErr: store.ResourceAlreadyExistsError, + }, + } + for _, tt := range tests { + mt.Run(tt.name, func(mt *mtest.T) { + tt.setup(mt) + rc := NewResourceRepository(tt.fields(mt).collection) + if err := rc.Create(tt.args(mt).resource); !errors.Is(err, tt.wantErr) { + t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestResourceRepository_GetByURN(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + defer mt.Close() + type fields struct { + collection *mongo.Collection + } + type args struct { + urn string + } + tests := []struct { + name string + setup func(mt *mtest.T) + fields func(mt *mtest.T) fields + args func(mt *mtest.T) args + want func(mt *mtest.T) *domain.Resource + wantErr error + }{ + { + name: "test resource get success", + setup: func(mt *mtest.T) { + mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.ns", mtest.FirstBatch, bson.D{ + {Key: "urn", Value: "p-testdata-gl-testname-firehose"}, + })) + }, + fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, + args: func(mt *mtest.T) args { return args{"p-testdata-gl-testname-firehose"} }, + want: func(mt *mtest.T) *domain.Resource { return &domain.Resource{Urn: "p-testdata-gl-testname-firehose"} }, + wantErr: nil, + }, + { + name: "test resource not found", + setup: func(mt *mtest.T) { + mt.AddMockResponses(bson.D{ + {Key: "ok", Value: 1}, + {Key: "cursor", Value: bson.D{ + {Key: "id", Value: int64(0)}, + {Key: "ns", Value: "test.ns"}, + {Key: string(mtest.FirstBatch), Value: bson.A{}}, + }}, + }) + }, + fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, + args: func(mt *mtest.T) args { return args{"p-testdata-gl-unknown-firehose"} }, + want: func(mt *mtest.T) *domain.Resource { return nil }, + wantErr: store.ResourceNotFoundError, + }, + } + for _, tt := range tests { + mt.Run(tt.name, func(mt *mtest.T) { + tt.setup(mt) + rc := NewResourceRepository(tt.fields(mt).collection) + got, err := rc.GetByURN(tt.args(mt).urn) + if !errors.Is(err, tt.wantErr) { + t.Errorf("GetByURN() error = %v, wantErr %v", err, tt.wantErr) + return + } + if want := tt.want(mt); !reflect.DeepEqual(got, want) { + t.Errorf("GetByURN() got = %v, want %v", got, want) + } + }) + } +} diff --git a/store/store.go b/store/store.go index 2fc54b75..8ad864c1 100644 --- a/store/store.go +++ b/store/store.go @@ -1,28 +1,21 @@ package store import ( - "context" - "fmt" - + "errors" "github.com/odpf/entropy/domain" - - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" ) -// New returns the database instance -func New(config *domain.DBConfig) (*mongo.Database, error) { - uri := fmt.Sprintf( - "mongodb://%s:%s/%s", - config.Host, - config.Port, - config.Name, - ) +// Custom errors which can be used by multiple DB vendors +var ( + ResourceAlreadyExistsError = errors.New("resource already exists") + ResourceNotFoundError = errors.New("no resource(s) found") +) - client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(uri)) - if err != nil { - panic(err) - } +var ResourceRepositoryName = "resources" - return client.Database(config.Name), err +type ResourceRepository interface { + Create(r *domain.Resource) error + Update(r *domain.Resource) error + GetByURN(urn string) (*domain.Resource, error) + Migrate() error }