Skip to content

Commit

Permalink
Merge pull request #61 from snyk/feat/prompt-when-scaffold-yaml-exists
Browse files Browse the repository at this point in the history
Adds prompt to overwrite when rerunning scaffold init
  • Loading branch information
jcsackett authored Nov 12, 2021
2 parents df6018b + 8d20aec commit c7793e7
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 12 deletions.
44 changes: 44 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
package cmd

import (
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

"github.com/manifoldco/promptui"
"github.com/urfave/cli/v2"
)

Expand All @@ -16,6 +20,7 @@ type VervetParams struct {
Stdin io.ReadCloser
Stdout io.WriteCloser
Stderr io.WriteCloser
Prompt VervetPrompt
}

// VervetApp contains the cli Application.
Expand All @@ -24,6 +29,21 @@ type VervetApp struct {
Params VervetParams
}

// VervetPrompt defines the interface for interactive prompts in vervet.
type VervetPrompt interface {
Confirm(label string) (bool, error)
}

type runKey string

var vervetKey = runKey("vervet")

// Run runs the cli.App with the Vervet config params.
func (v *VervetApp) Run(args []string) error {
context := context.WithValue(context.Background(), vervetKey, v)
return v.App.RunContext(context, args)
}

// NewApp returns a new VervetApp with the provided params.
func NewApp(vp VervetParams) *VervetApp {
return &VervetApp{
Expand Down Expand Up @@ -149,11 +169,35 @@ func NewApp(vp VervetParams) *VervetApp {
}
}

// Prompt is the default interactive prompt for vervet.
type Prompt struct{}

// Confirm implements VervetPrompt.Confirm
func (p Prompt) Confirm(label string) (bool, error) {
prompt := promptui.Prompt{
Label: fmt.Sprintf("%v (y/N)?", label),
Default: "N",
Validate: func(input string) error {
input = strings.ToLower(input)
if input != "n" && input != "y" {
return errors.New("you must pick y or n")
}
return nil
},
}
result, err := prompt.Run()
if err != nil {
return false, err
}
return (result == "y"), nil
}

// Vervet is the vervet application with the CLI application.
var Vervet = NewApp(VervetParams{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Prompt: Prompt{},
})

func absPath(path string) (string, error) {
Expand Down
6 changes: 3 additions & 3 deletions cmd/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func TestCompile(t *testing.T) {
c := qt.New(t)
dstDir := c.TempDir()
err := cmd.Vervet.App.Run([]string{"vervet", "compile", testdata.Path("resources"), dstDir})
err := cmd.Vervet.Run([]string{"vervet", "compile", testdata.Path("resources"), dstDir})
c.Assert(err, qt.IsNil)
tests := []struct {
version string
Expand Down Expand Up @@ -49,7 +49,7 @@ func TestCompile(t *testing.T) {
func TestCompileInclude(t *testing.T) {
c := qt.New(t)
dstDir := c.TempDir()
err := cmd.Vervet.App.Run([]string{"vervet", "compile", "-I", testdata.Path("resources/include.yaml"), testdata.Path("resources"), dstDir})
err := cmd.Vervet.Run([]string{"vervet", "compile", "-I", testdata.Path("resources/include.yaml"), testdata.Path("resources"), dstDir})
c.Assert(err, qt.IsNil)

tests := []struct {
Expand Down Expand Up @@ -87,6 +87,6 @@ func TestCompileInclude(t *testing.T) {
func TestCompileConflict(t *testing.T) {
c := qt.New(t)
dstDir := c.TempDir()
err := cmd.Vervet.App.Run([]string{"vervet", "compile", "../testdata/conflict", dstDir})
err := cmd.Vervet.Run([]string{"vervet", "compile", "../testdata/conflict", dstDir})
c.Assert(err, qt.ErrorMatches, `failed to load spec versions: conflict: .*`)
}
24 changes: 23 additions & 1 deletion cmd/scaffold.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -33,7 +34,28 @@ func ScaffoldInit(ctx *cli.Context) error {
return err
}
err = sc.Organize()
if err != nil {
if err == scaffold.ErrAlreadyInitialized {
// If the project files already exist, prompt the user to see if they want to overwrite them.
// TODO: replace using Context.Value directly with a helper func to be used in other commands.
vervetApp, ok := ctx.Context.Value(vervetKey).(*VervetApp)
if !ok {
return errors.New("could not retrieve vervet app from context")
}
prompt := vervetApp.Params.Prompt
overwrite, err := prompt.Confirm("Scaffold already initialized; do you want to overwrite")
if err != nil {
return err
}
if overwrite {
forceFn := scaffold.Force(true)
forceFn(sc)
// If an error happens with --force enabled, something new has gone wrong.
if err = sc.Organize(); err != nil {
return err
}
}
return nil
} else if err != nil {
return err
}
err = sc.Init()
Expand Down
86 changes: 82 additions & 4 deletions cmd/scaffold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd_test
import (
"io/ioutil"
"os"
"strings"
"testing"

qt "github.com/frankban/quicktest"
Expand All @@ -11,15 +12,92 @@ import (
"github.com/snyk/vervet/testdata"
)

var vervetConfigFile = "./.vervet.yaml"

type testPrompt struct {
ReturnVal bool
}

func (tp *testPrompt) Confirm(label string) (bool, error) {
return tp.ReturnVal, nil
}

var filemark = "bad wolf"

// markFile adds a string to a file so we can check if that file is being overwritten.
func markTestFile(filename string) error {
// Write a string to the file; we should see this string removed when
f, err := os.OpenFile(filename, os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()

if err != nil {
return err
}
_, err = f.Write([]byte(filemark))
return err
}

// markInFile checks if the filemark is present, determining if the file has been
// overwritten.
func markInFile(filename string) (bool, error) {
content, err := ioutil.ReadFile(vervetConfigFile)
if err != nil {
return false, err
}
return strings.Contains(string(content), "bad wolf"), nil
}

func TestScaffold(t *testing.T) {
c := qt.New(t)
dstDir := c.TempDir()
cd(c, dstDir)
// Create an API project from a scaffold
err := cmd.Vervet.App.Run([]string{"vervet", "scaffold", "init", testdata.Path("test-scaffold")})

prompt := testPrompt{}
testApp := cmd.NewApp(cmd.VervetParams{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Prompt: &prompt,
})

// Running init creates the project files.
err := testApp.Run([]string{"vervet", "scaffold", "init", testdata.Path("test-scaffold")})
c.Assert(err, qt.IsNil)

// Rerunning init asks the user if they want to overwrite; if they say no
// the command ends...
prompt.ReturnVal = false
err = markTestFile(vervetConfigFile)
c.Assert(err, qt.IsNil)
err = testApp.Run([]string{"vervet", "scaffold", "init", testdata.Path("test-scaffold")})
c.Assert(err, qt.IsNil)
fileMarked, err := markInFile(vervetConfigFile)
c.Assert(err, qt.IsNil)
c.Assert(fileMarked, qt.IsTrue)

// ...if the user selects yes, it will overwrite the project files.
prompt.ReturnVal = true
err = testApp.Run([]string{"vervet", "scaffold", "init", testdata.Path("test-scaffold")})
c.Assert(err, qt.IsNil)
fileMarked, err = markInFile(vervetConfigFile)
c.Assert(err, qt.IsNil)
c.Assert(fileMarked, qt.IsFalse)

// Rerunning init with the force option overwrites the project files.
prompt.ReturnVal = false
err = markTestFile(vervetConfigFile)
c.Assert(err, qt.IsNil)
// Generate a new resource version in the project
err = cmd.Vervet.App.Run([]string{"vervet", "version", "new", "--version", "2021-10-01", "v3", "foo"})
err = testApp.Run([]string{"vervet", "scaffold", "init", "--force", testdata.Path("test-scaffold")})
c.Assert(err, qt.IsNil)
fileMarked, err = markInFile(vervetConfigFile)
c.Assert(err, qt.IsNil)
c.Assert(fileMarked, qt.IsFalse)

// A new resource version can be generated in the project after initialization has completed.
err = testApp.Run([]string{"vervet", "version", "new", "--version", "2021-10-01", "v3", "foo"})
c.Assert(err, qt.IsNil)
for _, item := range []string{".vervet/templates/README.tmpl", ".vervet.yaml", ".vervet/extras/foo", ".vervet/extras/bar/bar"} {
_, err = os.Stat(item)
Expand Down
6 changes: 3 additions & 3 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestVersionFiles(t *testing.T) {
defer output.Close()
c.Patch(&os.Stdout, output)
cd(c, testdata.Path("."))
err = cmd.Vervet.App.Run([]string{"vervet", "version", "files"})
err = cmd.Vervet.Run([]string{"vervet", "version", "files"})
c.Assert(err, qt.IsNil)
})
out, err := ioutil.ReadFile(tmpFile)
Expand All @@ -64,7 +64,7 @@ func TestVersionList(t *testing.T) {
defer output.Close()
c.Patch(&os.Stdout, output)
cd(c, testdata.Path("."))
err = cmd.Vervet.App.Run([]string{"vervet", "version", "list"})
err = cmd.Vervet.Run([]string{"vervet", "version", "list"})
c.Assert(err, qt.IsNil)
})
out, err := ioutil.ReadFile(tmpFile)
Expand Down Expand Up @@ -95,7 +95,7 @@ func TestVersionNew(t *testing.T) {
copyToDir(c, testdata.Path(".vervet/resource/version/index.ts.tmpl"), versionTemplateDir)
copyToDir(c, testdata.Path(".vervet/resource/version/spec.yaml.tmpl"), versionTemplateDir)
cd(c, projectDir)
err := cmd.Vervet.App.Run([]string{"vervet", "version", "new", "testdata", "foo"})
err := cmd.Vervet.Run([]string{"vervet", "version", "new", "testdata", "foo"})
c.Assert(err, qt.IsNil)
versions, err := vervet.LoadResourceVersions(filepath.Join(projectDir, "generated", "foo"))
c.Assert(err, qt.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion cmd/vervet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func main() {
err := cmd.Vervet.App.Run(os.Args)
err := cmd.Vervet.Run(os.Args)
if err != nil {
log.Fatal(err)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.3.0
github.com/mailru/easyjson v0.7.7 // indirect
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/reflectwalk v1.0.2
github.com/olekukonko/tablewriter v0.0.5
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
Expand Down Expand Up @@ -38,6 +44,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
Expand All @@ -63,6 +71,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
16 changes: 16 additions & 0 deletions internal/scaffold/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"github.com/ghodss/yaml"
)

// ErrAlreadyInitialized is used when scaffolding is being run on a project that is already setup.
var ErrAlreadyInitialized = fmt.Errorf("project files already exist")

// Scaffold defines a Vervet API project scaffold.
type Scaffold struct {
dst, src string
Expand Down Expand Up @@ -85,6 +88,19 @@ func New(dst, src string, options ...Option) (*Scaffold, error) {
func (s *Scaffold) Organize() error {
for dstItem, srcItem := range s.manifest.Organize {
dstPath := filepath.Join(s.dst, dstItem)
// If we're not force overwriting, check if files already exist.
if !s.force {
_, err := os.Stat(dstPath)
if err == nil {
// Project files already exist.
return ErrAlreadyInitialized
}
if !os.IsNotExist(err) {
// Something else went wrong; the file not existing is the desired
// state.
return err
}
}
srcPath := filepath.Join(s.src, srcItem)
err := s.copyItem(dstPath, srcPath)
if err != nil {
Expand Down

0 comments on commit c7793e7

Please sign in to comment.