diff --git a/actions/component_actions.go b/actions/component_actions.go index a76dfa7..36473f6 100644 --- a/actions/component_actions.go +++ b/actions/component_actions.go @@ -1,35 +1,48 @@ package actions import ( + "errors" "fmt" "github.com/madridianfox/elc/core" "path" ) +func resolveCompNames(ws *core.Workspace, options *core.GlobalOptions, namesFromArgs []string) ([]string, error) { + var compNames []string + + if options.Tag != "" { + compNames = ws.FindComponentNamesByTag(options.Tag) + if len(compNames) == 0 { + return nil, errors.New(fmt.Sprintf("components with tag %s not found", options.Tag)) + } + } else if options.ComponentName != "" { + compNames = []string{options.ComponentName} + } else if len(namesFromArgs) > 0 { + compNames = namesFromArgs + } else { + currentCompName, err := ws.ComponentNameByPath() + if err != nil { + return nil, err + } + compNames = []string{currentCompName} + } + + return compNames, nil +} + func StartServiceAction(options *core.GlobalOptions, svcNames []string) error { ws, err := core.GetWorkspaceConfig() if err != nil { return err } - if options.ComponentName != "" { - svcNames = append(svcNames, options.ComponentName) + compNames, err := resolveCompNames(ws, options, svcNames) + if err != nil { + return err } - if len(svcNames) > 0 { - for _, svcName := range svcNames { - comp, err := ws.ComponentByName(svcName) - if err != nil { - return err - } - - err = comp.Start(options) - if err != nil { - return err - } - } - } else { - comp, err := ws.ComponentByPath() + for _, compName := range compNames { + comp, err := ws.ComponentByName(compName) if err != nil { return err } @@ -49,35 +62,22 @@ func StopServiceAction(stopAll bool, svcNames []string, destroy bool, options *c return err } - if options.ComponentName != "" { - svcNames = append(svcNames, options.ComponentName) - } + var compNames []string if stopAll { - svcNames = ws.GetComponentNames() - } - - if len(svcNames) > 0 { - for _, svcName := range svcNames { - comp, err := ws.ComponentByName(svcName) - if err != nil { - return err - } - if destroy { - err = comp.Destroy(options) - } else { - err = comp.Stop(options) - } - if err != nil { - return err - } - } + compNames = ws.GetComponentNames() } else { - comp, err := ws.ComponentByPath() + compNames, err = resolveCompNames(ws, options, svcNames) if err != nil { return err } + } + for _, compName := range compNames { + comp, err := ws.ComponentByName(compName) + if err != nil { + return err + } if destroy { err = comp.Destroy(options) } else { @@ -97,20 +97,13 @@ func RestartServiceAction(hardRestart bool, svcNames []string, options *core.Glo return err } - if len(svcNames) > 0 { - for _, svcName := range svcNames { - comp, err := ws.ComponentByName(svcName) - if err != nil { - return err - } + compNames, err := resolveCompNames(ws, options, svcNames) + if err != nil { + return err + } - err = comp.Restart(hardRestart, options) - if err != nil { - return err - } - } - } else { - comp, err := ws.ComponentByPath() + for _, compName := range compNames { + comp, err := ws.ComponentByName(compName) if err != nil { return err } @@ -124,24 +117,24 @@ func RestartServiceAction(hardRestart bool, svcNames []string, options *core.Glo return nil } -func PrintVarsAction(svcNames []string) error { +func PrintVarsAction(options *core.GlobalOptions, svcNames []string) error { ws, err := core.GetWorkspaceConfig() if err != nil { return err } - var comp *core.Component + compNames, err := resolveCompNames(ws, options, svcNames) + if err != nil { + return err + } - if len(svcNames) > 0 { - comp, err = ws.ComponentByName(svcNames[0]) - if err != nil { - return err - } - } else { - comp, err = ws.ComponentByPath() - if err != nil { - return err - } + if len(compNames) > 1 { + return errors.New("too many components for show") + } + + comp, err := ws.ComponentByName(compNames[0]) + if err != nil { + return err } err = comp.DumpVars() @@ -152,27 +145,29 @@ func PrintVarsAction(svcNames []string) error { return nil } -func ComposeCommandAction(args []string, composeParams core.GlobalOptions) error { +func ComposeCommandAction(options *core.GlobalOptions, args []string) error { ws, err := core.GetWorkspaceConfig() if err != nil { return err } - composeParams.Cmd = args + compNames, err := resolveCompNames(ws, options, []string{}) + if err != nil { + return err + } - if composeParams.ComponentName == "" { - composeParams.ComponentName, err = ws.ComponentNameByPath() - if err != nil { - return err - } + if len(compNames) > 1 { + return errors.New("too many components") } - comp, err := ws.ComponentByName(composeParams.ComponentName) + comp, err := ws.ComponentByName(compNames[0]) if err != nil { return err } - _, err = comp.Compose(&composeParams) + options.Cmd = args + + _, err = comp.Compose(options) if err != nil { return err } @@ -180,37 +175,40 @@ func ComposeCommandAction(args []string, composeParams core.GlobalOptions) error return nil } -func WrapCommandAction(globalOptions core.GlobalOptions, command []string) error { +func WrapCommandAction(options *core.GlobalOptions, command []string) error { ws, err := core.GetWorkspaceConfig() if err != nil { return err } - var comp *core.Component - - svcName := globalOptions.ComponentName + compNames, err := resolveCompNames(ws, options, []string{}) + if err != nil { + return err + } - if svcName == "" { - comp, err = ws.ComponentByPath() - } else { - comp, err = ws.ComponentByName(svcName) + if len(compNames) > 1 { + return errors.New("too many components") } + + comp, err := ws.ComponentByName(compNames[0]) if err != nil { return err } + var hostName string + if comp.Config.HostedIn != "" { - svcName = comp.Config.HostedIn + hostName = comp.Config.HostedIn } else { - svcName = comp.Name + hostName = comp.Name } - hostComp, err := ws.ComponentByName(svcName) + hostComp, err := ws.ComponentByName(hostName) if err != nil { return err } - _, err = hostComp.Wrap(command) + _, err = hostComp.Wrap(command, options) if err != nil { return err } @@ -218,27 +216,37 @@ func WrapCommandAction(globalOptions core.GlobalOptions, command []string) error return nil } -func ExecAction(options core.GlobalOptions) error { +func ExecAction(options *core.GlobalOptions) error { ws, err := core.GetWorkspaceConfig() if err != nil { return err } - var comp *core.Component + compNames, err := resolveCompNames(ws, options, []string{}) + if err != nil { + return err + } - if options.ComponentName == "" { - comp, err = ws.ComponentByPath() - } else { - comp, err = ws.ComponentByName(options.ComponentName) + if len(compNames) > 1 { + return errors.New("too many components") } + + comp, err := ws.ComponentByName(compNames[0]) if err != nil { return err } + var hostName string + if comp.Config.HostedIn != "" { - options.ComponentName = comp.Config.HostedIn + hostName = comp.Config.HostedIn } else { - options.ComponentName = comp.Name + hostName = comp.Name + } + + hostComp, err := ws.ComponentByName(hostName) + if err != nil { + return err } if comp.Config.ExecPath != "" { @@ -248,12 +256,7 @@ func ExecAction(options core.GlobalOptions) error { } } - hostComp, err := ws.ComponentByName(options.ComponentName) - if err != nil { - return err - } - - _, err = hostComp.Exec(&options) + _, err = hostComp.Exec(options) if err != nil { return err } @@ -287,3 +290,29 @@ func SetGitHooksAction(scriptsFolder string, elcBinary string) error { return nil } + +func CloneComponentAction(options *core.GlobalOptions, svcNames []string) error { + ws, err := core.GetWorkspaceConfig() + if err != nil { + return err + } + + compNames, err := resolveCompNames(ws, options, svcNames) + if err != nil { + return err + } + + for _, compName := range compNames { + comp, err := ws.ComponentByName(compName) + if err != nil { + return err + } + + err = comp.Clone(options) + if err != nil { + return err + } + } + + return nil +} diff --git a/actions/component_actions_test.go b/actions/component_actions_test.go index fa9984c..fd0698d 100644 --- a/actions/component_actions_test.go +++ b/actions/component_actions_test.go @@ -305,7 +305,7 @@ func TestServiceCompose(t *testing.T) { ExecInteractive([]string{"docker", "compose", "-f", path.Join(fakeWorkspacePath, "apps/test/docker-compose.yml"), "some", "command"}, gomock.Any()). Return(0, nil) - _ = ComposeCommandAction([]string{"some", "command"}, core.GlobalOptions{}) + _ = ComposeCommandAction(&core.GlobalOptions{}, []string{"some", "command"}) } func TestServiceComposeByName(t *testing.T) { @@ -317,9 +317,9 @@ func TestServiceComposeByName(t *testing.T) { ExecInteractive([]string{"docker", "compose", "-f", path.Join(fakeWorkspacePath, "apps/dep1/docker-compose.yml"), "some", "command"}, gomock.Any()). Return(0, nil) - _ = ComposeCommandAction([]string{"some", "command"}, core.GlobalOptions{ + _ = ComposeCommandAction(&core.GlobalOptions{ ComponentName: "dep1", - }) + }, []string{"some", "command"}) } func TestServiceExec(t *testing.T) { @@ -335,7 +335,7 @@ func TestServiceExec(t *testing.T) { ExecInteractive([]string{"docker", "compose", "-f", path.Join(fakeWorkspacePath, "apps/test/docker-compose.yml"), "exec", "-u", "1000:1000", "app", "some", "command"}, gomock.Any()). Return(0, nil) - _ = ExecAction(core.GlobalOptions{ + _ = ExecAction(&core.GlobalOptions{ Cmd: []string{"some", "command"}, UID: -1, }) @@ -354,7 +354,7 @@ func TestServiceExecWithoutTty(t *testing.T) { ExecInteractive([]string{"docker", "compose", "-f", path.Join(fakeWorkspacePath, "apps/test/docker-compose.yml"), "exec", "-u", "1000:1000", "-T", "app", "some", "command"}, gomock.Any()). Return(0, nil) - _ = ExecAction(core.GlobalOptions{ + _ = ExecAction(&core.GlobalOptions{ Cmd: []string{"some", "command"}, UID: -1, }) @@ -373,7 +373,7 @@ func TestServiceExecWithUid(t *testing.T) { ExecInteractive([]string{"docker", "compose", "-f", path.Join(fakeWorkspacePath, "apps/test/docker-compose.yml"), "exec", "-u", "1001", "app", "some", "command"}, gomock.Any()). Return(0, nil) - _ = ExecAction(core.GlobalOptions{ + _ = ExecAction(&core.GlobalOptions{ Cmd: []string{"some", "command"}, UID: 1001, }) @@ -411,8 +411,6 @@ func TestServiceVars(t *testing.T) { mockPc.EXPECT().Println("WORKSPACE_PATH=/tmp/workspaces/project1") mockPc.EXPECT().Println("WORKSPACE_NAME=ensi") - mockPc.EXPECT().Println("USER_ID=1000") - mockPc.EXPECT().Println("GROUP_ID=1000") mockPc.EXPECT().Println("V_GL=vglobal") mockPc.EXPECT().Println("V_GL_SIMPLE_VAR=vglobal-a") @@ -426,7 +424,7 @@ func TestServiceVars(t *testing.T) { mockPc.EXPECT().Println("V_IN_SVC=vinsvc") - _ = PrintVarsAction([]string{}) + _ = PrintVarsAction(&core.GlobalOptions{}, []string{}) } func TestServiceVarsWithTpl(t *testing.T) { @@ -452,5 +450,5 @@ func TestServiceVarsWithTpl(t *testing.T) { mockPc.EXPECT().Println("V_IN_SVC=vinsvc") - _ = PrintVarsAction([]string{"test1"}) + _ = PrintVarsAction(&core.GlobalOptions{}, []string{"test1"}) } diff --git a/cmd/elc.go b/cmd/elc.go index 80cb42a..a009a6a 100644 --- a/cmd/elc.go +++ b/cmd/elc.go @@ -21,11 +21,11 @@ func parseExecFlags(cmd *cobra.Command) { func InitCobra() *cobra.Command { globalOptions = core.GlobalOptions{} var rootCmd = &cobra.Command{ - Use: "elc [command]", - Args: cobra.MinimumNArgs(0), - SilenceUsage: true, - SilenceErrors: true, - Version: core.Version, + Use: "elc [command]", + Args: cobra.MinimumNArgs(0), + SilenceUsage: true, + //SilenceErrors: true, + Version: core.Version, PersistentPreRun: func(cmd *cobra.Command, args []string) { core.Pc = &core.RealPC{} }, @@ -34,7 +34,7 @@ func InitCobra() *cobra.Command { return cmd.Help() } else { globalOptions.Cmd = args - return actions.ExecAction(globalOptions) + return actions.ExecAction(&globalOptions) } }, } @@ -45,6 +45,8 @@ func InitCobra() *cobra.Command { rootCmd.PersistentFlags().StringVarP(&globalOptions.WorkspaceName, "workspace", "w", "", "name of workspace") rootCmd.PersistentFlags().StringVar(&globalOptions.ComponentName, "svc", "", "name of current component (deprecated, alias for component)") rootCmd.PersistentFlags().BoolVar(&globalOptions.Debug, "debug", false, "print debug messages") + rootCmd.PersistentFlags().BoolVar(&globalOptions.DryRun, "dry-run", false, "do not execute real command, only debug") + rootCmd.PersistentFlags().StringVar(&globalOptions.Tag, "tag", "", "select all components with tag") parseStartFlags(rootCmd) parseExecFlags(rootCmd) @@ -61,6 +63,7 @@ func InitCobra() *cobra.Command { NewServiceSetHooksCommand(rootCmd) NewUpdateCommand(rootCmd) NewFixUpdateCommand(rootCmd) + NewServiceCloneCommand(rootCmd) return rootCmd } @@ -215,7 +218,7 @@ func NewServiceVarsCommand(parentCommand *cobra.Command) { Long: "Print all variables computed for service.\nBy default uses service found with current directory, but you can pass name of another service instead.", Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { - return actions.PrintVarsAction(args) + return actions.PrintVarsAction(&globalOptions, args) }, } parentCommand.AddCommand(command) @@ -231,7 +234,7 @@ func NewServiceComposeCommand(parentCommand *cobra.Command) { if len(args) == 0 { return cmd.Help() } else { - return actions.ComposeCommandAction(args, globalOptions) + return actions.ComposeCommandAction(&globalOptions, args) } }, } @@ -246,7 +249,7 @@ func NewServiceWrapCommand(parentCommand *cobra.Command) { Long: "Execute command on host with env variables for service.\nFor module uses variables of linked service.\nBy default uses service/module found with current directory.", Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { - return actions.WrapCommandAction(globalOptions, args) + return actions.WrapCommandAction(&globalOptions, args) }, } parentCommand.AddCommand(command) @@ -263,7 +266,7 @@ func NewServiceExecCommand(parentCommand *cobra.Command) { return cmd.Usage() } else { globalOptions.Cmd = args - return actions.ExecAction(globalOptions) + return actions.ExecAction(&globalOptions) } }, } @@ -312,3 +315,18 @@ func NewFixUpdateCommand(parentCommand *cobra.Command) { } parentCommand.AddCommand(command) } + +func NewServiceCloneCommand(parentCommand *cobra.Command) { + var command = &cobra.Command{ + Use: "clone [NAME]", + Short: "Clone component to its path", + Long: "Clone component to its path.", + SilenceUsage: false, + SilenceErrors: false, + Args: cobra.ArbitraryArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return actions.CloneComponentAction(&globalOptions, args) + }, + } + parentCommand.AddCommand(command) +} diff --git a/core/component.go b/core/component.go index 3973738..7a48b2b 100644 --- a/core/component.go +++ b/core/component.go @@ -97,38 +97,56 @@ func (comp *Component) init() error { func (comp *Component) execComposeToString(composeCommand []string, options *GlobalOptions) (string, error) { composeFile, _ := comp.Context.find("COMPOSE_FILE") command := append([]string{"docker", "compose", "-f", composeFile}, composeCommand...) + if options.Debug { _, _ = Pc.Printf(">> %s\n", strings.Join(command, " ")) } - _, out, err := Pc.ExecToString(command, comp.Context.renderMapToEnv()) - if err != nil { - return "", err + + if !options.DryRun { + _, out, err := Pc.ExecToString(command, comp.Context.renderMapToEnv()) + if err != nil { + return "", err + } + return out, nil } - return out, nil + return "", nil } func (comp *Component) execComposeInteractive(composeCommand []string, options *GlobalOptions) (int, error) { composeFile, _ := comp.Context.find("COMPOSE_FILE") command := append([]string{"docker", "compose", "-f", composeFile}, composeCommand...) + if options.Debug { _, _ = Pc.Printf(">> %s\n", strings.Join(command, " ")) } - code, err := Pc.ExecInteractive(command, comp.Context.renderMapToEnv()) - if err != nil { - return 0, err + + if !options.DryRun { + code, err := Pc.ExecInteractive(command, comp.Context.renderMapToEnv()) + if err != nil { + return 0, err + } + + return code, nil } - return code, nil + return 0, nil } -func (comp *Component) execInteractive(command []string) (int, error) { - code, err := Pc.ExecInteractive(command, comp.Context.renderMapToEnv()) - if err != nil { - return 0, err +func (comp *Component) execInteractive(command []string, options *GlobalOptions) (int, error) { + if options.Debug { + _, _ = Pc.Printf(">> %s\n", strings.Join(command, " ")) } - return code, nil + if !options.DryRun { + code, err := Pc.ExecInteractive(command, comp.Context.renderMapToEnv()) + if err != nil { + return 0, err + } + return code, nil + } + + return 0, nil } func (comp *Component) IsRunning(options *GlobalOptions) (bool, error) { @@ -282,8 +300,8 @@ func (comp *Component) Exec(options *GlobalOptions) (int, error) { return code, nil } -func (comp *Component) Wrap(command []string) (int, error) { - code, err := comp.execInteractive(command) +func (comp *Component) Wrap(command []string, options *GlobalOptions) (int, error) { + code, err := comp.execInteractive(command, options) if err != nil { return 0, err } @@ -298,3 +316,16 @@ func (comp *Component) DumpVars() error { return nil } + +func (comp *Component) Clone(options *GlobalOptions) error { + if comp.Config.Repository == "" { + return errors.New(fmt.Sprintf("repository of component %s is not defined. Check workspace.yaml", comp.Name)) + } + svcPath, found := comp.Context.find("SVC_PATH") + if !found { + return errors.New("path of component is not defined.Check workspace.yaml") + } + _, err := comp.execInteractive([]string{"git", "clone", comp.Config.Repository, svcPath}, options) + + return err +} diff --git a/core/component_config.go b/core/component_config.go index 57bad7a..e2471e0 100644 --- a/core/component_config.go +++ b/core/component_config.go @@ -25,6 +25,8 @@ type ComponentConfig struct { Path string `yaml:"path"` Replace bool `yaml:"replace"` Variables yaml.MapSlice `yaml:"variables"` + Repository string `yaml:"repository"` + Tags []string `yaml:"tags"` } func (cc ComponentConfig) merge(cc2 ComponentConfig) ComponentConfig { @@ -50,7 +52,12 @@ func (cc ComponentConfig) merge(cc2 ComponentConfig) ComponentConfig { if cc2.Alias != "" { cc.Alias = cc2.Alias } + if cc2.Repository != "" { + cc.Repository = cc2.Repository + } + cc.Variables = append(cc.Variables, cc2.Variables...) + cc.Tags = append(cc.Tags, cc2.Tags...) for depSvc, modes := range cc2.Dependencies { if cc.Dependencies[depSvc] == nil { diff --git a/core/core.go b/core/core.go index e53a0a9..9ec7862 100644 --- a/core/core.go +++ b/core/core.go @@ -18,6 +18,8 @@ type GlobalOptions struct { Mode string WorkingDir string UID int + Tag string + DryRun bool } func contains(list []string, item string) bool { diff --git a/core/workspace.go b/core/workspace.go index 152e39b..c19addc 100644 --- a/core/workspace.go +++ b/core/workspace.go @@ -162,3 +162,18 @@ func (ws *Workspace) GetComponentNames() []string { return result } + +func (ws *Workspace) FindComponentNamesByTag(tag string) []string { + result := make([]string, 0) + for name, comp := range ws.Components { + if !comp.Config.IsTemplate { + for _, compTag := range comp.Config.Tags { + if compTag == tag { + result = append(result, name) + } + } + } + } + + return result +} diff --git a/examples/php-shared-fpm/workspace.yaml b/examples/php-shared-fpm/workspace.yaml index 6cd4a50..3c5c2b1 100644 --- a/examples/php-shared-fpm/workspace.yaml +++ b/examples/php-shared-fpm/workspace.yaml @@ -36,6 +36,7 @@ modules: path: ${APP1_PATH} hosted_in: fpm exec_path: ${APP1_MOUNT_PATH} + repository: git@gitlab.com:user/project.git app2: path: ${APP2_PATH} hosted_in: fpm