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
diffpkg "github.com/hasura/graphql-engine/cli/v2/internal/diff"
2022-11-14 10:28:23 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
2022-06-15 15:38:39 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/metadatautil"
2021-12-09 20:25:54 +03:00
"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/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"
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]" ,
2022-12-30 06:50:48 +03:00
Short : "(PREVIEW) Show a highlighted diff of the Hasura Metadata" ,
Long : "(PREVIEW) This command shows changes between two different sets of Hasura Metadata. By default, it shows changes between the exported Hasura Metadata and the Hasura Metadata on the server." ,
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 ) ,
2022-01-12 16:24:23 +03:00
PreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-11-14 10:28:23 +03:00
op := genOpName ( cmd , "PreRunE" )
2022-01-12 16:24:23 +03:00
if len ( opts . DiffType ) > 0 {
optsDiffType := DiffType ( opts . DiffType )
diffTypes := [ ] DiffType { DifftypeJSON , DifftypeYAML , DifftypeUnifiedJSON , DifftypeUnifiedYAML }
for _ , diffType := range diffTypes {
if optsDiffType == diffType {
return nil
}
}
2022-11-14 10:28:23 +03:00
return errors . E ( op , fmt . Errorf ( "metadata diff doesn't support difftype %s" , optsDiffType ) )
2022-01-12 16:24:23 +03:00
}
return nil
} ,
2019-10-30 16:54:22 +03:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-11-14 10:28:23 +03:00
op := genOpName ( cmd , "RunE" )
2020-02-24 19:14:46 +03:00
opts . Args = args
2021-12-09 20:25:54 +03:00
opts . DisableColor = ec . NoColor
2022-11-14 10:28:23 +03:00
if err := opts . Run ( ) ; err != nil {
return errors . E ( op , err )
}
return nil
2019-10-30 16:54:22 +03:00
} ,
}
2021-06-17 13:15:22 +03:00
f := metadataDiffCmd . Flags ( )
2022-01-12 16:24:23 +03:00
f . StringVar ( & opts . DiffType , "type" , "" , fmt . Sprintf ( ` specify a type of diff [allowed values: %v,%v, %v, %v] ` , DifftypeUnifiedJSON , DifftypeUnifiedYAML , 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 {
2022-11-14 10:28:23 +03:00
var op errors . Op = "commands.MetadataDiffOptions.Run"
2021-01-18 20:11:05 +03:00
if o . EC . Config . Version >= cli . V2 && o . EC . MetadataDir != "" {
2022-11-14 10:28:23 +03:00
if err := getMetadataModeHandler ( o . EC . MetadataMode ) . Diff ( o ) ; err != nil {
return errors . E ( op , err )
}
return nil
2021-04-01 08:13:24 +03:00
} else {
2022-11-14 10:28:23 +03:00
return errors . E ( op , 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"
2022-01-12 16:24:23 +03:00
const DifftypeUnifiedYAML DiffType = "unified-yaml"
2021-12-09 20:25:54 +03:00
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 {
2022-11-14 10:28:23 +03:00
var op errors . Op = "commands.printGeneratedMetadataFileDiffBetweenProjectDirectories"
2021-12-09 20:25:54 +03:00
// build server metadata
opts . projectMetadataHandler . SetMetadataObjects ( projectmetadata . GetMetadataObjectsWithDir ( opts . ec , opts . toProjectDirectory ) )
2022-03-10 11:12:55 +03:00
newYaml , err := opts . projectMetadataHandler . BuildYAMLMetadata ( )
2021-12-09 20:25:54 +03:00
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , err )
2021-06-18 09:24:16 +03:00
}
2021-12-09 20:25:54 +03:00
opts . projectMetadataHandler . SetMetadataObjects ( projectmetadata . GetMetadataObjectsWithDir ( opts . ec , opts . fromProjectDirectory ) )
2022-03-10 11:12:55 +03:00
oldYaml , err := opts . projectMetadataHandler . BuildYAMLMetadata ( )
2021-12-09 20:25:54 +03:00
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , err )
2021-12-09 20:25:54 +03:00
}
switch opts . diffType {
case DifftypeJSON :
2022-01-12 16:24:23 +03:00
newJson , err := convertYamlToJsonWithIndent ( newYaml )
2021-12-09 20:25:54 +03:00
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , fmt . Errorf ( "cannot unmarshal local metadata to json: %w" , err ) )
2021-12-09 20:25:54 +03:00
}
2022-01-12 16:24:23 +03:00
oldJson , err := convertYamlToJsonWithIndent ( oldYaml )
2021-12-09 20:25:54 +03:00
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , 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
2022-11-14 10:28:23 +03:00
if err := printMyersDiff ( string ( newJson ) , string ( oldJson ) , opts . toFriendlyName , opts . fromFriendlyName , opts . writer , opts . disableColor ) ; err != nil {
return errors . E ( op , err )
}
return nil
2021-12-09 20:25:54 +03:00
case DifftypeYAML :
2022-11-14 10:28:23 +03:00
if err := printMyersDiff ( string ( newYaml ) , string ( oldYaml ) , opts . toFriendlyName , opts . fromFriendlyName , opts . writer , opts . disableColor ) ; err != nil {
return errors . E ( op , err )
}
return nil
2022-01-12 16:24:23 +03:00
case DifftypeUnifiedYAML :
printUnifiedDiff ( string ( newYaml ) , string ( oldYaml ) , opts . writer )
2021-12-09 20:25:54 +03:00
case DifftypeUnifiedJSON :
2022-01-12 16:24:23 +03:00
newJson , err := convertYamlToJsonWithIndent ( newYaml )
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , fmt . Errorf ( "cannot unmarshal local metadata to json: %w" , err ) )
2022-01-12 16:24:23 +03:00
}
oldJson , err := convertYamlToJsonWithIndent ( oldYaml )
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , fmt . Errorf ( "cannot unmarshal server metadata to json: %w" , err ) )
2022-01-12 16:24:23 +03:00
}
printUnifiedDiff ( string ( newJson ) , string ( oldJson ) , 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 {
2022-11-14 10:28:23 +03:00
var op errors . Op = "commands.printMyersDiff"
2021-12-09 20:25:54 +03:00
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 {
2022-11-14 10:28:23 +03:00
return errors . E ( op , err )
2021-12-09 20:25:54 +03:00
}
if count == 0 {
fmt . Fprintln ( writer , zeroDifferencesFound )
}
2021-06-17 13:15:22 +03:00
return nil
}
2022-01-12 16:24:23 +03:00
func printUnifiedDiff ( 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 {
2022-11-14 10:28:23 +03:00
var op errors . Op = "commands.checkDir"
2020-02-24 19:14:46 +03:00
file , err := os . Stat ( path )
if err != nil {
2022-11-14 10:28:23 +03:00
return errors . E ( op , err )
2020-02-24 19:14:46 +03:00
}
if ! file . IsDir ( ) {
2022-11-14 10:28:23 +03:00
return errors . E ( op , fmt . Errorf ( "metadata diff only works with folder but got file %s" , path ) )
2020-02-24 19:14:46 +03:00
}
return nil
}
2022-01-12 16:24:23 +03:00
func convertYamlToJsonWithIndent ( yamlByt [ ] byte ) ( [ ] byte , error ) {
2022-11-14 10:28:23 +03:00
var op errors . Op = "commands.convertYamlToJsonWithIndent"
2022-06-15 15:38:39 +03:00
jsonByt , err := metadatautil . YAMLToJSON ( yamlByt )
2022-01-12 16:24:23 +03:00
if err != nil {
2022-11-14 10:28:23 +03:00
return nil , errors . E ( op , fmt . Errorf ( "cannot convert yaml to json: %w" , err ) )
2022-01-12 16:24:23 +03:00
}
var jsonBuf bytes . Buffer
err = json . Indent ( & jsonBuf , jsonByt , "" , " " )
if err != nil {
2022-11-14 10:28:23 +03:00
return nil , errors . E ( op , fmt . Errorf ( "cannot indent json: %w" , err ) )
2022-01-12 16:24:23 +03:00
}
return jsonBuf . Bytes ( ) , nil
}