diff --git a/CHANGELOG.md b/CHANGELOG.md index cbdfca663..47d5fa170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ # Change Log +## 2025-03-28 - Runtime 0.17.7 + +- feat: support new Dgraph connection string format [#803](https://github.com/hypermodeinc/modus/pull/803) + ## 2025-03-20 - Runtime 0.17.6 - fix: correct json in introspection query results [#798](https://github.com/hypermodeinc/modus/pull/798) diff --git a/lib/manifest/dgraph.go b/lib/manifest/dgraph.go index bc51c705a..a5f73e977 100644 --- a/lib/manifest/dgraph.go +++ b/lib/manifest/dgraph.go @@ -14,6 +14,7 @@ const ConnectionTypeDgraph ConnectionType = "dgraph" type DgraphConnectionInfo struct { Name string `json:"-"` Type ConnectionType `json:"type"` + ConnStr string `json:"connString"` GrpcTarget string `json:"grpcTarget"` Key string `json:"key"` } diff --git a/lib/manifest/modus_schema.json b/lib/manifest/modus_schema.json index 3925d6fb8..2aa44acf9 100644 --- a/lib/manifest/modus_schema.json +++ b/lib/manifest/modus_schema.json @@ -237,6 +237,24 @@ "required": ["type", "connString"], "additionalProperties": false }, + { + "properties": { + "type": { + "type": "string", + "const": "dgraph", + "description": "Type of the connection." + }, + "connString": { + "type": "string", + "minLength": 1, + "pattern": "^dgraph:\\/\\/(.*?@)?([0-9a-zA-Z.-]*?)(:\\d+)?(\\/[0-9a-zA-Z.-]+)?(\\?.+)?$", + "description": "The Dgraph connection string in URI format.", + "markdownDescription": "The Dgraph connection string in URI format.\n\nReference: https://docs.hypermode.com/modus/app-manifest#dgraph-connection" + } + }, + "required": ["type", "connString"], + "additionalProperties": false + }, { "properties": { "type": { diff --git a/lib/manifest/test/manifest_test.go b/lib/manifest/test/manifest_test.go index fc1975f91..c44db8edc 100644 --- a/lib/manifest/test/manifest_test.go +++ b/lib/manifest/test/manifest_test.go @@ -114,6 +114,11 @@ func TestReadManifest(t *testing.T) { GrpcTarget: "localhost:9080", Key: "", }, + "dgraph-with-connstr": manifest.DgraphConnectionInfo{ + Name: "dgraph-with-connstr", + Type: manifest.ConnectionTypeDgraph, + ConnStr: "dgraph://localhost:9080?sslmode=disable", + }, "my-neo4j": manifest.Neo4jConnectionInfo{ Name: "my-neo4j", Type: manifest.ConnectionTypeNeo4j, diff --git a/lib/manifest/test/valid_modus.json b/lib/manifest/test/valid_modus.json index 9c9ebb008..ae82f0005 100644 --- a/lib/manifest/test/valid_modus.json +++ b/lib/manifest/test/valid_modus.json @@ -77,6 +77,10 @@ "type": "dgraph", "grpcTarget": "localhost:9080" }, + "dgraph-with-connstr": { + "type": "dgraph", + "connString": "dgraph://localhost:9080?sslmode=disable" + }, "my-neo4j": { "type": "neo4j", "dbUri": "bolt://localhost:7687", diff --git a/runtime/dgraphclient/dgraph.go b/runtime/dgraphclient/dgraph.go index 8d7bfc96d..56e6b930b 100644 --- a/runtime/dgraphclient/dgraph.go +++ b/runtime/dgraphclient/dgraph.go @@ -17,14 +17,16 @@ import ( "github.com/dgraph-io/dgo/v240" "github.com/dgraph-io/dgo/v240/protos/api" - "google.golang.org/grpc" ) type dgraphConnector struct { - conn *grpc.ClientConn dgClient *dgo.Dgraph } +func newDgraphConnector(dgClient *dgo.Dgraph) *dgraphConnector { + return &dgraphConnector{dgClient} +} + func (dc *dgraphConnector) alterSchema(ctx context.Context, schema string) (string, error) { op := &api.Operation{Schema: schema} if err := dc.dgClient.Alter(ctx, op); err != nil { diff --git a/runtime/dgraphclient/registry.go b/runtime/dgraphclient/registry.go index 260151d08..b786ab0ae 100644 --- a/runtime/dgraphclient/registry.go +++ b/runtime/dgraphclient/registry.go @@ -11,7 +11,6 @@ package dgraphclient import ( "context" - "crypto/x509" "fmt" "strings" @@ -19,13 +18,10 @@ import ( "github.com/hypermodeinc/modus/runtime/manifestdata" "github.com/hypermodeinc/modus/runtime/secrets" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "github.com/dgraph-io/dgo/v240" - "github.com/dgraph-io/dgo/v240/protos/api" "github.com/puzpuzpuz/xsync/v3" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) var dgr = newDgraphRegistry() @@ -34,33 +30,6 @@ type dgraphRegistry struct { cache *xsync.MapOf[string, *dgraphConnector] } -type authCreds struct { - token string -} - -func (a *authCreds) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - if len(a.token) == 0 { - return nil, nil - } - - headers := make(map[string]string, 1) - if len(uri) > 0 && strings.Contains(strings.ToLower(uri[0]), "cloud.dgraph.io") { - headers["X-Auth-Token"] = a.token - } else { - token := a.token - if !strings.HasPrefix(token, "Bearer ") { - token = "Bearer " + token - } - headers["Authorization"] = token - } - - return headers, nil -} - -func (a *authCreds) RequireTransportSecurity() bool { - return true -} - func newDgraphRegistry() *dgraphRegistry { return &dgraphRegistry{ cache: xsync.NewMapOf[string, *dgraphConnector](), @@ -70,7 +39,7 @@ func newDgraphRegistry() *dgraphRegistry { func ShutdownConns() { dgr.cache.Range(func(key string, _ *dgraphConnector) bool { if connector, ok := dgr.cache.LoadAndDelete(key); ok { - connector.conn.Close() + connector.dgClient.Close() } return true }) @@ -101,42 +70,65 @@ func createConnector(ctx context.Context, dgName string) (*dgraphConnector, erro } connection := info.(manifest.DgraphConnectionInfo) - if connection.GrpcTarget == "" { - return nil, fmt.Errorf("dgraph connection [%s] has empty GrpcTarget", dgName) - } - - var opts []grpc.DialOption - if strings.Split(connection.GrpcTarget, ":")[0] == "localhost" { - opts = []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), + if connection.ConnStr != "" { + if connection.GrpcTarget != "" { + return nil, fmt.Errorf("dgraph connection [%s] has both connString and grpcTarget (use one or the other)", dgName) + } else if connection.Key != "" { + return nil, fmt.Errorf("dgraph connection [%s] has both connString and key (the key should be part of the connection string)", dgName) } - } else { - pool, err := x509.SystemCertPool() + connStr, err := secrets.ApplySecretsToString(ctx, info, connection.ConnStr) if err != nil { return nil, err } - creds := credentials.NewClientTLSFromCert(pool, "") - opts = []grpc.DialOption{ - grpc.WithTransportCredentials(creds), + return connectWithConnectionString(connStr) + } else if connection.GrpcTarget != "" { + target, err := secrets.ApplySecretsToString(ctx, info, connection.GrpcTarget) + if err != nil { + return nil, err } - if connection.Key != "" { - if conKey, err := secrets.ApplySecretsToString(ctx, info, connection.Key); err != nil { - return nil, err - } else if conKey != "" { - opts = append(opts, grpc.WithPerRPCCredentials(&authCreds{conKey})) - } + key, err := secrets.ApplySecretsToString(ctx, info, connection.Key) + if err != nil { + return nil, err } + return connectWithGrpcTarget(target, key) + } else { + return nil, fmt.Errorf("dgraph connection [%s] needs either a connString or a grpcTarget", dgName) } +} - conn, err := grpc.NewClient(connection.GrpcTarget, opts...) - if err != nil { +func connectWithConnectionString(connStr string) (*dgraphConnector, error) { + if dgClient, err := dgo.Open(connStr); err != nil { return nil, err + } else { + return newDgraphConnector(dgClient), nil } +} - ds := &dgraphConnector{ - conn: conn, - dgClient: dgo.NewDgraphClient(api.NewDgraphClient(conn)), +func connectWithGrpcTarget(target string, key string) (*dgraphConnector, error) { + var opts []dgo.ClientOption + if strings.Split(target, ":")[0] == "localhost" { + opts = []dgo.ClientOption{ + dgo.WithGrpcOption(grpc.WithTransportCredentials(insecure.NewCredentials())), + } + } else if key == "" { + opts = []dgo.ClientOption{ + dgo.WithSystemCertPool(), + } + } else if strings.Contains(strings.ToLower(target), "cloud.dgraph.io") { + opts = []dgo.ClientOption{ + dgo.WithSystemCertPool(), + dgo.WithDgraphAPIKey(key), + } + } else { + opts = []dgo.ClientOption{ + dgo.WithSystemCertPool(), + dgo.WithBearerToken(key), + } } - return ds, nil + if dgClient, err := dgo.NewClient(target, opts...); err != nil { + return nil, err + } else { + return newDgraphConnector(dgClient), nil + } } diff --git a/runtime/go.mod b/runtime/go.mod index a22483e57..b2db7715f 100644 --- a/runtime/go.mod +++ b/runtime/go.mod @@ -3,7 +3,7 @@ module github.com/hypermodeinc/modus/runtime go 1.24.0 require ( - github.com/hypermodeinc/modus/lib/manifest v0.17.0 + github.com/hypermodeinc/modus/lib/manifest v0.17.1 github.com/hypermodeinc/modus/lib/metadata v0.15.0 github.com/hypermodeinc/modus/lib/wasmextractor v0.13.0 // indirect ) @@ -16,7 +16,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 github.com/buger/jsonparser v1.1.1 github.com/chewxy/math32 v1.11.1 - github.com/dgraph-io/dgo/v240 v240.1.0 + github.com/dgraph-io/dgo/v240 v240.2.0 github.com/docker/docker v28.0.2+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.18.0 @@ -55,17 +55,6 @@ require ( google.golang.org/grpc v1.71.0 ) -require ( - github.com/containerd/log v0.1.0 // indirect - github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0 // indirect - github.com/moby/term v0.5.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect -) - require ( contrib.go.opencensus.io/exporter/jaeger v0.2.1 // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect @@ -103,6 +92,7 @@ require ( github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgraph-io/badger/v4 v4.5.1 // indirect @@ -153,6 +143,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/api v1.15.0 // indirect + github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -182,11 +173,14 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/panicwrap v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d // indirect github.com/philhofer/fwd v1.1.2 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect @@ -199,10 +193,13 @@ require ( github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/runtime/go.sum b/runtime/go.sum index 9399cb28c..bce921230 100644 --- a/runtime/go.sum +++ b/runtime/go.sum @@ -173,8 +173,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= -github.com/dgraph-io/dgo/v240 v240.1.0 h1:xd8z9kEXDWOAblaLJ2HLg2tXD6ngMQwq3ehLUS7GKNg= -github.com/dgraph-io/dgo/v240 v240.1.0/go.mod h1:r8WASETKfodzKqThSAhhTNIzcEMychArKKlZXQufWuA= +github.com/dgraph-io/dgo/v240 v240.2.0 h1:dX7CCx7OSPl38GQ7Pa6jUB1RN21tyNlYpjNFgOsqp7k= +github.com/dgraph-io/dgo/v240 v240.2.0/go.mod h1:CyEuwJgQ8NoNjWbj2ZnvVFPixe5akCbWPR1O0fHpQ+U= github.com/dgraph-io/gqlgen v0.13.2 h1:TNhndk+eHKj5qE7BenKKSYdSIdOGhLqxR1rCiMso9KM= github.com/dgraph-io/gqlgen v0.13.2/go.mod h1:iCOrOv9lngN7KAo+jMgvUPVDlYHdf7qDwsTkQby2Sis= github.com/dgraph-io/gqlparser/v2 v2.1.1/go.mod h1:MYS4jppjyx8b9tuUtjV7jU1UFZK6P9fvO8TsIsQtRKU= @@ -412,8 +412,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0 h1:yOWDWYXrF8cfjXbB3fqabYDKcMqJWqgPKAraauinRe4= github.com/hypermodeinc/dgraph/v24 v24.0.3-0.20250123224129-a0d027dcffe0/go.mod h1:cxYPGOzHMDPQbv5uf9i2CI/dWKrKcyVmRLX7j/P3DxM= -github.com/hypermodeinc/modus/lib/manifest v0.17.0 h1:IelVTN1Bj3ErdyqRLk+3CCPKJJxKnEDsIwL3Rl48TKY= -github.com/hypermodeinc/modus/lib/manifest v0.17.0/go.mod h1:E/lB1Je+vugQhIOfbnJJShTurKzNBp9ZfgQnOrqmfQI= +github.com/hypermodeinc/modus/lib/manifest v0.17.1 h1:EuZRni0s+CNPbN+hFcTcFGmo6n2Mr22Mq7l71Lu4YcQ= +github.com/hypermodeinc/modus/lib/manifest v0.17.1/go.mod h1:HVrZY6o4wuZThtbOXEWInga4I7tVzdgK1gSNg1bsYX0= github.com/hypermodeinc/modus/lib/metadata v0.15.0 h1:Qu75TZg7l43Fi61EhnjasTHZvztrGA90vzDLnCB6ILI= github.com/hypermodeinc/modus/lib/metadata v0.15.0/go.mod h1:vnIwX2DpQyGk93DawGgIaqC5jEdvMeA9tGtvefbwTJw= github.com/hypermodeinc/modus/lib/wasmextractor v0.13.0 h1:9o8qqAllL9qIPYqc5adF+Aw3XWLmLqPiBPMu0AIyiMI= diff --git a/sdk/go/go.mod b/sdk/go/go.mod index 1ecb8d651..fec468c69 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -5,7 +5,7 @@ go 1.23.1 toolchain go1.24.1 require ( - github.com/hypermodeinc/modus/lib/manifest v0.17.0 + github.com/hypermodeinc/modus/lib/manifest v0.17.1 github.com/hypermodeinc/modus/lib/wasmextractor v0.13.0 ) diff --git a/sdk/go/go.sum b/sdk/go/go.sum index 4f1f74432..a956d8392 100644 --- a/sdk/go/go.sum +++ b/sdk/go/go.sum @@ -6,8 +6,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hypermodeinc/modus/lib/manifest v0.17.0 h1:IelVTN1Bj3ErdyqRLk+3CCPKJJxKnEDsIwL3Rl48TKY= -github.com/hypermodeinc/modus/lib/manifest v0.17.0/go.mod h1:E/lB1Je+vugQhIOfbnJJShTurKzNBp9ZfgQnOrqmfQI= +github.com/hypermodeinc/modus/lib/manifest v0.17.1 h1:EuZRni0s+CNPbN+hFcTcFGmo6n2Mr22Mq7l71Lu4YcQ= +github.com/hypermodeinc/modus/lib/manifest v0.17.1/go.mod h1:HVrZY6o4wuZThtbOXEWInga4I7tVzdgK1gSNg1bsYX0= github.com/hypermodeinc/modus/lib/wasmextractor v0.13.0 h1:9o8qqAllL9qIPYqc5adF+Aw3XWLmLqPiBPMu0AIyiMI= github.com/hypermodeinc/modus/lib/wasmextractor v0.13.0/go.mod h1:YCesMU95vF5qkscLMKSYr92OloLe1KGwyiqW2i4OmnE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=