Skip to content

Commit 0c811ef

Browse files
Merge pull request #4 from tz1and/feat/hasura-config
Feat/hasura config
2 parents c342e37 + ba44065 commit 0c811ef

File tree

5 files changed

+153
-58
lines changed

5 files changed

+153
-58
lines changed

config/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Config struct {
2121

2222
// Substitute -
2323
func (c *Config) Substitute() error {
24+
c.Hasura.SetSourceName()
2425
return nil
2526
}
2627

@@ -52,11 +53,19 @@ type Database struct {
5253
type Hasura struct {
5354
URL string `yaml:"url" validate:"required,url"`
5455
Secret string `yaml:"admin_secret" validate:"required"`
56+
Source string `yaml:"source" validate:"omitempty"`
5557
RowsLimit uint64 `yaml:"select_limit" validate:"gt=0"`
5658
EnableAggregations bool `yaml:"allow_aggregation"`
59+
AddSource bool `yaml:"add_source"`
5760
Rest *bool `yaml:"rest"`
5861
}
5962

63+
func (s *Hasura) SetSourceName() {
64+
if s.Source == "" {
65+
s.Source = "default"
66+
}
67+
}
68+
6069
// Prometheus -
6170
type Prometheus struct {
6271
URL string `yaml:"url" validate:"required"`

hasura/api.go

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package hasura
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"net/http"
78
"net/url"
89
"path"
910
"time"
1011

12+
"github.com/dipdup-net/go-lib/config"
1113
jsoniter "github.com/json-iterator/go"
1214

1315
"github.com/pkg/errors"
@@ -120,25 +122,47 @@ func (api *API) Health(ctx context.Context) error {
120122
return err
121123
}
122124

123-
// ExportMetadata -
124-
func (api *API) ExportMetadata(ctx context.Context, data *Metadata) (ExportMetadataResponse, error) {
125-
req := request{
126-
Type: "export_metadata",
127-
Args: data,
125+
// AddSource -
126+
func (api *API) AddSource(ctx context.Context, hasura *config.Hasura, cfg config.Database) error {
127+
req := Request{
128+
Type: "pg_add_source",
129+
Args: map[string]interface{}{
130+
"name": hasura.Source,
131+
"configuration": Configuration{
132+
ConnectionInfo: ConnectionInfo{
133+
DatabaseUrl: fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database),
134+
UsePreparedStatements: true,
135+
IsolationLevel: "read-committed",
136+
},
137+
},
138+
"replace_configuration": true,
139+
},
128140
}
129-
var resp ExportMetadataResponse
130-
err := api.post(ctx, "/v1/query", nil, req, &resp)
141+
err := api.post(ctx, "/v1/metadata", nil, req, nil)
142+
return err
143+
}
144+
145+
// ExportMetadata -
146+
func (api *API) ExportMetadata(ctx context.Context) (Metadata, error) {
147+
req := versionedRequest{
148+
Type: "export_metadata",
149+
Version: 2,
150+
Args: map[string]interface{}{},
151+
}
152+
var resp Metadata
153+
err := api.post(ctx, "/v1/metadata", nil, req, &resp)
131154
return resp, err
132155
}
133156

134157
// ReplaceMetadata -
135158
func (api *API) ReplaceMetadata(ctx context.Context, data *Metadata) error {
136-
req := request{
137-
Type: "replace_metadata",
138-
Args: data,
159+
req := versionedRequest{
160+
Type: "replace_metadata",
161+
Version: 1,
162+
Args: data,
139163
}
140164
var resp replaceMetadataResponse
141-
if err := api.post(ctx, "/v1/query", nil, req, &resp); err != nil {
165+
if err := api.post(ctx, "/v1/metadata", nil, req, &resp); err != nil {
142166
return err
143167
}
144168
if resp.Message == "success" {
@@ -148,45 +172,52 @@ func (api *API) ReplaceMetadata(ctx context.Context, data *Metadata) error {
148172
}
149173

150174
// TrackTable -
151-
func (api *API) TrackTable(ctx context.Context, schema, name string) error {
152-
req := request{
153-
Type: "track_table",
175+
func (api *API) TrackTable(ctx context.Context, name string, source string) error {
176+
req := Request{
177+
Type: "pg_track_table",
154178
Args: map[string]string{
155-
"schema": schema,
156-
"name": name,
179+
"table": name,
180+
"source": source,
157181
},
158182
}
159-
return api.post(ctx, "/v1/query", nil, req, nil)
183+
return api.post(ctx, "/v1/metadata", nil, req, nil)
184+
}
185+
186+
// CustomConfiguration
187+
func (api *API) CustomConfiguration(ctx context.Context, conf interface{}) error {
188+
return api.post(ctx, "/v1/metadata", nil, conf, nil)
160189
}
161190

162191
// CreateSelectPermissions - A select permission is used to restrict access to only the specified columns and rows.
163-
func (api *API) CreateSelectPermissions(ctx context.Context, table, role string, perm Permission) error {
164-
req := request{
165-
Type: "create_select_permission",
192+
func (api *API) CreateSelectPermissions(ctx context.Context, table, source string, role string, perm Permission) error {
193+
req := Request{
194+
Type: "pg_create_select_permission",
166195
Args: map[string]interface{}{
167196
"table": table,
168197
"role": role,
169198
"permission": perm,
199+
"source": source,
170200
},
171201
}
172-
return api.post(ctx, "/v1/query", nil, req, nil)
202+
return api.post(ctx, "/v1/metadata", nil, req, nil)
173203
}
174204

175205
// DropSelectPermissions -
176-
func (api *API) DropSelectPermissions(ctx context.Context, table, role string) error {
177-
req := request{
178-
Type: "drop_select_permission",
206+
func (api *API) DropSelectPermissions(ctx context.Context, table, source string, role string) error {
207+
req := Request{
208+
Type: "pg_drop_select_permission",
179209
Args: map[string]interface{}{
180-
"table": table,
181-
"role": role,
210+
"table": table,
211+
"role": role,
212+
"source": source,
182213
},
183214
}
184-
return api.post(ctx, "/v1/query", nil, req, nil)
215+
return api.post(ctx, "/v1/metadata", nil, req, nil)
185216
}
186217

187218
// CreateRestEndpoint -
188219
func (api *API) CreateRestEndpoint(ctx context.Context, name, url, queryName, collectionName string) error {
189-
req := request{
220+
req := Request{
190221
Type: "create_rest_endpoint",
191222
Args: map[string]interface{}{
192223
"name": name,
@@ -200,5 +231,5 @@ func (api *API) CreateRestEndpoint(ctx context.Context, name, url, queryName, co
200231
},
201232
},
202233
}
203-
return api.post(ctx, "/v1/query", nil, req, nil)
234+
return api.post(ctx, "/v1/metadata", nil, req, nil)
204235
}

hasura/hasura.go

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,49 +41,66 @@ func checkHealth(ctx context.Context, api *API) {
4141
}
4242

4343
// Create - creates hasura models
44-
func Create(ctx context.Context, hasura *config.Hasura, cfg config.Database, views []string, models ...interface{}) error {
44+
func Create(ctx context.Context, hasura *config.Hasura, cfg config.Database, views []string, custom []Request, models ...interface{}) error {
4545
if hasura == nil {
4646
return nil
4747
}
4848
api := New(hasura.URL, hasura.Secret)
4949

5050
checkHealth(ctx, api)
5151

52+
if hasura.AddSource {
53+
log.Info().Msg("Adding source...")
54+
err := api.AddSource(ctx, hasura, cfg)
55+
if err != nil {
56+
return err
57+
}
58+
}
59+
5260
metadata, err := Generate(*hasura, cfg, models...)
5361
if err != nil {
5462
return err
5563
}
5664

5765
log.Info().Msg("Fetching existing metadata...")
58-
export, err := api.ExportMetadata(ctx, metadata)
66+
export, err := api.ExportMetadata(ctx)
5967
if err != nil {
6068
return err
6169
}
6270

63-
log.Info().Msg("Merging metadata...")
64-
tables := make(map[string]struct{})
65-
for i := range metadata.Tables {
66-
tables[metadata.Tables[i].Schema.Name] = struct{}{}
71+
// Find our source in the existing metadata
72+
var selected_source *Source = nil
73+
for idx := range export.Sources {
74+
if export.Sources[idx].Name == hasura.Source {
75+
selected_source = &export.Sources[idx]
76+
break
77+
}
78+
}
79+
if selected_source == nil {
80+
return errors.Errorf("Source '%s' not found on exported metadata", hasura.Source)
6781
}
6882

69-
for _, table := range export.Tables {
70-
if _, ok := tables[table.Schema.Name]; !ok {
71-
metadata.Tables = append(metadata.Tables, table)
72-
}
83+
log.Info().Msg("Merging metadata...")
84+
// Clear tables
85+
// TODO: maybe instead replace tables by name.
86+
selected_source.Tables = make([]Table, 0)
87+
// Insert generated tables
88+
for _, table := range metadata.Sources[0].Tables {
89+
selected_source.Tables = append(selected_source.Tables, table)
7390
}
7491

75-
if err := createQueryCollections(metadata); err != nil {
92+
if err := createQueryCollections(&export); err != nil {
7693
return err
7794
}
7895

7996
log.Info().Msg("Replacing metadata...")
80-
if err := api.ReplaceMetadata(ctx, metadata); err != nil {
97+
if err := api.ReplaceMetadata(ctx, &export); err != nil {
8198
return err
8299
}
83100

84-
if len(metadata.QueryCollections) > 0 && (hasura.Rest == nil || *hasura.Rest) {
101+
if len(export.QueryCollections) > 0 && (hasura.Rest == nil || *hasura.Rest) {
85102
log.Info().Msg("Creating REST endpoints...")
86-
for _, query := range metadata.QueryCollections[0].Definition.Queries {
103+
for _, query := range export.QueryCollections[0].Definition.Queries {
87104
if err := api.CreateRestEndpoint(ctx, query.Name, query.Name, query.Name, allowedQueries); err != nil {
88105
if e, ok := err.(APIError); !ok || !e.AlreadyExists() {
89106
return err
@@ -94,15 +111,15 @@ func Create(ctx context.Context, hasura *config.Hasura, cfg config.Database, vie
94111

95112
log.Info().Msg("Tracking views...")
96113
for i := range views {
97-
if err := api.TrackTable(ctx, "public", views[i]); err != nil {
114+
if err := api.TrackTable(ctx, views[i], hasura.Source); err != nil {
98115
if !strings.Contains(err.Error(), "view/table already tracked") {
99116
return err
100117
}
101118
}
102-
if err := api.DropSelectPermissions(ctx, views[i], "user"); err != nil {
119+
if err := api.DropSelectPermissions(ctx, views[i], hasura.Source, "user"); err != nil {
103120
log.Warn().Err(err).Msg("")
104121
}
105-
if err := api.CreateSelectPermissions(ctx, views[i], "user", Permission{
122+
if err := api.CreateSelectPermissions(ctx, views[i], hasura.Source, "user", Permission{
106123
Limit: hasura.RowsLimit,
107124
AllowAggs: hasura.EnableAggregations,
108125
Columns: Columns{"*"},
@@ -112,22 +129,32 @@ func Create(ctx context.Context, hasura *config.Hasura, cfg config.Database, vie
112129
}
113130
}
114131

132+
log.Info().Msg("Running custom configurations...")
133+
for _, conf := range custom {
134+
if err := api.CustomConfiguration(ctx, conf); err != nil {
135+
log.Warn().Err(err).Msg("")
136+
}
137+
}
138+
115139
return nil
116140
}
117141

118142
// Generate - creates hasura table structure in JSON from `models`. `models` should be pointer to your table models. `cfg` is DB config from YAML.
119143
func Generate(hasura config.Hasura, cfg config.Database, models ...interface{}) (*Metadata, error) {
120-
tables := make([]Table, 0)
121144
schema := getSchema(cfg)
145+
source := Source{
146+
Name: hasura.Source,
147+
Tables: make([]Table, 0),
148+
}
122149
for _, model := range models {
123150
table, err := generateOne(hasura, schema, model)
124151
if err != nil {
125152
return nil, err
126153
}
127-
tables = append(tables, table.HasuraSchema)
154+
source.Tables = append(source.Tables, table.HasuraSchema)
128155
}
129156

130-
return newMetadata(2, tables), nil
157+
return newMetadata(3, []Source{source}), nil
131158
}
132159

133160
type table struct {

hasura/requests.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ package hasura
22

33
import "github.com/pkg/errors"
44

5-
type request struct {
5+
type Request struct {
66
Type string `json:"type"`
77
Args interface{} `json:"args"`
88
}
99

10+
type versionedRequest struct {
11+
Type string `json:"type"`
12+
Version int `json:"int"`
13+
Args interface{} `json:"args"`
14+
}
15+
1016
// Permission -
1117
type Permission struct {
1218
Columns Columns `json:"columns"`
@@ -18,22 +24,42 @@ type Permission struct {
1824
// Metadata -
1925
type Metadata struct {
2026
Version int `json:"version"`
21-
Tables []Table `json:"tables"`
27+
Sources []Source `json:"sources"`
2228
QueryCollections []QueryCollection `json:"query_collections,omitempty"`
29+
RestEndpoints []interface{} `json:"rest_endpoints"`
2330
}
2431

25-
func newMetadata(version int, tables []Table) *Metadata {
32+
func newMetadata(version int, sources []Source) *Metadata {
2633
return &Metadata{
2734
Version: version,
28-
Tables: tables,
35+
Sources: sources,
2936
}
3037
}
3138

39+
type Configuration struct {
40+
ConnectionInfo ConnectionInfo `json:"connection_info"`
41+
}
42+
43+
type ConnectionInfo struct {
44+
UsePreparedStatements bool `json:"use_prepared_statements"`
45+
IsolationLevel string `json:"isolation_level"`
46+
DatabaseUrl interface{} `json:"database_url"`
47+
}
48+
49+
// Source -
50+
type Source struct {
51+
Name string `json:"name"`
52+
Kind string `json:"kind"`
53+
Tables []Table `json:"tables"`
54+
Configuration Configuration `json:"configuration"`
55+
}
56+
3257
// Table -
3358
type Table struct {
3459
ObjectRelationships []interface{} `json:"object_relationships"`
3560
ArrayRelationships []interface{} `json:"array_relationships"`
3661
SelectPermissions []SelectPermission `json:"select_permissions"`
62+
Configuration TableConfiguration `json:"configuration"`
3763
Schema TableSchema `json:"table"`
3864
}
3965

@@ -49,6 +75,13 @@ func newMetadataTable(name, schema string) Table {
4975
}
5076
}
5177

78+
// TableConfiguration -
79+
type TableConfiguration struct {
80+
Comment *string `json:"comment"`
81+
CustomRootFields map[string]string `json:"custom_root_fields"`
82+
CustomColumnNames map[string]string `json:"custom_column_names"`
83+
}
84+
5285
// TableSchema -
5386
type TableSchema struct {
5487
Schema string `json:"schema"`

0 commit comments

Comments
 (0)