diff --git a/cmd/add.go b/cmd/add.go index 06a32e0..f9bef1a 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "os" "github.com/spf13/cobra" @@ -9,8 +8,8 @@ import ( "github.com/guumaster/hostctl/pkg/host" ) -// addFromFileCmd represents the fromFile command -var addFromFileCmd = &cobra.Command{ +// addCmd represents the fromFile command +var addCmd = &cobra.Command{ Use: "add", Short: "Add content to a profile in your hosts file.", Long: ` @@ -33,39 +32,32 @@ If the profile already exists it will be added to it.`, from, _ := cmd.Flags().GetString("from") profile, _ := cmd.Flags().GetString("profile") - var err error if isPiped() { - fmt.Println("IS PIPED") - err = host.AddFromReader(os.Stdin, &host.AddFromFileOptions{ - Dst: src, - Profile: profile, - Reset: false, - }) - } else { - fmt.Println("FROM FILE") - err = host.AddFromFile(&host.AddFromFileOptions{ - From: from, + return host.AddFromReader(os.Stdin, &host.AddFromFileOptions{ Dst: src, Profile: profile, Reset: false, }) } - if err != nil { - return err - } - return host.ListProfiles(src, &host.ListOptions{ + return host.AddFromFile(&host.AddFromFileOptions{ + From: from, + Dst: src, Profile: profile, + Reset: false, }) }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, removeCmd) + }, } func init() { - rootCmd.AddCommand(addFromFileCmd) - - addFromFileCmd.Flags().StringP("from", "f", "", "file to read") + rootCmd.AddCommand(addCmd) + addCmd.AddCommand(addDomainsCmd) - addFromFileCmd.AddCommand(addDomainsCmd) + addCmd.Flags().StringP("from", "f", "", "file to read") + addCmd.PersistentFlags().DurationP("wait", "w", -1, "Enables a profile for a specific amount of time") addDomainsCmd.Flags().String("ip", "127.0.0.1", "domains ip") } diff --git a/cmd/add_domains.go b/cmd/add_domains.go index 1eb580b..458c644 100644 --- a/cmd/add_domains.go +++ b/cmd/add_domains.go @@ -29,7 +29,6 @@ If the profile already exists it will be added to it.`, src, _ := cmd.Flags().GetString("host-file") ip, _ := cmd.Flags().GetString("ip") profile, _ := cmd.Flags().GetString("profile") - quiet, _ := cmd.Flags().GetBool("quiet") err := host.AddFromArgs(&host.AddFromArgsOptions{ Domains: args, @@ -42,17 +41,9 @@ If the profile already exists it will be added to it.`, return err } - err = host.Enable(src, profile) - if err != nil { - return err - } - - if quiet { - return nil - } - - return host.ListProfiles(src, &host.ListOptions{ - Profile: profile, - }) + return host.Enable(src, profile) + }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, removeDomainsCmd) }, } diff --git a/cmd/disable.go b/cmd/disable.go index 6963af0..e5a7dc6 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -6,54 +6,51 @@ import ( "github.com/guumaster/hostctl/pkg/host" ) -// disableCmd represents the disable command -var disableCmd = &cobra.Command{ - Use: "disable", - Short: "Disable a profile from your hosts file.", - Long: ` +var disableCmd *cobra.Command + +func init() { + + // disableCmd represents the disable command + disableCmd := &cobra.Command{ + Use: "disable", + Short: "Disable a profile from your hosts file.", + Long: ` Disable a profile from your hosts file without removing it. It will be listed as "off" while it is disabled. `, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - all, _ := cmd.Flags().GetBool("all") - - if !all && profile == "" { - return host.MissingProfileError - } - - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - src, _ := cmd.Flags().GetString("host-file") - profile, _ := cmd.Flags().GetString("profile") - quiet, _ := cmd.Flags().GetBool("quiet") - - all, _ := cmd.Flags().GetBool("all") - - var err error - if all { - profile = "" - } - err = host.Disable(src, profile) - if err != nil { - return err - } - - if quiet { + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + all, _ := cmd.Flags().GetBool("all") + + if !all && profile == "" { + return host.MissingProfileError + } + + if profile == "default" { + return host.DefaultProfileError + } return nil - } - return host.ListProfiles(src, &host.ListOptions{ - Profile: profile, - }) - }, -} + }, + RunE: func(cmd *cobra.Command, args []string) error { + src, _ := cmd.Flags().GetString("host-file") + profile, _ := cmd.Flags().GetString("profile") + + all, _ := cmd.Flags().GetBool("all") + + if all { + profile = "" + } + return host.Disable(src, profile) + + }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, enableCmd) + }, + } -func init() { rootCmd.AddCommand(disableCmd) disableCmd.Flags().BoolP("all", "", false, "Disable all profiles") + disableCmd.Flags().DurationP("wait", "w", -1, "Enables a profile for a specific amount of time") + } diff --git a/cmd/enable.go b/cmd/enable.go index ef36e38..2df37df 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -6,61 +6,54 @@ import ( "github.com/guumaster/hostctl/pkg/host" ) -// enableCmd represents the enable command -var enableCmd = &cobra.Command{ - Use: "enable", - Short: "Enable a profile on your hosts file.", - Long: ` +var enableCmd *cobra.Command + +func init() { + // enableCmd represents the enable command + enableCmd = &cobra.Command{ + Use: "enable", + Short: "Enable a profile on your hosts file.", + Long: ` Enables an existing profile. It will be listed as "on" while it is enabled. `, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - all, _ := cmd.Flags().GetBool("all") - - if !all && profile == "" { - return host.MissingProfileError - } - - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + all, _ := cmd.Flags().GetBool("all") - all, _ := cmd.Flags().GetBool("all") - if all { - profile = "" - } + if !all && profile == "" { + return host.MissingProfileError + } - src, _ := cmd.Flags().GetString("host-file") - enableOnly, _ := cmd.Flags().GetBool("only") - quiet, _ := cmd.Flags().GetBool("quiet") - - var err error - if enableOnly { - err = host.EnableOnly(src, profile) - } else { - err = host.Enable(src, profile) - } - if err != nil { - return err - } - - if quiet { + if profile == "default" { + return host.DefaultProfileError + } return nil - } - return host.ListProfiles(src, &host.ListOptions{ - Profile: profile, - }) - }, -} + }, + RunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + src, _ := cmd.Flags().GetString("host-file") + enableOnly, _ := cmd.Flags().GetBool("only") + + all, _ := cmd.Flags().GetBool("all") + if all { + profile = "" + } + + if enableOnly && !all { + return host.EnableOnly(src, profile) + } + return host.Enable(src, profile) + + }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, disableCmd) + }, + } -func init() { rootCmd.AddCommand(enableCmd) enableCmd.Flags().BoolP("all", "", false, "Enable all profiles") enableCmd.Flags().Bool("only", false, "Disable all other profiles") + enableCmd.Flags().DurationP("wait", "w", -1, "Enables a profile for a specific amount of time") } diff --git a/cmd/list.go b/cmd/list.go index 8a1d15a..8b8f66e 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -23,13 +23,11 @@ The "default" profile is all the content that is not handled by hostctl tool. raw, _ := cmd.Flags().GetBool("raw") cols, _ := cmd.Flags().GetStringSlice("column") - err := host.ListProfiles(src, &host.ListOptions{ + return host.ListProfiles(src, &host.ListOptions{ Profile: profile, RawTable: raw, Columns: cols, }) - - return err }, } @@ -39,6 +37,4 @@ func init() { listCmd.AddCommand(makeListStatusCmd(host.Enabled)) listCmd.AddCommand(makeListStatusCmd(host.Disabled)) - listCmd.PersistentFlags().StringSliceP("column", "c", nil, "Columns to show on lists") - listCmd.PersistentFlags().Bool("raw", false, "Output without table borders") } diff --git a/cmd/post_action.go b/cmd/post_action.go new file mode 100644 index 0000000..b54f73f --- /dev/null +++ b/cmd/post_action.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +var postActionCmd = func(cmd *cobra.Command, args []string, postCmd *cobra.Command) error { + src, _ := cmd.Flags().GetString("host-file") + quiet, _ := cmd.Flags().GetBool("quiet") + duration, _ := cmd.Flags().GetDuration("wait") + profile, _ := cmd.Flags().GetString("profile") + raw, _ := cmd.Flags().GetBool("raw") + cols, _ := cmd.Flags().GetStringSlice("column") + + var err error + if !quiet { + err = host.ListProfiles(src, &host.ListOptions{ + Profile: profile, + RawTable: raw, + Columns: cols, + }) + if err != nil { + return err + } + } + + action := postCmd.Name() + if action == "domains" { + action = "remove domains" + } + + if duration >= 0 && !quiet { + fmt.Printf("\nWaiting for %s or ctrl+c to %s from profile '%s'\n\n", duration, action, profile) + } + + if duration >= 0 { + doneCh := waitSignalOrDuration(duration) + <-doneCh + + return postCmd.RunE(cmd, args) + } + return nil +} + +func waitSignalOrDuration(d time.Duration) <-chan struct{} { + done := make(chan struct{}, 0) + sig := make(chan os.Signal) + + if d < 0 { + d = -d + } + + if d == 0 { + // NOTE: It's large enough, practically it will never timeout. + d = 999999 * time.Hour + } + + signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT) + + go func() { + for { + select { + case <-time.After(d): + done <- struct{}{} + return + case <-sig: + done <- struct{}{} + return + } + } + }() + + return done +} diff --git a/cmd/root.go b/cmd/root.go index 91f054f..d751157 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -61,6 +61,9 @@ func init() { rootCmd.PersistentFlags().StringP("profile", "p", "", "Choose a profile") rootCmd.PersistentFlags().String("host-file", getDefaultHostFile(), "Hosts file path") rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Run command without output") + + rootCmd.PersistentFlags().StringSliceP("column", "c", nil, "Columns to show on lists") + rootCmd.PersistentFlags().Bool("raw", false, "Output without table borders") } // isPiped detect if there is any input through STDIN diff --git a/cmd/sync.go b/cmd/sync.go index c9b5000..0a78d04 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -24,4 +24,5 @@ func init() { syncCmd.PersistentFlags().String("network", "", "Filter containers from a specific network") syncCmd.PersistentFlags().StringP("domain", "d", "loc", "domain where your docker containers will be added") + syncCmd.PersistentFlags().DurationP("wait", "w", -1, "Enables a profile for a specific amount of time") } diff --git a/cmd/sync_docker.go b/cmd/sync_docker.go index 4e0b4f7..7fe8b49 100644 --- a/cmd/sync_docker.go +++ b/cmd/sync_docker.go @@ -32,10 +32,9 @@ Reads from Docker the list of containers and add names and IPs to a profile in y profile, _ := cmd.Flags().GetString("profile") domain, _ := cmd.Flags().GetString("domain") network, _ := cmd.Flags().GetString("network") - quiet, _ := cmd.Flags().GetBool("quiet") ctx := context.Background() - err := host.AddFromDocker(ctx, &host.AddFromDockerOptions{ + return host.AddFromDocker(ctx, &host.AddFromDockerOptions{ Dst: hostFile, Domain: domain, Profile: profile, @@ -44,15 +43,8 @@ Reads from Docker the list of containers and add names and IPs to a profile in y Network: network, }, }) - if err != nil { - return err - } - - if quiet { - return nil - } - return host.ListProfiles(hostFile, &host.ListOptions{ - Profile: profile, - }) + }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, removeCmd) }, } diff --git a/cmd/sync_docker_compose.go b/cmd/sync_docker_compose.go index 034d69a..0a362c5 100644 --- a/cmd/sync_docker_compose.go +++ b/cmd/sync_docker_compose.go @@ -33,30 +33,20 @@ Reads from a docker-compose.yml file the list of containers and add names and I profile, _ := cmd.Flags().GetString("profile") domain, _ := cmd.Flags().GetString("domain") network, _ := cmd.Flags().GetString("network") - composeFile, _ := cmd.Flags().GetString("compose-file") - projectName, _ := cmd.Flags().GetString("project-name") prefix, _ := cmd.Flags().GetBool("prefix") - quiet, _ := cmd.Flags().GetBool("quiet") - if composeFile == "" { - cwd, err := os.Getwd() - if err != nil { - return err - } - - composeFile = path.Join(cwd, "docker-compose.yml") - } - - if projectName == "" { - projectName = guessProjectName(composeFile) + compose, err := getComposeInfo(cmd) + if err != nil { + return err } - if profile == "" && projectName == "" { + if profile == "" && compose.ProjectName == "" { return host.MissingProfileError } if profile == "" { - profile = projectName + profile = compose.ProjectName + _ = cmd.Flags().Set("profile", profile) } if domain == "" { @@ -65,35 +55,50 @@ Reads from a docker-compose.yml file the list of containers and add names and I ctx := context.Background() - err := host.AddFromDocker(ctx, &host.AddFromDockerOptions{ + return host.AddFromDocker(ctx, &host.AddFromDockerOptions{ Dst: hostFile, Domain: domain, Profile: profile, Watch: false, Docker: &host.DockerOptions{ - ComposeFile: composeFile, - ProjectName: projectName, + ComposeFile: compose.File, + ProjectName: compose.ProjectName, Network: network, KeepPrefix: prefix, }, }) + }, + PostRunE: func(cmd *cobra.Command, args []string) error { + return postActionCmd(cmd, args, removeCmd) + }, +} + +type ComposeInfo struct { + ProjectName string + File string +} + +func getComposeInfo(cmd *cobra.Command) (*ComposeInfo, error) { + name, _ := cmd.Flags().GetString("project-name") + f, _ := cmd.Flags().GetString("compose-file") + + if f == "" { + cwd, err := os.Getwd() if err != nil { - return err + return nil, err } - if quiet { - return nil - } - return host.ListProfiles(hostFile, &host.ListOptions{ - Profile: profile, - }) - }, -} + f = path.Join(cwd, "docker-compose.yml") + } -func guessProjectName(composeFile string) string { - reg := regexp.MustCompile("[^a-z0-9-]+") - base := path.Base(path.Dir(composeFile)) - base = strings.ToLower(base) - base = reg.ReplaceAllString(base, "") - return base + if name == "" { + reg := regexp.MustCompile("[^a-z0-9-]+") + name = path.Base(path.Dir(f)) + name = strings.ToLower(name) + name = reg.ReplaceAllString(name, "") + } + return &ComposeInfo{ + ProjectName: name, + File: f, + }, nil }