From cc24e50515d6337c2076a317671de51e60513931 Mon Sep 17 00:00:00 2001 From: Aravind K P Date: Wed, 21 Apr 2021 15:39:09 +0530 Subject: [PATCH] cli: support `api_limits` in metadata GitOrigin-RevId: e0d2e30bb2775f761232218aa67cf3781df3e49a --- CHANGELOG.md | 4 +- .../metadataobject/api_limits/api_limits.go | 94 ++++++++++++ .../api_limits/api_limits_test.go | 136 ++++++++++++++++++ .../api_limits/testdata/metadata.json | 14 ++ .../testdata/metadata/api_limits.yaml | 6 + cli/internal/metadataobject/metadataobject.go | 2 + .../metadataobject/tables/v3metadata.go | 4 + 7 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 cli/internal/metadataobject/api_limits/api_limits.go create mode 100644 cli/internal/metadataobject/api_limits/api_limits_test.go create mode 100644 cli/internal/metadataobject/api_limits/testdata/metadata.json create mode 100644 cli/internal/metadataobject/api_limits/testdata/metadata/api_limits.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index d060d32a5e3bd..9db70243fcaff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Next release (Add entries below in the order of: server, console, cli, docs, others) +- cli: fix regression - `metadata apply —dry-run` was overwriting local metadata files with metadata on server when it should just display the differences. +- cli: add support for `api_limits` metadata object + ## v2.0.0-alpha.9 ### Support comparing columns across related tables in permission's boolean expressions @@ -22,7 +25,6 @@ only when there are enough present in the items inventory. - server: an inherited role's limit will be the max limit of all the roles (#6671) - console: add bigquery support (#1000) - cli: add support for bigquery in metadata operations -- cli: fix regression - `metadata apply —dry-run` was overwriting local metadata files with metadata on server when it should just display the differences. ## v2.0.0-alpha.8 diff --git a/cli/internal/metadataobject/api_limits/api_limits.go b/cli/internal/metadataobject/api_limits/api_limits.go new file mode 100644 index 0000000000000..aefda6f6b5907 --- /dev/null +++ b/cli/internal/metadataobject/api_limits/api_limits.go @@ -0,0 +1,94 @@ +package apilimits + +import ( + "io/ioutil" + "path/filepath" + + "github.com/sirupsen/logrus" + + "github.com/hasura/graphql-engine/cli" + "gopkg.in/yaml.v2" +) + +const ( + MetadataFilename string = "api_limits.yaml" +) + +type MetadataObject struct { + MetadataDir string + + logger *logrus.Logger +} + +func New(ec *cli.ExecutionContext, baseDir string) *MetadataObject { + return &MetadataObject{ + MetadataDir: baseDir, + logger: ec.Logger, + } +} + +func (o *MetadataObject) Validate() error { + return nil +} + +func (o *MetadataObject) CreateFiles() error { + var v interface{} + data, err := yaml.Marshal(v) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(o.MetadataDir, MetadataFilename), data, 0644) + if err != nil { + return err + } + return nil +} + +func (o *MetadataObject) Build(metadata *yaml.MapSlice) error { + data, err := ioutil.ReadFile(filepath.Join(o.MetadataDir, MetadataFilename)) + if err != nil { + return err + } + item := yaml.MapItem{ + Key: o.Name(), + } + var obj yaml.MapSlice + err = yaml.Unmarshal(data, &obj) + if err != nil { + return err + } + if len(obj) > 0 { + item.Value = obj + *metadata = append(*metadata, item) + } + return nil +} + +func (o *MetadataObject) Export(metadata yaml.MapSlice) (map[string][]byte, error) { + var apiLimits interface{} + for _, item := range metadata { + k, ok := item.Key.(string) + if !ok || k != o.Name() { + continue + } + apiLimits = item.Value + } + if apiLimits == nil { + o.logger.WithFields(logrus.Fields{ + "object": o.Name(), + "reason": "not found in metadata", + }).Debugf("skipped building %s", o.Name()) + return nil, nil + } + data, err := yaml.Marshal(apiLimits) + if err != nil { + return nil, err + } + return map[string][]byte{ + filepath.Join(o.MetadataDir, MetadataFilename): data, + }, nil +} + +func (o *MetadataObject) Name() string { + return "api_limits" +} diff --git a/cli/internal/metadataobject/api_limits/api_limits_test.go b/cli/internal/metadataobject/api_limits/api_limits_test.go new file mode 100644 index 0000000000000..f51916a7699fa --- /dev/null +++ b/cli/internal/metadataobject/api_limits/api_limits_test.go @@ -0,0 +1,136 @@ +package apilimits + +import ( + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "io/ioutil" + "testing" + + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +func TestMetadataObject_Build(t *testing.T) { + type fields struct { + MetadataDir string + logger *logrus.Logger + } + type args struct { + metadata *yaml.MapSlice + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + "can build from file", + fields{ + MetadataDir: "testdata/metadata", + logger: logrus.New(), + }, + args{ + metadata: new(yaml.MapSlice), + }, + `api_limits: + disabled: false + rate_limit: + per_role: {} + global: + unique_params: IP + max_reqs_per_min: 1 +`, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MetadataObject{ + MetadataDir: tt.fields.MetadataDir, + logger: tt.fields.logger, + } + err := m.Build(tt.args.metadata) + if tt.wantErr { + require.Error(t, err) + }else { + b, err := yaml.Marshal(tt.args.metadata) + assert.NoError(t, err) + assert.Equal(t, tt.want, string(b)) + } + }) + } +} + +func TestMetadataObject_Export(t *testing.T) { + type fields struct { + MetadataDir string + logger *logrus.Logger + } + type args struct { + metadata yaml.MapSlice + } + tests := []struct { + name string + fields fields + args args + want map[string][]byte + wantErr bool + }{ + { + "can export metadata with api_limits", + fields{ + MetadataDir: "testdata/metadata", + logger: logrus.New(), + }, + args{ + metadata: func() yaml.MapSlice { + var metadata yaml.MapSlice + jsonb, err := ioutil.ReadFile("testdata/metadata.json") + assert.NoError(t, err) + assert.NoError(t, yaml.Unmarshal(jsonb, &metadata)) + return metadata + }(), + }, + map[string][]byte{ + "testdata/metadata/api_limits.yaml": []byte(`disabled: false +rate_limit: + per_role: {} + global: + unique_params: IP + max_reqs_per_min: 1 +`), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + obj := &MetadataObject{ + MetadataDir: tt.fields.MetadataDir, + logger: tt.fields.logger, + } + got, err := obj.Export(tt.args.metadata) + if tt.wantErr { + require.Error(t, err) + }else { + require.NoError(t, err) + var wantContent = map[string]string{} + var gotContent = map[string]string{} + for k, v := range got { + gotContent[k] = string(v) + } + for k, v := range tt.want { + wantContent[k] = string(v) + } + assert.NoError(t, err) + assert.NoError(t, err) + if diff := cmp.Diff(wantContent, gotContent); diff != "" { + t.Errorf("Export() mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/cli/internal/metadataobject/api_limits/testdata/metadata.json b/cli/internal/metadataobject/api_limits/testdata/metadata.json new file mode 100644 index 0000000000000..e516239d89d32 --- /dev/null +++ b/cli/internal/metadataobject/api_limits/testdata/metadata.json @@ -0,0 +1,14 @@ +{ + "version": 3, + "sources": [], + "api_limits": { + "disabled": false, + "rate_limit": { + "per_role": {}, + "global": { + "unique_params": "IP", + "max_reqs_per_min": 1 + } + } + } +} \ No newline at end of file diff --git a/cli/internal/metadataobject/api_limits/testdata/metadata/api_limits.yaml b/cli/internal/metadataobject/api_limits/testdata/metadata/api_limits.yaml new file mode 100644 index 0000000000000..b0030fa2a210c --- /dev/null +++ b/cli/internal/metadataobject/api_limits/testdata/metadata/api_limits.yaml @@ -0,0 +1,6 @@ +disabled: false +rate_limit: + per_role: {} + global: + unique_params: IP + max_reqs_per_min: 1 \ No newline at end of file diff --git a/cli/internal/metadataobject/metadataobject.go b/cli/internal/metadataobject/metadataobject.go index 19f77afd7ec69..90d0147c101e7 100644 --- a/cli/internal/metadataobject/metadataobject.go +++ b/cli/internal/metadataobject/metadataobject.go @@ -4,6 +4,7 @@ import ( "github.com/hasura/graphql-engine/cli" "github.com/hasura/graphql-engine/cli/internal/metadataobject/actions" "github.com/hasura/graphql-engine/cli/internal/metadataobject/allowlist" + apilimits "github.com/hasura/graphql-engine/cli/internal/metadataobject/api_limits" crontriggers "github.com/hasura/graphql-engine/cli/internal/metadataobject/cron_triggers" "github.com/hasura/graphql-engine/cli/internal/metadataobject/functions" inheritedroles "github.com/hasura/graphql-engine/cli/internal/metadataobject/inherited_roles" @@ -43,6 +44,7 @@ func GetMetadataObjectsWithDir(ec *cli.ExecutionContext, dir ...string) Objects objects = append(objects, crontriggers.New(ec, metadataDir)) objects = append(objects, restendpoints.New(ec, metadataDir)) objects = append(objects, inheritedroles.New(ec, metadataDir)) + objects = append(objects, apilimits.New(ec, metadataDir)) if ec.HasMetadataV3 { if ec.Config.Version >= cli.V3 { diff --git a/cli/internal/metadataobject/tables/v3metadata.go b/cli/internal/metadataobject/tables/v3metadata.go index 4f4fdc4cd4449..c03c61e429d22 100644 --- a/cli/internal/metadataobject/tables/v3metadata.go +++ b/cli/internal/metadataobject/tables/v3metadata.go @@ -8,6 +8,10 @@ import ( "gopkg.in/yaml.v2" ) +/* +V3MetadataTableConfig is responsible for exporting and applying "tables" metadata objects +in config v2 format on a server with v3 metadata + */ type V3MetadataTableConfig struct { *TableConfig }