From 86d5a69ef4592d96f8655956867e80506ca7e779 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 8 Apr 2020 14:21:33 +0200 Subject: [PATCH] adds several features and refactor: * add status command * add --quiet persistent flag to silence * refactor one command or subcommand per files * add --only to enable command Closes #30 Closes #24 --- cmd/add.go | 70 ++++--------------- cmd/add_domains.go | 58 ++++++++++++++++ cmd/backup.go | 12 ++-- cmd/disable.go | 4 ++ cmd/enable.go | 13 +++- cmd/list.go | 9 ++- cmd/list_status.go | 41 +++++++++++ cmd/remove.go | 42 ++---------- cmd/remove_domains.go | 46 +++++++++++++ cmd/restore.go | 10 ++- cmd/root.go | 15 ++++- cmd/set.go | 48 +------------ cmd/set_domains.go | 57 ++++++++++++++++ cmd/status.go | 38 +++++++++++ cmd/sync.go | 135 ------------------------------------- cmd/sync_docker.go | 58 ++++++++++++++++ cmd/sync_docker_compose.go | 99 +++++++++++++++++++++++++++ doc.go | 8 ++- go.sum | 3 + pkg/host/enable.go | 18 +++++ pkg/host/list.go | 39 +++++++---- 21 files changed, 520 insertions(+), 303 deletions(-) create mode 100644 cmd/add_domains.go create mode 100644 cmd/list_status.go create mode 100644 cmd/remove_domains.go create mode 100644 cmd/set_domains.go create mode 100644 cmd/status.go create mode 100644 cmd/sync_docker.go create mode 100644 cmd/sync_docker_compose.go diff --git a/cmd/add.go b/cmd/add.go index 94a2db2..06a32e0 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -16,6 +16,18 @@ var addFromFileCmd = &cobra.Command{ Long: ` Reads from a file and set content to a profile in your hosts file. If the profile already exists it will be added to it.`, + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if 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") from, _ := cmd.Flags().GetString("from") @@ -42,64 +54,6 @@ If the profile already exists it will be added to it.`, return err } - return host.ListProfiles(src, &host.ListOptions{ - Profile: profile, - }) - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if profile == "" { - return host.MissingProfileError - } - - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, -} - -// addDomainsCmd represents the fromFile command -var addDomainsCmd = &cobra.Command{ - Use: "domains", - Short: "Add content in your hosts file.", - Long: ` -Set content in your hosts file. -If the profile already exists it will be added to it.`, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if 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") - ip, _ := cmd.Flags().GetString("ip") - profile, _ := cmd.Flags().GetString("profile") - - err := host.AddFromArgs(&host.AddFromArgsOptions{ - Domains: args, - IP: ip, - Dst: src, - Profile: profile, - Reset: false, - }) - if err != nil { - return err - } - - err = host.Enable(src, profile) - if err != nil { - return err - } - return host.ListProfiles(src, &host.ListOptions{ Profile: profile, }) diff --git a/cmd/add_domains.go b/cmd/add_domains.go new file mode 100644 index 0000000..1eb580b --- /dev/null +++ b/cmd/add_domains.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// addDomainsCmd represents the fromFile command +var addDomainsCmd = &cobra.Command{ + Use: "domains", + Short: "Add content in your hosts file.", + Long: ` +Set content in your hosts file. +If the profile already exists it will be added to it.`, + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if 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") + ip, _ := cmd.Flags().GetString("ip") + profile, _ := cmd.Flags().GetString("profile") + quiet, _ := cmd.Flags().GetBool("quiet") + + err := host.AddFromArgs(&host.AddFromArgsOptions{ + Domains: args, + IP: ip, + Dst: src, + Profile: profile, + Reset: false, + }) + if err != nil { + return err + } + + err = host.Enable(src, profile) + if err != nil { + return err + } + + if quiet { + return nil + } + + return host.ListProfiles(src, &host.ListOptions{ + Profile: profile, + }) + }, +} diff --git a/cmd/backup.go b/cmd/backup.go index 4befe2c..343b47f 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -16,8 +16,8 @@ var backupCmd = &cobra.Command{ Use: "backup", Short: "Creates a backup copy of your hosts file", Long: ` -Creates a backup copy of your hosts file with the date in .YYYYMMDD -as extension. +Creates a backup copy of your hosts file with the date in .YYYYMMDD +as extension. `, PreRunE: func(cmd *cobra.Command, args []string) error { profile, _ := cmd.Flags().GetString("profile") @@ -30,17 +30,19 @@ as extension. RunE: func(cmd *cobra.Command, args []string) error { src, _ := cmd.Flags().GetString("host-file") dst, _ := cmd.Flags().GetString("path") + quiet, _ := cmd.Flags().GetBool("quiet") backupFile, err := host.BackupFile(src, dst) if err != nil { return err } - _ = host.ListProfiles(backupFile, &host.ListOptions{}) - + if quiet { + return nil + } fmt.Printf("Backup completed.") - return nil + return host.ListProfiles(backupFile, &host.ListOptions{}) }, } diff --git a/cmd/disable.go b/cmd/disable.go index 95190bc..6963af0 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -30,6 +30,7 @@ It will be listed as "off" while it is disabled. 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") @@ -42,6 +43,9 @@ It will be listed as "off" while it is disabled. return err } + if quiet { + return nil + } return host.ListProfiles(src, &host.ListOptions{ Profile: profile, }) diff --git a/cmd/enable.go b/cmd/enable.go index 66933fc..ef36e38 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -36,12 +36,22 @@ It will be listed as "on" while it is enabled. } src, _ := cmd.Flags().GetString("host-file") + enableOnly, _ := cmd.Flags().GetBool("only") + quiet, _ := cmd.Flags().GetBool("quiet") - err := host.Enable(src, profile) + var err error + if enableOnly { + err = host.EnableOnly(src, profile) + } else { + err = host.Enable(src, profile) + } if err != nil { return err } + if quiet { + return nil + } return host.ListProfiles(src, &host.ListOptions{ Profile: profile, }) @@ -52,4 +62,5 @@ func init() { rootCmd.AddCommand(enableCmd) enableCmd.Flags().BoolP("all", "", false, "Enable all profiles") + enableCmd.Flags().Bool("only", false, "Disable all other profiles") } diff --git a/cmd/list.go b/cmd/list.go index 4f43e1a..99a97f2 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -14,7 +14,7 @@ var listCmd = &cobra.Command{ Shows a detailed list of profiles on your hosts file with name, ip and host name. You can filter by profile name. -The "default"" profile is all the content that is not handled by hostctl tool. +The "default" profile is all the content that is not handled by hostctl tool. `, RunE: func(cmd *cobra.Command, args []string) error { profile, _ := cmd.Flags().GetString("profile") @@ -36,6 +36,9 @@ The "default"" profile is all the content that is not handled by hostctl tool. func init() { rootCmd.AddCommand(listCmd) - listCmd.Flags().StringSliceP("column", "c", nil, "Columns to show on lists") - listCmd.Flags().Bool("raw", false, "Output without table borders") + listCmd.AddCommand(makeListStatusCmd("enabled")) + listCmd.AddCommand(makeListStatusCmd("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/list_status.go b/cmd/list_status.go new file mode 100644 index 0000000..da8619d --- /dev/null +++ b/cmd/list_status.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// makeListStatusCmd represents the list enabled command +var makeListStatusCmd = func(cmd string) *cobra.Command { + status := "" + switch cmd { + case "enabled": + status = "on" + case "disabled": + status = "off" + } + return &cobra.Command{ + Use: cmd, + Aliases: []string{status}, + Short: fmt.Sprintf("Shows list of %s profiles on your hosts file.", cmd), + Long: fmt.Sprintf(` +Shows a detailed list of %s profiles on your hosts file with name, ip and host name. +`, cmd), + RunE: func(cmd *cobra.Command, args []string) error { + src, _ := cmd.Flags().GetString("host-file") + raw, _ := cmd.Flags().GetBool("raw") + cols, _ := cmd.Flags().GetStringSlice("column") + + err := host.ListProfiles(src, &host.ListOptions{ + RawTable: raw, + Columns: cols, + StatusFilter: status, + }) + + return err + }, + } +} diff --git a/cmd/remove.go b/cmd/remove.go index 0dc3791..4ad13cb 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -16,7 +16,7 @@ var removeCmd = &cobra.Command{ Completely remove a profile content from your hosts file. It cannot be undone unless you have a backup and restore it. -If you want to remove a profile but would like to use it later, +If you want to remove a profile but would like to use it later, use 'hosts disable' instead. `, PreRunE: func(cmd *cobra.Command, args []string) error { @@ -35,50 +35,20 @@ use 'hosts disable' instead. RunE: func(cmd *cobra.Command, args []string) error { profile, _ := cmd.Flags().GetString("profile") dst, _ := cmd.Flags().GetString("host-file") + quiet, _ := cmd.Flags().GetBool("quiet") err := host.RemoveProfile(dst, profile) if err != nil { return err } - fmt.Printf("Profile '%s' removed.", profile) - - return nil - }, -} - -// removeDomainsCmd represents the remove command -var removeDomainsCmd = &cobra.Command{ - Use: "domains", - Short: "Remove domains from your hosts file.", - Long: ` -Completely remove domains from your hosts file. -It cannot be undone unless you have a backup and restore it. -`, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if profile == "" { - return host.MissingProfileError + if quiet { + return nil } - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - dst, _ := cmd.Flags().GetString("host-file") - - err := host.RemoveDomains(dst, profile, args) - if err != nil { - return err - } + fmt.Printf("Profile '%s' removed.\n\n", profile) - return host.ListProfiles(dst, &host.ListOptions{ - Profile: profile, - }) + return nil }, } diff --git a/cmd/remove_domains.go b/cmd/remove_domains.go new file mode 100644 index 0000000..681f34a --- /dev/null +++ b/cmd/remove_domains.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// removeDomainsCmd represents the remove command +var removeDomainsCmd = &cobra.Command{ + Use: "domains", + Short: "Remove domains from your hosts file.", + Long: ` +Completely remove domains from your hosts file. +It cannot be undone unless you have a backup and restore it. +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if 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") + dst, _ := cmd.Flags().GetString("host-file") + quiet, _ := cmd.Flags().GetBool("quiet") + + err := host.RemoveDomains(dst, profile, args) + if err != nil { + return err + } + + if quiet { + return nil + } + return host.ListProfiles(dst, &host.ListOptions{ + Profile: profile, + }) + }, +} diff --git a/cmd/restore.go b/cmd/restore.go index fd305db..036f754 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -29,16 +29,20 @@ WARNING: the complete hosts file will be overwritten with the backup data. RunE: func(cmd *cobra.Command, args []string) error { dst, _ := cmd.Flags().GetString("host-file") from, _ := cmd.Flags().GetString("from") + quiet, _ := cmd.Flags().GetBool("quiet") err := host.RestoreFile(from, dst) if err != nil { return err } - _ = host.ListProfiles(dst, &host.ListOptions{}) - fmt.Printf("Restore completed.") + if quiet { + return nil + } - return nil + fmt.Printf("File '%s' restored.\n\n", from) + + return host.ListProfiles(dst, &host.ListOptions{}) }, } diff --git a/cmd/root.go b/cmd/root.go index 057e1a8..9008b96 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,12 +8,19 @@ import ( "github.com/spf13/cobra" ) +var defaultHostsFile = "/etc/hosts" + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "hostctl", Short: "Manage your hosts file like a pro", Long: ` - πŸ„·πŸ„ΎπŸ…‚πŸ…ƒπŸ„²πŸ…ƒπŸ„» + __ __ __ __ + / /_ ____ _____ / /_ _____ / /_ / / + / __ \ / __ \ / ___/ / __/ / ___/ / __/ / / + / / / // /_/ / (__ ) / /_ / /__ / /_ / / +/_/ /_/ \____/ /____/ \__/ \___/ \__/ /_/ + hostctl is a CLI tool to manage your hosts file with ease. You can have multiple profiles, enable/disable exactly what @@ -22,8 +29,11 @@ you need each time with a simple interface. SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { host, _ := cmd.Flags().GetString("host-file") + quiet, _ := cmd.Flags().GetBool("quiet") - fmt.Printf("Using hosts file: %s\n", host) + if host != defaultHostsFile && !quiet { + fmt.Printf("Using hosts file: %s\n", host) + } return nil }, @@ -49,6 +59,7 @@ func init() { rootCmd.PersistentFlags().StringP("profile", "p", "", "Choose a profile") rootCmd.PersistentFlags().String("host-file", defaultHostsFile, "Hosts file path") + rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Run command without output") } // isPiped detect if there is any input through STDIN diff --git a/cmd/set.go b/cmd/set.go index 973785a..76937e3 100644 --- a/cmd/set.go +++ b/cmd/set.go @@ -32,6 +32,7 @@ If the profile already exists it will be overwritten. src, _ := cmd.Flags().GetString("host-file") from, _ := cmd.Flags().GetString("from") profile, _ := cmd.Flags().GetString("profile") + quiet, _ := cmd.Flags().GetBool("quiet") h, _ := cmd.Flags().GetString("host-file") @@ -54,52 +55,9 @@ If the profile already exists it will be overwritten. return err } - return host.ListProfiles(src, &host.ListOptions{ - Profile: profile, - }) - }, -} - -// setDomainsCmd represents the fromFile command -var setDomainsCmd = &cobra.Command{ - Use: "domains", - Short: "Add content in your hosts file.", - Long: ` -Set content in your hosts file. -If the profile already exists it will be added to it.`, - PreRunE: func(cmd *cobra.Command, domains []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if profile == "" { - return host.MissingProfileError - } - - if profile == "default" { - return host.DefaultProfileError + if quiet { + return nil } - - if len(domains) == 0 { - return host.MissingDomainsError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - src, _ := cmd.Flags().GetString("host-file") - ip, _ := cmd.Flags().GetString("ip") - profile, _ := cmd.Flags().GetString("profile") - h, _ := cmd.Flags().GetString("host-file") - - err := host.AddFromArgs(&host.AddFromArgsOptions{ - Domains: args, - IP: ip, - Dst: h, - Profile: profile, - Reset: true, - }) - if err != nil { - return err - } - return host.ListProfiles(src, &host.ListOptions{ Profile: profile, }) diff --git a/cmd/set_domains.go b/cmd/set_domains.go new file mode 100644 index 0000000..d2ec6e1 --- /dev/null +++ b/cmd/set_domains.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// setDomainsCmd represents the fromFile command +var setDomainsCmd = &cobra.Command{ + Use: "domains", + Short: "Add content in your hosts file.", + Long: ` +Set content in your hosts file. +If the profile already exists it will be added to it.`, + PreRunE: func(cmd *cobra.Command, domains []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if profile == "" { + return host.MissingProfileError + } + + if profile == "default" { + return host.DefaultProfileError + } + + if len(domains) == 0 { + return host.MissingDomainsError + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + src, _ := cmd.Flags().GetString("host-file") + ip, _ := cmd.Flags().GetString("ip") + profile, _ := cmd.Flags().GetString("profile") + h, _ := cmd.Flags().GetString("host-file") + quiet, _ := cmd.Flags().GetBool("quiet") + + err := host.AddFromArgs(&host.AddFromArgsOptions{ + Domains: args, + IP: ip, + Dst: h, + Profile: profile, + Reset: true, + }) + if err != nil { + return err + } + + if quiet { + return nil + } + return host.ListProfiles(src, &host.ListOptions{ + Profile: profile, + }) + }, +} diff --git a/cmd/status.go b/cmd/status.go new file mode 100644 index 0000000..6800c82 --- /dev/null +++ b/cmd/status.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// statusCmd represents the list command +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Shows a list of profile names and statuses on your hosts file.", + Long: ` +Shows a list of unique profile names on your hosts file with its status. + +The "default" profile is always on and will be skipped. +`, + RunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + src, _ := cmd.Flags().GetString("host-file") + raw, _ := cmd.Flags().GetBool("raw") + + err := host.ListProfiles(src, &host.ListOptions{ + Profile: profile, + RawTable: raw, + ProfilesOnly: true, + }) + + return err + }, +} + +func init() { + rootCmd.AddCommand(statusCmd) + + statusCmd.Flags().Bool("raw", false, "Output without table borders") +} diff --git a/cmd/sync.go b/cmd/sync.go index 62fc92a..c9b5000 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -1,16 +1,7 @@ package cmd import ( - "context" - "fmt" - "os" - "path" - "regexp" - "strings" - "github.com/spf13/cobra" - - "github.com/guumaster/hostctl/pkg/host" ) // syncCmd represents the sync command @@ -22,132 +13,6 @@ Reads IPs and names from some local system and sync it with a profile in your ho `, } -// syncDockerCmd represents the sync docker command -var syncDockerCmd = &cobra.Command{ - Use: "docker", - Short: "Sync your Docker containers IPs with a profile.", - Long: ` -Reads from Docker the list of containers and add names and IPs to a profile in your hosts file. -`, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if profile == "" { - return host.MissingProfileError - } - - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - hostFile, _ := cmd.Flags().GetString("host-file") - profile, _ := cmd.Flags().GetString("profile") - domain, _ := cmd.Flags().GetString("domain") - network, _ := cmd.Flags().GetString("network") - - ctx := context.Background() - err := host.AddFromDocker(ctx, &host.AddFromDockerOptions{ - Dst: hostFile, - Domain: domain, - Profile: profile, - Watch: false, - Docker: &host.DockerOptions{ - Network: network, - }, - }) - if err != nil { - return err - } - - return host.ListProfiles(hostFile, &host.ListOptions{ - Profile: profile, - }) - }, -} - -// syncDockerComposeCmd represents the sync docker command -var syncDockerComposeCmd = &cobra.Command{ - Use: "docker-compose", - Short: "Sync your docker-compose containers IPs with a profile.", - Long: ` -Reads from a docker-compose.yml file the list of containers and add names and IPs to a profile in your hosts file. -`, - PreRunE: func(cmd *cobra.Command, args []string) error { - profile, _ := cmd.Flags().GetString("profile") - - if profile == "default" { - return host.DefaultProfileError - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - hostFile, _ := cmd.Flags().GetString("host-file") - 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") - - if composeFile == "" { - cwd, err := os.Getwd() - if err != nil { - return err - } - - composeFile = path.Join(cwd, "docker-compose.yml") - } - - if projectName == "" { - projectName = guessProjectName(composeFile) - } - - if profile == "" && projectName == "" { - return host.MissingProfileError - } - - if profile == "" { - profile = projectName - } - - if domain == "" { - domain = fmt.Sprintf("%s.loc", profile) - } - - ctx := context.Background() - - err := host.AddFromDocker(ctx, &host.AddFromDockerOptions{ - Dst: hostFile, - Domain: domain, - Profile: profile, - Watch: false, - Docker: &host.DockerOptions{ - ComposeFile: composeFile, - ProjectName: projectName, - Network: network, - KeepPrefix: prefix, - }, - }) - if err != nil { - return err - } - - return host.ListProfiles(hostFile, &host.ListOptions{ - Profile: profile, - }) - }, -} - -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 -} - func init() { rootCmd.AddCommand(syncCmd) syncCmd.AddCommand(syncDockerCmd) diff --git a/cmd/sync_docker.go b/cmd/sync_docker.go new file mode 100644 index 0000000..4e0b4f7 --- /dev/null +++ b/cmd/sync_docker.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// syncDockerCmd represents the sync docker command +var syncDockerCmd = &cobra.Command{ + Use: "docker", + Short: "Sync your Docker containers IPs with a profile.", + Long: ` +Reads from Docker the list of containers and add names and IPs to a profile in your hosts file. +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if profile == "" { + return host.MissingProfileError + } + + if profile == "default" { + return host.DefaultProfileError + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + hostFile, _ := cmd.Flags().GetString("host-file") + 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{ + Dst: hostFile, + Domain: domain, + Profile: profile, + Watch: false, + Docker: &host.DockerOptions{ + Network: network, + }, + }) + if err != nil { + return err + } + + if quiet { + return nil + } + return host.ListProfiles(hostFile, &host.ListOptions{ + Profile: profile, + }) + }, +} diff --git a/cmd/sync_docker_compose.go b/cmd/sync_docker_compose.go new file mode 100644 index 0000000..034d69a --- /dev/null +++ b/cmd/sync_docker_compose.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "path" + "regexp" + "strings" + + "github.com/spf13/cobra" + + "github.com/guumaster/hostctl/pkg/host" +) + +// syncDockerComposeCmd represents the sync docker command +var syncDockerComposeCmd = &cobra.Command{ + Use: "docker-compose", + Short: "Sync your docker-compose containers IPs with a profile.", + Long: ` +Reads from a docker-compose.yml file the list of containers and add names and IPs to a profile in your hosts file. +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + profile, _ := cmd.Flags().GetString("profile") + + if profile == "default" { + return host.DefaultProfileError + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + hostFile, _ := cmd.Flags().GetString("host-file") + 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) + } + + if profile == "" && projectName == "" { + return host.MissingProfileError + } + + if profile == "" { + profile = projectName + } + + if domain == "" { + domain = fmt.Sprintf("%s.loc", profile) + } + + ctx := context.Background() + + err := host.AddFromDocker(ctx, &host.AddFromDockerOptions{ + Dst: hostFile, + Domain: domain, + Profile: profile, + Watch: false, + Docker: &host.DockerOptions{ + ComposeFile: composeFile, + ProjectName: projectName, + Network: network, + KeepPrefix: prefix, + }, + }) + if err != nil { + return err + } + + if quiet { + return nil + } + return host.ListProfiles(hostFile, &host.ListOptions{ + Profile: profile, + }) + }, +} + +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 +} diff --git a/doc.go b/doc.go index d530ac0..3ffed76 100644 --- a/doc.go +++ b/doc.go @@ -1,7 +1,9 @@ /* - _ _ _____ _______ _______ _______ _______ - |_____| | | |______ | | | | - | | |_____| ______| | |_____ | |_____ + __ __ __ __ + / /_ ____ _____ / /_ _____ / /_ / / + / __ \ / __ \ / ___/ / __/ / ___/ / __/ / / + / / / // /_/ / (__ ) / /_ / /__ / /_ / / +/_/ /_/ \____/ /____/ \__/ \___/ \__/ /_/ hostctl is a CLI tool to manage your hosts file with ease. You can have multiple profiles, enable/disable exactly what diff --git a/go.sum b/go.sum index ff40e59..d561f6e 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -89,7 +90,9 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= diff --git a/pkg/host/enable.go b/pkg/host/enable.go index bfdae23..0cdc500 100644 --- a/pkg/host/enable.go +++ b/pkg/host/enable.go @@ -21,6 +21,24 @@ func Enable(dst, profile string) error { return writeHostData(dst, h) } +// EnableOnly marks a profile as enable and disable all other profiles +func EnableOnly(dst, profile string) error { + h, err := getHostData(dst, profile) + if err != nil { + return err + } + + for p := range h.profiles { + if p == profile { + enableProfile(h, p) + } else if p != "default" { + disableProfile(h, p) + } + } + + return writeHostData(dst, h) +} + func enableProfile(h *hostFile, profile string) { for i, r := range h.profiles[profile] { if IsDisabled(r) { diff --git a/pkg/host/list.go b/pkg/host/list.go index 329fb04..86f14f0 100644 --- a/pkg/host/list.go +++ b/pkg/host/list.go @@ -10,11 +10,16 @@ import ( // DefaultColumns is the list of default columns to use when showing table list var DefaultColumns = []string{"profile", "status", "ip", "domain"} +// ProfilesOnlyColumns are the columns used for profile status list +var ProfilesOnlyColumns = []string{"profile", "status"} + // ListOptions contains available options for listing. type ListOptions struct { - Profile string - RawTable bool - Columns []string + Profile string + RawTable bool + Columns []string + ProfilesOnly bool + StatusFilter string } // ListProfiles shows a table with profile names status and routing information @@ -34,12 +39,14 @@ func ListProfiles(src string, opts *ListOptions) error { table := tablewriter.NewWriter(os.Stdout) - cols := opts.Columns - if len(cols) == 0 { - cols = DefaultColumns + if len(opts.Columns) == 0 { + opts.Columns = DefaultColumns + } + if opts.ProfilesOnly { + opts.Columns = ProfilesOnlyColumns } - table.SetHeader(cols) + table.SetHeader(opts.Columns) if opts.RawTable { table.SetAutoWrapText(false) @@ -55,8 +62,9 @@ func ListProfiles(src string, opts *ListOptions) error { table.SetNoWhiteSpace(true) } - if profile == "default" || profile == "" { - appendProfile("default", table, cols, h.profiles["default"]) + // First check if default should be shown + if (profile == "default" || profile == "") && !opts.ProfilesOnly { + appendProfile("default", table, h.profiles["default"], opts) if len(h.profiles) > 1 && !opts.RawTable { table.AddSeparator() @@ -73,7 +81,7 @@ func ListProfiles(src string, opts *ListOptions) error { continue } - appendProfile(p, table, cols, data) + appendProfile(p, table, data, opts) if i < len(h.profiles) && !opts.RawTable { table.AddSeparator() @@ -83,7 +91,7 @@ func ListProfiles(src string, opts *ListOptions) error { return nil } -func appendProfile(profile string, table *tablewriter.Table, cols []string, data hostLines) { +func appendProfile(profile string, table *tablewriter.Table, data hostLines, opts *ListOptions) { for _, r := range data { if r == "" { continue @@ -103,8 +111,15 @@ func appendProfile(profile string, table *tablewriter.Table, cols []string, data status = "off" ip, domain = rs[1], rs[2] } + if opts.StatusFilter != "" && status != opts.StatusFilter { + continue + } + if opts.ProfilesOnly { + table.Append([]string{profile, status}) + return + } var row []string - for _, c := range cols { + for _, c := range opts.Columns { switch c { case "profile": row = append(row, profile)