2019-10-30 16:54:22 +03:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
2021-12-09 20:25:54 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2021-07-28 09:44:13 +03:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strings"
|
2021-07-23 12:49:44 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
goyaml "github.com/goccy/go-yaml"
|
|
|
|
|
|
|
|
diffpkg "github.com/hasura/graphql-engine/cli/v2/internal/diff"
|
|
|
|
|
|
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/projectmetadata"
|
2021-07-23 12:49:44 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
|
|
"github.com/aryann/difflib"
|
2021-07-28 09:44:13 +03:00
|
|
|
"github.com/hasura/graphql-engine/cli/v2"
|
|
|
|
"github.com/mgutz/ansi"
|
|
|
|
"github.com/spf13/cobra"
|
2021-12-09 20:25:54 +03:00
|
|
|
v2yaml "gopkg.in/yaml.v2"
|
2019-10-30 16:54:22 +03:00
|
|
|
)
|
|
|
|
|
2020-02-24 19:14:46 +03:00
|
|
|
type MetadataDiffOptions struct {
|
2021-06-18 09:24:16 +03:00
|
|
|
EC *cli.ExecutionContext
|
|
|
|
Output io.Writer
|
|
|
|
Args []string
|
|
|
|
DiffType string
|
|
|
|
DisableColor bool
|
2020-02-24 19:14:46 +03:00
|
|
|
// two Metadata to diff, 2nd is server if it's empty
|
|
|
|
Metadata [2]string
|
2019-10-30 16:54:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func newMetadataDiffCmd(ec *cli.ExecutionContext) *cobra.Command {
|
2020-02-24 19:14:46 +03:00
|
|
|
opts := &MetadataDiffOptions{
|
2019-10-30 16:54:22 +03:00
|
|
|
EC: ec,
|
2021-07-16 08:26:00 +03:00
|
|
|
Output: ec.Stdout,
|
2019-10-30 16:54:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
metadataDiffCmd := &cobra.Command{
|
|
|
|
Use: "diff [file1] [file2]",
|
|
|
|
Short: "(PREVIEW) Show a highlighted diff of Hasura metadata",
|
|
|
|
Long: `(PREVIEW) Show changes between two different sets of Hasura metadata.
|
2021-03-15 18:40:52 +03:00
|
|
|
By default, it shows changes between the exported metadata file and server metadata`,
|
2019-10-30 16:54:22 +03:00
|
|
|
Example: ` # NOTE: This command is in preview, usage and diff format may change.
|
|
|
|
|
|
|
|
# Show changes between server metadata and the exported metadata file:
|
|
|
|
hasura metadata diff
|
2021-12-09 20:25:54 +03:00
|
|
|
|
|
|
|
# Apply admin secret for Hasura GraphQL engine:
|
2019-12-12 08:16:36 +03:00
|
|
|
hasura metadata diff --admin-secret "<admin-secret>"
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
# Specify a diff type
|
|
|
|
hasura metadata diff --type "unified-json"
|
|
|
|
hasura metadata diff --type "json"
|
2021-06-17 13:15:22 +03:00
|
|
|
|
2019-12-12 08:16:36 +03:00
|
|
|
# Diff metadata on a different Hasura instance:
|
|
|
|
hasura metadata diff --endpoint "<endpoint>"`,
|
2019-10-30 16:54:22 +03:00
|
|
|
Args: cobra.MaximumNArgs(2),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2020-02-24 19:14:46 +03:00
|
|
|
opts.Args = args
|
2021-12-09 20:25:54 +03:00
|
|
|
opts.DisableColor = ec.NoColor
|
2020-02-24 19:14:46 +03:00
|
|
|
return opts.Run()
|
2019-10-30 16:54:22 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-06-17 13:15:22 +03:00
|
|
|
f := metadataDiffCmd.Flags()
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
f.StringVar(&opts.DiffType, "type", "", fmt.Sprintf(`specify a type of diff [allowed values: %v, %v, %v]`, DifftypeUnifiedJSON, DifftypeYAML, DifftypeJSON))
|
2021-06-17 13:15:22 +03:00
|
|
|
|
2020-02-24 19:14:46 +03:00
|
|
|
return metadataDiffCmd
|
|
|
|
}
|
2019-10-30 16:54:22 +03:00
|
|
|
|
2020-02-24 19:14:46 +03:00
|
|
|
func (o *MetadataDiffOptions) Run() error {
|
2021-01-18 20:11:05 +03:00
|
|
|
if o.EC.Config.Version >= cli.V2 && o.EC.MetadataDir != "" {
|
2021-09-29 14:11:24 +03:00
|
|
|
return getMetadataModeHandler(o.EC.MetadataMode).Diff(o)
|
2021-04-01 08:13:24 +03:00
|
|
|
} else {
|
|
|
|
return fmt.Errorf("metadata diff for config %d not supported", o.EC.Config.Version)
|
2020-02-24 19:14:46 +03:00
|
|
|
}
|
|
|
|
}
|
2021-06-18 09:24:16 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
type DiffType string
|
2021-06-18 09:24:16 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
const DifftypeUnifiedJSON DiffType = "unified-json"
|
|
|
|
const DifftypeYAML DiffType = "yaml"
|
|
|
|
const DifftypeJSON DiffType = "json"
|
2021-06-17 13:15:22 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
const zeroDifferencesFound = "zero differences found"
|
|
|
|
|
|
|
|
type printGeneratedMetadataFileDiffOpts struct {
|
|
|
|
projectMetadataHandler *projectmetadata.Handler
|
|
|
|
// actual directory paths to project directory
|
|
|
|
fromProjectDirectory string
|
|
|
|
toProjectDirectory string
|
|
|
|
|
|
|
|
// friendly names if any to both from and to directories
|
|
|
|
// for example the diff can between the current project directory and server
|
|
|
|
fromFriendlyName string
|
|
|
|
toFriendlyName string
|
|
|
|
|
|
|
|
disableColor bool
|
|
|
|
diffType DiffType
|
|
|
|
metadataMode cli.MetadataMode
|
|
|
|
writer io.Writer
|
|
|
|
ec *cli.ExecutionContext
|
2021-06-17 13:15:22 +03:00
|
|
|
}
|
2020-02-24 19:14:46 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
func printGeneratedMetadataFileDiffBetweenProjectDirectories(opts printGeneratedMetadataFileDiffOpts) error {
|
|
|
|
// build server metadata
|
|
|
|
opts.projectMetadataHandler.SetMetadataObjects(projectmetadata.GetMetadataObjectsWithDir(opts.ec, opts.toProjectDirectory))
|
|
|
|
serverMeta, err := opts.projectMetadataHandler.BuildMetadata()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-06-18 09:24:16 +03:00
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
newYaml, err := v2yaml.Marshal(serverMeta)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "cannot unmarshall server metadata")
|
|
|
|
}
|
|
|
|
opts.projectMetadataHandler.SetMetadataObjects(projectmetadata.GetMetadataObjectsWithDir(opts.ec, opts.fromProjectDirectory))
|
|
|
|
localMeta, err := opts.projectMetadataHandler.BuildMetadata()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
oldYaml, err := v2yaml.Marshal(localMeta)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "cannot unmarshal local metadata")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch opts.diffType {
|
|
|
|
case DifftypeJSON:
|
|
|
|
newJson, err := goyaml.YAMLToJSON(newYaml)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot unmarshal local metadata to json: %w", err)
|
|
|
|
}
|
|
|
|
oldJson, err := goyaml.YAMLToJSON(oldYaml)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot unmarshal server metadata to json: %w", err)
|
2021-06-17 13:15:22 +03:00
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
var newJsonBuf, oldJsonBuf bytes.Buffer
|
|
|
|
err = json.Indent(&newJsonBuf, newJson, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot indent server localmetadata to json: %w", err)
|
2019-10-30 16:54:22 +03:00
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
err = json.Indent(&oldJsonBuf, oldJson, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot indent server metadata to json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return printMyersDiff(string(newYaml), string(oldYaml), opts.toFriendlyName, opts.fromFriendlyName, opts.writer, opts.disableColor)
|
|
|
|
case DifftypeYAML:
|
|
|
|
return printMyersDiff(string(newYaml), string(oldYaml), opts.toFriendlyName, opts.fromFriendlyName, opts.writer, opts.disableColor)
|
|
|
|
case DifftypeUnifiedJSON:
|
|
|
|
printUnifiedJSONDiff(string(newYaml), string(oldYaml), opts.writer)
|
2019-10-30 16:54:22 +03:00
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
return nil
|
|
|
|
}
|
2021-06-17 13:15:22 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
func printMyersDiff(before, after, from, to string, writer io.Writer, disableColor bool) error {
|
|
|
|
fmt.Fprintf(writer, "- %s\n", diffpkg.MakeDiffLine(from, "red", disableColor))
|
|
|
|
fmt.Fprintf(writer, "+ %s\n", diffpkg.MakeDiffLine(to, "green", disableColor))
|
|
|
|
count, err := diffpkg.MyersDiff(before, after, from, to, writer, disableColor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if count == 0 {
|
|
|
|
fmt.Fprintln(writer, zeroDifferencesFound)
|
|
|
|
}
|
2021-06-17 13:15:22 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
func printUnifiedJSONDiff(before, after string, to io.Writer) {
|
2021-06-18 09:24:16 +03:00
|
|
|
diffs := difflib.Diff(strings.Split(before, "\n"), strings.Split(after, "\n"))
|
|
|
|
|
|
|
|
for _, diff := range diffs {
|
|
|
|
text := diff.Payload
|
|
|
|
switch diff.Delta {
|
|
|
|
case difflib.RightOnly:
|
|
|
|
fmt.Fprintf(to, "%s\n", ansi.Color(text, "green"))
|
|
|
|
case difflib.LeftOnly:
|
|
|
|
fmt.Fprintf(to, "%s\n", ansi.Color(text, "red"))
|
|
|
|
case difflib.Common:
|
|
|
|
fmt.Fprintf(to, "%s\n", text)
|
|
|
|
}
|
|
|
|
}
|
2019-10-30 16:54:22 +03:00
|
|
|
}
|
2020-02-24 19:14:46 +03:00
|
|
|
|
|
|
|
func checkDir(path string) error {
|
|
|
|
file, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !file.IsDir() {
|
|
|
|
return fmt.Errorf("metadata diff only works with folder but got file %s", path)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|