-
Notifications
You must be signed in to change notification settings - Fork 15
tt: added tt create module #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/apex/log" | ||
"github.com/spf13/cobra" | ||
"github.com/tarantool/tt/cli/cmdcontext" | ||
"github.com/tarantool/tt/cli/configure" | ||
"github.com/tarantool/tt/cli/create" | ||
"github.com/tarantool/tt/cli/modules" | ||
) | ||
|
||
var ( | ||
appName string | ||
dstPath string | ||
forceMode bool | ||
nonInteractiveMode bool | ||
varsFromCli *[]string | ||
varsFile string | ||
) | ||
|
||
// NewCreateCmd creates an application from a template. | ||
func NewCreateCmd() *cobra.Command { | ||
var createCmd = &cobra.Command{ | ||
Use: "create [TEMPLATE] [flags]", | ||
Short: "Create an application from a template", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
err := modules.RunCmd(&cmdCtx, cmd.Name(), &modulesInfo, internalCreateModule, args) | ||
if err != nil { | ||
log.Fatalf(err.Error()) | ||
} | ||
}, | ||
Example: ` | ||
# Create an application app1 from a template. | ||
|
||
$ tt create <template name> --name app1 | ||
|
||
# Create cartridge_app application in /opt/tt/apps/, set user_name value, | ||
# force replacing of application directory (cartridge_app) if it exists. ` + | ||
`User interaction is disabled. | ||
|
||
$ tt create <template name> --name cartridge_app --var user_name=admin -f ` + | ||
`--non-interactive -dst /opt/tt/apps/`, | ||
} | ||
|
||
createCmd.Flags().StringVarP(&appName, "name", "n", "", "Application name") | ||
createCmd.MarkFlagRequired("name") | ||
createCmd.Flags().BoolVarP(&forceMode, "force", "f", false, | ||
`Force rewrite application directory if already exists`) | ||
createCmd.Flags().BoolVarP(&nonInteractiveMode, "non-interactive", "s", false, | ||
`Non-interactive mode`) | ||
|
||
varsFromCli = createCmd.Flags().StringArray("var", []string{}, | ||
"Variable definition. Usage: --var var_name=value") | ||
createCmd.Flags().StringVarP(&varsFile, "vars-file", "", "", "Variables definition file path") | ||
createCmd.Flags().StringVarP(&dstPath, "dst", "d", "", | ||
"Path to the directory where an application will be created.") | ||
|
||
return createCmd | ||
} | ||
|
||
// internalCreateModule is a default create module. | ||
func internalCreateModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { | ||
cliOpts, err := configure.GetCliOpts(cmdCtx.Cli.ConfigPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cmdCtx.Create.AppName = appName | ||
cmdCtx.Create.ForceMode = forceMode | ||
cmdCtx.Create.SilentMode = nonInteractiveMode | ||
cmdCtx.Create.VarsFromCli = *varsFromCli | ||
cmdCtx.Create.VarsFile = varsFile | ||
cmdCtx.Create.DestinationDir = dstPath | ||
|
||
if err = create.FillCtx(cliOpts, cmdCtx, args); err != nil { | ||
return err | ||
} | ||
|
||
return create.Run(&cmdCtx.Create) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,8 @@ type CmdCtx struct { | |
Connect ConnectCtx | ||
// CommandName contains name of the command. | ||
CommandName string | ||
// Create contains information to create an application. | ||
Create CreateCtx | ||
} | ||
|
||
// CliCtx - CLI context. Contains flags passed when starting | ||
|
@@ -29,6 +31,9 @@ type CliCtx struct { | |
TarantoolExecutable string | ||
// Tarantool version. | ||
TarantoolVersion string | ||
// WorkDir is stores original tt working directory, before making chdir | ||
// to local launch directory. | ||
WorkDir string | ||
} | ||
|
||
// RunningCtx contain information for running an application instance. | ||
|
@@ -83,3 +88,28 @@ type ConnectCtx struct { | |
// SrcFile describes the source of code for the evaluation. | ||
SrcFile string | ||
} | ||
|
||
// CreateCtx contains information for creating applications from templates. | ||
type CreateCtx struct { | ||
// AppName is application name to create. | ||
AppName string | ||
// WorkDir is tt launch working directory. | ||
WorkDir string | ||
// DestinationDir is the path where an application will be created. | ||
DestinationDir string | ||
// TemplateSearchPaths is a set of path to search for a template. | ||
TemplateSearchPaths []string | ||
// TemplateName is a template to use for application creation. | ||
TemplateName string | ||
// VarsFromCli base directory for instances available. | ||
VarsFromCli []string | ||
// ForceMode - if flag is set, remove application existing application directory. | ||
ForceMode bool | ||
// SilentMode if set, disables user interaction. All invalid format errors fail | ||
// app creation. | ||
SilentMode bool | ||
// VarsFile is a file with variables definitions. | ||
VarsFile string | ||
// ConfigLocation stores config file location. | ||
ConfigLocation string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This parameter looks like a common one for all commands, not unique to create. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These commands have access to ConfigPath common data. And they can make filepath.Dir if config directory is required. Do we really need to store file path and its directory next to each other? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package create | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/tarantool/tt/cli/cmdcontext" | ||
"github.com/tarantool/tt/cli/config" | ||
"github.com/tarantool/tt/cli/create/internal/steps" | ||
"github.com/tarantool/tt/cli/util" | ||
"github.com/tarantool/tt/cli/version" | ||
) | ||
|
||
// FillCtx fills create context. | ||
func FillCtx(cliOpts *config.CliOpts, cmdCtx *cmdcontext.CmdCtx, args []string) error { | ||
for _, p := range cliOpts.Templates { | ||
cmdCtx.Create.TemplateSearchPaths = append(cmdCtx.Create.TemplateSearchPaths, p.Path) | ||
} | ||
|
||
if len(args) >= 1 { | ||
cmdCtx.Create.TemplateName = args[0] | ||
} else { | ||
return fmt.Errorf("Missing template name argument. " + | ||
"Try `tt create --help` for more information.") | ||
} | ||
|
||
cmdCtx.Create.WorkDir = cmdCtx.Cli.WorkDir | ||
cmdCtx.Create.ConfigLocation = filepath.Dir(cmdCtx.Cli.ConfigPath) | ||
|
||
return nil | ||
} | ||
|
||
// RollbackOnErr removes temporary application directory. | ||
func rollbackOnErr(templateCtx *steps.TemplateCtx) { | ||
if templateCtx.AppPath != "" { | ||
os.RemoveAll(templateCtx.AppPath) | ||
} | ||
templateCtx.AppPath = "" | ||
} | ||
|
||
// Run creates an application from a template. | ||
func Run(createCtx *cmdcontext.CreateCtx) error { | ||
util.CheckRecommendedBinaries("git") | ||
|
||
if err := checkCtx(createCtx); err != nil { | ||
return util.InternalError("Create context check failed: %s", version.GetVersion, err) | ||
} | ||
|
||
stepsChain := []steps.Step{ | ||
steps.FillTemplateVarsFromCli{}, | ||
steps.LoadVarsFile{}, | ||
steps.CreateAppDirectory{}, | ||
steps.CopyAppTemplate{}, | ||
steps.LoadManifest{}, | ||
steps.CollectTemplateVarsFromUser{Reader: steps.NewConsoleReader()}, | ||
steps.RunHook{HookType: "pre"}, | ||
steps.RenderTemplate{}, | ||
steps.RunHook{HookType: "post"}, | ||
steps.Cleanup{}, | ||
steps.CreateDockerfile{}, | ||
} | ||
|
||
templateCtx := steps.NewTemplateContext() | ||
for _, step := range stepsChain { | ||
if err := step.Run(createCtx, &templateCtx); err != nil { | ||
rollbackOnErr(&templateCtx) | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// checkCtx checks create context for validity. | ||
func checkCtx(ctx *cmdcontext.CreateCtx) error { | ||
if ctx.TemplateName == "" { | ||
return fmt.Errorf("Template name is missing") | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package app_template | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
"github.com/tarantool/tt/cli/util" | ||
) | ||
|
||
const ( | ||
DefaultManifestName = "MANIFEST.yaml" | ||
) | ||
|
||
// UserPrompt describes interactive prompt to get the value of variable from a user. | ||
type UserPrompt struct { | ||
psergee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Prompt is an input prompt for the variable. | ||
Prompt string | ||
// Name is a variable name to store a value to. | ||
Name string | ||
// Default is a default value. | ||
Default string | ||
// Re is a regular expression for the value validation. | ||
Re string | ||
} | ||
|
||
// TemplateManifest is a manifest for application template. | ||
type TemplateManifest struct { | ||
psergee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Description is a template description. | ||
Description string | ||
// Vars is a set of variables, which values are to be | ||
// requested from a user. | ||
Vars []UserPrompt | ||
// PreHook is a path to the executable to run before template instantiation. | ||
// Application path is passed as a first parameter. | ||
PreHook string `mapstructure:"pre-hook"` | ||
// PostHook is a path to the executable to run after template instantiation. | ||
// Application path is passed as a first parameter. | ||
PostHook string `mapstructure:"post-hook"` | ||
// Include contains a list of files to keep after template instantiaion. | ||
Include []string | ||
} | ||
|
||
func validateManifest(manifest *TemplateManifest) error { | ||
for _, varInfo := range manifest.Vars { | ||
if varInfo.Prompt == "" { | ||
return fmt.Errorf("Missing user prompt.") | ||
} | ||
if varInfo.Name == "" { | ||
return fmt.Errorf("Missing variable name.") | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// LoadManifest loads template manifest from manifestPath. | ||
func LoadManifest(manifestPath string) (TemplateManifest, error) { | ||
var templateManifest TemplateManifest | ||
if _, err := os.Stat(manifestPath); err != nil { | ||
return templateManifest, fmt.Errorf("Failed to get access to manifest file: %s", err) | ||
} | ||
|
||
rawConfigOpts, err := util.ParseYAML(manifestPath) | ||
if err != nil { | ||
return templateManifest, err | ||
} | ||
|
||
if err := mapstructure.Decode(rawConfigOpts, &templateManifest); err != nil { | ||
return templateManifest, fmt.Errorf("Failed to decode template manifest: %s", err) | ||
} | ||
|
||
if err := validateManifest(&templateManifest); err != nil { | ||
return templateManifest, fmt.Errorf("Invalid manifest format: %s", err) | ||
} | ||
|
||
return templateManifest, nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.