From 906fcab8ba172f531bcc2f48d83647ad7bd9a601 Mon Sep 17 00:00:00 2001 From: Dovlet Hojayev Date: Fri, 7 Jun 2024 14:07:40 +0200 Subject: [PATCH] - implemented connection url parsing - implemented connection using golang-migrate cli - added tests --- .golangci.yml | 1 + pkg/opensearch/opensearch.go | 38 +++++++++++---- pkg/opensearch/transport.go | 56 ++++++++++++++++++++++ pkg/opensearch/transport_test.go | 82 ++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 pkg/opensearch/transport.go create mode 100644 pkg/opensearch/transport_test.go diff --git a/.golangci.yml b/.golangci.yml index b9f0e8c..992442a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -88,6 +88,7 @@ issues: - path: _test\.go linters: - funlen + - gosec - lll - wrapcheck - path: tests/.*\.go diff --git a/pkg/opensearch/opensearch.go b/pkg/opensearch/opensearch.go index 733f538..494bfad 100644 --- a/pkg/opensearch/opensearch.go +++ b/pkg/opensearch/opensearch.go @@ -4,21 +4,29 @@ package opensearch import ( "encoding/json" + "errors" "fmt" "io" "net/http" "github.com/golang-migrate/migrate/v4/database" + "github.com/opensearch-project/opensearch-go/v2" "github.com/opensearch-project/opensearch-go/v2/opensearchapi" "go.uber.org/atomic" ) const ( - nullVersion = -1 - versionIndexName = ".migrations" - errTemplateUnsupportedOperation = "unsupported operation '%s'" + driverName = "opensearch" + nullVersion = -1 + versionIndexName = ".migrations" ) +var ErrUnsupportedOperationDrop = errors.New("unsupported operation 'drop'") + +func init() { + database.Register(driverName, &OpenSearch{}) +} + type OpenSearch struct { transport opensearchapi.Transport manager MigrationsIndexManagerInterface @@ -38,6 +46,21 @@ func NewDriver( } } +//nolint:ireturn +func (o *OpenSearch) Open(url string) (database.Driver, error) { + config, err := NewTransportConfigFromURL(url) + if err != nil { + return nil, err + } + + transport, err := opensearch.NewClient(config) + if err != nil { + return nil, fmt.Errorf("failed to initialize OpenSearch client: %w", err) + } + + return NewDriver(transport, NewMigrationsIndexManager(transport)), nil +} + func (o *OpenSearch) Lock() error { if !o.isLocked.CompareAndSwap(false, true) { return database.ErrLocked @@ -153,15 +176,10 @@ func (o *OpenSearch) Version() (version int, dirty bool, err error) { return parsedResp.Source.Version, parsedResp.Source.Dirty, nil } -//nolint:ireturn -func (o *OpenSearch) Open(_ string) (database.Driver, error) { - return nil, fmt.Errorf(errTemplateUnsupportedOperation, "open") -} - func (o *OpenSearch) Close() error { - return fmt.Errorf(errTemplateUnsupportedOperation, "close") + return nil } func (o *OpenSearch) Drop() error { - return fmt.Errorf(errTemplateUnsupportedOperation, "drop") + return ErrUnsupportedOperationDrop } diff --git a/pkg/opensearch/transport.go b/pkg/opensearch/transport.go new file mode 100644 index 0000000..0b6323a --- /dev/null +++ b/pkg/opensearch/transport.go @@ -0,0 +1,56 @@ +package opensearch + +import ( + "crypto/tls" + "fmt" + "net/http" + neturl "net/url" + + "github.com/opensearch-project/opensearch-go/v2" +) + +const ( + DefaultScheme = "https" + DefaultPort = "9200" + QueryNameInsecureSkipVerify = "insecure-skip-verify" +) + +func NewTransportConfigFromURL(url string) (opensearch.Config, error) { + parsedURL, err := neturl.Parse(url) + if err != nil { + return opensearch.Config{}, fmt.Errorf("failed to parse url: %w", err) + } + + scheme := parsedURL.Scheme + + if scheme == "" { + scheme = DefaultScheme + } + + baseURL := scheme + "://" + parsedURL.Hostname() + port := parsedURL.Port() + + if port == "" { + port = DefaultPort + } + + baseURL += ":" + port + + config := opensearch.Config{ + Addresses: []string{baseURL}, + Username: parsedURL.User.Username(), + } + + passwd, isSet := parsedURL.User.Password() + if isSet { + config.Password = passwd + } + + if parsedURL.Query().Get(QueryNameInsecureSkipVerify) == "true" { + config.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + } + } + + return config, nil +} diff --git a/pkg/opensearch/transport_test.go b/pkg/opensearch/transport_test.go new file mode 100644 index 0000000..9f43f7f --- /dev/null +++ b/pkg/opensearch/transport_test.go @@ -0,0 +1,82 @@ +package opensearch_test + +import ( + "crypto/tls" + "fmt" + "net/http" + "testing" + + "github.com/opensearch-project/opensearch-go/v2" + "github.com/stretchr/testify/assert" + + opensearchdriver "github.com/limangotech/opensearch-driver/pkg/opensearch" +) + +func TestItParsesURL(t *testing.T) { + t.Parallel() + + testCases := []struct { + url string + expected opensearch.Config + }{ + { + // https connection + url: "https://admin:password@opensearch:1234", + expected: opensearch.Config{ + Addresses: []string{"https://opensearch:1234"}, + Username: "admin", + Password: "password", + }, + }, + { + // http connection + url: "http://admin:password@opensearch:1234", + expected: opensearch.Config{ + Addresses: []string{"http://opensearch:1234"}, + Username: "admin", + Password: "password", + }, + }, + { + // connection with no password + url: "http://admin@opensearch:1234", + expected: opensearch.Config{ + Addresses: []string{"http://opensearch:1234"}, + Username: "admin", + }, + }, + { + // connection with no auth + url: "https://opensearch:1234", + expected: opensearch.Config{Addresses: []string{"https://opensearch:1234"}}, + }, + { + // connection with no port + url: "https://admin:password@opensearch", + expected: opensearch.Config{ + Addresses: []string{"https://opensearch:9200"}, + Username: "admin", + Password: "password", + }, + }, + { + // connection with no TLS check + url: "https://admin:password@opensearch?insecure-skip-verify=true", + expected: opensearch.Config{ + Addresses: []string{"https://opensearch:9200"}, + Username: "admin", + Password: "password", + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + }, + }, + } + + for i, testCase := range testCases { + actual, err := opensearchdriver.NewTransportConfigFromURL(testCase.url) + + assert.NoError(t, err, fmt.Sprintf("Case %d", i)) + assert.Equal(t, testCase.expected, actual, fmt.Sprintf("Case %d", i)) + } +}