2018-06-24 16:47:01 +03:00
// Package cli and it's sub packages implements the command line tool for Hasura
// GraphQL Engine. The CLI operates on a directory, denoted by
// "ExecutionDirectory" in the "ExecutionContext" struct.
//
// The ExecutionContext is passed to all the subcommands so that a singleton
// context is available for the execution. Logger and Spinner comes from the same
// context.
package cli
2018-06-24 16:40:48 +03:00
import (
2022-03-10 11:12:55 +03:00
"bytes"
2022-02-04 14:10:33 +03:00
"context"
2020-06-03 07:06:23 +03:00
"encoding/json"
2022-11-28 11:44:18 +03:00
stderrors "errors"
2020-03-26 06:24:05 +03:00
"fmt"
2021-06-18 09:24:16 +03:00
"io"
2022-11-28 11:44:18 +03:00
"io/fs"
2020-02-24 19:14:46 +03:00
"io/ioutil"
2020-04-28 14:59:57 +03:00
"net/http"
2018-07-09 16:47:38 +03:00
"net/url"
2018-06-24 16:40:48 +03:00
"os"
2020-04-08 13:59:21 +03:00
"path"
2018-06-24 16:40:48 +03:00
"path/filepath"
2020-04-08 13:59:21 +03:00
"reflect"
2020-03-26 06:24:05 +03:00
"strconv"
2020-02-24 19:14:46 +03:00
"strings"
2018-06-24 16:40:48 +03:00
"time"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/pgdump"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/v1graphql"
"github.com/hasura/graphql-engine/cli/v2/migrate/database/hasuradb"
2021-02-17 14:15:26 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/v1metadata"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/v1query"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/v2query"
2021-02-17 07:20:19 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura/commonmetadata"
2021-02-17 07:20:19 +03:00
2022-11-03 11:07:50 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/httpc"
2021-02-17 07:20:19 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/statestore/settings"
2021-02-17 07:20:19 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/statestore/migrations"
2021-02-17 07:20:19 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/statestore"
2021-02-17 07:20:19 +03:00
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
2021-01-18 20:11:05 +03:00
2021-05-20 12:13:39 +03:00
"github.com/Masterminds/semver"
2018-06-24 16:40:48 +03:00
"github.com/briandowns/spinner"
2021-09-29 14:11:24 +03:00
"github.com/cockroachdb/redact"
2019-01-28 16:55:28 +03:00
"github.com/gofrs/uuid"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/metadataobject/actions/types"
"github.com/hasura/graphql-engine/cli/v2/plugins"
"github.com/hasura/graphql-engine/cli/v2/telemetry"
"github.com/hasura/graphql-engine/cli/v2/util"
"github.com/hasura/graphql-engine/cli/v2/version"
2018-06-24 16:40:48 +03:00
"github.com/sirupsen/logrus"
2018-06-24 16:47:01 +03:00
"github.com/spf13/viper"
2020-04-23 05:47:51 +03:00
"github.com/subosito/gotenv"
2021-10-13 17:38:07 +03:00
"golang.org/x/term"
2022-03-10 11:12:55 +03:00
"gopkg.in/yaml.v3"
2018-06-24 16:40:48 +03:00
)
2018-06-27 15:04:09 +03:00
// Other constants used in the package
const (
// Name of the global configuration directory
2019-02-14 12:37:47 +03:00
GlobalConfigDirName = ".hasura"
2018-06-27 15:04:09 +03:00
// Name of the global configuration file
2019-02-14 12:37:47 +03:00
GlobalConfigFileName = "config.json"
2019-02-04 13:51:29 +03:00
// Name of the file to store last update check time
LastUpdateCheckFileName = "last_update_check_at"
2020-04-13 06:16:06 +03:00
2020-06-16 15:15:04 +03:00
DefaultMigrationsDirectory = "migrations"
DefaultMetadataDirectory = "metadata"
DefaultSeedsDirectory = "seeds"
2018-06-27 15:04:09 +03:00
)
2020-04-09 12:30:47 +03:00
const (
XHasuraAdminSecret = "X-Hasura-Admin-Secret"
XHasuraAccessKey = "X-Hasura-Access-Key"
)
2019-01-28 16:55:28 +03:00
const (
2021-09-29 14:11:24 +03:00
TelemetryNotice = ` Help us improve Hasura ! The cli collects anonymized usage stats which
2019-01-28 16:55:28 +03:00
allow us to keep improving Hasura at warp speed . To opt - out or read more ,
2021-03-01 21:50:24 +03:00
visit https : //hasura.io/docs/latest/graphql/core/guides/telemetry.html
2019-01-28 16:55:28 +03:00
`
)
2020-02-24 19:14:46 +03:00
// ConfigVersion defines the version of the Config.
type ConfigVersion int
2019-02-14 12:37:47 +03:00
2020-02-24 19:14:46 +03:00
const (
// V1 represents config version 1
V1 ConfigVersion = iota + 1
// V2 represents config version 2
V2
2021-01-18 20:11:05 +03:00
V3
2020-02-24 19:14:46 +03:00
)
2019-02-14 12:37:47 +03:00
2021-09-29 14:11:24 +03:00
type MetadataMode int
const (
MetadataModeDirectory MetadataMode = iota
MetadataModeJSON
MetadataModeYAML
)
2020-04-08 13:59:21 +03:00
// ServerAPIPaths has the custom paths defined for server api
type ServerAPIPaths struct {
2021-02-17 07:20:19 +03:00
V1Query string ` yaml:"v1_query,omitempty" `
V2Query string ` yaml:"v2_query,omitempty" `
V1Metadata string ` yaml:"v1_metadata,omitempty" `
GraphQL string ` yaml:"graphql,omitempty" `
Config string ` yaml:"config,omitempty" `
PGDump string ` yaml:"pg_dump,omitempty" `
Version string ` yaml:"version,omitempty" `
2020-04-08 13:59:21 +03:00
}
// 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
}
2020-03-26 06:24:05 +03:00
// ErrInvalidConfigVersion - if the config version is not valid
var ErrInvalidConfigVersion error = fmt . Errorf ( "invalid config version" )
// NewConfigVersionValue returns ConfigVersion set with default value
func NewConfigVersionValue ( val ConfigVersion , p * ConfigVersion ) * ConfigVersion {
* p = val
return p
}
// Set sets the value of the named command-line flag.
func ( c * ConfigVersion ) Set ( s string ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ConfigVersion.Set"
2020-03-26 06:24:05 +03:00
v , err := strconv . ParseInt ( s , 0 , 64 )
* c = ConfigVersion ( v )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2020-03-26 06:24:05 +03:00
}
if ! c . IsValid ( ) {
2022-11-03 11:07:50 +03:00
return errors . E ( op , ErrInvalidConfigVersion )
2020-03-26 06:24:05 +03:00
}
return nil
}
// Type returns a string that uniquely represents this flag's type.
func ( c * ConfigVersion ) Type ( ) string {
return "int"
}
func ( c * ConfigVersion ) String ( ) string {
return strconv . Itoa ( int ( * c ) )
}
// IsValid returns if its a valid config version
func ( c ConfigVersion ) IsValid ( ) bool {
2021-01-18 20:11:05 +03:00
return c != 0 && c <= V3
2020-03-26 06:24:05 +03:00
}
2020-02-24 19:14:46 +03:00
// ServerConfig has the config values required to contact the server
type ServerConfig struct {
2018-06-24 16:47:01 +03:00
// Endpoint for the GraphQL Engine
2020-02-24 19:14:46 +03:00
Endpoint string ` yaml:"endpoint" `
2019-02-14 12:37:47 +03:00
// AccessKey (deprecated) (optional) Admin secret key required to query the endpoint
2020-02-24 19:14:46 +03:00
AccessKey string ` yaml:"access_key,omitempty" `
2019-02-14 12:37:47 +03:00
// AdminSecret (optional) Admin secret required to query the endpoint
2020-02-24 19:14:46 +03:00
AdminSecret string ` yaml:"admin_secret,omitempty" `
2022-06-03 16:27:45 +03:00
// Config option to allow specifying multiple admin secrets
// https://hasura.io/docs/latest/graphql/cloud/security/multiple-admin-secrets/
AdminSecrets [ ] string ` yaml:"admin_secrets,omitempty" `
2020-04-08 13:59:21 +03:00
// APIPaths (optional) API paths for server
APIPaths * ServerAPIPaths ` yaml:"api_paths,omitempty" `
2020-04-28 14:59:57 +03:00
// InsecureSkipTLSVerify - indicates if TLS verification is disabled or not.
InsecureSkipTLSVerify bool ` yaml:"insecure_skip_tls_verify,omitempty" `
// CAPath - Path to a cert file for the certificate authority
CAPath string ` yaml:"certificate_authority,omitempty" `
2018-07-09 16:47:38 +03:00
2020-02-24 19:14:46 +03:00
ParsedEndpoint * url . URL ` yaml:"-" `
2020-04-28 14:59:57 +03:00
2022-02-04 14:10:33 +03:00
HTTPClient * httpc . Client ` yaml:"-" `
2020-06-03 07:06:23 +03:00
HasuraServerInternalConfig HasuraServerInternalConfig ` yaml:"-" `
}
2022-06-03 16:27:45 +03:00
func ( c * ServerConfig ) GetAdminSecret ( ) string {
// when HGE is configured with an admin secret, all API requests to HGE should be
// authenticated using a x-hasura-admin-secret header.
// admin secrets can be configured with two environment variables
// - HASURA_GRAPHQL_ADMIN_SECRET (ref: https://hasura.io/docs/latest/graphql/core/deployment/deployment-guides/docker/#docker-secure)
// - HASURA_GRAPHQL_ADMIN_SECRETS (ref: https://hasura.io/docs/latest/graphql/cloud/security/multiple-admin-secrets/)
// the environment variable HASURA_GRAPHQL_ADMIN_SECRETS takes precedence when set
if len ( c . AdminSecrets ) > 0 {
// when HASURA_GRAPHQL_ADMIN_SECRETS environment variable is set, use the first available admin secret as the value of the header
return c . AdminSecrets [ 0 ]
} else if c . AdminSecret != "" {
return c . AdminSecret
}
return ""
}
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetHasuraInternalServerConfig ( client * httpc . Client ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ServerConfig.GetHasuraInternalServerConfig"
2020-06-03 07:06:23 +03:00
// Determine from where assets should be served
url := c . getConfigEndpoint ( )
2022-02-04 14:10:33 +03:00
ctx , cancelFunc := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancelFunc ( )
2020-06-03 07:06:23 +03:00
req , err := http . NewRequest ( "GET" , url , nil )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "error fetching config from server: %w" , err ) )
2020-06-03 07:06:23 +03:00
}
2022-02-04 14:10:33 +03:00
r , err := client . Do ( ctx , req , & c . HasuraServerInternalConfig )
2020-06-03 07:06:23 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , errors . KindNetwork , err )
2020-06-03 07:06:23 +03:00
}
defer r . Body . Close ( )
if r . StatusCode != http . StatusOK {
var horror hasuradb . HasuraError
err := json . NewDecoder ( r . Body ) . Decode ( & horror )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , errors . KindHasuraAPI , fmt . Errorf ( "error unmarshalling fetching server config" ) )
2020-06-03 07:06:23 +03:00
}
2022-11-03 11:07:50 +03:00
return errors . E ( op , errors . KindHasuraAPI , fmt . Errorf ( "error fetching server config: %v" , horror . Error ( ) ) )
2020-06-03 07:06:23 +03:00
}
2022-02-04 14:10:33 +03:00
return nil
2020-06-03 07:06:23 +03:00
}
// HasuraServerConfig is the type returned by the v1alpha1/config API
// TODO: Move this type to a client implementation for hasura
type HasuraServerInternalConfig struct {
ConsoleAssetsDir string ` json:"console_assets_dir" `
2019-02-14 12:37:47 +03:00
}
2020-04-08 13:59:21 +03:00
// GetVersionEndpoint provides the url to contact the version API
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetVersionEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . Version )
2020-04-08 13:59:21 +03:00
return nurl . String ( )
}
2020-04-28 14:59:57 +03:00
// GetQueryEndpoint provides the url to contact the query API
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetV1QueryEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . V1Query )
2021-02-17 07:20:19 +03:00
return nurl . String ( )
}
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetV2QueryEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . V2Query )
2020-04-28 14:59:57 +03:00
return nurl . String ( )
}
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetPGDumpEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . PGDump )
2021-04-01 13:38:55 +03:00
return nurl . String ( )
}
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetV1GraphqlEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . GraphQL )
2021-04-12 20:27:33 +03:00
return nurl . String ( )
}
2021-01-18 20:11:05 +03:00
// GetQueryEndpoint provides the url to contact the query API
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) GetV1MetadataEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . V1Metadata )
2021-01-18 20:11:05 +03:00
return nurl . String ( )
}
2020-06-03 07:06:23 +03:00
// GetVersionEndpoint provides the url to contact the config API
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) getConfigEndpoint ( ) string {
nurl := * c . ParsedEndpoint
nurl . Path = path . Join ( nurl . Path , c . APIPaths . Config )
2020-06-03 07:06:23 +03:00
return nurl . String ( )
}
2018-07-09 16:47:38 +03:00
// ParseEndpoint ensures the endpoint is valid.
2022-02-04 14:10:33 +03:00
func ( c * ServerConfig ) ParseEndpoint ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ServerConfig.ParseEndpoint"
2022-02-04 14:10:33 +03:00
nurl , err := url . ParseRequestURI ( c . Endpoint )
2018-07-09 16:47:38 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2018-07-09 16:47:38 +03:00
}
2022-02-04 14:10:33 +03:00
c . ParsedEndpoint = nurl
2020-04-28 14:59:57 +03:00
return nil
}
2020-02-24 19:14:46 +03:00
// Config represents configuration required for the CLI to function
type Config struct {
// Version of the config.
2020-03-26 06:24:05 +03:00
Version ConfigVersion ` yaml:"version,omitempty" `
2020-02-24 19:14:46 +03:00
2021-06-09 10:12:56 +03:00
// DisableInteractive disables interactive prompt
DisableInteractive bool ` yaml:"disable_interactive,omitempty" `
2020-02-24 19:14:46 +03:00
// ServerConfig to be used by CLI to contact server.
ServerConfig ` yaml:",inline" `
// MetadataDirectory defines the directory where the metadata files were stored.
2020-03-26 06:24:05 +03:00
MetadataDirectory string ` yaml:"metadata_directory,omitempty" `
2021-09-29 14:11:24 +03:00
// MetadataFile defines the path in which a JSON/YAML metadata file should be stored
MetadataFile string ` yaml:"metadata_file,omitempty" `
2020-02-24 19:14:46 +03:00
// MigrationsDirectory defines the directory where the migration files were stored.
MigrationsDirectory string ` yaml:"migrations_directory,omitempty" `
2020-06-16 15:15:04 +03:00
// SeedsDirectory defines the directory where seed files will be stored
SeedsDirectory string ` yaml:"seeds_directory,omitempty" `
2021-09-29 14:11:24 +03:00
2020-02-24 19:14:46 +03:00
// ActionConfig defines the config required to create or generate codegen for an action.
2021-02-18 16:15:43 +03:00
ActionConfig * types . ActionExecutionConfig ` yaml:"actions,omitempty" `
2020-02-24 19:14:46 +03:00
}
2018-06-24 16:47:01 +03:00
// ExecutionContext contains various contextual information required by the cli
// at various points of it's execution. Values are filled in by the
// initializers and passed on to each command. Commands can also fill in values
// to be used further down the line.
2018-06-24 16:40:48 +03:00
type ExecutionContext struct {
2018-06-24 16:47:01 +03:00
// CMDName is the name of CMD (os.Args[0]). To be filled in later to
// correctly render example strings etc.
2021-06-18 09:24:16 +03:00
CMDName string
Stderr , Stdout io . Writer
2018-06-24 16:47:01 +03:00
2019-01-28 16:55:28 +03:00
// ID is a unique ID for this Execution
ID string
// ServerUUID is the unique ID for the server this execution is contacting.
ServerUUID string
2018-06-24 16:47:01 +03:00
// Spinner is the global spinner object used to show progress across the cli.
2018-06-24 16:40:48 +03:00
Spinner * spinner . Spinner
2018-06-24 16:47:01 +03:00
// Logger is the global logger object to print logs.
Logger * logrus . Logger
2018-06-24 16:40:48 +03:00
2018-06-24 16:47:01 +03:00
// ExecutionDirectory is the directory in which command is being executed.
2018-06-24 16:40:48 +03:00
ExecutionDirectory string
2020-04-23 05:47:51 +03:00
// Envfile is the .env file to load ENV vars from
Envfile string
2018-06-24 16:47:01 +03:00
// MigrationDir is the name of directory where migrations are stored.
MigrationDir string
2021-09-29 14:11:24 +03:00
2020-02-24 19:14:46 +03:00
// MetadataDir is the name of directory where metadata files are stored.
MetadataDir string
2021-09-29 14:11:24 +03:00
// MetadataFile is the name of json/yaml file where metadata will be stored
MetadataFile string
2020-06-16 15:15:04 +03:00
// Seed directory -- directory in which seed files are to be stored
SeedsDirectory string
2018-06-24 16:47:01 +03:00
// ConfigFile is the file where endpoint etc. are stored.
ConfigFile string
2020-04-09 12:30:47 +03:00
// HGE Headers, are the custom headers which can be passed to HGE API
HGEHeaders map [ string ] string
2018-06-24 16:47:01 +03:00
2020-02-24 19:14:46 +03:00
// Config is the configuration object storing the endpoint and admin secret
2018-06-24 16:47:01 +03:00
// information after reading from config file or env var.
2020-02-24 19:14:46 +03:00
Config * Config
2018-06-24 16:47:01 +03:00
// GlobalConfigDir is the ~/.hasura-graphql directory to store configuration
// globally.
GlobalConfigDir string
// GlobalConfigFile is the file inside GlobalConfigDir where values are
// stored.
GlobalConfigFile string
2019-01-28 16:55:28 +03:00
// GlobalConfig holds all the configuration options.
GlobalConfig * GlobalConfig
2018-06-24 16:47:01 +03:00
// IsStableRelease indicates if the CLI release is stable or not.
IsStableRelease bool
2018-07-04 15:43:52 +03:00
// Version indicates the version object
Version * version . Version
2018-06-24 16:40:48 +03:00
2018-06-24 16:47:01 +03:00
// Viper indicates the viper object for the execution
Viper * viper . Viper
2018-06-24 16:40:48 +03:00
2018-06-24 16:47:01 +03:00
// LogLevel indicates the logrus default logging level
LogLevel string
2019-01-28 16:55:28 +03:00
2019-07-30 09:55:38 +03:00
// NoColor indicates if the outputs shouldn't be colorized
NoColor bool
2019-01-28 16:55:28 +03:00
// Telemetry collects the telemetry data throughout the execution
Telemetry * telemetry . Data
2019-02-04 13:51:29 +03:00
// LastUpdateCheckFile is the file where the timestamp of last update check is stored
LastUpdateCheckFile string
2019-02-14 09:16:36 +03:00
// SkipUpdateCheck will skip the auto update check if set to true
SkipUpdateCheck bool
2019-12-25 17:44:02 +03:00
2020-02-24 19:14:46 +03:00
// PluginsConfig defines the config for plugins
PluginsConfig * plugins . Config
// CodegenAssetsRepo defines the config to handle codegen-assets repo
CodegenAssetsRepo * util . GitUtil
// InitTemplatesRepo defines the config to handle init-templates repo
InitTemplatesRepo * util . GitUtil
2019-12-25 17:44:02 +03:00
// IsTerminal indicates whether the current session is a terminal or not
IsTerminal bool
2021-01-18 20:11:05 +03:00
// instance of API client which communicates with Hasura API
2021-02-17 07:20:19 +03:00
APIClient * hasura . Client
2021-01-18 20:11:05 +03:00
2021-02-17 15:51:43 +03:00
// current database on which operation is being done
2021-03-08 14:59:35 +03:00
Source Source
2021-01-18 20:11:05 +03:00
HasMetadataV3 bool
2021-05-17 18:19:15 +03:00
2021-12-23 18:58:53 +03:00
// AllDatabases should be taken only incase if database isn't mentioned
AllDatabases bool
2021-05-17 18:19:15 +03:00
// after a `scripts update-config-v3` all migrate commands will try to automatically
// move cli state from hdb_catalog.* tables to catalog state if that hasn't happened
// already this configuration option will disable this step
// more details in: https://github.com/hasura/graphql-engine/issues/6861
DisableAutoStateMigration bool
2021-05-20 12:13:39 +03:00
2021-06-27 16:49:31 +03:00
// CliExtDestinationDir is the directory path that will be used to setup cli-ext
CliExtDestinationDir string
2021-06-18 19:13:30 +03:00
2021-06-27 16:49:31 +03:00
// CliExtDestinationBinPath is the full path of the cli-ext binary
CliExtDestinationBinPath string
// CLIExtSourceBinPath is the full path to a copy of cli-ext binary in the local file system
CliExtSourceBinPath string
2021-06-18 19:13:30 +03:00
2021-05-20 12:13:39 +03:00
// proPluginVersionValidated is used to avoid validating pro plugin multiple times
// while preparing the execution context
proPluginVersionValidated bool
2021-09-29 14:11:24 +03:00
MetadataMode MetadataMode
2022-02-04 14:10:33 +03:00
// Any request headers that has to be sent with every HTTP request that CLI sends to HGE
requestHeaders map [ string ] string
}
func ( ec * ExecutionContext ) AddRequestHeaders ( headers map [ string ] string ) {
if ec . requestHeaders == nil {
ec . requestHeaders = map [ string ] string { }
}
for k , v := range headers {
ec . requestHeaders [ k ] = v
}
2019-01-28 16:55:28 +03:00
}
2021-03-08 14:59:35 +03:00
type Source struct {
Name string
Kind hasura . SourceKind
}
2019-01-28 16:55:28 +03:00
// NewExecutionContext returns a new instance of execution context
func NewExecutionContext ( ) * ExecutionContext {
2021-06-18 09:24:16 +03:00
ec := & ExecutionContext {
Stderr : os . Stderr ,
Stdout : os . Stdout ,
}
2021-09-29 14:11:24 +03:00
ec . MetadataMode = MetadataModeDirectory
2019-01-28 16:55:28 +03:00
ec . Telemetry = telemetry . BuildEvent ( )
ec . Telemetry . Version = version . BuildVersion
return ec
2018-06-24 16:40:48 +03:00
}
2018-06-24 16:47:01 +03:00
// Prepare as the name suggests, prepares the ExecutionContext ec by
// initializing most of the variables to sensible defaults, if it is not already
// set.
2018-06-24 16:40:48 +03:00
func ( ec * ExecutionContext ) Prepare ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.Prepare"
2018-06-24 16:47:01 +03:00
// set the command name
2018-06-24 16:40:48 +03:00
cmdName := os . Args [ 0 ]
if len ( cmdName ) == 0 {
cmdName = "hasura"
}
ec . CMDName = cmdName
2021-09-22 15:52:50 +03:00
ec . IsTerminal = term . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
2019-12-25 17:44:02 +03:00
2018-06-24 16:40:48 +03:00
// set spinner
ec . setupSpinner ( )
// set logger
ec . setupLogger ( )
2018-06-28 08:40:18 +03:00
// populate version
ec . setVersion ( )
2019-01-28 16:55:28 +03:00
// setup global config
err := ec . setupGlobalConfig ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "setting up global config failed: %w" , err ) )
2019-01-28 16:55:28 +03:00
}
2021-05-20 12:13:39 +03:00
if ! ec . proPluginVersionValidated {
ec . validateProPluginVersion ( )
ec . proPluginVersionValidated = true
}
2019-02-04 13:51:29 +03:00
ec . LastUpdateCheckFile = filepath . Join ( ec . GlobalConfigDir , LastUpdateCheckFileName )
2018-06-24 16:40:48 +03:00
2019-02-04 13:51:29 +03:00
// initialize a blank server config
2020-02-24 19:14:46 +03:00
if ec . Config == nil {
ec . Config = & Config { }
2019-01-28 16:55:28 +03:00
}
// generate an execution id
if ec . ID == "" {
id := "00000000-0000-0000-0000-000000000000"
u , err := uuid . NewV4 ( )
if err == nil {
id = u . String ( )
} else {
ec . Logger . Debugf ( "generating uuid for execution ID failed, %v" , err )
}
ec . ID = id
ec . Logger . Debugf ( "execution id: %v" , ec . ID )
}
ec . Telemetry . ExecutionID = ec . ID
2018-06-24 16:47:01 +03:00
2018-06-24 16:40:48 +03:00
return nil
}
2021-11-10 12:21:06 +03:00
// SetupPlugins create and returns the inferred paths for hasura. By default, it assumes
2020-02-24 19:14:46 +03:00
// $HOME/.hasura as the base path
2021-11-10 12:21:06 +03:00
func ( ec * ExecutionContext ) SetupPlugins ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.SetupPlugins"
2020-02-24 19:14:46 +03:00
base := filepath . Join ( ec . GlobalConfigDir , "plugins" )
base , err := filepath . Abs ( base )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot get absolute path: %w" , err ) )
2020-02-24 19:14:46 +03:00
}
ec . PluginsConfig = plugins . New ( base )
ec . PluginsConfig . Logger = ec . Logger
2020-04-22 12:15:42 +03:00
ec . PluginsConfig . Repo . Logger = ec . Logger
if ec . GlobalConfig . CLIEnvironment == ServerOnDockerEnvironment {
ec . PluginsConfig . Repo . DisableCloneOrUpdate = true
}
2022-11-03 11:07:50 +03:00
err = ec . PluginsConfig . Prepare ( )
if err != nil {
return errors . E ( op , err )
}
return nil
2020-02-24 19:14:46 +03:00
}
2021-05-20 12:13:39 +03:00
func ( ec * ExecutionContext ) validateProPluginVersion ( ) {
2021-11-10 12:21:06 +03:00
if err := ec . SetupPlugins ( ) ; err != nil {
ec . Logger . Debugf ( "Validating installed pro plugin version failed: %v" , err )
return
}
2021-05-20 12:13:39 +03:00
installedPlugins , err := ec . PluginsConfig . ListInstalledPlugins ( )
if err != nil {
return
}
proPluginVersion := installedPlugins [ "pro" ]
cliVersion := ec . Version . GetCLIVersion ( )
proPluginSemVer , _ := semver . NewVersion ( proPluginVersion )
cliSemVer := ec . Version . CLISemver
if proPluginSemVer == nil || cliSemVer == nil {
return
}
if cliSemVer . Major ( ) != proPluginSemVer . Major ( ) {
ec . Logger . Warnf ( "[cli: %s] [pro plugin: %s] incompatible version of cli and pro plugin." , cliVersion , proPluginVersion )
ec . Logger . Warn ( "Try running `hasura plugins upgrade pro` or `hasura plugins install pro --version <version>`" )
}
}
2021-11-10 12:21:06 +03:00
func ( ec * ExecutionContext ) SetupCodegenAssetsRepo ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.SetupCodegenAssetsRepo"
2020-02-24 19:14:46 +03:00
base := filepath . Join ( ec . GlobalConfigDir , util . ActionsCodegenDirName )
base , err := filepath . Abs ( base )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot get absolute path: %w" , err ) )
2020-02-24 19:14:46 +03:00
}
ec . CodegenAssetsRepo = util . NewGitUtil ( util . ActionsCodegenRepoURI , base , "" )
2020-04-22 12:15:42 +03:00
ec . CodegenAssetsRepo . Logger = ec . Logger
if ec . GlobalConfig . CLIEnvironment == ServerOnDockerEnvironment {
ec . CodegenAssetsRepo . DisableCloneOrUpdate = true
}
2020-02-24 19:14:46 +03:00
return nil
}
2018-06-24 16:47:01 +03:00
// Validate prepares the ExecutionContext ec and then validates the
// ExecutionDirectory to see if all the required files and directories are in
// place.
2018-06-24 16:40:48 +03:00
func ( ec * ExecutionContext ) Validate ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.Validate"
2018-06-24 16:47:01 +03:00
// validate execution directory
2021-11-10 12:21:06 +03:00
err := ec . validateDirectory ( )
2018-06-24 16:40:48 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "validating current directory failed: %w" , err ) )
2018-06-24 16:40:48 +03:00
}
2020-04-23 05:47:51 +03:00
// load .env file
err = ec . loadEnvfile ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "loading .env file failed: %w" , err ) )
2020-04-23 05:47:51 +03:00
}
2020-04-28 14:59:57 +03:00
2020-02-24 19:14:46 +03:00
// set names of config file
2018-06-24 16:40:48 +03:00
ec . ConfigFile = filepath . Join ( ec . ExecutionDirectory , "config.yaml" )
2018-06-24 16:47:01 +03:00
// read config and parse the values into Config
2018-06-24 16:40:48 +03:00
err = ec . readConfig ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot read config: %w" , err ) )
2018-06-24 16:40:48 +03:00
}
2022-02-04 14:10:33 +03:00
// initialize HTTP client
// CLI uses a common http client defined in internal/httpc.Client
// get TLS Config
tlsConfig , err := httpc . GenerateTLSConfig ( ec . Config . CAPath , ec . Config . InsecureSkipTLSVerify )
if err != nil || tlsConfig == nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "error while getting TLS config" ) )
2022-02-04 14:10:33 +03:00
}
// create a net/http.Client with TLS Config
standardHttpClient , err := httpc . NewHttpClientWithTLSConfig ( tlsConfig )
if err != nil || standardHttpClient == nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "error while creating http client with TLS configuration %w" , err ) )
2022-02-04 14:10:33 +03:00
}
// create httpc.Client
httpClient , err := httpc . New ( standardHttpClient , ec . Config . Endpoint , ec . HGEHeaders )
if err != nil || httpClient == nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2022-02-04 14:10:33 +03:00
}
ec . Config . HTTPClient = httpClient
err = util . GetServerStatus ( ec . Config . GetVersionEndpoint ( ) , ec . Config . HTTPClient )
if err != nil {
ec . Logger . Error ( "connecting to graphql-engine server failed" )
ec . Logger . Info ( "possible reasons:" )
ec . Logger . Info ( "1) Provided root endpoint of graphql-engine server is wrong. Verify endpoint key in config.yaml or/and value of --endpoint flag" )
ec . Logger . Info ( "2) Endpoint should NOT be your GraphQL API, ie endpoint is NOT https://hasura-cloud-app.io/v1/graphql it should be: https://hasura-cloud-app.io" )
ec . Logger . Info ( "3) Server might be unhealthy and is not running/accepting API requests" )
ec . Logger . Info ( "4) Admin secret is not correct/set" )
ec . Logger . Infoln ( )
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2022-02-04 14:10:33 +03:00
}
// get version from the server and match with the cli version
err = ec . checkServerVersion ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "version check: %w" , err ) )
2022-02-04 14:10:33 +03:00
}
// get the server feature flags
err = ec . Version . GetServerFeatureFlags ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "error in getting server feature flags %w" , err ) )
2022-02-04 14:10:33 +03:00
}
2022-06-03 16:27:45 +03:00
ec . AddRequestHeaders ( map [ string ] string { GetAdminSecretHeaderName ( ec . Version ) : ec . Config . GetAdminSecret ( ) } )
2022-02-04 14:10:33 +03:00
ec . Config . HTTPClient . SetHeaders ( ec . requestHeaders )
// this populates the ec.Config.ServerConfig.HasuraServerInternalConfig
err = ec . Config . ServerConfig . GetHasuraInternalServerConfig ( httpClient )
if err != nil {
// If config API is not enabled log it and don't fail
ec . Logger . Debugf ( "cannot get config information from server, this might be because config API is not enabled: %v" , err )
}
2020-02-24 19:14:46 +03:00
// set name of migration directory
ec . MigrationDir = filepath . Join ( ec . ExecutionDirectory , ec . Config . MigrationsDirectory )
2022-11-28 11:44:18 +03:00
if _ , err := os . Stat ( ec . MigrationDir ) ; stderrors . Is ( err , fs . ErrNotExist ) {
2020-02-24 19:14:46 +03:00
err = os . MkdirAll ( ec . MigrationDir , os . ModePerm )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot create migrations directory: %w" , err ) )
2020-02-24 19:14:46 +03:00
}
}
2020-06-16 15:15:04 +03:00
ec . SeedsDirectory = filepath . Join ( ec . ExecutionDirectory , ec . Config . SeedsDirectory )
2022-11-28 11:44:18 +03:00
if _ , err := os . Stat ( ec . SeedsDirectory ) ; stderrors . Is ( err , fs . ErrNotExist ) {
2020-06-16 15:15:04 +03:00
err = os . MkdirAll ( ec . SeedsDirectory , os . ModePerm )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot create seeds directory: %w" , err ) )
2020-06-16 15:15:04 +03:00
}
}
2021-01-18 20:11:05 +03:00
if ec . Config . Version >= V2 && ec . Config . MetadataDirectory != "" {
2021-09-29 14:11:24 +03:00
if len ( ec . Config . MetadataFile ) > 0 {
ec . MetadataFile = filepath . Join ( ec . ExecutionDirectory , ec . Config . MetadataFile )
2022-11-28 11:44:18 +03:00
if _ , err := os . Stat ( ec . MetadataFile ) ; stderrors . Is ( err , fs . ErrNotExist ) {
2021-09-29 14:11:24 +03:00
if err := ioutil . WriteFile ( ec . MetadataFile , [ ] byte ( "" ) , os . ModePerm ) ; err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2021-09-29 14:11:24 +03:00
}
}
switch filepath . Ext ( ec . MetadataFile ) {
case ".json" :
ec . MetadataMode = MetadataModeJSON
case ".yaml" :
ec . MetadataMode = MetadataModeYAML
default :
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "unrecogonized file extension. only .json/.yaml files are allowed for value of metadata_file" ) )
2021-09-29 14:11:24 +03:00
}
}
2020-02-24 19:14:46 +03:00
// set name of metadata directory
ec . MetadataDir = filepath . Join ( ec . ExecutionDirectory , ec . Config . MetadataDirectory )
2022-11-28 11:44:18 +03:00
if _ , err := os . Stat ( ec . MetadataDir ) ; stderrors . Is ( err , fs . ErrNotExist ) && ! ( len ( ec . MetadataFile ) > 0 ) {
2020-02-24 19:14:46 +03:00
err = os . MkdirAll ( ec . MetadataDir , os . ModePerm )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot create metadata directory: %w" , err ) )
2020-02-24 19:14:46 +03:00
}
}
}
ec . Logger . Debug ( "graphql engine endpoint: " , ec . Config . ServerConfig . Endpoint )
2021-09-29 14:11:24 +03:00
ec . Logger . Debug ( redact . Sprintf ( "graphql engine admin_secret: %s" , ec . Config . ServerConfig . AdminSecret ) . Redact ( ) )
2018-06-24 16:40:48 +03:00
2021-09-27 17:52:46 +03:00
uri , err := url . Parse ( ec . Config . Endpoint )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "error while parsing the endpoint :%w" , err ) )
2021-09-27 17:52:46 +03:00
}
2021-01-18 20:11:05 +03:00
// check if server is using metadata v3
2021-02-17 07:20:19 +03:00
if ec . Config . APIPaths . V1Query != "" {
2021-08-09 11:40:14 +03:00
uri . Path = path . Join ( uri . Path , ec . Config . APIPaths . V1Query )
2021-02-17 07:20:19 +03:00
} else {
2021-08-09 11:40:14 +03:00
uri . Path = path . Join ( uri . Path , "v1/query" )
2021-01-18 20:11:05 +03:00
}
2021-08-09 11:40:14 +03:00
requestUri := uri . String ( )
2021-02-17 07:20:19 +03:00
metadata , err := commonmetadata . New ( httpClient , requestUri ) . ExportMetadata ( )
2021-01-18 20:11:05 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2021-01-18 20:11:05 +03:00
}
2021-02-17 07:20:19 +03:00
var v struct {
Version int ` json:"version" `
2021-01-18 20:11:05 +03:00
}
2021-02-17 07:20:19 +03:00
if err := json . NewDecoder ( metadata ) . Decode ( & v ) ; err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2021-01-18 20:11:05 +03:00
}
2021-02-17 07:20:19 +03:00
if v . Version == 3 {
ec . HasMetadataV3 = true
2021-01-18 20:11:05 +03:00
}
2021-02-17 07:20:19 +03:00
if ec . Config . Version >= V3 && ! ec . HasMetadataV3 {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( ` config v3 can only be used with servers having metadata version >= 3
2022-04-25 16:05:26 +03:00
You could fix this problem by taking one of the following actions :
1. Upgrade your Hasura server to a newer version ( >= v2 .0 .0 ) ie upgrade to a version which supports metadata v3
2022-11-03 11:07:50 +03:00
2. Force CLI to use an older config version via the -- version < VERSION > flag ` ) )
2021-01-18 20:11:05 +03:00
}
2021-02-17 07:20:19 +03:00
ec . APIClient = & hasura . Client {
V1Metadata : v1metadata . New ( httpClient , ec . Config . GetV1MetadataEndpoint ( ) ) ,
V1Query : v1query . New ( httpClient , ec . Config . GetV1QueryEndpoint ( ) ) ,
V2Query : v2query . New ( httpClient , ec . Config . GetV2QueryEndpoint ( ) ) ,
2021-04-01 13:38:55 +03:00
PGDump : pgdump . New ( httpClient , ec . Config . GetPGDumpEndpoint ( ) ) ,
2021-04-12 20:27:33 +03:00
V1Graphql : v1graphql . New ( httpClient , ec . Config . GetV1GraphqlEndpoint ( ) ) ,
2021-02-17 07:20:19 +03:00
}
2021-01-18 20:11:05 +03:00
var state * util . ServerState
if ec . HasMetadataV3 {
2021-12-15 20:54:47 +03:00
state = util . GetServerState ( httpClient , ec . Config . GetV1MetadataEndpoint ( ) , ec . HasMetadataV3 , ec . Logger )
2021-02-17 07:20:19 +03:00
} else {
2021-12-15 20:54:47 +03:00
state = util . GetServerState ( httpClient , ec . Config . GetV1QueryEndpoint ( ) , ec . HasMetadataV3 , ec . Logger )
2021-01-18 20:11:05 +03:00
}
ec . ServerUUID = state . UUID
ec . Telemetry . ServerUUID = ec . ServerUUID
ec . Logger . Debugf ( "server: uuid: %s" , ec . ServerUUID )
// Set headers required for communicating with HGE
2019-01-28 16:55:28 +03:00
return nil
2018-07-03 20:10:13 +03:00
}
func ( ec * ExecutionContext ) checkServerVersion ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.checkServerVersion"
2022-02-04 14:10:33 +03:00
v , err := version . FetchServerVersion ( ec . Config . ServerConfig . GetVersionEndpoint ( ) , ec . Config . HTTPClient )
2018-07-03 20:10:13 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "failed to get version from server: %w" , err ) )
2018-07-03 20:10:13 +03:00
}
2018-07-04 15:43:52 +03:00
ec . Version . SetServerVersion ( v )
2019-01-28 16:55:28 +03:00
ec . Telemetry . ServerVersion = ec . Version . GetServerVersion ( )
2018-07-04 15:43:52 +03:00
isCompatible , reason := ec . Version . CheckCLIServerCompatibility ( )
ec . Logger . Debugf ( "versions: cli: [%s] server: [%s]" , ec . Version . GetCLIVersion ( ) , ec . Version . GetServerVersion ( ) )
ec . Logger . Debugf ( "compatibility check: [%v] %v" , isCompatible , reason )
if ! isCompatible {
2020-01-16 06:53:18 +03:00
ec . Logger . Warnf ( "[cli: %s] [server: %s] version mismatch: %s" , ec . Version . GetCLIVersion ( ) , ec . Version . GetServerVersion ( ) , reason )
2018-07-03 20:10:13 +03:00
}
2018-06-24 16:40:48 +03:00
return nil
}
2020-02-24 19:14:46 +03:00
// WriteConfig writes the configuration from ec.Config or input config
func ( ec * ExecutionContext ) WriteConfig ( config * Config ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.WriteConfig"
2020-02-24 19:14:46 +03:00
var cfg * Config
if config != nil {
cfg = config
} else {
cfg = ec . Config
}
2022-03-10 11:12:55 +03:00
buf := new ( bytes . Buffer )
encoder := yaml . NewEncoder ( buf )
encoder . SetIndent ( 2 )
err := encoder . Encode ( cfg )
2020-02-24 19:14:46 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2020-02-24 19:14:46 +03:00
}
2022-11-03 11:07:50 +03:00
err = ioutil . WriteFile ( ec . ConfigFile , buf . Bytes ( ) , 0644 )
if err != nil {
return errors . E ( op , err )
}
return nil
2020-02-24 19:14:46 +03:00
}
2021-01-18 20:11:05 +03:00
type DefaultAPIPath string
2018-06-24 16:47:01 +03:00
// readConfig reads the configuration from config file, flags and env vars,
// through viper.
2018-06-24 16:40:48 +03:00
func ( ec * ExecutionContext ) readConfig ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.readConfig"
2018-06-24 16:47:01 +03:00
// need to get existing viper because https://github.com/spf13/viper/issues/233
v := ec . Viper
2020-04-08 14:55:42 +03:00
v . SetEnvPrefix ( util . ViperEnvPrefix )
v . SetEnvKeyReplacer ( util . ViperEnvReplacer )
2018-06-24 16:47:01 +03:00
v . AutomaticEnv ( )
v . SetConfigName ( "config" )
2020-02-24 19:14:46 +03:00
v . SetDefault ( "version" , "1" )
2019-01-28 16:55:28 +03:00
v . SetDefault ( "endpoint" , "http://localhost:8080" )
2019-02-14 12:37:47 +03:00
v . SetDefault ( "admin_secret" , "" )
2019-01-28 16:55:28 +03:00
v . SetDefault ( "access_key" , "" )
2021-02-17 07:20:19 +03:00
v . SetDefault ( "api_paths.query" , "v1/query" )
v . SetDefault ( "api_paths.v2_query" , "v2/query" )
v . SetDefault ( "api_paths.v1_metadata" , "v1/metadata" )
2020-04-08 13:59:21 +03:00
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" )
2021-06-23 09:33:19 +03:00
v . SetDefault ( "metadata_directory" , DefaultMetadataDirectory )
2020-06-16 15:15:04 +03:00
v . SetDefault ( "migrations_directory" , DefaultMigrationsDirectory )
v . SetDefault ( "seeds_directory" , DefaultSeedsDirectory )
2020-02-24 19:14:46 +03:00
v . SetDefault ( "actions.kind" , "synchronous" )
v . SetDefault ( "actions.handler_webhook_baseurl" , "http://localhost:3000" )
v . SetDefault ( "actions.codegen.framework" , "" )
v . SetDefault ( "actions.codegen.output_dir" , "" )
v . SetDefault ( "actions.codegen.uri" , "" )
2018-06-24 16:47:01 +03:00
v . AddConfigPath ( ec . ExecutionDirectory )
err := v . ReadInConfig ( )
2018-06-24 16:40:48 +03:00
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "cannot read config from file/env: %w" , err ) )
2019-02-14 12:37:47 +03:00
}
adminSecret := v . GetString ( "admin_secret" )
if adminSecret == "" {
adminSecret = v . GetString ( "access_key" )
2018-06-24 16:40:48 +03:00
}
2020-06-03 07:06:23 +03:00
2022-06-03 16:27:45 +03:00
// Admin secrets can be specified as a string value of format
// ["secret1", "secret2"], similar to how the corresponding environment variable
// HASURA_GRAPHQL_ADMIN_SECRETS is configured with the server
adminSecretsList := v . GetString ( "admin_secrets" )
adminSecrets := [ ] string { }
if len ( adminSecretsList ) > 0 {
if err = json . Unmarshal ( [ ] byte ( adminSecretsList ) , & adminSecrets ) ; err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "parsing 'admin_secrets' from config.yaml / environment variable HASURA_GRAPHQL_ADMIN_SECRETS failed: expected value of format [\"secret1\", \"secret2\"]: %w" , err ) )
2022-06-03 16:27:45 +03:00
}
}
2020-02-24 19:14:46 +03:00
ec . Config = & Config {
2021-06-09 10:12:56 +03:00
Version : ConfigVersion ( v . GetInt ( "version" ) ) ,
DisableInteractive : v . GetBool ( "disable_interactive" ) ,
2020-02-24 19:14:46 +03:00
ServerConfig : ServerConfig {
2022-06-03 16:27:45 +03:00
Endpoint : v . GetString ( "endpoint" ) ,
AdminSecret : adminSecret ,
AdminSecrets : adminSecrets ,
2020-04-08 13:59:21 +03:00
APIPaths : & ServerAPIPaths {
2021-02-17 07:20:19 +03:00
V1Query : v . GetString ( "api_paths.query" ) ,
V2Query : v . GetString ( "api_paths.v2_query" ) ,
V1Metadata : v . GetString ( "api_paths.v1_metadata" ) ,
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" ) ,
2020-04-08 13:59:21 +03:00
} ,
2020-04-28 14:59:57 +03:00
InsecureSkipTLSVerify : v . GetBool ( "insecure_skip_tls_verify" ) ,
CAPath : v . GetString ( "certificate_authority" ) ,
2020-02-24 19:14:46 +03:00
} ,
MetadataDirectory : v . GetString ( "metadata_directory" ) ,
2021-09-29 14:11:24 +03:00
MetadataFile : v . GetString ( "metadata_file" ) ,
2020-02-24 19:14:46 +03:00
MigrationsDirectory : v . GetString ( "migrations_directory" ) ,
2020-06-16 15:15:04 +03:00
SeedsDirectory : v . GetString ( "seeds_directory" ) ,
2020-03-26 06:24:05 +03:00
ActionConfig : & types . ActionExecutionConfig {
2020-02-24 19:14:46 +03:00
Kind : v . GetString ( "actions.kind" ) ,
HandlerWebhookBaseURL : v . GetString ( "actions.handler_webhook_baseurl" ) ,
Codegen : & types . CodegenExecutionConfig {
Framework : v . GetString ( "actions.codegen.framework" ) ,
OutputDir : v . GetString ( "actions.codegen.output_dir" ) ,
URI : v . GetString ( "actions.codegen.uri" ) ,
} ,
} ,
2018-06-24 16:40:48 +03:00
}
2020-03-26 06:24:05 +03:00
if ! ec . Config . Version . IsValid ( ) {
2022-11-03 11:07:50 +03:00
return errors . E ( op , ErrInvalidConfigVersion )
2020-03-26 06:24:05 +03:00
}
2020-04-28 14:59:57 +03:00
err = ec . Config . ServerConfig . ParseEndpoint ( )
if err != nil {
2022-11-03 11:07:50 +03:00
return errors . E ( op , fmt . Errorf ( "unable to parse server endpoint: %w" , err ) )
2020-04-28 14:59:57 +03:00
}
2022-02-04 14:10:33 +03:00
return nil
2018-06-24 16:40:48 +03:00
}
2018-06-24 16:47:01 +03:00
// setupSpinner creates a default spinner if the context does not already have
// one.
2018-06-24 16:40:48 +03:00
func ( ec * ExecutionContext ) setupSpinner ( ) {
if ec . Spinner == nil {
spnr := spinner . New ( spinner . CharSets [ 7 ] , 100 * time . Millisecond )
2021-06-18 09:24:16 +03:00
spnr . Writer = ec . Stderr
2018-06-24 16:40:48 +03:00
ec . Spinner = spnr
}
}
2018-06-27 15:04:09 +03:00
// Spin stops any existing spinner and starts a new one with the given message.
func ( ec * ExecutionContext ) Spin ( message string ) {
2019-12-25 17:44:02 +03:00
if ec . IsTerminal {
ec . Spinner . Stop ( )
ec . Spinner . Prefix = message
ec . Spinner . Start ( )
} else {
ec . Logger . Println ( message )
}
2018-06-27 15:04:09 +03:00
}
2020-04-23 05:47:51 +03:00
// loadEnvfile loads .env file
func ( ec * ExecutionContext ) loadEnvfile ( ) error {
2022-11-03 11:07:50 +03:00
var op errors . Op = "cli.ExecutionContext.loadEnvfile"
2021-10-08 17:43:51 +03:00
envfile := ec . Envfile
if ! filepath . IsAbs ( ec . Envfile ) {
envfile = filepath . Join ( ec . ExecutionDirectory , ec . Envfile )
}
2020-04-23 05:47:51 +03:00
err := gotenv . Load ( envfile )
if err != nil {
// return error if user provided envfile name
if ec . Envfile != ".env" {
2022-11-03 11:07:50 +03:00
return errors . E ( op , err )
2020-04-23 05:47:51 +03:00
}
2022-11-28 11:44:18 +03:00
if ! stderrors . Is ( err , fs . ErrNotExist ) {
2020-04-23 05:47:51 +03:00
ec . Logger . Warn ( err )
}
}
if err == nil {
ec . Logger . Debug ( "ENV vars read from: " , envfile )
}
return nil
}
2018-06-24 16:47:01 +03:00
// setupLogger creates a default logger if context does not have one set.
2018-06-24 16:40:48 +03:00
func ( ec * ExecutionContext ) setupLogger ( ) {
if ec . Logger == nil {
logger := logrus . New ( )
ec . Logger = logger
2021-06-18 09:24:16 +03:00
ec . Logger . SetOutput ( ec . Stderr )
2018-06-24 16:40:48 +03:00
}
2018-06-24 16:47:01 +03:00
if ec . LogLevel != "" {
level , err := logrus . ParseLevel ( ec . LogLevel )
if err != nil {
ec . Logger . WithError ( err ) . Error ( "error parsing log-level flag" )
return
}
ec . Logger . SetLevel ( level )
}
2019-01-28 16:55:28 +03:00
2020-02-24 19:14:46 +03:00
ec . Logger . Hooks = make ( logrus . LevelHooks )
ec . Logger . AddHook ( newSpinnerHandlerHook ( ec . Logger , ec . Spinner , ec . IsTerminal , ec . NoColor ) )
2019-01-28 16:55:28 +03:00
// set the logger for telemetry
if ec . Telemetry . Logger == nil {
ec . Telemetry . Logger = ec . Logger
}
2018-06-24 16:40:48 +03:00
}
2018-06-28 08:40:18 +03:00
// SetVersion sets the version inside context, according to the variable
2018-06-24 16:47:01 +03:00
// 'version' set during build context.
func ( ec * ExecutionContext ) setVersion ( ) {
2018-07-04 15:43:52 +03:00
if ec . Version == nil {
ec . Version = version . New ( )
2018-06-24 16:47:01 +03:00
}
}
2020-04-09 12:30:47 +03:00
func GetAdminSecretHeaderName ( v * version . Version ) string {
if v . ServerFeatureFlags . HasAccessKey {
return XHasuraAccessKey
}
return XHasuraAdminSecret
}
2021-02-17 07:20:19 +03:00
func GetCommonMetadataOps ( ec * ExecutionContext ) hasura . CommonMetadataOperations {
if ! ec . HasMetadataV3 {
return ec . APIClient . V1Query
}
return ec . APIClient . V1Metadata
}
func GetMigrationsStateStore ( ec * ExecutionContext ) statestore . MigrationsStateStore {
if ec . Config . Version <= V2 {
if ! ec . HasMetadataV3 {
2021-05-17 18:19:15 +03:00
return migrations . NewMigrationStateStoreHdbTable ( ec . APIClient . V1Query , migrations . DefaultSchema , migrations . DefaultMigrationsTable )
2021-02-17 07:20:19 +03:00
}
2021-05-17 18:19:15 +03:00
return migrations . NewMigrationStateStoreHdbTable ( ec . APIClient . V2Query , migrations . DefaultSchema , migrations . DefaultMigrationsTable )
2021-02-17 07:20:19 +03:00
}
return migrations . NewCatalogStateStore ( statestore . NewCLICatalogState ( ec . APIClient . V1Metadata ) )
}
2021-05-28 09:04:36 +03:00
func GetSettingsStateStore ( ec * ExecutionContext , databaseName string ) statestore . SettingsStateStore {
2021-02-17 07:20:19 +03:00
const (
defaultSettingsTable = "migration_settings"
defaultSchema = "hdb_catalog"
)
if ec . Config . Version <= V2 {
if ! ec . HasMetadataV3 {
2021-05-28 09:04:36 +03:00
return settings . NewStateStoreHdbTable ( ec . APIClient . V1Query , databaseName , defaultSchema , defaultSettingsTable )
2021-02-17 07:20:19 +03:00
}
2021-05-28 09:04:36 +03:00
return settings . NewStateStoreHdbTable ( ec . APIClient . V2Query , databaseName , defaultSchema , defaultSettingsTable )
2021-02-17 07:20:19 +03:00
}
return settings . NewStateStoreCatalog ( statestore . NewCLICatalogState ( ec . APIClient . V1Metadata ) )
}