mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
7f7640f988
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6719 GitOrigin-RevId: 92540116b5aa8c106f6543a2a12096c69d93dbd9
364 lines
12 KiB
Go
364 lines
12 KiB
Go
package commands
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/hasura/graphql-engine/cli/v2"
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/metadatautil"
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/projectmetadata"
|
|
)
|
|
|
|
type MetadataModeHandler interface {
|
|
Export(*MetadataExportOptions) error
|
|
Apply(*MetadataApplyOptions) error
|
|
Diff(*MetadataDiffOptions) error
|
|
}
|
|
|
|
func getMetadataModeHandler(mode cli.MetadataMode) MetadataModeHandler {
|
|
switch mode {
|
|
case cli.MetadataModeJSON:
|
|
return &metadataModeJSONHandler{}
|
|
case cli.MetadataModeYAML:
|
|
return &metadataModeYAMLHandler{}
|
|
default:
|
|
return &metadataModeDirectoryHandler{}
|
|
}
|
|
}
|
|
|
|
type metadataModeDirectoryHandler struct{}
|
|
|
|
func (m *metadataModeDirectoryHandler) Export(o *MetadataExportOptions) error {
|
|
var op errors.Op = "commands.metadataModeDirectoryHandler.Export"
|
|
metadataHandler := projectmetadata.NewHandlerFromEC(o.EC)
|
|
files, err := metadataHandler.ExportMetadata()
|
|
o.EC.Spinner.Stop()
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("failed to export metadata: %w", err))
|
|
}
|
|
err = metadataHandler.WriteMetadata(files)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("cannot write metadata to project: %w", err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeDirectoryHandler) Apply(o *MetadataApplyOptions) error {
|
|
var op errors.Op = "commands.metadataModeDirectoryHandler.Apply"
|
|
metadataHandler := projectmetadata.NewHandlerFromEC(o.EC)
|
|
if !o.DryRun {
|
|
if o.EC.Config.Version == cli.V2 {
|
|
_, err := metadataHandler.V1ApplyMetadata()
|
|
if err != nil {
|
|
return errors.E(op, errorApplyingMetadata(err))
|
|
}
|
|
o.EC.Logger.Debug("metadata applied using v1 replace_metadata")
|
|
} else {
|
|
r, err := metadataHandler.V2ApplyMetadata(o.DisallowInconsistencies)
|
|
if err != nil {
|
|
return errors.E(op, errorApplyingMetadata(err))
|
|
}
|
|
if !r.IsConsistent {
|
|
o.EC.Logger.Warn("Metadata is inconsistent")
|
|
o.EC.Logger.Warn("Use 'hasura metadata ic list' command to list inconsistent objects")
|
|
}
|
|
o.EC.Logger.Debug("metadata applied using v2 replace_metadata")
|
|
}
|
|
if len(o.rawOutput) != 0 {
|
|
// if not a dry run fetch metadata from and server and print it to stdout
|
|
if err := getMetadataFromServerAndWriteToStdoutByFormat(o.EC, rawOutputFormat(o.rawOutput)); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if o.DryRun {
|
|
projectMetadataJSON, err := metadataHandler.BuildJSONMetadata()
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("error building project metadata: %w", err))
|
|
}
|
|
|
|
if o.DryRun && len(o.rawOutput) == 0 {
|
|
// ie users who probably expect old behaviour
|
|
// show a warning about change in behaviour
|
|
o.rawOutput = string(rawOutputFormatJSON)
|
|
o.EC.Logger.Warn("behaviour of --dry-run flag has changed from v2.0.0. It used to show a diff between metadata on server and local project")
|
|
o.EC.Logger.Warn("new behaviour is to output local project metadata as JSON by default. The output format is configurable by -o flag eg: `hasura metadata apply --dry-run -o yaml`")
|
|
o.EC.Logger.Warn("the old behaviour can be achieved using `hasura metadata diff` command")
|
|
}
|
|
|
|
if err := writeByOutputFormat(o.EC.Stdout, projectMetadataJSON, rawOutputFormat(o.rawOutput)); err != nil {
|
|
return errors.E(op, fmt.Errorf("displaying metadata failed: %w", err))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeDirectoryHandler) Diff(o *MetadataDiffOptions) error {
|
|
var op errors.Op = "commands.metadataModeDirectoryHandler.Diff"
|
|
args := o.Args
|
|
messageFormat := "Showing diff between %s and %s..."
|
|
message := ""
|
|
metadataHandler := projectmetadata.NewHandlerFromEC(o.EC)
|
|
fromFriendlyName := "project"
|
|
toFriendlyName := "server"
|
|
fromDirectory, toDirectory := "", ""
|
|
switch len(args) {
|
|
case 0:
|
|
o.Metadata[0] = o.EC.MetadataDir
|
|
fromFriendlyName = "project"
|
|
fromDirectory = o.EC.MetadataDir
|
|
case 1:
|
|
// 1 arg, diff given directory and the metadata on server
|
|
err := checkDir(args[0])
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
o.Metadata[0] = args[0]
|
|
fromFriendlyName = o.Metadata[0]
|
|
fromDirectory = o.Metadata[0]
|
|
case 2:
|
|
err := checkDir(args[0])
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
o.Metadata[0] = args[0]
|
|
fromFriendlyName = o.Metadata[0]
|
|
fromDirectory = o.Metadata[0]
|
|
|
|
err = checkDir(args[1])
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
o.Metadata[1] = args[1]
|
|
toFriendlyName = o.Metadata[1]
|
|
toDirectory = o.Metadata[1]
|
|
}
|
|
|
|
message = fmt.Sprintf(messageFormat, fromFriendlyName, toFriendlyName)
|
|
o.EC.Logger.Info(message)
|
|
|
|
if o.Metadata[1] == "" {
|
|
// if no arguments are provided to `metadata diff` command, then it means
|
|
// we have to compare project directory with the server
|
|
// For that, we are creating a temporary metadata directory and exporting
|
|
// metadata from the server to it and then setting it as the "toDirectory"
|
|
tmpDir, err := ioutil.TempDir("", "*")
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
metadataHandler.SetMetadataObjects(projectmetadata.GetMetadataObjectsWithDir(o.EC, tmpDir))
|
|
var files map[string][]byte
|
|
files, err = metadataHandler.ExportMetadata()
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
err = metadataHandler.WriteMetadata(files)
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
toDirectory = tmpDir
|
|
}
|
|
if len(o.DiffType) > 0 {
|
|
opts := printGeneratedMetadataFileDiffOpts{
|
|
projectMetadataHandler: metadataHandler,
|
|
fromProjectDirectory: fromDirectory,
|
|
toProjectDirectory: toDirectory,
|
|
fromFriendlyName: fromFriendlyName,
|
|
toFriendlyName: toFriendlyName,
|
|
disableColor: o.DisableColor,
|
|
diffType: DiffType(o.DiffType),
|
|
metadataMode: o.EC.MetadataMode,
|
|
writer: o.Output,
|
|
ec: o.EC,
|
|
}
|
|
if err := printGeneratedMetadataFileDiffBetweenProjectDirectories(opts); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
opts := projectmetadata.PrintContextRichDiffBetweenProjectDirectoriesOpts{
|
|
EC: o.EC,
|
|
MetadataHandler: metadataHandler,
|
|
FromDirectory: toDirectory,
|
|
ToDirectory: fromDirectory,
|
|
Writer: o.Output,
|
|
DisableColor: o.DisableColor,
|
|
}
|
|
if err := projectmetadata.PrintContextRichDiffBetweenProjectDirectories(opts); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type metadataModeJSONHandler struct{}
|
|
|
|
func (m *metadataModeJSONHandler) Export(o *MetadataExportOptions) error {
|
|
var op errors.Op = "commands.metadataModeJSONHandler.Export"
|
|
if err := export(o, o.EC.MetadataMode); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeJSONHandler) Apply(o *MetadataApplyOptions) error {
|
|
var op errors.Op = "commands.metadataModeJSONHandler.Apply"
|
|
if err := apply(o, o.EC.MetadataMode); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeJSONHandler) Diff(o *MetadataDiffOptions) error {
|
|
var op errors.Op = "commands.metadataModeJSONHandler.Diff"
|
|
if err := diff(o, DifftypeJSON); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type metadataModeYAMLHandler struct{}
|
|
|
|
func (m *metadataModeYAMLHandler) Export(o *MetadataExportOptions) error {
|
|
var op errors.Op = "commands.metadataModeYAMLHandler.Export"
|
|
if err := export(o, o.EC.MetadataMode); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeYAMLHandler) Apply(o *MetadataApplyOptions) error {
|
|
var op errors.Op = "commands.metadataModeYAMLHandler.Apply"
|
|
if err := apply(o, o.EC.MetadataMode); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *metadataModeYAMLHandler) Diff(o *MetadataDiffOptions) error {
|
|
var op errors.Op = "commands.metadataModeYAMLHandler.Diff"
|
|
if err := diff(o, DifftypeYAML); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func export(o *MetadataExportOptions, mode cli.MetadataMode) error {
|
|
var op errors.Op = "commands.export"
|
|
metadata, err := ec.APIClient.V1Metadata.ExportMetadata()
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("exporting metadata from server: %w", err))
|
|
}
|
|
var metadataBytes []byte
|
|
metadataBytes, err = ioutil.ReadAll(metadata)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("reading metadata from response: %w", err))
|
|
}
|
|
if mode == cli.MetadataModeYAML {
|
|
metadataBytes, err = metadatautil.JSONToYAML(metadataBytes)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("parsing metadata to yaml: %w", err))
|
|
}
|
|
}
|
|
err = ioutil.WriteFile(o.EC.MetadataFile, metadataBytes, os.ModePerm)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("writing metadata to file: %w", err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func apply(o *MetadataApplyOptions, mode cli.MetadataMode) error {
|
|
var op errors.Op = "commands.apply"
|
|
var localMetadataBytes []byte
|
|
var err error
|
|
|
|
localMetadataBytes, err = ioutil.ReadFile(o.EC.MetadataFile)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("reading metadata file: %w", err))
|
|
}
|
|
if o.DryRun {
|
|
if len(o.rawOutput) == 0 {
|
|
o.rawOutput = string(rawOutputFormatJSON)
|
|
}
|
|
if err := writeByOutputFormat(o.EC.Stdout, localMetadataBytes, rawOutputFormat(o.rawOutput)); err != nil {
|
|
return errors.E(op, fmt.Errorf("displaying metadata failed: %w", err))
|
|
}
|
|
return nil
|
|
}
|
|
if mode == cli.MetadataModeYAML {
|
|
localMetadataBytes, err = metadatautil.YAMLToJSON(localMetadataBytes)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("parsing yaml metadata to json: %w", err))
|
|
}
|
|
}
|
|
var metadata interface{}
|
|
err = json.Unmarshal(localMetadataBytes, &metadata)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("parsing metadata as json: %w", err))
|
|
}
|
|
if o.EC.Config.Version == cli.V2 {
|
|
_, err := cli.GetCommonMetadataOps(o.EC).ReplaceMetadata(bytes.NewReader(localMetadataBytes))
|
|
if err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
r, err := o.EC.APIClient.V1Metadata.V2ReplaceMetadata(hasura.V2ReplaceMetadataArgs{
|
|
AllowInconsistentMetadata: !o.DisallowInconsistencies,
|
|
Metadata: metadata,
|
|
})
|
|
if err != nil {
|
|
return errors.E(op, errorApplyingMetadata(err))
|
|
}
|
|
if !r.IsConsistent {
|
|
o.EC.Logger.Warn("Metadata is inconsistent")
|
|
o.EC.Logger.Warn("Use 'hasura metadata ic list' command to list inconsistent objects")
|
|
}
|
|
if len(o.rawOutput) != 0 {
|
|
// if not a dry run fetch metadata from and server and print it to stdout
|
|
if err := getMetadataFromServerAndWriteToStdoutByFormat(o.EC, rawOutputFormat(o.rawOutput)); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func diff(o *MetadataDiffOptions, diffType DiffType) error {
|
|
var op errors.Op = "commands.diff"
|
|
if len(o.Args) > 0 {
|
|
return errors.E(op, fmt.Errorf("expected 0 arguments, found: %v", o.Args))
|
|
}
|
|
serverMetadata, err := cli.GetCommonMetadataOps(o.EC).ExportMetadata()
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("exporting metadata from server: %w", err))
|
|
}
|
|
var serverMetadataBytes []byte
|
|
serverMetadataBytes, err = ioutil.ReadAll(serverMetadata)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("reading server metadata: %w", err))
|
|
}
|
|
if diffType == DifftypeYAML {
|
|
serverMetadataBytes, err = metadatautil.JSONToYAML(serverMetadataBytes)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("parsing server metadata as yaml: %w", err))
|
|
}
|
|
}
|
|
localMetadataBytes, err := ioutil.ReadFile(o.EC.MetadataFile)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("reading local metadata: %w", err))
|
|
}
|
|
if err := printMyersDiff(string(serverMetadataBytes), string(localMetadataBytes), "server", "project", o.Output, o.DisableColor); err != nil {
|
|
return errors.E(op, err)
|
|
}
|
|
return nil
|
|
}
|