diff --git a/CHANGELOG.md b/CHANGELOG.md index 68765cce811..833f79bddb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The order and collapsed state of columns is now persisted across page navigation ### Bug fixes and improvements +- cli: allow customization of server api paths (close #4016) - cli: clean up migration files created during a failed migrate api (close #4312) (#4319) - cli: add support for multiple versions of plugin (close #4105) - cli: template assets path in console HTML for unversioned builds diff --git a/cli/cli.go b/cli/cli.go index 4a0344e9de3..4a4a4fd5c5a 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -12,7 +12,9 @@ import ( "io/ioutil" "net/url" "os" + "path" "path/filepath" + "reflect" "strconv" "strings" "time" @@ -61,6 +63,36 @@ const ( V2 ) +// ServerAPIPaths has the custom paths defined for server api +type ServerAPIPaths struct { + Query string `yaml:"query,omitempty"` + GraphQL string `yaml:"graphql,omitempty"` + Config string `yaml:"config,omitempty"` + PGDump string `yaml:"pg_dump,omitempty"` + Version string `yaml:"version,omitempty"` +} + +// GetQueryParams - encodes the values in url +func (s ServerAPIPaths) GetQueryParams() url.Values { + vals := url.Values{} + t := reflect.TypeOf(s) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tag := field.Tag.Get("yaml") + splitTag := strings.Split(tag, ",") + if len(splitTag) == 0 { + continue + } + name := splitTag[0] + if name == "-" { + continue + } + v := reflect.ValueOf(s).Field(i) + vals.Add(name, v.String()) + } + return vals +} + // ErrInvalidConfigVersion - if the config version is not valid var ErrInvalidConfigVersion error = fmt.Errorf("invalid config version") @@ -105,10 +137,19 @@ type ServerConfig struct { AccessKey string `yaml:"access_key,omitempty"` // AdminSecret (optional) Admin secret required to query the endpoint AdminSecret string `yaml:"admin_secret,omitempty"` + // APIPaths (optional) API paths for server + APIPaths *ServerAPIPaths `yaml:"api_paths,omitempty"` ParsedEndpoint *url.URL `yaml:"-"` } +// GetVersionEndpoint provides the url to contact the version API +func (s *ServerConfig) GetVersionEndpoint() string { + nurl := *s.ParsedEndpoint + nurl.Path = path.Join(nurl.Path, s.APIPaths.Version) + return nurl.String() +} + // ParseEndpoint ensures the endpoint is valid. func (s *ServerConfig) ParseEndpoint() error { nurl, err := url.Parse(s.Endpoint) @@ -398,7 +439,7 @@ func (ec *ExecutionContext) Validate() error { } func (ec *ExecutionContext) checkServerVersion() error { - v, err := version.FetchServerVersion(ec.Config.ServerConfig.Endpoint) + v, err := version.FetchServerVersion(ec.Config.ServerConfig.GetVersionEndpoint()) if err != nil { return errors.Wrap(err, "failed to get version from server") } @@ -441,6 +482,11 @@ func (ec *ExecutionContext) readConfig() error { v.SetDefault("endpoint", "http://localhost:8080") v.SetDefault("admin_secret", "") v.SetDefault("access_key", "") + v.SetDefault("api_paths.query", "v1/query") + v.SetDefault("api_paths.graphql", "v1/graphql") + v.SetDefault("api_paths.config", "v1alpha1/config") + v.SetDefault("api_paths.pg_dump", "v1alpha1/pg_dump") + v.SetDefault("api_paths.version", "v1/version") v.SetDefault("metadata_directory", "") v.SetDefault("migrations_directory", "migrations") v.SetDefault("actions.kind", "synchronous") @@ -462,6 +508,13 @@ func (ec *ExecutionContext) readConfig() error { ServerConfig: ServerConfig{ Endpoint: v.GetString("endpoint"), AdminSecret: adminSecret, + APIPaths: &ServerAPIPaths{ + Query: v.GetString("api_paths.query"), + GraphQL: v.GetString("api_paths.graphql"), + Config: v.GetString("api_paths.config"), + PGDump: v.GetString("api_paths.pg_dump"), + Version: v.GetString("api_paths.version"), + }, }, MetadataDirectory: v.GetString("metadata_directory"), MigrationsDirectory: v.GetString("migrations_directory"), diff --git a/cli/commands/scripts_update_config_v2.go b/cli/commands/scripts_update_config_v2.go index 30d778e7bc7..61b38eb56df 100644 --- a/cli/commands/scripts_update_config_v2.go +++ b/cli/commands/scripts_update_config_v2.go @@ -12,6 +12,7 @@ import ( "gopkg.in/yaml.v2" "github.com/hasura/graphql-engine/cli" + "github.com/hasura/graphql-engine/cli/metadata/actions/types" "github.com/hasura/graphql-engine/cli/migrate/database/hasuradb" "github.com/hasura/graphql-engine/cli/migrate/source" "github.com/hasura/graphql-engine/cli/migrate/source/file" @@ -319,7 +320,23 @@ func newScriptsUpdateConfigV2Cmd(ec *cli.ExecutionContext) *cobra.Command { return errors.Wrap(err, "cannot write metadata") } ec.Spin("Writing new config file...") - err = ec.WriteConfig(nil) + // Read the config from config.yaml + cfgByt, err := ioutil.ReadFile(ec.ConfigFile) + if err != nil { + return errors.Wrap(err, "cannot read config file") + } + var cfg cli.Config + err = yaml.Unmarshal(cfgByt, &cfg) + if err != nil { + return errors.Wrap(err, "cannot parse config file") + } + cfg.Version = cli.V2 + cfg.MetadataDirectory = ec.Viper.GetString("metadata_directory") + cfg.ActionConfig = &types.ActionExecutionConfig{ + Kind: ec.Viper.GetString("actions.kind"), + HandlerWebhookBaseURL: ec.Viper.GetString("actions.handler_webhook_baseurl"), + } + err = ec.WriteConfig(&cfg) if err != nil { return errors.Wrap(err, "cannot write config file") } diff --git a/cli/migrate/database/hasuradb/hasuradb.go b/cli/migrate/database/hasuradb/hasuradb.go index 60e92f8a8b2..841bdb37415 100644 --- a/cli/migrate/database/hasuradb/hasuradb.go +++ b/cli/migrate/database/hasuradb/hasuradb.go @@ -40,9 +40,9 @@ var ( type Config struct { MigrationsTable string SettingsTable string - v1URL *nurl.URL + queryURL *nurl.URL graphqlURL *nurl.URL - schemDumpURL *nurl.URL + pgDumpURL *nurl.URL Headers map[string]string isCMD bool Plugins types.MetadataPlugins @@ -115,20 +115,20 @@ func (h *HasuraDB) Open(url string, isCMD bool, logger *log.Logger) (database.Dr config := &Config{ MigrationsTable: DefaultMigrationsTable, SettingsTable: DefaultSettingsTable, - v1URL: &nurl.URL{ + queryURL: &nurl.URL{ Scheme: scheme, Host: hurl.Host, - Path: path.Join(hurl.Path, "v1/query"), + Path: path.Join(hurl.Path, params.Get("query")), }, graphqlURL: &nurl.URL{ Scheme: scheme, Host: hurl.Host, - Path: path.Join(hurl.Path, "v1/graphql"), + Path: path.Join(hurl.Path, params.Get("graphql")), }, - schemDumpURL: &nurl.URL{ + pgDumpURL: &nurl.URL{ Scheme: scheme, Host: hurl.Host, - Path: path.Join(hurl.Path, "v1alpha1/pg_dump"), + Path: path.Join(hurl.Path, params.Get("pg_dump")), }, isCMD: isCMD, Headers: headers, @@ -417,7 +417,7 @@ func (h *HasuraDB) ensureVersionTable() error { func (h *HasuraDB) sendv1Query(m interface{}) (resp *http.Response, body []byte, err error) { request := gorequest.New() - request = request.Post(h.config.v1URL.String()).Send(m) + request = request.Post(h.config.queryURL.String()).Send(m) for headerName, headerValue := range h.config.Headers { request.Set(headerName, headerValue) } @@ -455,7 +455,7 @@ func (h *HasuraDB) sendv1GraphQL(query interface{}) (resp *http.Response, body [ func (h *HasuraDB) sendSchemaDumpQuery(m interface{}) (resp *http.Response, body []byte, err error) { request := gorequest.New() - request = request.Post(h.config.schemDumpURL.String()).Send(m) + request = request.Post(h.config.pgDumpURL.String()).Send(m) for headerName, headerValue := range h.config.Headers { request.Set(headerName, headerValue) diff --git a/cli/migrate/util.go b/cli/migrate/util.go index 0dfda094994..bbed7201eef 100644 --- a/cli/migrate/util.go +++ b/cli/migrate/util.go @@ -121,7 +121,7 @@ func FilterCustomQuery(u *nurl.URL) *nurl.URL { } func NewMigrate(ec *cli.ExecutionContext, isCmd bool) (*Migrate, error) { - dbURL := GetDataPath(ec.Config.ServerConfig.ParsedEndpoint, GetAdminSecretHeaderName(ec.Version), ec.Config.ServerConfig.AdminSecret) + dbURL := GetDataPath(ec) fileURL := GetFilePath(ec.MigrationDir) t, err := New(fileURL.String(), dbURL.String(), isCmd, int(ec.Config.Version), ec.Logger) if err != nil { @@ -132,13 +132,15 @@ func NewMigrate(ec *cli.ExecutionContext, isCmd bool) (*Migrate, error) { return t, nil } -func GetDataPath(url *nurl.URL, adminSecretHeader, adminSecretValue string) *nurl.URL { +func GetDataPath(ec *cli.ExecutionContext) *nurl.URL { + url := ec.Config.ServerConfig.ParsedEndpoint host := &nurl.URL{ - Scheme: "hasuradb", - Host: url.Host, - Path: url.Path, + Scheme: "hasuradb", + Host: url.Host, + Path: url.Path, + RawQuery: ec.Config.ServerConfig.APIPaths.GetQueryParams().Encode(), } - q := url.Query() + q := host.Query() // Set sslmode in query switch scheme := url.Scheme; scheme { case "https": @@ -146,8 +148,8 @@ func GetDataPath(url *nurl.URL, adminSecretHeader, adminSecretValue string) *nur default: q.Set("sslmode", "disable") } - if adminSecretValue != "" { - q.Add("headers", fmt.Sprintf("%s:%s", adminSecretHeader, adminSecretValue)) + if GetAdminSecretHeaderName(ec.Version) != "" { + q.Add("headers", fmt.Sprintf("%s:%s", GetAdminSecretHeaderName(ec.Version), ec.Config.ServerConfig.AdminSecret)) } host.RawQuery = q.Encode() return host diff --git a/cli/version/server.go b/cli/version/server.go index 1fa5f26620e..c1b2f8a928a 100644 --- a/cli/version/server.go +++ b/cli/version/server.go @@ -1,10 +1,8 @@ package version import ( - "fmt" "io/ioutil" "net/http" - "net/url" yaml "github.com/ghodss/yaml" "github.com/pkg/errors" @@ -16,12 +14,7 @@ type serverVersionResponse struct { // FetchServerVersion reads the version from server. func FetchServerVersion(endpoint string) (version string, err error) { - ep, err := url.Parse(endpoint) - if err != nil { - return "", errors.Wrap(err, "cannot parse endpoint as a valid url") - } - versionEndpoint := fmt.Sprintf("%s/v1/version", ep.String()) - response, err := http.Get(versionEndpoint) + response, err := http.Get(endpoint) if err != nil { return "", errors.Wrap(err, "failed making version api call") } @@ -30,7 +23,7 @@ func FetchServerVersion(endpoint string) (version string, err error) { case http.StatusNotFound: return "", nil default: - return "", errors.Errorf("GET %s failed - [%d]", versionEndpoint, response.StatusCode) + return "", errors.Errorf("GET %s failed - [%d]", endpoint, response.StatusCode) } } else { defer response.Body.Close()