diff --git a/cli/cmd/fetch.go b/cli/cmd/fetch.go index 89faa545e..184dc1450 100644 --- a/cli/cmd/fetch.go +++ b/cli/cmd/fetch.go @@ -1,30 +1,23 @@ package cmd import ( + "github.com/creasty/defaults" "github.com/spf13/cobra" "github.com/up9inc/mizu/cli/mizu" + "github.com/up9inc/mizu/cli/mizu/configStructs" ) -type MizuFetchOptions struct { - FromTimestamp int64 - ToTimestamp int64 - Directory string - MizuPort uint16 -} - -var mizuFetchOptions = MizuFetchOptions{} - var fetchCmd = &cobra.Command{ Use: "fetch", Short: "Download recorded traffic to files", RunE: func(cmd *cobra.Command, args []string) error { - go mizu.ReportRun("fetch", mizuTapOptions) - if isCompatible, err := mizu.CheckVersionCompatibility(mizuFetchOptions.MizuPort); err != nil { + go mizu.ReportRun("fetch", mizu.Config.Fetch) + if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.MizuPort); err != nil { return err } else if !isCompatible { return nil } - RunMizuFetch(&mizuFetchOptions) + RunMizuFetch() return nil }, } @@ -32,8 +25,11 @@ var fetchCmd = &cobra.Command{ func init() { rootCmd.AddCommand(fetchCmd) - fetchCmd.Flags().StringVarP(&mizuFetchOptions.Directory, "directory", "d", ".", "Provide a custom directory for fetched entries") - fetchCmd.Flags().Int64Var(&mizuFetchOptions.FromTimestamp, "from", 0, "Custom start timestamp for fetched entries") - fetchCmd.Flags().Int64Var(&mizuFetchOptions.ToTimestamp, "to", 0, "Custom end timestamp fetched entries") - fetchCmd.Flags().Uint16VarP(&mizuFetchOptions.MizuPort, "port", "p", 8899, "Custom port for mizu") + defaultFetchConfig := configStructs.FetchConfig{} + defaults.Set(&defaultFetchConfig) + + fetchCmd.Flags().StringP(configStructs.DirectoryFetchName, "d", defaultFetchConfig.Directory, "Provide a custom directory for fetched entries") + fetchCmd.Flags().Int(configStructs.FromTimestampFetchName, defaultFetchConfig.FromTimestamp, "Custom start timestamp for fetched entries") + fetchCmd.Flags().Int(configStructs.ToTimestampFetchName, defaultFetchConfig.ToTimestamp, "Custom end timestamp fetched entries") + fetchCmd.Flags().Uint16P(configStructs.MizuPortFetchName, "p", defaultFetchConfig.MizuPort, "Custom port for mizu") } diff --git a/cli/cmd/fetchRunner.go b/cli/cmd/fetchRunner.go index fd7c6c72b..3cc966481 100644 --- a/cli/cmd/fetchRunner.go +++ b/cli/cmd/fetchRunner.go @@ -15,9 +15,9 @@ import ( "strings" ) -func RunMizuFetch(fetch *MizuFetchOptions) { - mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(fetch.MizuPort) - resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, fetch.FromTimestamp, fetch.ToTimestamp)) +func RunMizuFetch() { + mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort) + resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, mizu.Config.Fetch.FromTimestamp, mizu.Config.Fetch.ToTimestamp)) if err != nil { log.Fatal(err) } @@ -33,8 +33,8 @@ func RunMizuFetch(fetch *MizuFetchOptions) { if err != nil { log.Fatal(err) } - _ = Unzip(zipReader, fetch.Directory) + _ = Unzip(zipReader, mizu.Config.Fetch.Directory) } func Unzip(reader *zip.Reader, dest string) error { diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 0e311a312..434248263 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -7,26 +7,23 @@ import ( "github.com/up9inc/mizu/cli/mizu" ) -var commandLineFlags []string - var rootCmd = &cobra.Command{ Use: "mizu", Short: "A web traffic viewer for kubernetes", Long: `A web traffic viewer for kubernetes Further info is available at https://github.com/up9inc/mizu`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if err := mizu.InitConfig(commandLineFlags); err != nil { + if err := mizu.InitConfig(cmd); err != nil { mizu.Log.Errorf("Invalid config, Exit %s", err) return errors.New(fmt.Sprintf("%v", err)) } - prettifiedConfig := mizu.GetConfigStr() - mizu.Log.Debugf("Final Config: %s", prettifiedConfig) + return nil }, } func init() { - rootCmd.PersistentFlags().StringSliceVar(&commandLineFlags, "set", []string{}, "Override values using --set") + rootCmd.PersistentFlags().StringSlice(mizu.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", mizu.SetCommandName)) } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cli/cmd/tap.go b/cli/cmd/tap.go index 16072facb..608392f20 100644 --- a/cli/cmd/tap.go +++ b/cli/cmd/tap.go @@ -2,39 +2,14 @@ package cmd import ( "errors" - "fmt" + "github.com/creasty/defaults" "github.com/spf13/cobra" "github.com/up9inc/mizu/cli/mizu" + "github.com/up9inc/mizu/cli/mizu/configStructs" "github.com/up9inc/mizu/cli/uiUtils" - "github.com/up9inc/mizu/shared/units" "os" - "regexp" - "strings" ) -type MizuTapOptions struct { - GuiPort uint16 - Namespace string - AllNamespaces bool - Analysis bool - AnalysisDestination string - KubeConfigPath string - MizuImage string - PlainTextFilterRegexes []string - TapOutgoing bool - HideHealthChecks bool - MaxEntriesDBSizeBytes int64 - SleepIntervalSec uint16 - DisableRedaction bool -} - -var mizuTapOptions = &MizuTapOptions{} -var direction string -var humanMaxEntriesDBSize string -var regex *regexp.Regexp - -const maxEntriesDBSizeFlagName = "max-entries-db-size" - const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.` var tapCmd = &cobra.Command{ @@ -43,53 +18,31 @@ var tapCmd = &cobra.Command{ Long: `Record the ingoing traffic of a kubernetes pod. Supported protocols are HTTP and gRPC.`, RunE: func(cmd *cobra.Command, args []string) error { - go mizu.ReportRun("tap", mizuTapOptions) - RunMizuTap(regex, mizuTapOptions) + go mizu.ReportRun("tap", mizu.Config.Tap) + RunMizuTap() return nil - }, PreRunE: func(cmd *cobra.Command, args []string) error { - mizu.Log.Debugf("Getting params") - mizuTapOptions.AnalysisDestination = mizu.GetString(mizu.ConfigurationKeyAnalyzingDestination) - mizuTapOptions.SleepIntervalSec = uint16(mizu.GetInt(mizu.ConfigurationKeyUploadInterval)) - mizuTapOptions.MizuImage = mizu.GetString(mizu.ConfigurationKeyMizuImage) - mizu.Log.Debugf(uiUtils.PrettyJson(mizuTapOptions)) - - if len(args) == 0 { - return errors.New("POD REGEX argument is required") + if len(args) == 1 { + mizu.Config.Tap.PodRegexStr = args[0] } else if len(args) > 1 { return errors.New("unexpected number of arguments") } - var compileErr error - regex, compileErr = regexp.Compile(args[0]) - if compileErr != nil { - return errors.New(fmt.Sprintf("%s is not a valid regex %s", args[0], compileErr)) + if err := mizu.Config.Tap.Validate(); err != nil { + return err } - var parseHumanDataSizeErr error - mizuTapOptions.MaxEntriesDBSizeBytes, parseHumanDataSizeErr = units.HumanReadableToBytes(humanMaxEntriesDBSize) - if parseHumanDataSizeErr != nil { - return errors.New(fmt.Sprintf("Could not parse --max-entries-db-size value %s", humanMaxEntriesDBSize)) - } - mizu.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", units.BytesToHumanReadable(mizuTapOptions.MaxEntriesDBSizeBytes)) + mizu.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", mizu.Config.Tap.HumanMaxEntriesDBSize) - directionLowerCase := strings.ToLower(direction) - if directionLowerCase == "any" { - mizuTapOptions.TapOutgoing = true - } else if directionLowerCase == "in" { - mizuTapOptions.TapOutgoing = false - } else { - return errors.New(fmt.Sprintf("%s is not a valid value for flag --direction. Acceptable values are in/any.", direction)) - } - - if mizuTapOptions.Analysis { + if mizu.Config.Tap.Analysis { mizu.Log.Infof(analysisMessageToConfirm) if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") { mizu.Log.Infof("You can always run mizu without analysis, aborting") os.Exit(0) } } + return nil }, } @@ -97,14 +50,18 @@ Supported protocols are HTTP and gRPC.`, func init() { rootCmd.AddCommand(tapCmd) - tapCmd.Flags().Uint16VarP(&mizuTapOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver") - tapCmd.Flags().StringVarP(&mizuTapOptions.Namespace, "namespace", "n", "", "Namespace selector") - tapCmd.Flags().BoolVar(&mizuTapOptions.Analysis, "analysis", false, "Uploads traffic to UP9 for further analysis (Beta)") - tapCmd.Flags().BoolVarP(&mizuTapOptions.AllNamespaces, "all-namespaces", "A", false, "Tap all namespaces") - tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file") - tapCmd.Flags().StringArrayVarP(&mizuTapOptions.PlainTextFilterRegexes, "regex-masking", "r", nil, "List of regex expressions that are used to filter matching values from text/plain http bodies") - tapCmd.Flags().StringVarP(&direction, "direction", "", "in", "Record traffic that goes in this direction (relative to the tapped pod): in/any") - tapCmd.Flags().BoolVar(&mizuTapOptions.HideHealthChecks, "hide-healthchecks", false, "hides requests with kube-probe or prometheus user-agent headers") - tapCmd.Flags().StringVarP(&humanMaxEntriesDBSize, maxEntriesDBSizeFlagName, "", "200MB", "override the default max entries db size of 200mb") - tapCmd.Flags().BoolVar(&mizuTapOptions.DisableRedaction, "no-redact", false, "Disables redaction of potentially sensitive request/response headers and body values") + defaultTapConfig := configStructs.TapConfig{} + defaults.Set(&defaultTapConfig) + + tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver") + tapCmd.Flags().StringP(configStructs.NamespaceTapName, "n", defaultTapConfig.Namespace, "Namespace selector") + tapCmd.Flags().Bool(configStructs.AnalysisTapName, defaultTapConfig.Analysis, "Uploads traffic to UP9 for further analysis (Beta)") + tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces") + tapCmd.Flags().StringP(configStructs.KubeConfigPathTapName, "k", defaultTapConfig.KubeConfigPath, "Path to kube-config file") + tapCmd.Flags().StringArrayP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies") + tapCmd.Flags().Bool(configStructs.HideHealthChecksTapName, defaultTapConfig.HideHealthChecks, "hides requests with kube-probe or prometheus user-agent headers") + tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values") + tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "override the default max entries db size of 200mb") + tapCmd.Flags().String(configStructs.DirectionTapName, defaultTapConfig.Direction, "Record traffic that goes in this direction (relative to the tapped pod): in/any") + tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them") } diff --git a/cli/cmd/tapRunner.go b/cli/cmd/tapRunner.go index 5e4ddc29f..d2b11b304 100644 --- a/cli/cmd/tapRunner.go +++ b/cli/cmd/tapRunner.go @@ -11,7 +11,6 @@ import ( core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/clientcmd" - "log" "net/http" "net/url" "os" @@ -31,13 +30,13 @@ const ( var currentlyTappedPods []core.Pod -func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { - mizuApiFilteringOptions, err := getMizuApiFilteringOptions(tappingOptions) +func RunMizuTap() { + mizuApiFilteringOptions, err := getMizuApiFilteringOptions() if err != nil { return } - kubernetesProvider, err := kubernetes.NewProvider(tappingOptions.KubeConfigPath) + kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.Tap.KubeConfigPath) if err != nil { if clientcmd.IsEmptyConfig(err) { mizu.Log.Infof(uiUtils.Red, "Couldn't find the kube config file, or file is empty. Try adding '--kube-config='\n") @@ -53,17 +52,21 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel will be called when this function exits - targetNamespace := getNamespace(tappingOptions, kubernetesProvider) - if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, podRegexQuery, targetNamespace); err != nil { + targetNamespace := getNamespace(kubernetesProvider) + if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespace); err != nil { mizu.Log.Infof("Error listing pods: %v", err) return } + + if mizu.Config.Tap.DryRun { + return + } + urlReadyChan := make(chan string) go func() { mizu.Log.Infof("Mizu is available at http://%s", <-urlReadyChan) }() - var namespacesStr string if targetNamespace != mizu.K8sAllNamespaces { namespacesStr = fmt.Sprintf("namespace \"%s\"", targetNamespace) @@ -85,29 +88,29 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { return } - if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions, mizuApiFilteringOptions); err != nil { + if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil { return } mizu.CheckNewerVersion() - go portForwardApiPod(ctx, kubernetesProvider, cancel, tappingOptions, urlReadyChan) // TODO convert this to job for built in pod ttl or have the running app handle this - go watchPodsForTapping(ctx, kubernetesProvider, cancel, podRegexQuery, tappingOptions) - go syncApiStatus(ctx, cancel, tappingOptions) + go portForwardApiPod(ctx, kubernetesProvider, cancel, urlReadyChan) // TODO convert this to job for built in pod ttl or have the running app handle this + go watchPodsForTapping(ctx, kubernetesProvider, cancel) + go syncApiStatus(ctx, cancel) //block until exit signal or error waitForFinish(ctx, cancel) } -func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { +func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { if err := createMizuNamespace(ctx, kubernetesProvider); err != nil { return err } - if err := createMizuApiServer(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil { + if err := createMizuApiServer(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil { return err } - if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil { + if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil { return err } @@ -123,7 +126,7 @@ func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Pro return err } -func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { +func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error { var err error mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider) @@ -133,7 +136,7 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro } else { serviceAccountName = "" } - _, err = kubernetesProvider.CreateMizuApiServerPod(ctx, mizu.ResourcesNamespace, mizu.ApiServerPodName, tappingOptions.MizuImage, serviceAccountName, mizuApiFilteringOptions, tappingOptions.MaxEntriesDBSizeBytes) + _, err = kubernetesProvider.CreateMizuApiServerPod(ctx, mizu.ResourcesNamespace, mizu.ApiServerPodName, mizu.Config.MizuImage, serviceAccountName, mizuApiFilteringOptions, mizu.Config.Tap.MaxEntriesDBSizeBytes()) if err != nil { mizu.Log.Infof("Error creating mizu %s pod: %v", mizu.ApiServerPodName, err) return err @@ -148,12 +151,12 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro return nil } -func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.TrafficFilteringOptions, error) { +func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) { var compiledRegexSlice []*shared.SerializableRegexp - if tappingOptions.PlainTextFilterRegexes != nil && len(tappingOptions.PlainTextFilterRegexes) > 0 { + if mizu.Config.Tap.PlainTextFilterRegexes != nil && len(mizu.Config.Tap.PlainTextFilterRegexes) > 0 { compiledRegexSlice = make([]*shared.SerializableRegexp, 0) - for _, regexStr := range tappingOptions.PlainTextFilterRegexes { + for _, regexStr := range mizu.Config.Tap.PlainTextFilterRegexes { compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr) if err != nil { mizu.Log.Infof("Regex %s is invalid: %v", regexStr, err) @@ -163,10 +166,10 @@ func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.Traffic } } - return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice, HideHealthChecks: tappingOptions.HideHealthChecks, DisableRedaction: tappingOptions.DisableRedaction}, nil + return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice, HideHealthChecks: mizu.Config.Tap.HideHealthChecks, DisableRedaction: mizu.Config.Tap.DisableRedaction}, nil } -func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions) error { +func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string) error { if len(nodeToTappedPodIPMap) > 0 { var serviceAccountName string if mizuServiceAccountExists { @@ -179,12 +182,12 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi ctx, mizu.ResourcesNamespace, mizu.TapperDaemonSetName, - tappingOptions.MizuImage, + mizu.Config.MizuImage, mizu.TapperPodName, fmt.Sprintf("%s.%s.svc.cluster.local", apiServerService.Name, apiServerService.Namespace), nodeToTappedPodIPMap, serviceAccountName, - tappingOptions.TapOutgoing, + mizu.Config.Tap.TapOutgoing(), ); err != nil { mizu.Log.Infof("Error creating mizu tapper daemonset: %v", err) return err @@ -234,13 +237,12 @@ func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) { } } -func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, podRegex *regexp.Regexp, tappingOptions *MizuTapOptions) { - targetNamespace := getNamespace(tappingOptions, kubernetesProvider) - - added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, targetNamespace), podRegex) +func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) { + targetNamespace := getNamespace(kubernetesProvider) + added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, targetNamespace), mizu.Config.Tap.PodRegex()) restartTappers := func() { - if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, podRegex, targetNamespace); err != nil { + if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespace); err != nil { mizu.Log.Infof("Error getting pods by regex: %s (%v,%+v)", err, err, err) cancel() } @@ -251,7 +253,7 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro cancel() } - if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil { + if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil { mizu.Log.Infof("Error updating daemonset: %s (%v,%+v)", err, err, err) cancel() } @@ -284,8 +286,8 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro } } -func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, podRegex *regexp.Regexp, targetNamespace string) error { - if matchingPods, err := kubernetesProvider.GetAllRunningPodsMatchingRegex(ctx, podRegex, targetNamespace); err != nil { +func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespace string) error { + if matchingPods, err := kubernetesProvider.GetAllRunningPodsMatchingRegex(ctx, mizu.Config.Tap.PodRegex(), targetNamespace); err != nil { mizu.Log.Infof("Error getting pods by regex: %s (%v,%+v)", err, err, err) return err } else { @@ -298,6 +300,7 @@ func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx cont } currentlyTappedPods = matchingPods } + return nil } @@ -326,16 +329,16 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod { return missingPods } -func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, tappingOptions *MizuTapOptions, urlReadyChan chan string) { +func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, urlReadyChan chan string) { podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName)) added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, mizu.ResourcesNamespace), podExactRegex) isPodReady := false timeAfter := time.After(25 * time.Second) + for { select { case <-ctx.Done(): return - case <-added: continue case <-removed: @@ -346,43 +349,45 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi if modifiedPod.Status.Phase == "Running" && !isPodReady { isPodReady = true go func() { - err := kubernetes.StartProxy(kubernetesProvider, tappingOptions.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName) + err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName) if err != nil { mizu.Log.Infof("Error occurred while running k8s proxy %v", err) cancel() } }() } - urlReadyChan <- kubernetes.GetMizuApiServerProxiedHostAndPath(tappingOptions.GuiPort) - time.Sleep(time.Second * 5) // Waiting to be sure the proxy is ready - requestForAnalysis(tappingOptions) + urlReadyChan <- kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort) + time.Sleep(time.Second * 5) // Waiting to be sure the proxy is ready + requestForAnalysis() case <-timeAfter: if !isPodReady { mizu.Log.Errorf("error: %s pod was not ready in time", mizu.ApiServerPodName) cancel() } - case <-errorChan: cancel() } } } -func requestForAnalysis(tappingOptions *MizuTapOptions) { - if !tappingOptions.Analysis { +func requestForAnalysis() { + if !mizu.Config.Tap.Analysis { return } - mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(tappingOptions.GuiPort) - urlPath := fmt.Sprintf("http://%s/api/uploadEntries?dest=%s&interval=%v", mizuProxiedUrl, url.QueryEscape(tappingOptions.AnalysisDestination), tappingOptions.SleepIntervalSec) - u, err := url.ParseRequestURI(urlPath) - if err != nil { - log.Fatal(fmt.Sprintf("Failed parsing the URL %v\n", err)) + mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort) + urlPath := fmt.Sprintf("http://%s/api/uploadEntries?dest=%s&interval=%v", mizuProxiedUrl, url.QueryEscape(mizu.Config.Tap.AnalysisDestination), mizu.Config.Tap.SleepIntervalSec) + u, parseErr := url.ParseRequestURI(urlPath) + if parseErr != nil { + mizu.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr) } + mizu.Log.Debugf("Sending get request to %v", u.String()) - if response, err := http.Get(u.String()); err != nil || response.StatusCode != 200 { - mizu.Log.Infof("error sending upload entries req, status code: %v, err: %v", response.StatusCode, err) + if response, requestErr := http.Get(u.String()); requestErr != nil { + mizu.Log.Errorf("Failed to notify agent for analysis, err: %v", requestErr) + } else if response.StatusCode != 200 { + mizu.Log.Errorf("Failed to notify agent for analysis, status code: %v", response.StatusCode) } else { mizu.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis") } @@ -430,8 +435,8 @@ func waitForFinish(ctx context.Context, cancel context.CancelFunc) { } } -func syncApiStatus(ctx context.Context, cancel context.CancelFunc, tappingOptions *MizuTapOptions) { - controlSocketStr := fmt.Sprintf("ws://%s/ws", kubernetes.GetMizuApiServerProxiedHostAndPath(tappingOptions.GuiPort)) +func syncApiStatus(ctx context.Context, cancel context.CancelFunc) { + controlSocketStr := fmt.Sprintf("ws://%s/ws", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)) controlSocket, err := mizu.CreateControlSocket(controlSocketStr) if err != nil { mizu.Log.Infof("error establishing control socket connection %s", err) @@ -452,11 +457,11 @@ func syncApiStatus(ctx context.Context, cancel context.CancelFunc, tappingOption } } -func getNamespace(tappingOptions *MizuTapOptions, kubernetesProvider *kubernetes.Provider) string { - if tappingOptions.AllNamespaces { +func getNamespace(kubernetesProvider *kubernetes.Provider) string { + if mizu.Config.Tap.AllNamespaces { return mizu.K8sAllNamespaces - } else if len(tappingOptions.Namespace) > 0 { - return tappingOptions.Namespace + } else if len(mizu.Config.Tap.Namespace) > 0 { + return mizu.Config.Tap.Namespace } else { return kubernetesProvider.CurrentNamespace() } diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 3a7a68667..9b0fe8b51 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -1,24 +1,20 @@ package cmd import ( + "github.com/creasty/defaults" "github.com/spf13/cobra" "github.com/up9inc/mizu/cli/mizu" + "github.com/up9inc/mizu/cli/mizu/configStructs" "strconv" "time" ) -type MizuVersionOptions struct { - DebugInfo bool -} - -var mizuVersionOptions = &MizuVersionOptions{} - var versionCmd = &cobra.Command{ Use: "version", Short: "Print version info", RunE: func(cmd *cobra.Command, args []string) error { - go mizu.ReportRun("version", mizuVersionOptions) - if mizuVersionOptions.DebugInfo { + go mizu.ReportRun("version", mizu.Config.Version) + if mizu.Config.Version.DebugInfo { timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0) mizu.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash) mizu.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0)) @@ -33,6 +29,9 @@ var versionCmd = &cobra.Command{ func init() { rootCmd.AddCommand(versionCmd) - versionCmd.Flags().BoolVarP(&mizuVersionOptions.DebugInfo, "debug", "d", false, "Provide all information about version") + defaultVersionConfig := configStructs.VersionConfig{} + defaults.Set(&defaultVersionConfig) + + versionCmd.Flags().BoolP(configStructs.DebugInfoVersionName, "d", defaultVersionConfig.DebugInfo, "Provide all information about version") } diff --git a/cli/cmd/view.go b/cli/cmd/view.go index 8d6bfd0fe..86ee5bf42 100644 --- a/cli/cmd/view.go +++ b/cli/cmd/view.go @@ -1,28 +1,23 @@ package cmd import ( + "github.com/creasty/defaults" "github.com/spf13/cobra" "github.com/up9inc/mizu/cli/mizu" + "github.com/up9inc/mizu/cli/mizu/configStructs" ) -type MizuViewOptions struct { - GuiPort uint16 - KubeConfigPath string -} - -var mizuViewOptions = &MizuViewOptions{} - var viewCmd = &cobra.Command{ Use: "view", Short: "Open GUI in browser", RunE: func(cmd *cobra.Command, args []string) error { - go mizu.ReportRun("view", mizuViewOptions) - if isCompatible, err := mizu.CheckVersionCompatibility(mizuViewOptions.GuiPort); err != nil { + go mizu.ReportRun("view", mizu.Config.View) + if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil { return err } else if !isCompatible { return nil } - runMizuView(mizuViewOptions) + runMizuView() return nil }, } @@ -30,6 +25,9 @@ var viewCmd = &cobra.Command{ func init() { rootCmd.AddCommand(viewCmd) - viewCmd.Flags().Uint16VarP(&mizuViewOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver") - viewCmd.Flags().StringVarP(&mizuViewOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file") + defaultViewConfig := configStructs.ViewConfig{} + defaults.Set(&defaultViewConfig) + + viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver") + viewCmd.Flags().StringP(configStructs.KubeConfigPathViewName, "k", defaultViewConfig.KubeConfigPath, "Path to kube-config file") } diff --git a/cli/cmd/viewRunner.go b/cli/cmd/viewRunner.go index cbb9bc7fd..e97c2e719 100644 --- a/cli/cmd/viewRunner.go +++ b/cli/cmd/viewRunner.go @@ -10,8 +10,8 @@ import ( "net/http" ) -func runMizuView(mizuViewOptions *MizuViewOptions) { - kubernetesProvider, err := kubernetes.NewProvider(mizuViewOptions.KubeConfigPath) +func runMizuView() { + kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath) if err != nil { if clientcmd.IsEmptyConfig(err) { mizu.Log.Infof("Couldn't find the kube config file, or file is empty. Try adding '--kube-config='") @@ -35,16 +35,16 @@ func runMizuView(mizuViewOptions *MizuViewOptions) { return } - mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizuViewOptions.GuiPort) + mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort) _, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl)) if err == nil { - mizu.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, mizuViewOptions.GuiPort) + mizu.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, mizu.Config.View.GuiPort) return } mizu.Log.Infof("Found service %s, creating k8s proxy", mizu.ApiServerPodName) - mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizuViewOptions.GuiPort)) - err = kubernetes.StartProxy(kubernetesProvider, mizuViewOptions.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName) + mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort)) + err = kubernetes.StartProxy(kubernetesProvider, mizu.Config.View.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName) if err != nil { mizu.Log.Infof("Error occured while running k8s proxy %v", err) } diff --git a/cli/go.mod b/cli/go.mod index 436fce829..9879f012e 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -3,10 +3,12 @@ module github.com/up9inc/mizu/cli go 1.16 require ( + github.com/creasty/defaults v1.5.1 github.com/google/go-github/v37 v37.0.0 github.com/gorilla/websocket v1.4.2 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/spf13/cobra v1.1.3 + github.com/spf13/pflag v1.0.5 github.com/up9inc/mizu/shared v0.0.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.21.2 diff --git a/cli/go.sum b/cli/go.sum index cef04c856..1cbdcbeae 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -82,6 +82,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creasty/defaults v1.5.1 h1:j8WexcS3d/t4ZmllX4GEkl4wIB/trOr035ajcLHCISM= +github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= diff --git a/cli/mizu/config.go b/cli/mizu/config.go index 566124065..1809abb0c 100644 --- a/cli/mizu/config.go +++ b/cli/mizu/config.go @@ -3,6 +3,9 @@ package mizu import ( "errors" "fmt" + "github.com/creasty/defaults" + "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/up9inc/mizu/cli/uiUtils" "gopkg.in/yaml.v3" "io/ioutil" @@ -13,209 +16,182 @@ import ( "strings" ) -const separator = "=" - -var configObj = map[string]interface{}{} - -type CommandLineFlag struct { - CommandLineName string - YamlHierarchyName string - DefaultValue interface{} -} - const ( - ConfigurationKeyAnalyzingDestination = "tap.dest" - ConfigurationKeyUploadInterval = "tap.uploadInterval" - ConfigurationKeyMizuImage = "mizuImage" - ConfigurationKeyTelemetry = "telemetry" + Separator = "=" + SetCommandName = "set" ) -var allowedSetFlags = []CommandLineFlag{ - { - CommandLineName: "dest", - YamlHierarchyName: ConfigurationKeyAnalyzingDestination, - DefaultValue: "up9.app", - // TODO: maybe add short description that we can show - }, - { - CommandLineName: "uploadInterval", - YamlHierarchyName: ConfigurationKeyUploadInterval, - DefaultValue: 10, - }, - { - CommandLineName: "mizuImage", - YamlHierarchyName: ConfigurationKeyMizuImage, - DefaultValue: fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", Branch, SemVer), - }, - { - CommandLineName: "telemetry", - YamlHierarchyName: ConfigurationKeyTelemetry, - DefaultValue: true, - }, -} +var Config = ConfigStruct{} -func GetString(key string) string { - return fmt.Sprintf("%v", getValueFromMergedConfig(key)) -} - -func GetBool(key string) bool { - stringVal := GetString(key) - Log.Debugf("Found string value %v", stringVal) - - val, err := strconv.ParseBool(stringVal) - if err != nil { - Log.Warningf(uiUtils.Red, fmt.Sprintf( "Invalid value %v for key %s, expected bool", stringVal, key)) - os.Exit(1) +func InitConfig(cmd *cobra.Command) error { + if err := defaults.Set(&Config); err != nil { + return err } - return val -} -func GetInt(key string) int { - stringVal := GetString(key) - Log.Debugf("Found string value %v", stringVal) + if err := mergeConfigFile(); err != nil { + Log.Infof(uiUtils.Red, "Invalid config file") + return err + } - val, err := strconv.Atoi(stringVal) - if err != nil { - Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for key %s, expected int", stringVal, key)) - os.Exit(1) - } - return val -} + cmd.Flags().Visit(initFlag) -func InitConfig(commandLineValues []string) error { - Log.Debugf("Merging default values") - mergeDefaultValues() - Log.Debugf("Merging config file values") - if err1 := mergeConfigFile(); err1 != nil { - Log.Infof(fmt.Sprintf(uiUtils.Red, "Invalid config file\n")) - return err1 - } - Log.Debugf("Merging command line values") - if err2 := mergeCommandLineFlags(commandLineValues); err2 != nil { - Log.Infof(fmt.Sprintf(uiUtils.Red, "Invalid commanad argument\n")) - return err2 - } - finalConfigPrettified, _ := uiUtils.PrettyJson(configObj) + finalConfigPrettified, _ := uiUtils.PrettyJson(Config) Log.Debugf("Merged all config successfully\n Final config: %v", finalConfigPrettified) + return nil } func GetTemplateConfig() string { - templateConfig := map[string]interface{}{} - for _, allowedFlag := range allowedSetFlags { - addToConfigObj(allowedFlag.YamlHierarchyName, allowedFlag.DefaultValue, templateConfig) - } - prettifiedConfig, _ := uiUtils.PrettyYaml(templateConfig) + prettifiedConfig, _ := uiUtils.PrettyYaml(Config) return prettifiedConfig } -func GetConfigStr() string { - val, _ := uiUtils.PrettyYaml(configObj) - return val -} - -func getValueFromMergedConfig(key string) interface{} { - if a, ok := configObj[key]; ok { - return a - } - return nil -} - -func mergeDefaultValues() { - for _, allowedFlag := range allowedSetFlags { - Log.Debugf("Setting %v to %v", allowedFlag.YamlHierarchyName, allowedFlag.DefaultValue) - configObj[allowedFlag.YamlHierarchyName] = allowedFlag.DefaultValue - } -} - func mergeConfigFile() error { - Log.Debugf("Merging mizu config file values") + Log.Debugf("Merging config file values") home, homeDirErr := os.UserHomeDir() if homeDirErr != nil { - return nil + return homeDirErr } + reader, openErr := os.Open(path.Join(home, ".mizu", "config.yaml")) if openErr != nil { - return nil + return openErr } + buf, readErr := ioutil.ReadAll(reader) if readErr != nil { return readErr } - m := make(map[string]interface{}) - if err := yaml.Unmarshal(buf, &m); err != nil { + + if err := yaml.Unmarshal(buf, &Config); err != nil { return err } - for k, v := range m { - addToConfig(k, v) - } + return nil } -func addToConfig(prefix string, value interface{}) { - typ := reflect.TypeOf(value).Kind() - if typ == reflect.Map { - for k1, v1 := range value.(map[string]interface{}) { - addToConfig(fmt.Sprintf("%s.%s", prefix, k1), v1) - } - } else { - validateConfigFileKey(prefix) - configObj[prefix] = value +func initFlag(f *pflag.Flag) { + configElem := reflect.ValueOf(&Config).Elem() + + sliceValue, isSliceValue := f.Value.(pflag.SliceValue) + if !isSliceValue { + mergeFlagValue(configElem, f.Name, f.Value.String()) + return } + + if f.Name == SetCommandName { + if setError := mergeSetFlag(sliceValue.GetSlice()); setError != nil { + Log.Infof(uiUtils.Red, "Invalid set argument") + } + return + } + + mergeFlagValues(configElem, f.Name, sliceValue.GetSlice()) } -func mergeCommandLineFlags(commandLineValues []string) error { - Log.Debugf("Merging Command line flags") - for _, e := range commandLineValues { - if !strings.Contains(e, separator) { - return errors.New(fmt.Sprintf("invalid set argument %s", e)) +func mergeSetFlag(setValues []string) error { + configElem := reflect.ValueOf(&Config).Elem() + + for _, setValue := range setValues { + if !strings.Contains(setValue, Separator) { + return errors.New(fmt.Sprintf("invalid set argument %s", setValue)) } - split := strings.SplitN(e, separator, 2) + + split := strings.SplitN(setValue, Separator, 2) if len(split) != 2 { - return errors.New(fmt.Sprintf("invalid set argument %s", e)) - } - setFlagKey, argumentValue := split[0], split[1] - argumentNameInConfig, err := flagFromAllowed(setFlagKey) - if err != nil { - return err + return errors.New(fmt.Sprintf("invalid set argument %s", setValue)) } - configObj[argumentNameInConfig] = argumentValue + argumentKey, argumentValue := split[0], split[1] + mergeFlagValue(configElem, argumentKey, argumentValue) } + return nil } -func flagFromAllowed(setFlagKey string) (string, error) { - for _, allowedFlag := range allowedSetFlags { - if strings.ToLower(allowedFlag.CommandLineName) == strings.ToLower(setFlagKey) { - return allowedFlag.YamlHierarchyName, nil - } - } - return "", errors.New(fmt.Sprintf("invalid set argument %s", setFlagKey)) -} +func mergeFlagValue(currentElem reflect.Value, flagKey string, flagValue string) { + for i := 0; i < currentElem.NumField(); i++ { + currentField := currentElem.Type().Field(i) + currentFieldByName := currentElem.FieldByName(currentField.Name) -func validateConfigFileKey(configFileKey string) { - for _, allowedFlag := range allowedSetFlags { - if allowedFlag.YamlHierarchyName == configFileKey { + if currentField.Type.Kind() == reflect.Struct { + mergeFlagValue(currentFieldByName, flagKey, flagValue) + continue + } + + if currentField.Tag.Get("yaml") != flagKey { + continue + } + + flagValueKind := currentField.Type.Kind() + + parsedValue, err := getParsedValue(flagValueKind, flagValue) + if err != nil { + Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for key %s, expected %s", flagValue, flagKey, flagValueKind)) return } + + currentFieldByName.Set(parsedValue) } - Log.Info(fmt.Sprintf("Unknown argument: %s. Exit", configFileKey)) - os.Exit(1) } -func addToConfigObj(key string, value interface{}, configObj map[string]interface{}) { - typ := reflect.TypeOf(value).Kind() - if typ != reflect.Map { - if strings.Contains(key, ".") { - split := strings.SplitN(key, ".", 2) - firstLevelKey := split[0] - if _, ok := configObj[firstLevelKey]; !ok { - configObj[firstLevelKey] = map[string]interface{}{} - } - addToConfigObj(split[1], value, configObj[firstLevelKey].(map[string]interface{})) - } else { - configObj[key] = value +func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []string) { + for i := 0; i < currentElem.NumField(); i++ { + currentField := currentElem.Type().Field(i) + currentFieldByName := currentElem.FieldByName(currentField.Name) + + if currentField.Type.Kind() == reflect.Struct { + mergeFlagValues(currentFieldByName, flagKey, flagValues) + continue } + + if currentField.Tag.Get("yaml") != flagKey { + continue + } + + flagValueKind := currentField.Type.Elem().Kind() + + parsedValues := reflect.MakeSlice(reflect.SliceOf(currentField.Type.Elem()), 0, 0) + for _, flagValue := range flagValues { + parsedValue, err := getParsedValue(flagValueKind, flagValue) + if err != nil { + Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for key %s, expected %s", flagValue, flagKey, flagValueKind)) + return + } + + parsedValues = reflect.Append(parsedValues, parsedValue) + } + + currentFieldByName.Set(parsedValues) } } + +func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) { + switch kind { + case reflect.String: + return reflect.ValueOf(value), nil + case reflect.Bool: + boolArgumentValue, err := strconv.ParseBool(value) + if err != nil { + break + } + + return reflect.ValueOf(boolArgumentValue), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intArgumentValue, err := strconv.ParseInt(value, 10, 64) + if err != nil { + break + } + + return reflect.ValueOf(intArgumentValue), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uintArgumentValue, err := strconv.ParseUint(value, 10, 64) + if err != nil { + break + } + + return reflect.ValueOf(uintArgumentValue), nil + } + + return reflect.ValueOf(nil), errors.New("value to parse does not match type") +} diff --git a/cli/mizu/configStruct.go b/cli/mizu/configStruct.go new file mode 100644 index 000000000..2a08f44aa --- /dev/null +++ b/cli/mizu/configStruct.go @@ -0,0 +1,19 @@ +package mizu + +import ( + "fmt" + "github.com/up9inc/mizu/cli/mizu/configStructs" +) + +type ConfigStruct struct { + Tap configStructs.TapConfig `yaml:"tap"` + Fetch configStructs.FetchConfig `yaml:"fetch"` + Version configStructs.VersionConfig `yaml:"version"` + View configStructs.ViewConfig `yaml:"view"` + MizuImage string `yaml:"mizu-image"` + Telemetry bool `yaml:"telemetry" default:"true"` +} + +func (config *ConfigStruct) SetDefaults() { + config.MizuImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", Branch, SemVer) +} diff --git a/cli/mizu/configStructs/fetchConfig.go b/cli/mizu/configStructs/fetchConfig.go new file mode 100644 index 000000000..8d2eab946 --- /dev/null +++ b/cli/mizu/configStructs/fetchConfig.go @@ -0,0 +1,15 @@ +package configStructs + +const ( + DirectoryFetchName = "directory" + FromTimestampFetchName = "from" + ToTimestampFetchName = "to" + MizuPortFetchName = "port" +) + +type FetchConfig struct { + Directory string `yaml:"directory" default:"."` + FromTimestamp int `yaml:"from" default:"0"` + ToTimestamp int `yaml:"to" default:"0"` + MizuPort uint16 `yaml:"port" default:"8899"` +} diff --git a/cli/mizu/configStructs/tapConfig.go b/cli/mizu/configStructs/tapConfig.go new file mode 100644 index 000000000..ee4fda63f --- /dev/null +++ b/cli/mizu/configStructs/tapConfig.go @@ -0,0 +1,78 @@ +package configStructs + +import ( + "errors" + "fmt" + "github.com/up9inc/mizu/shared/units" + "regexp" + "strings" +) + +const ( + GuiPortTapName = "gui-port" + NamespaceTapName = "namespace" + AnalysisTapName = "analysis" + AllNamespacesTapName = "all-namespaces" + KubeConfigPathTapName = "kube-config" + PlainTextFilterRegexesTapName = "regex-masking" + HideHealthChecksTapName = "hide-healthchecks" + DisableRedactionTapName = "no-redact" + HumanMaxEntriesDBSizeTapName = "max-entries-db-size" + DirectionTapName = "direction" + DryRunTapName = "dry-run" +) + +type TapConfig struct { + AnalysisDestination string `yaml:"dest" default:"up9.app"` + SleepIntervalSec int `yaml:"upload-interval" default:"10"` + PodRegexStr string `yaml:"regex" default:".*"` + GuiPort uint16 `yaml:"gui-port" default:"8899"` + Namespace string `yaml:"namespace"` + Analysis bool `yaml:"analysis" default:"false"` + AllNamespaces bool `yaml:"all-namespaces" default:"false"` + KubeConfigPath string `yaml:"kube-config"` + PlainTextFilterRegexes []string `yaml:"regex-masking"` + HideHealthChecks bool `yaml:"hide-healthchecks" default:"false"` + DisableRedaction bool `yaml:"no-redact" default:"false"` + HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"` + Direction string `yaml:"direction" default:"in"` + DryRun bool `yaml:"dry-run" default:"false"` +} + +func (config *TapConfig) PodRegex() *regexp.Regexp { + podRegex, _ := regexp.Compile(config.PodRegexStr) + return podRegex +} + +func (config *TapConfig) TapOutgoing() bool { + directionLowerCase := strings.ToLower(config.Direction) + if directionLowerCase == "any" { + return true + } + + return false +} + +func (config *TapConfig) MaxEntriesDBSizeBytes() int64 { + maxEntriesDBSizeBytes, _ := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize) + return maxEntriesDBSizeBytes +} + +func (config *TapConfig) Validate() error { + _, compileErr := regexp.Compile(config.PodRegexStr) + if compileErr != nil { + return errors.New(fmt.Sprintf("%s is not a valid regex %s", config.PodRegexStr, compileErr)) + } + + _, parseHumanDataSizeErr := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize) + if parseHumanDataSizeErr != nil { + return errors.New(fmt.Sprintf("Could not parse --%s value %s", HumanMaxEntriesDBSizeTapName, config.HumanMaxEntriesDBSize)) + } + + directionLowerCase := strings.ToLower(config.Direction) + if directionLowerCase != "any" && directionLowerCase != "in" { + return errors.New(fmt.Sprintf("%s is not a valid value for flag --%s. Acceptable values are in/any.", config.Direction, DirectionTapName)) + } + + return nil +} diff --git a/cli/mizu/configStructs/versionConfig.go b/cli/mizu/configStructs/versionConfig.go new file mode 100644 index 000000000..fd09a4747 --- /dev/null +++ b/cli/mizu/configStructs/versionConfig.go @@ -0,0 +1,9 @@ +package configStructs + +const ( + DebugInfoVersionName = "debug" +) + +type VersionConfig struct { + DebugInfo bool `yaml:"debug" default:"false"` +} diff --git a/cli/mizu/configStructs/viewConfig.go b/cli/mizu/configStructs/viewConfig.go new file mode 100644 index 000000000..6f26ca9dc --- /dev/null +++ b/cli/mizu/configStructs/viewConfig.go @@ -0,0 +1,11 @@ +package configStructs + +const ( + GuiPortViewName = "gui-port" + KubeConfigPathViewName = "kube-config" +) + +type ViewConfig struct { + GuiPort uint16 `yaml:"gui-port" default:"8899"` + KubeConfigPath string `yaml:"kube-config"` +} diff --git a/cli/mizu/telemetry.go b/cli/mizu/telemetry.go index fadc321a0..a8924f972 100644 --- a/cli/mizu/telemetry.go +++ b/cli/mizu/telemetry.go @@ -10,7 +10,7 @@ import ( const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry" func ReportRun(cmd string, args interface{}) { - if !GetBool(ConfigurationKeyTelemetry) { + if !Config.Telemetry { Log.Debugf("not reporting due to config value") return }