cli: Support migrations for Cockroach DB in CLI Console

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7550
GitOrigin-RevId: eb9d6c8dae1c1f7980df45f5080f8d7cab8791c9
This commit is contained in:
Mohd Bilal 2023-01-19 16:19:47 +05:30 committed by hasura-bot
parent 0dfeea2c5a
commit 094b5e6db2
10 changed files with 288 additions and 15 deletions

View File

@ -38,15 +38,17 @@ type CatalogStateOperations interface {
type SourceKind string
const (
SourceKindPG SourceKind = "postgres"
SourceKindMSSQL SourceKind = "mssql"
SourceKindCitus SourceKind = "citus"
SourceKindPG SourceKind = "postgres"
SourceKindMSSQL SourceKind = "mssql"
SourceKindCitus SourceKind = "citus"
SourceKindCockroach SourceKind = "cockroach"
)
type V2Query interface {
PGSourceOps
MSSQLSourceOps
CitusSourceOps
CockroachSourceOps
Send(requestBody interface{}) (httpcResponse *httpc.Response, body io.Reader, error error)
Bulk([]RequestBody) (io.Reader, error)
}

View File

@ -40,3 +40,11 @@ type CitusSourceOps interface {
type CitusRunSQLInput PGRunSQLInput
type CitusRunSQLOutput PGRunSQLOutput
type CockroachSourceOps interface {
CockroachRunSQL(input CockroachRunSQLInput) (response *CockroachRunSQLOutput, err error)
}
type CockroachRunSQLInput PGRunSQLInput
type CockroachRunSQLOutput PGRunSQLOutput

View File

@ -0,0 +1,32 @@
package cockroach
import (
"context"
"io"
"net/http"
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
"github.com/hasura/graphql-engine/cli/v2/internal/httpc"
)
type SourceOps struct {
*httpc.Client
path string
}
func New(client *httpc.Client, path string) *SourceOps {
return &SourceOps{client, path}
}
func (d *SourceOps) send(body interface{}, responseBodyWriter io.Writer) (*httpc.Response, error) {
var op errors.Op = "cockroach.SourceOps.send"
req, err := d.NewRequest(http.MethodPost, d.path, body)
if err != nil {
return nil, errors.E(op, err)
}
resp, err := d.LockAndDo(context.Background(), req, responseBodyWriter)
if err != nil {
return nil, errors.E(op, err)
}
return resp, nil
}

View File

@ -0,0 +1,36 @@
package cockroach
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
)
func (s *SourceOps) CockroachRunSQL(input hasura.CockroachRunSQLInput) (*hasura.CockroachRunSQLOutput, error) {
var op errors.Op = "cockroach.SourceOps.CockroachRunSQL"
body := hasura.RequestBody{
Type: "cockroach_run_sql",
Args: input,
}
var b = new(bytes.Buffer)
resp, err := s.send(body, b)
if err != nil {
return nil, errors.E(op, err)
}
if resp.StatusCode != http.StatusOK {
if b.Len() > 0 {
return nil, errors.E(op, errors.KindHasuraAPI, b.String())
} else {
return nil, errors.E(op, errors.KindHasuraAPI, fmt.Errorf("cockroach_run_sql api request failed %d", resp.StatusCode))
}
}
parsedResp := new(hasura.CockroachRunSQLOutput)
if err = json.NewDecoder(b).Decode(parsedResp); err != nil {
return nil, errors.E(op, err)
}
return parsedResp, nil
}

View File

@ -0,0 +1,68 @@
package cockroach
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
"github.com/hasura/graphql-engine/cli/v2/internal/httpc"
"github.com/hasura/graphql-engine/cli/v2/internal/testutil"
)
func TestHasuraDatabaseOperations_RunSQL(t *testing.T) {
port, source, teardown := testutil.StartHasuraWithCockroachSource(t, testutil.HasuraDockerImage)
defer teardown()
type fields struct {
httpClient *httpc.Client
path string
}
type args struct {
input hasura.CockroachRunSQLInput
}
tests := []struct {
name string
fields fields
args args
want *hasura.CockroachRunSQLOutput
wantErr bool
assertErr require.ErrorAssertionFunc
}{
{
"can send a run_sql request",
fields{
httpClient: testutil.NewHttpcClient(t, port, nil),
path: "v2/query",
},
args{
input: hasura.CockroachRunSQLInput{
SQL: "CREATE TABLE users2();",
Source: source,
},
},
&hasura.CockroachRunSQLOutput{
ResultType: hasura.CommandOK,
Result: nil,
},
false,
require.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
test := func() {
h := &SourceOps{
Client: tt.fields.httpClient,
path: tt.fields.path,
}
got, err := h.CockroachRunSQL(tt.args.input)
tt.assertErr(t, err)
if !tt.wantErr {
assert.Equal(t, tt.want, got)
}
}
test()
})
}
}

View File

@ -7,6 +7,7 @@ import (
"net/http"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/sourceops/citus"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/sourceops/cockroach"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/sourceops/mssql"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/sourceops/postgres"
@ -20,16 +21,18 @@ type Client struct {
hasura.PGSourceOps
hasura.MSSQLSourceOps
hasura.CitusSourceOps
hasura.CockroachSourceOps
path string
}
func New(c *httpc.Client, path string) *Client {
client := &Client{
Client: c,
PGSourceOps: postgres.New(c, path),
MSSQLSourceOps: mssql.New(c, path),
CitusSourceOps: citus.New(c, path),
path: path,
Client: c,
PGSourceOps: postgres.New(c, path),
MSSQLSourceOps: mssql.New(c, path),
CitusSourceOps: citus.New(c, path),
CockroachSourceOps: cockroach.New(c, path),
path: path,
}
return client
}

View File

@ -348,6 +348,11 @@ func AddDatabaseToHasura(t TestingT, hgeEndpoint, sourceName, databaseKind strin
return connectionStringMSSQL, teardownMSSQL
}
if databaseKind == "cockroach" {
connectionString, teardown := StartCockroachContainer(t)
AddCockroachSourceToHasura(t, hgeEndpoint, connectionString, sourceName)
return connectionString, teardown
}
return "", nil
}
@ -555,3 +560,115 @@ func AddCitusSourceToHasura(t TestingT, hasuraEndpoint, connectionString, source
t.Fatalf("cannot add citus source to hasura: %s", string(body))
}
}
func StartHasuraWithCockroachSource(t TestingT, image string) (hasuraPort, sourceName string, teardown func()) {
hasuraPort, hasuraTeardown := StartHasuraWithMetadataDatabase(t, image)
sourceName = randomdata.SillyName()
connectionStr, cocTeardown := StartCockroachContainer(t)
teardown = func() {
hasuraTeardown()
cocTeardown()
}
hasuraEndpoint := fmt.Sprintf("%s:%s", BaseURL, hasuraPort)
AddCockroachSourceToHasura(t, hasuraEndpoint, connectionStr, sourceName)
return hasuraPort, sourceName, teardown
}
func StartCockroachContainer(t TestingT) (connectionString string, teardown func()) {
user := "root"
database := "defaultdb"
pool, err := dockertest.NewPool("")
if err != nil {
t.Fatalf("Could not connect to Docker: %s", err)
}
uniqueName := getUniqueName(t)
containerOpts := &dockertest.RunOptions{
Name: fmt.Sprintf("%s-%s", uniqueName, "cockroach"),
Repository: "cockroachdb/cockroach-unstable",
Tag: "v22.2.0-beta.4",
Env: []string{
fmt.Sprintf("COCKROACH_USER=%s", user),
fmt.Sprintf("COCKROACH_DATABASE=%s", database),
},
Cmd: []string{
"start-single-node",
"--insecure",
"--accept-sql-without-tls",
},
ExposedPorts: []string{"26257",
"8080", // port for cockroach console
},
Auth: getDockerAuthConfig(t),
}
container, err := pool.RunWithOptions(containerOpts)
if err != nil {
t.Fatalf("Could not start CockroachDB container: %s", err)
}
var db *sql.DB
if err = pool.Retry(func() error {
var err error
connectionString = fmt.Sprintf("postgresql://%s@%s:%s/%s?sslmode=disable", user, "0.0.0.0", container.GetPort("26257/tcp"), database)
db, err = sql.Open("postgres", connectionString)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
t.Fatal(err)
}
teardown = func() {
if err = pool.Purge(container); err != nil {
t.Fatalf("Could not purge CockroachDB container: %s", err)
}
}
connectionString = fmt.Sprintf("postgresql://%s@%s:%s/%s?sslmode=disable", user, DockerSwitchIP, container.GetPort("26257/tcp"), database)
return connectionString, teardown
}
func AddCockroachSourceToHasura(t TestingT, hasuraEndpoint, connectionString, sourceName string) {
addSourceToHasura(t, hasuraEndpoint, connectionString, sourceName, "cockroach_add_source")
}
func addSourceToHasura(t TestingT, hasuraEndpoint, connectionString, sourceName, requestType string) {
url := fmt.Sprintf("%s/v1/metadata", hasuraEndpoint)
body := fmt.Sprintf(`
{
"type": "%s",
"args": {
"name": "%s",
"configuration": {
"connection_info": {
"database_url": "%s"
}
}
}
}
`, requestType, sourceName, connectionString)
fmt.Println(connectionString)
fmt.Println(hasuraEndpoint)
req, err := http.NewRequest("POST", url, strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
adminSecret := os.Getenv("HASURA_GRAPHQL_TEST_ADMIN_SECRET")
if adminSecret != "" {
req.Header.Set("x-hasura-admin-secret", adminSecret)
}
r, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if r.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
defer r.Body.Close()
t.Fatalf("cannot add %s source to hasura: %s", sourceName, string(body))
}
}

View File

@ -156,6 +156,8 @@ func MigrateAPI(c *gin.Context) {
sourceKind = hasura.SourceKindMSSQL
case "citus":
sourceKind = hasura.SourceKindCitus
case "cockroach":
sourceKind = hasura.SourceKindCockroach
default:
c.JSON(http.StatusInternalServerError, &Response{Code: "request_parse_error", Message: fmt.Sprintf("cannot determine database kind for '%v'", sourceName)})
return

View File

@ -10,16 +10,13 @@ import (
"path"
"strings"
"github.com/hasura/graphql-engine/cli/v2/internal/statestore"
"github.com/hasura/graphql-engine/cli/v2/internal/statestore/settings"
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
"github.com/hasura/graphql-engine/cli/v2/internal/statestore"
"github.com/hasura/graphql-engine/cli/v2/internal/statestore/settings"
"github.com/hasura/graphql-engine/cli/v2/migrate/database"
"github.com/mitchellh/mapstructure"
"github.com/hasura/graphql-engine/cli/v2/migrate/database"
log "github.com/sirupsen/logrus"
)
@ -42,6 +39,7 @@ var (
"select", "insert", "update", "delete", "count", "run_sql", "bulk",
"mssql_select", "mssql_insert", "mssql_update", "mssql_delete", "mssql_count", "mssql_run_sql",
"citus_select", "citus_insert", "citus_update", "citus_delete", "citus_count", "citus_run_sql",
"cockroach_run_sql",
}
queryTypesMap = func() map[string]bool {
var m = map[string]bool{}
@ -80,6 +78,7 @@ type HasuraDB struct {
pgSourceOps hasura.PGSourceOps
mssqlSourceOps hasura.MSSQLSourceOps
citusSourceOps hasura.CitusSourceOps
cockroachSourceOps hasura.CockroachSourceOps
genericQueryRequest hasura.GenericSend
hasuraClient *hasura.Client
migrationsStateStore statestore.MigrationsStateStore
@ -264,6 +263,11 @@ func (h *HasuraDB) Run(migration io.Reader, fileType, fileName string) error {
if err != nil {
return errors.E(op, err)
}
case hasura.SourceKindCockroach:
_, err := h.cockroachSourceOps.CockroachRunSQL(hasura.CockroachRunSQLInput(sqlInput))
if err != nil {
return errors.E(op, err)
}
default:
return errors.E(op, fmt.Errorf("unsupported source kind, source name: %v kind: %v", h.hasuraOpts.SourceName, h.hasuraOpts.SourceKind))
@ -284,6 +288,7 @@ func (h *HasuraDB) Run(migration io.Reader, fileType, fileName string) error {
return nil
}
// responsible for deciding which request to send to v1/metadata and which to send to v2/query
func sendMetadataMigrations(hasuradb *HasuraDB, requests []interface{}) error {
var op errors.Op = "hasuradb.sendMetadataMigrations"
var metadataRequests []interface{}

View File

@ -218,7 +218,7 @@ func GetFilePath(dir string) *nurl.URL {
func IsMigrationsSupported(kind hasura.SourceKind) bool {
switch kind {
case hasura.SourceKindMSSQL, hasura.SourceKindPG, hasura.SourceKindCitus:
case hasura.SourceKindMSSQL, hasura.SourceKindPG, hasura.SourceKindCitus, hasura.SourceKindCockroach:
return true
}
return false