Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve CLI configuration troubleshooting after updated #35

Merged
merged 8 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
HTTP Connector allows you to quickly convert HTTP APIs to NDC schema and proxy requests from GraphQL Engine v3 to remote services.
The connector can automatically transform OpenAPI 2.0 and 3.0 definitions to NDC schema.

![HTTP connector](./assets/rest_connector.png)
![HTTP connector](./docs/assets/rest_connector.png)

> [!NOTE]
> HTTP connector is configuration-based HTTP engine and isn't limited to the OpenAPI specs only. Use [OpenAPI Connector](https://hasura.io/docs/3.0/connectors/external-apis/open-api) if you want to take more control of OpenAPI via code generation.
Expand Down
30 changes: 15 additions & 15 deletions connector/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,20 +1375,20 @@ func TestConnectorTLS(t *testing.T) {
testServer := connServer.BuildTestServer()
defer testServer.Close()

t.Run("default_cert", func(t *testing.T) {
func() {
findPetsBody := []byte(`{
"collection": "findPets",
"query": {
"fields": {
"__value": {
"type": "column",
"column": "__value"
"collection": "findPets",
"query": {
"fields": {
"__value": {
"type": "column",
"column": "__value"
}
}
}
},
"arguments": {},
"collection_relationships": {}
}`)
},
"arguments": {},
"collection_relationships": {}
}`)

res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(findPetsBody))
assert.NilError(t, err)
Expand All @@ -1401,9 +1401,9 @@ func TestConnectorTLS(t *testing.T) {
},
},
})
})
}()

t.Run("server1_cert", func(t *testing.T) {
func() {
findPetsBody := []byte(`{
"collection": "findPets",
"query": {
Expand Down Expand Up @@ -1436,7 +1436,7 @@ func TestConnectorTLS(t *testing.T) {
},
},
})
})
}()

time.Sleep(2 * time.Second)
assert.Equal(t, 1, mockServer.Count())
Expand Down
20 changes: 14 additions & 6 deletions connector/internal/security/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ import (

// OAuth2Client represent the client of the OAuth2 client credentials
type OAuth2Client struct {
client *http.Client
client *http.Client
isEmpty bool
}

var _ Credential = &OAuth2Client{}

// NewOAuth2Client creates an OAuth2 client from the security scheme
func NewOAuth2Client(ctx context.Context, httpClient *http.Client, flowType schema.OAuthFlowType, config *schema.OAuthFlow) (*OAuth2Client, error) {
if flowType != schema.ClientCredentialsFlow {
if flowType != schema.ClientCredentialsFlow || config.TokenURL == nil || config.ClientID == nil || config.ClientSecret == nil {
return &OAuth2Client{
client: httpClient,
client: httpClient,
isEmpty: true,
}, nil
}

Expand Down Expand Up @@ -52,11 +54,13 @@ func NewOAuth2Client(ctx context.Context, httpClient *http.Client, flowType sche

var endpointParams url.Values
for key, envValue := range config.EndpointParams {
value, err := envValue.Get()
value, err := envValue.GetOrDefault("")
if err != nil {
return nil, fmt.Errorf("endpointParams[%s]: %w", key, err)
}
endpointParams.Set(key, value)
if value != "" {
endpointParams.Set(key, value)
}
}

ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
Expand All @@ -82,11 +86,15 @@ func (oc OAuth2Client) GetClient() *http.Client {

// Inject the credential into the incoming request
func (oc OAuth2Client) Inject(req *http.Request) (bool, error) {
return true, nil
return !oc.isEmpty, nil
}

// InjectMock injects the mock credential into the incoming request for explain APIs.
func (oc OAuth2Client) InjectMock(req *http.Request) bool {
if oc.isEmpty {
return false
}

req.Header.Set(schema.AuthorizationHeader, "Bearer xxx")

return true
Expand Down
9 changes: 4 additions & 5 deletions connector/testdata/tls/create-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
set -e

pushd `dirname $0` > /dev/null
SCRIPTPATH=`pwd -P`
SCRIPT_PATH=`pwd -P`
popd > /dev/null
SCRIPTFILE=`basename $0`

DAYS=36500

Expand All @@ -24,10 +23,10 @@ function generate_client() {
}

function generate_cert() {
rm -r ${SCRIPTPATH}/$1
mkdir -p ${SCRIPTPATH}/$1
rm -r ${SCRIPT_PATH}/$1
mkdir -p ${SCRIPT_PATH}/$1

pushd ${SCRIPTPATH}/$1
pushd ${SCRIPT_PATH}/$1

# generate a self-signed rootCA file that would be used to sign both the server and client cert.
# Alternatively, we can use different CA files to sign the server and client, but for our use case, we would use a single CA.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toolchain go1.23.1
require (
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/google/uuid v1.6.0
github.com/hasura/ndc-http/ndc-http-schema v0.0.0-20241124160706-95bf5710211d
github.com/hasura/ndc-http/ndc-http-schema v0.0.0-20241201015041-4da425f13917
github.com/hasura/ndc-sdk-go v1.6.3-0.20241127025002-02d7a257e75f
go.opentelemetry.io/otel v1.32.0
go.opentelemetry.io/otel/trace v1.32.0
Expand All @@ -17,7 +17,7 @@ require (
)

require (
github.com/alecthomas/kong v1.4.0 // indirect
github.com/alecthomas/kong v1.5.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA=
github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.5.0 h1:pvJ7ucmgyBrGcdHVYD3xc9rqbcnVNRQ63mYv6KNrwYs=
github.com/alecthomas/kong v1.5.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
Expand Down
30 changes: 27 additions & 3 deletions ndc-http-schema/command/update.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
package command

import (
"errors"
"log/slog"
"os"
"time"

"github.com/hasura/ndc-http/ndc-http-schema/configuration"
)

// UpdateCommandArguments represent input arguments of the `update` command
type UpdateCommandArguments struct {
Dir string `default:"." env:"HASURA_PLUGIN_CONNECTOR_CONTEXT_PATH" help:"The directory where the config.yaml file is present" short:"d"`
Dir string `default:"." env:"HASURA_PLUGIN_CONNECTOR_CONTEXT_PATH" help:"The directory where the config.yaml file is present" short:"d"`
Yes bool `default:"false" help:"Skip the continue confirmation prompt" short:"y"`
}

// UpdateConfiguration updates the configuration for the HTTP connector
func UpdateConfiguration(args *UpdateCommandArguments, logger *slog.Logger) error {
func UpdateConfiguration(args *UpdateCommandArguments, logger *slog.Logger, noColor bool) error {
start := time.Now()
if err := configuration.UpdateHTTPConfiguration(args.Dir, logger); err != nil {
config, schemas, err := configuration.UpdateHTTPConfiguration(args.Dir, logger)
if err != nil {
return err
}

validStatus, err := configuration.ValidateConfiguration(config, args.Dir, schemas, logger, noColor)
if err != nil {
return err
}

if validStatus.IsOk() {
return nil
}

validStatus.Render(os.Stderr)
if validStatus.HasError() {
return errors.New("Detected configuration errors. Update your configuration and try again.")
}

if !args.Yes {
if err := validStatus.PrintWarningConfirmation(); err != nil {
return err
}
}

logger.Info("updated successfully", slog.Duration("exec_time", time.Since(start)))

return nil
Expand Down
4 changes: 3 additions & 1 deletion ndc-http-schema/command/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ func TestUpdateCommand(t *testing.T) {
{
Argument: UpdateCommandArguments{
Dir: "testdata/patch",
Yes: true,
},
Expected: "testdata/patch/expected.json",
},
// go run ./ndc-http-schema update -d ./ndc-http-schema/command/testdata/auth
{
Argument: UpdateCommandArguments{
Dir: "testdata/auth",
Yes: true,
},
Expected: "testdata/auth/expected.json",
},
}

for _, tc := range testCases {
t.Run(tc.Argument.Dir, func(t *testing.T) {
assert.NilError(t, UpdateConfiguration(&tc.Argument, slog.Default()))
assert.NilError(t, UpdateConfiguration(&tc.Argument, slog.Default(), true))

output := readRuntimeSchemaFile(t, tc.Argument.Dir+"/schema.output.json")
expected := readRuntimeSchemaFile(t, tc.Argument.Dir+"/expected.json")
Expand Down
11 changes: 11 additions & 0 deletions ndc-http-schema/configuration/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ func buildSchemaFile(config *Configuration, configDir string, configItem *Config
}

if ndcSchema.Settings == nil || len(ndcSchema.Settings.Servers) == 0 {
templates, err := getTemplates()
if err != nil {
return nil, err
}
if err := templates.ExecuteTemplate(os.Stderr, templateEmptySettings, map[string]any{
"ContextPath": configDir,
"Namespace": configItem.ConvertConfig.File,
}); err != nil {
logger.Warn(err.Error())
}

return nil, fmt.Errorf("the servers setting of schema %s is empty", configItem.ConvertConfig.File)
}

Expand Down
66 changes: 66 additions & 0 deletions ndc-http-schema/configuration/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package configuration

import (
"embed"
"io"
"text/template"
)

//go:embed all:templates/* templates
var templateFS embed.FS
var _templates *template.Template

const (
templateEmptySettings = "server_empty.gotmpl"
templateEnvVariables = "env_variables.gotmpl"
)

const (
ansiReset = "\033[0m"
ansiFaint = "\033[2m"
ansiResetFaint = "\033[22m"
ansiBrightRed = "\033[91m"
ansiBrightGreen = "\033[92m"
ansiBrightYellow = "\033[93m"
ansiBrightRedFaint = "\033[91;2m"
)

func getTemplates() (*template.Template, error) {
if _templates != nil {
return _templates, nil
}

var err error
_templates, err = template.ParseFS(templateFS, "templates/*.gotmpl")
if err != nil {
return nil, err
}

return _templates, nil
}

func writeColorTextIf(w io.Writer, text string, color string, noColor bool) {
if noColor {
_, _ = w.Write([]byte(text))

return
}

_, _ = w.Write([]byte(color))
_, _ = w.Write([]byte(text))
_, _ = w.Write([]byte(ansiReset))
}

func writeErrorIf(w io.Writer, text string, noColor bool) {
writeColorTextIf(w, "ERROR", ansiBrightRed, noColor)
if text != "" {
_, _ = w.Write([]byte(text))
}
}

func writeWarningIf(w io.Writer, text string, noColor bool) {
writeColorTextIf(w, "WARNING", ansiBrightYellow, noColor)
if text != "" {
_, _ = w.Write([]byte(text))
}
}
21 changes: 21 additions & 0 deletions ndc-http-schema/configuration/templates/env_variables.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Make sure that the following environment variables were added to your subgraph configuration:

``` {{ .ContextPath }}/docker.yaml
services:
{{ .ServiceName }}:
environment:
{{range $index, $variable := .Variables }}{{ index $variable 0 }}: ${{ index $variable 1 }}
{{end}}# ...
```

``` {{ .ContextPath }}/connector.yaml
envMapping:
{{range $index, $variable := .Variables }}{{ index $variable 0 }}
fromEnv: {{ index $variable 1 }}
{{end}}# ...
```

``` .env
{{range $index, $variable := .Variables }}{{ index $variable 1 }}=
{{end}}# ...
```
19 changes: 19 additions & 0 deletions ndc-http-schema/configuration/templates/server_empty.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Empty `settings.servers`. Modify the API definition file or add a JSON patch config:

``` {{ .ContextPath }}/config.yaml
files:
- file: {{ .Namespace }}
patchAfter:
- path: patch-settings.yaml
strategy: merge
```

``` {{ .ContextPath }}/patch-settings.yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/hasura/ndc-http/refs/heads/main/ndc-http-schema/jsonschema/ndc-http-schema.schema.json",
settings:
servers:
- url:
# Set either value or env. If both fields are set the value field is default.
# value: http://localhost:1234
env: SERVER_URL
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# yaml-language-server: $schema=../../../../jsonschema/configuration.schema.json
strict: true
forwardHeaders:
enabled: false
argumentField: headers
responseHeaders: null
concurrency:
query: 1
mutation: 1
http: 0
files:
- file: schema.yaml
spec: ndc
Loading