From cd0e2ec8e8f12a75ba62c712b89de3418cdae73e Mon Sep 17 00:00:00 2001 From: Aravind Date: Fri, 3 Jul 2020 09:49:09 +0530 Subject: [PATCH] cli: fix permission issues when creating symlinks on windows, misc fix (close #5111, close #4879) (#5164) --- CHANGELOG.md | 1 + cli/go.mod | 1 + cli/migrate/database/hasuradb/types.go | 64 ++++++++++++------ cli/migrate/database/hasuradb/types_test.go | 74 +++++++++++++++++++++ cli/plugins/util.go | 20 +++++- 5 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 cli/migrate/database/hasuradb/types_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ede76dd807..581cf48a244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - console: support tracking partitioned tables (close #5071) (#5258) - console: add button to cancel one-off scheduled events and cron-trigger events (close #5161) (#5236) - console: handle generated and identity columns in console data section (close #4552, #4863) (#4761) +- cli: fix plugins install failing due to permission issues on windows (close #5111) - docs: add note for managed databases in postgres requirements (close #1677, #3783) (#5228) - docs: add 1-click deployment to Nhost page to the deployment guides (#5180) - docs: add hasura cloud to getting started section (close #5206) (#5208) diff --git a/cli/go.mod b/cli/go.mod index 08e39e6227c..012f1e70c52 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -30,6 +30,7 @@ require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/microcosm-cc/bluemonday v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/mapstructure v1.1.2 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 github.com/parnurzeal/gorequest v0.2.16 github.com/pkg/errors v0.8.1 diff --git a/cli/migrate/database/hasuradb/types.go b/cli/migrate/database/hasuradb/types.go index a8d98480916..51bcf25ba0f 100644 --- a/cli/migrate/database/hasuradb/types.go +++ b/cli/migrate/database/hasuradb/types.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/mitchellh/mapstructure" + "github.com/hasura/graphql-engine/cli/migrate/database" "github.com/qor/transition" @@ -223,25 +225,25 @@ type HasuraError struct { // MigrationFile is used internally for hasuractl migrationFile string migrationQuery string - Path string `json:"path"` - ErrorMessage string `json:"error"` - Internal *SQLInternalError `json:"internal,omitempty"` - Message string `json:"message,omitempty"` - Code string `json:"code"` + Path string `json:"path"` + ErrorMessage string `json:"error"` + Internal interface{} `json:"internal,omitempty"` + Message string `json:"message,omitempty"` + Code string `json:"code"` } type SQLInternalError struct { - Arguments []string `json:"arguments"` - Error PostgresError `json:"error"` - Prepared bool `json:"prepared"` - Statement string `json:"statement"` + Arguments []string `json:"arguments" mapstructure:"arguments,omitempty"` + Error PostgresError `json:"error" mapstructure:"error,omitempty"` + Prepared bool `json:"prepared" mapstructure:"prepared,omitempty"` + Statement string `json:"statement" mapstructure:"statement,omitempty"` } type PostgresError struct { - StatusCode string `json:"status_code"` - ExecStatus string `json:"exec_status"` - Message string `json:"message"` - Description string `json:"description"` - Hint string `json:"hint"` + StatusCode string `json:"status_code" mapstructure:"status_code,omitempty"` + ExecStatus string `json:"exec_status" mapstructure:"exec_status,omitempty"` + Message string `json:"message" mapstructure:"message,omitempty"` + Description string `json:"description" mapstructure:"description,omitempty"` + Hint string `json:"hint" mapstructure:"hint,omitempty"` } type SchemaDump struct { @@ -258,14 +260,34 @@ func (h HasuraError) Error() string { if h.migrationQuery != "" { errorStrings = append(errorStrings, fmt.Sprintf("%s", h.migrationQuery)) } - if h.Internal != nil { - // postgres error - errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", h.Internal.Error.StatusCode, h.Internal.Error.ExecStatus, h.Internal.Error.Message)) - if len(h.Internal.Error.Description) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", h.Internal.Error.Description)) + var internalError SQLInternalError + var internalErrors []SQLInternalError + if v, ok := h.Internal.(map[string]interface{}); ok { + err := mapstructure.Decode(v, &internalError) + if err == nil { + // postgres error + errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) + if len(internalError.Error.Description) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + } + if len(internalError.Error.Hint) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + } } - if len(h.Internal.Error.Hint) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", h.Internal.Error.Hint)) + } + if v, ok := h.Internal.([]interface{}); ok { + err := mapstructure.Decode(v, &internalErrors) + if err == nil { + for _, internalError := range internalErrors { + // postgres error + errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) + if len(internalError.Error.Description) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + } + if len(internalError.Error.Hint) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + } + } } } return strings.Join(errorStrings, "\r\n") diff --git a/cli/migrate/database/hasuradb/types_test.go b/cli/migrate/database/hasuradb/types_test.go new file mode 100644 index 00000000000..bfd3cf8b506 --- /dev/null +++ b/cli/migrate/database/hasuradb/types_test.go @@ -0,0 +1,74 @@ +package hasuradb + +import ( + "encoding/json" + "testing" +) + +func TestHasuraError_Error(t *testing.T) { + type fields struct { + migrationFile string + migrationQuery string + Path string + ErrorMessage string + Internal interface{} + Message string + Code string + } + tests := []struct { + name string + fields fields + want string + }{ + { + "can unmarshal internal error", + fields{ + Internal: func() interface{} { + d := []byte(` +{ +"error": { + "status_code": "1" +} +}`) + var v interface{} + json.Unmarshal(d, &v) + return v + }(), + }, + "[] ()\r\n[1] : ", + }, + { + "can unmarshal internal errors", + fields{ + Internal: func() interface{} { + d := []byte(` +[{ +"error": { + "status_code": "2" +} +}]`) + var v interface{} + json.Unmarshal(d, &v) + return v + }(), + }, + "[] ()\r\n[2] : ", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := HasuraError{ + migrationFile: tt.fields.migrationFile, + migrationQuery: tt.fields.migrationQuery, + Path: tt.fields.Path, + ErrorMessage: tt.fields.ErrorMessage, + Internal: tt.fields.Internal, + Message: tt.fields.Message, + Code: tt.fields.Code, + } + if got := h.Error(); got != tt.want { + t.Errorf("Error() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cli/plugins/util.go b/cli/plugins/util.go index e38f6c4b82d..f7414d2bb40 100644 --- a/cli/plugins/util.go +++ b/cli/plugins/util.go @@ -15,6 +15,7 @@ import ( "regexp" "runtime" "strings" + "syscall" "github.com/hasura/graphql-engine/cli/plugins/download" "github.com/pkg/errors" @@ -145,7 +146,22 @@ func createOrUpdateLink(binDir, binary, plugin string) error { // Create new if err := os.Symlink(binary, dst); err != nil { - return errors.Wrapf(err, "failed to create a symlink from %q to %q", binary, dst) + if IsWindows() { + // If cloning the symlink fails on Windows because the user + // does not have the required privileges, ignore the error and + // fall back to copying the file contents. + // + // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { + return err + } + if err := copyFile(binary, dst, 0755); err != nil { + return err + } + } else { + return errors.Wrapf(err, "failed to create a symlink from %q to %q", binary, dst) + } } return nil } @@ -159,7 +175,7 @@ func removeLink(path string) error { return errors.Wrapf(err, "failed to read the symlink in %q", path) } - if fi.Mode()&os.ModeSymlink == 0 { + if fi.Mode()&os.ModeSymlink == 0 && !IsWindows() { return errors.Errorf("file %q is not a symlink (mode=%s)", path, fi.Mode()) } if err := os.Remove(path); err != nil {