Skip to content

Commit

Permalink
Samples integration (#92)
Browse files Browse the repository at this point in the history
* Clone recipes into a local cache folder

* Fix broken test

* Copy the cached repo to a new folder

* Copy specific integration and langauge for recipes

* User can select all integrations

* Start cleaning up

* Let users select their integration and copy all the files over

* Re-order some functions

* Add comments

* Add os test file

* Add comment for exported functions

* Add afero to os operations

* Add tests for os and recipes, move git functions to separate package

* Have a boolean for checking an integration

* Remove some unused go packages

* Fix some broken tests

* Rename all things recipes => samples (#96)

* Setup samples commands (#118)

* Clone recipes into a local cache folder

* Fix broken test

* Copy the cached repo to a new folder

* Copy specific integration and langauge for recipes

* User can select all integrations

* Start cleaning up

* Let users select their integration and copy all the files over

* Re-order some functions

* Add comments

* Add os test file

* Add comment for exported functions

* Add afero to os operations

* Add tests for os and recipes, move git functions to separate package

* Have a boolean for checking an integration

* Remove some unused go packages

* Fix some broken tests

* Rename all things recipes => samples (#96)

* Add samples create and list subcommands

* Update comment for samples list

* Fix linter errors

* Actually fix linter errors (needed newlines between imports)

* Run go mod tidy

* Add missing newlines for sample data

* Change to existing pattern of returning the cmd struct

* Add comments to exported structs

* Configure .env for samples (#117)

* Clone recipes into a local cache folder

* Fix broken test

* Copy the cached repo to a new folder

* Copy specific integration and langauge for recipes

* User can select all integrations

* Start cleaning up

* Let users select their integration and copy all the files over

* Re-order some functions

* Add comments

* Add os test file

* Add comment for exported functions

* Add afero to os operations

* Add tests for os and recipes, move git functions to separate package

* Have a boolean for checking an integration

* Remove some unused go packages

* Fix some broken tests

* Rename all things recipes => samples (#96)

* Add samples create and list subcommands

* Move .env file to the correct location

* Write .env file with the api key and signing secret

* Update comment for samples list

* Update static dir path for the .env

* Modify the server files to point to the correct dotenv location (#132)

* Set publishable key when creating a sample (#135)
  • Loading branch information
tomer-stripe committed Sep 3, 2019
1 parent e3eb35d commit 31fc5e1
Show file tree
Hide file tree
Showing 15 changed files with 1,406 additions and 16 deletions.
11 changes: 9 additions & 2 deletions go.mod
Expand Up @@ -6,22 +6,27 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/briandowns/spinner v1.6.1
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/gorilla/websocket v1.4.0
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365
github.com/joho/godotenv v1.3.0
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
github.com/magiconair/properties v1.8.1 // indirect
github.com/manifoldco/promptui v0.3.2
github.com/mattn/go-isatty v0.0.9 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
github.com/otiai10/copy v1.0.1
github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/russross/blackfriday v1.5.2
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.4.0
Expand All @@ -32,5 +37,7 @@ require (
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4 // indirect
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect
gopkg.in/src-d/go-git.v4 v4.13.1
)
118 changes: 118 additions & 0 deletions go.sum

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pkg/cmd/root.go
Expand Up @@ -76,13 +76,15 @@ func init() {
viper.SetEnvPrefix("stripe")
viper.AutomaticEnv() // read in environment variables that match

rootCmd.AddCommand(newSamplesCmd().cmd)
rootCmd.AddCommand(newCompletionCmd().cmd)
rootCmd.AddCommand(newConfigCmd().cmd)
rootCmd.AddCommand(newLoginCmd().cmd)
rootCmd.AddCommand(newDeleteCmd().reqs.Cmd)
rootCmd.AddCommand(newFeedbackdCmd().cmd)
rootCmd.AddCommand(newGetCmd().reqs.Cmd)
rootCmd.AddCommand(newListenCmd().cmd)
rootCmd.AddCommand(newLoginCmd().cmd)
rootCmd.AddCommand(newPostCmd().reqs.Cmd)
rootCmd.AddCommand(newStatusCmd().cmd)
rootCmd.AddCommand(newTriggerCmd().cmd)
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/root_test.go
Expand Up @@ -25,7 +25,7 @@ func executeCommandC(root *cobra.Command, args ...string) (c *cobra.Command, out
}

func TestGetPathNoXDG(t *testing.T) {
actual := Config.GetProfilesFolder("")
actual := Config.GetConfigFolder("")
expected, err := homedir.Dir()
expected += "/.config/stripe"

Expand All @@ -34,7 +34,7 @@ func TestGetPathNoXDG(t *testing.T) {
}

func TestGetPathXDG(t *testing.T) {
actual := Config.GetProfilesFolder("/some/xdg/path")
actual := Config.GetConfigFolder("/some/xdg/path")
expected := "/some/xdg/path/stripe"

require.Equal(t, actual, expected)
Expand Down
27 changes: 27 additions & 0 deletions pkg/cmd/samples.go
@@ -0,0 +1,27 @@
package cmd

import (
"github.com/spf13/cobra"

"github.com/stripe/stripe-cli/pkg/cmd/samples"
)

type samplesCmd struct {
cmd *cobra.Command
}

func newSamplesCmd() *samplesCmd {
samplesCmd := &samplesCmd{
cmd: &cobra.Command{
// TODO: fixtures subcommand
Use: "samples",
Short: `Sample integrations built by Stripe`,
Long: ``,
},
}

samplesCmd.cmd.AddCommand(samples.NewCreateCmd(&Config).Cmd)
samplesCmd.cmd.AddCommand(samples.NewListCmd().Cmd)

return samplesCmd
}
110 changes: 110 additions & 0 deletions pkg/cmd/samples/create.go
@@ -0,0 +1,110 @@
package samples

import (
"fmt"
"os"

"github.com/spf13/afero"
"github.com/spf13/cobra"

"github.com/stripe/stripe-cli/pkg/ansi"
"github.com/stripe/stripe-cli/pkg/config"
gitpkg "github.com/stripe/stripe-cli/pkg/git"
"github.com/stripe/stripe-cli/pkg/samples"
"github.com/stripe/stripe-cli/pkg/validators"

"gopkg.in/src-d/go-git.v4"
)

// CreateCmd wraps the `create` command for samples which generates a new
// project
type CreateCmd struct {
cfg *config.Config
Cmd *cobra.Command
}

// NewCreateCmd creates and returns a create command for samples
func NewCreateCmd(config *config.Config) *CreateCmd {
createCmd := &CreateCmd{
cfg: config,
}
createCmd.Cmd = &cobra.Command{
Use: "create",
Args: validators.ExactArgs(1),
ValidArgs: samples.Names(),
Short: "create a Stripe sample",
RunE: createCmd.runCreateCmd,
}

return createCmd
}

func (cc *CreateCmd) runCreateCmd(cmd *cobra.Command, args []string) error {
sample := samples.Samples{
Config: cc.cfg,
Fs: afero.NewOsFs(),
Git: gitpkg.Operations{},
}
selectedSample := args[0]

spinner := ansi.StartSpinner(fmt.Sprintf("Downloading %s", selectedSample), os.Stdout)
// Initialize the selected sample in the local cache directory.
// This will either clone or update the specified sample,
// depending on whether or not it's. Additionally, this
// identifies if the sample has multiple integrations and what
// languages it supports.
err := sample.Initialize(selectedSample)
if err != nil {
switch e := err.Error(); e {
case git.NoErrAlreadyUpToDate.Error():
// Repo is already up to date. This isn't a program
// error to continue as normal
break
case git.ErrRepositoryAlreadyExists.Error():
// If the repository already exists and we don't pull
// for some reason, that's fine as we can use the existing
// repository
break
default:
ansi.StopSpinner(spinner, "An error occurred.", os.Stdout)
return err
}
}
ansi.StopSpinner(spinner, "Finished downloading.", os.Stdout)

// Once we've initialized the sample in the local cache
// directory, the user needs to select which integration they
// want to work with (if selectedSamplelicable) and which language they
// want to copy
err = sample.SelectOptions()
if err != nil {
return err
}

// Create the target folder to copy the sample in to. We do
/// this here in case any of the steps above fail, minimizing
// the change that we create a dangling empty folder
targetPath, err := sample.MakeFolder(selectedSample)
if err != nil {
return err
}

// Perform the copy of the sample given the selected options
// from the selections above
err = sample.Copy(targetPath)
if err != nil {
return err
}

err = sample.ConfigureDotEnv(targetPath)
if err != nil {
return err
}

err = sample.PointToDotEnv(targetPath)
if err != nil {
return err
}

return nil
}
42 changes: 42 additions & 0 deletions pkg/cmd/samples/list.go
@@ -0,0 +1,42 @@
package samples

import (
"fmt"

"github.com/spf13/cobra"

"github.com/stripe/stripe-cli/pkg/samples"
"github.com/stripe/stripe-cli/pkg/validators"
)

// ListCmd prints a list of all the available sample projects that users can
// generate
type ListCmd struct {
Cmd *cobra.Command
}

// NewListCmd creates and returns a list command for samples
func NewListCmd() *ListCmd {
listCmd := &ListCmd{}
listCmd.Cmd = &cobra.Command{
Use: "list",
Args: validators.NoArgs,
Short: "list available Stripe samples",
Long: `A list of available Stripe Sample integrations`,
Run: listCmd.runListCmd,
}

return listCmd
}

func (lc *ListCmd) runListCmd(cmd *cobra.Command, args []string) {
fmt.Println("A list of available Stripe Sample integrations:")
fmt.Println()

for _, sample := range samples.List {
fmt.Println(sample.BoldName())
fmt.Println(sample.Description)
fmt.Println(fmt.Sprintf("Repo: %s", sample.URL))
fmt.Println()
}
}
23 changes: 11 additions & 12 deletions pkg/config/config.go
Expand Up @@ -37,27 +37,27 @@ type Config struct {
ProfilesFile string
}

// GetProfilesFolder retrieves the folder where the profiles file is stored
// GetConfigFolder retrieves the folder where the profiles file is stored
// It searches for the xdg environment path first and will secondarily
// place it in the home directory
func (c *Config) GetProfilesFolder(xdgPath string) string {
profilesPath := xdgPath
func (c *Config) GetConfigFolder(xdgPath string) string {
configPath := xdgPath

log.WithFields(log.Fields{
"prefix": "config.Config.GetProfilesFolder",
"path": profilesPath,
"path": configPath,
}).Debug("Using profiles file")

if profilesPath == "" {
if configPath == "" {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
profilesPath = filepath.Join(home, ".config")
configPath = filepath.Join(home, ".config")
}

return filepath.Join(profilesPath, "stripe")
return filepath.Join(configPath, "stripe")
}

// InitConfig reads in profiles file and ENV variables if set.
Expand All @@ -70,12 +70,11 @@ func (c *Config) InitConfig() {
if c.ProfilesFile != "" {
viper.SetConfigFile(c.ProfilesFile)
} else {
profilesFolder := c.GetProfilesFolder(os.Getenv("XDG_CONFIG_HOME"))
profilesFile := filepath.Join(profilesFolder, "config.toml")
c.ProfilesFile = profilesFile

configFolder := c.GetConfigFolder(os.Getenv("XDG_CONFIG_HOME"))
configFile := filepath.Join(configFolder, "config.toml")
c.ProfilesFile = configFile
viper.SetConfigType("toml")
viper.SetConfigFile(profilesFile)
viper.SetConfigFile(configFile)
}

// If a profiles file is found, read it in.
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/profile.go
Expand Up @@ -99,6 +99,15 @@ func (p *Profile) GetAPIKey(livemode bool) (string, error) {
return "", errors.New("your API key has not been configured. Use `stripe login` to set your API key")
}

// GetPublishableKey returns the publishable key for the user
func (p *Profile) GetPublishableKey() string {
if err := viper.ReadInConfig(); err == nil {
return viper.GetString(p.GetConfigField("publishable_key"))
}

return ""
}

// GetConfigField returns the configuration field for the specific profile
func (p *Profile) GetConfigField(field string) string {
return p.ProfileName + "." + field
Expand Down
50 changes: 50 additions & 0 deletions pkg/git/git.go
@@ -0,0 +1,50 @@
package git

import "gopkg.in/src-d/go-git.v4"

// Operations contains the behaviors of the internal git package
type Operations struct{}

// Interface defines the behaviors of the internal git package
type Interface interface {
Clone(string, string) error
Pull(string) error
}

// Clone clones a repo locally, returns an error if it fails
func (g Operations) Clone(appCachePath, app string) error {
_, err := git.PlainClone(appCachePath, false, &git.CloneOptions{
URL: app,
})
if err != nil {
return err
}

return nil
}

// Pull will update the changes for the provided repo or fails
func (g Operations) Pull(appCachePath string) error {
repo, err := git.PlainOpen(appCachePath)
if err != nil {
return err
}

repo.Fetch(&git.FetchOptions{
Force: true,
})

worktree, err := repo.Worktree()
if err != nil {
return err
}

err = worktree.Reset(&git.ResetOptions{
Mode: git.HardReset,
})
if err != nil {
return nil
}

return nil
}

0 comments on commit 31fc5e1

Please sign in to comment.