Feature/tra 3474 config refactor (#155)

This commit is contained in:
RoyUP9
2021-08-03 17:23:52 +03:00
committed by GitHub
parent f9396e01ca
commit 69a9deab4b
17 changed files with 393 additions and 329 deletions

View File

@@ -1,30 +1,23 @@
package cmd package cmd
import ( import (
"github.com/creasty/defaults"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/mizu" "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{ var fetchCmd = &cobra.Command{
Use: "fetch", Use: "fetch",
Short: "Download recorded traffic to files", Short: "Download recorded traffic to files",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("fetch", mizuTapOptions) go mizu.ReportRun("fetch", mizu.Config.Fetch)
if isCompatible, err := mizu.CheckVersionCompatibility(mizuFetchOptions.MizuPort); err != nil { if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.MizuPort); err != nil {
return err return err
} else if !isCompatible { } else if !isCompatible {
return nil return nil
} }
RunMizuFetch(&mizuFetchOptions) RunMizuFetch()
return nil return nil
}, },
} }
@@ -32,8 +25,11 @@ var fetchCmd = &cobra.Command{
func init() { func init() {
rootCmd.AddCommand(fetchCmd) rootCmd.AddCommand(fetchCmd)
fetchCmd.Flags().StringVarP(&mizuFetchOptions.Directory, "directory", "d", ".", "Provide a custom directory for fetched entries") defaultFetchConfig := configStructs.FetchConfig{}
fetchCmd.Flags().Int64Var(&mizuFetchOptions.FromTimestamp, "from", 0, "Custom start timestamp for fetched entries") defaults.Set(&defaultFetchConfig)
fetchCmd.Flags().Int64Var(&mizuFetchOptions.ToTimestamp, "to", 0, "Custom end timestamp fetched entries")
fetchCmd.Flags().Uint16VarP(&mizuFetchOptions.MizuPort, "port", "p", 8899, "Custom port for mizu") 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")
} }

View File

@@ -15,9 +15,9 @@ import (
"strings" "strings"
) )
func RunMizuFetch(fetch *MizuFetchOptions) { func RunMizuFetch() {
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(fetch.MizuPort) mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, fetch.FromTimestamp, fetch.ToTimestamp)) 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -33,8 +33,8 @@ func RunMizuFetch(fetch *MizuFetchOptions) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
_ = Unzip(zipReader, fetch.Directory)
_ = Unzip(zipReader, mizu.Config.Fetch.Directory)
} }
func Unzip(reader *zip.Reader, dest string) error { func Unzip(reader *zip.Reader, dest string) error {

View File

@@ -7,26 +7,23 @@ import (
"github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/cli/mizu"
) )
var commandLineFlags []string
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "mizu", Use: "mizu",
Short: "A web traffic viewer for kubernetes", Short: "A web traffic viewer for kubernetes",
Long: `A web traffic viewer for kubernetes Long: `A web traffic viewer for kubernetes
Further info is available at https://github.com/up9inc/mizu`, Further info is available at https://github.com/up9inc/mizu`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 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) mizu.Log.Errorf("Invalid config, Exit %s", err)
return errors.New(fmt.Sprintf("%v", err)) return errors.New(fmt.Sprintf("%v", err))
} }
prettifiedConfig := mizu.GetConfigStr()
mizu.Log.Debugf("Final Config: %s", prettifiedConfig)
return nil return nil
}, },
} }
func init() { 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. // Execute adds all child commands to the root command and sets flags appropriately.

View File

@@ -2,39 +2,14 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "github.com/creasty/defaults"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/cli/mizu"
"github.com/up9inc/mizu/cli/mizu/configStructs"
"github.com/up9inc/mizu/cli/uiUtils" "github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared/units"
"os" "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.` const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.`
var tapCmd = &cobra.Command{ var tapCmd = &cobra.Command{
@@ -43,53 +18,31 @@ var tapCmd = &cobra.Command{
Long: `Record the ingoing traffic of a kubernetes pod. Long: `Record the ingoing traffic of a kubernetes pod.
Supported protocols are HTTP and gRPC.`, Supported protocols are HTTP and gRPC.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("tap", mizuTapOptions) go mizu.ReportRun("tap", mizu.Config.Tap)
RunMizuTap(regex, mizuTapOptions) RunMizuTap()
return nil return nil
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
mizu.Log.Debugf("Getting params") if len(args) == 1 {
mizuTapOptions.AnalysisDestination = mizu.GetString(mizu.ConfigurationKeyAnalyzingDestination) mizu.Config.Tap.PodRegexStr = args[0]
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")
} else if len(args) > 1 { } else if len(args) > 1 {
return errors.New("unexpected number of arguments") return errors.New("unexpected number of arguments")
} }
var compileErr error if err := mizu.Config.Tap.Validate(); err != nil {
regex, compileErr = regexp.Compile(args[0]) return err
if compileErr != nil {
return errors.New(fmt.Sprintf("%s is not a valid regex %s", args[0], compileErr))
} }
var parseHumanDataSizeErr error 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)
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))
directionLowerCase := strings.ToLower(direction) if mizu.Config.Tap.Analysis {
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 {
mizu.Log.Infof(analysisMessageToConfirm) mizu.Log.Infof(analysisMessageToConfirm)
if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") { if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") {
mizu.Log.Infof("You can always run mizu without analysis, aborting") mizu.Log.Infof("You can always run mizu without analysis, aborting")
os.Exit(0) os.Exit(0)
} }
} }
return nil return nil
}, },
} }
@@ -97,14 +50,18 @@ Supported protocols are HTTP and gRPC.`,
func init() { func init() {
rootCmd.AddCommand(tapCmd) rootCmd.AddCommand(tapCmd)
tapCmd.Flags().Uint16VarP(&mizuTapOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver") defaultTapConfig := configStructs.TapConfig{}
tapCmd.Flags().StringVarP(&mizuTapOptions.Namespace, "namespace", "n", "", "Namespace selector") defaults.Set(&defaultTapConfig)
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().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file") tapCmd.Flags().StringP(configStructs.NamespaceTapName, "n", defaultTapConfig.Namespace, "Namespace selector")
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().Bool(configStructs.AnalysisTapName, defaultTapConfig.Analysis, "Uploads traffic to UP9 for further analysis (Beta)")
tapCmd.Flags().StringVarP(&direction, "direction", "", "in", "Record traffic that goes in this direction (relative to the tapped pod): in/any") tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
tapCmd.Flags().BoolVar(&mizuTapOptions.HideHealthChecks, "hide-healthchecks", false, "hides requests with kube-probe or prometheus user-agent headers") tapCmd.Flags().StringP(configStructs.KubeConfigPathTapName, "k", defaultTapConfig.KubeConfigPath, "Path to kube-config file")
tapCmd.Flags().StringVarP(&humanMaxEntriesDBSize, maxEntriesDBSizeFlagName, "", "200MB", "override the default max entries db size of 200mb") 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().BoolVar(&mizuTapOptions.DisableRedaction, "no-redact", false, "Disables redaction of potentially sensitive request/response headers and body values") 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")
} }

View File

@@ -11,7 +11,6 @@ import (
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"log"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -31,13 +30,13 @@ const (
var currentlyTappedPods []core.Pod var currentlyTappedPods []core.Pod
func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) { func RunMizuTap() {
mizuApiFilteringOptions, err := getMizuApiFilteringOptions(tappingOptions) mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
if err != nil { if err != nil {
return return
} }
kubernetesProvider, err := kubernetes.NewProvider(tappingOptions.KubeConfigPath) kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.Tap.KubeConfigPath)
if err != nil { if err != nil {
if clientcmd.IsEmptyConfig(err) { if clientcmd.IsEmptyConfig(err) {
mizu.Log.Infof(uiUtils.Red, "Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'\n") mizu.Log.Infof(uiUtils.Red, "Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'\n")
@@ -53,17 +52,21 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel will be called when this function exits defer cancel() // cancel will be called when this function exits
targetNamespace := getNamespace(tappingOptions, kubernetesProvider) targetNamespace := getNamespace(kubernetesProvider)
if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, podRegexQuery, targetNamespace); err != nil { if err := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespace); err != nil {
mizu.Log.Infof("Error listing pods: %v", err) mizu.Log.Infof("Error listing pods: %v", err)
return return
} }
if mizu.Config.Tap.DryRun {
return
}
urlReadyChan := make(chan string) urlReadyChan := make(chan string)
go func() { go func() {
mizu.Log.Infof("Mizu is available at http://%s", <-urlReadyChan) mizu.Log.Infof("Mizu is available at http://%s", <-urlReadyChan)
}() }()
var namespacesStr string var namespacesStr string
if targetNamespace != mizu.K8sAllNamespaces { if targetNamespace != mizu.K8sAllNamespaces {
namespacesStr = fmt.Sprintf("namespace \"%s\"", targetNamespace) namespacesStr = fmt.Sprintf("namespace \"%s\"", targetNamespace)
@@ -85,29 +88,29 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
return return
} }
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions, mizuApiFilteringOptions); err != nil { if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
return return
} }
mizu.CheckNewerVersion() 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 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, podRegexQuery, tappingOptions) go watchPodsForTapping(ctx, kubernetesProvider, cancel)
go syncApiStatus(ctx, cancel, tappingOptions) go syncApiStatus(ctx, cancel)
//block until exit signal or error //block until exit signal or error
waitForFinish(ctx, cancel) 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 { if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
return err return err
} }
if err := createMizuApiServer(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil { if err := createMizuApiServer(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
return err return err
} }
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil { if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
return err return err
} }
@@ -123,7 +126,7 @@ func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Pro
return err 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 var err error
mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider) mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider)
@@ -133,7 +136,7 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
} else { } else {
serviceAccountName = "" 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 { if err != nil {
mizu.Log.Infof("Error creating mizu %s pod: %v", mizu.ApiServerPodName, err) mizu.Log.Infof("Error creating mizu %s pod: %v", mizu.ApiServerPodName, err)
return err return err
@@ -148,12 +151,12 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
return nil return nil
} }
func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.TrafficFilteringOptions, error) { func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
var compiledRegexSlice []*shared.SerializableRegexp 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) compiledRegexSlice = make([]*shared.SerializableRegexp, 0)
for _, regexStr := range tappingOptions.PlainTextFilterRegexes { for _, regexStr := range mizu.Config.Tap.PlainTextFilterRegexes {
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr) compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
if err != nil { if err != nil {
mizu.Log.Infof("Regex %s is invalid: %v", regexStr, err) 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 { if len(nodeToTappedPodIPMap) > 0 {
var serviceAccountName string var serviceAccountName string
if mizuServiceAccountExists { if mizuServiceAccountExists {
@@ -179,12 +182,12 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
ctx, ctx,
mizu.ResourcesNamespace, mizu.ResourcesNamespace,
mizu.TapperDaemonSetName, mizu.TapperDaemonSetName,
tappingOptions.MizuImage, mizu.Config.MizuImage,
mizu.TapperPodName, mizu.TapperPodName,
fmt.Sprintf("%s.%s.svc.cluster.local", apiServerService.Name, apiServerService.Namespace), fmt.Sprintf("%s.%s.svc.cluster.local", apiServerService.Name, apiServerService.Namespace),
nodeToTappedPodIPMap, nodeToTappedPodIPMap,
serviceAccountName, serviceAccountName,
tappingOptions.TapOutgoing, mizu.Config.Tap.TapOutgoing(),
); err != nil { ); err != nil {
mizu.Log.Infof("Error creating mizu tapper daemonset: %v", err) mizu.Log.Infof("Error creating mizu tapper daemonset: %v", err)
return 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) { func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
targetNamespace := getNamespace(tappingOptions, kubernetesProvider) targetNamespace := getNamespace(kubernetesProvider)
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, targetNamespace), mizu.Config.Tap.PodRegex())
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, targetNamespace), podRegex)
restartTappers := func() { 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) mizu.Log.Infof("Error getting pods by regex: %s (%v,%+v)", err, err, err)
cancel() cancel()
} }
@@ -251,7 +253,7 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
cancel() 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) mizu.Log.Infof("Error updating daemonset: %s (%v,%+v)", err, err, err)
cancel() 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 { func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespace string) error {
if matchingPods, err := kubernetesProvider.GetAllRunningPodsMatchingRegex(ctx, podRegex, targetNamespace); err != nil { 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) mizu.Log.Infof("Error getting pods by regex: %s (%v,%+v)", err, err, err)
return err return err
} else { } else {
@@ -298,6 +300,7 @@ func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx cont
} }
currentlyTappedPods = matchingPods currentlyTappedPods = matchingPods
} }
return nil return nil
} }
@@ -326,16 +329,16 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
return missingPods 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)) podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, mizu.ResourcesNamespace), podExactRegex) added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, mizu.ResourcesNamespace), podExactRegex)
isPodReady := false isPodReady := false
timeAfter := time.After(25 * time.Second) timeAfter := time.After(25 * time.Second)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-added: case <-added:
continue continue
case <-removed: case <-removed:
@@ -346,43 +349,45 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
if modifiedPod.Status.Phase == "Running" && !isPodReady { if modifiedPod.Status.Phase == "Running" && !isPodReady {
isPodReady = true isPodReady = true
go func() { 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 { if err != nil {
mizu.Log.Infof("Error occurred while running k8s proxy %v", err) mizu.Log.Infof("Error occurred while running k8s proxy %v", err)
cancel() 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: case <-timeAfter:
if !isPodReady { if !isPodReady {
mizu.Log.Errorf("error: %s pod was not ready in time", mizu.ApiServerPodName) mizu.Log.Errorf("error: %s pod was not ready in time", mizu.ApiServerPodName)
cancel() cancel()
} }
case <-errorChan: case <-errorChan:
cancel() cancel()
} }
} }
} }
func requestForAnalysis(tappingOptions *MizuTapOptions) { func requestForAnalysis() {
if !tappingOptions.Analysis { if !mizu.Config.Tap.Analysis {
return 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 { mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
log.Fatal(fmt.Sprintf("Failed parsing the URL %v\n", err)) 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()) mizu.Log.Debugf("Sending get request to %v", u.String())
if response, err := http.Get(u.String()); err != nil || response.StatusCode != 200 { if response, requestErr := http.Get(u.String()); requestErr != nil {
mizu.Log.Infof("error sending upload entries req, status code: %v, err: %v", response.StatusCode, err) 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 { } else {
mizu.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis") 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) { func syncApiStatus(ctx context.Context, cancel context.CancelFunc) {
controlSocketStr := fmt.Sprintf("ws://%s/ws", kubernetes.GetMizuApiServerProxiedHostAndPath(tappingOptions.GuiPort)) controlSocketStr := fmt.Sprintf("ws://%s/ws", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort))
controlSocket, err := mizu.CreateControlSocket(controlSocketStr) controlSocket, err := mizu.CreateControlSocket(controlSocketStr)
if err != nil { if err != nil {
mizu.Log.Infof("error establishing control socket connection %s", err) 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 { func getNamespace(kubernetesProvider *kubernetes.Provider) string {
if tappingOptions.AllNamespaces { if mizu.Config.Tap.AllNamespaces {
return mizu.K8sAllNamespaces return mizu.K8sAllNamespaces
} else if len(tappingOptions.Namespace) > 0 { } else if len(mizu.Config.Tap.Namespace) > 0 {
return tappingOptions.Namespace return mizu.Config.Tap.Namespace
} else { } else {
return kubernetesProvider.CurrentNamespace() return kubernetesProvider.CurrentNamespace()
} }

View File

@@ -1,24 +1,20 @@
package cmd package cmd
import ( import (
"github.com/creasty/defaults"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/cli/mizu"
"github.com/up9inc/mizu/cli/mizu/configStructs"
"strconv" "strconv"
"time" "time"
) )
type MizuVersionOptions struct {
DebugInfo bool
}
var mizuVersionOptions = &MizuVersionOptions{}
var versionCmd = &cobra.Command{ var versionCmd = &cobra.Command{
Use: "version", Use: "version",
Short: "Print version info", Short: "Print version info",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("version", mizuVersionOptions) go mizu.ReportRun("version", mizu.Config.Version)
if mizuVersionOptions.DebugInfo { if mizu.Config.Version.DebugInfo {
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0) timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
mizu.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash) 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)) mizu.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
@@ -33,6 +29,9 @@ var versionCmd = &cobra.Command{
func init() { func init() {
rootCmd.AddCommand(versionCmd) 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")
} }

View File

@@ -1,28 +1,23 @@
package cmd package cmd
import ( import (
"github.com/creasty/defaults"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/mizu" "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{ var viewCmd = &cobra.Command{
Use: "view", Use: "view",
Short: "Open GUI in browser", Short: "Open GUI in browser",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("view", mizuViewOptions) go mizu.ReportRun("view", mizu.Config.View)
if isCompatible, err := mizu.CheckVersionCompatibility(mizuViewOptions.GuiPort); err != nil { if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
return err return err
} else if !isCompatible { } else if !isCompatible {
return nil return nil
} }
runMizuView(mizuViewOptions) runMizuView()
return nil return nil
}, },
} }
@@ -30,6 +25,9 @@ var viewCmd = &cobra.Command{
func init() { func init() {
rootCmd.AddCommand(viewCmd) rootCmd.AddCommand(viewCmd)
viewCmd.Flags().Uint16VarP(&mizuViewOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver") defaultViewConfig := configStructs.ViewConfig{}
viewCmd.Flags().StringVarP(&mizuViewOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file") 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")
} }

View File

@@ -10,8 +10,8 @@ import (
"net/http" "net/http"
) )
func runMizuView(mizuViewOptions *MizuViewOptions) { func runMizuView() {
kubernetesProvider, err := kubernetes.NewProvider(mizuViewOptions.KubeConfigPath) kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
if err != nil { if err != nil {
if clientcmd.IsEmptyConfig(err) { if clientcmd.IsEmptyConfig(err) {
mizu.Log.Infof("Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'") mizu.Log.Infof("Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'")
@@ -35,16 +35,16 @@ func runMizuView(mizuViewOptions *MizuViewOptions) {
return return
} }
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizuViewOptions.GuiPort) mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort)
_, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl)) _, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl))
if err == nil { 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 return
} }
mizu.Log.Infof("Found service %s, creating k8s proxy", mizu.ApiServerPodName) 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)) mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort))
err = kubernetes.StartProxy(kubernetesProvider, mizuViewOptions.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName) err = kubernetes.StartProxy(kubernetesProvider, mizu.Config.View.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName)
if err != nil { if err != nil {
mizu.Log.Infof("Error occured while running k8s proxy %v", err) mizu.Log.Infof("Error occured while running k8s proxy %v", err)
} }

View File

@@ -3,10 +3,12 @@ module github.com/up9inc/mizu/cli
go 1.16 go 1.16
require ( require (
github.com/creasty/defaults v1.5.1
github.com/google/go-github/v37 v37.0.0 github.com/google/go-github/v37 v37.0.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/up9inc/mizu/shared v0.0.0 github.com/up9inc/mizu/shared v0.0.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.21.2 k8s.io/api v0.21.2

View File

@@ -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/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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -3,6 +3,9 @@ package mizu
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/creasty/defaults"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/up9inc/mizu/cli/uiUtils" "github.com/up9inc/mizu/cli/uiUtils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
@@ -13,209 +16,182 @@ import (
"strings" "strings"
) )
const separator = "="
var configObj = map[string]interface{}{}
type CommandLineFlag struct {
CommandLineName string
YamlHierarchyName string
DefaultValue interface{}
}
const ( const (
ConfigurationKeyAnalyzingDestination = "tap.dest" Separator = "="
ConfigurationKeyUploadInterval = "tap.uploadInterval" SetCommandName = "set"
ConfigurationKeyMizuImage = "mizuImage"
ConfigurationKeyTelemetry = "telemetry"
) )
var allowedSetFlags = []CommandLineFlag{ var Config = ConfigStruct{}
{
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,
},
}
func GetString(key string) string { func InitConfig(cmd *cobra.Command) error {
return fmt.Sprintf("%v", getValueFromMergedConfig(key)) if err := defaults.Set(&Config); err != nil {
} return err
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)
} }
return val
}
func GetInt(key string) int { if err := mergeConfigFile(); err != nil {
stringVal := GetString(key) Log.Infof(uiUtils.Red, "Invalid config file")
Log.Debugf("Found string value %v", stringVal) return err
}
val, err := strconv.Atoi(stringVal) cmd.Flags().Visit(initFlag)
if err != nil {
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for key %s, expected int", stringVal, key))
os.Exit(1)
}
return val
}
func InitConfig(commandLineValues []string) error { finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
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)
Log.Debugf("Merged all config successfully\n Final config: %v", finalConfigPrettified) Log.Debugf("Merged all config successfully\n Final config: %v", finalConfigPrettified)
return nil return nil
} }
func GetTemplateConfig() string { func GetTemplateConfig() string {
templateConfig := map[string]interface{}{} prettifiedConfig, _ := uiUtils.PrettyYaml(Config)
for _, allowedFlag := range allowedSetFlags {
addToConfigObj(allowedFlag.YamlHierarchyName, allowedFlag.DefaultValue, templateConfig)
}
prettifiedConfig, _ := uiUtils.PrettyYaml(templateConfig)
return prettifiedConfig 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 { func mergeConfigFile() error {
Log.Debugf("Merging mizu config file values") Log.Debugf("Merging config file values")
home, homeDirErr := os.UserHomeDir() home, homeDirErr := os.UserHomeDir()
if homeDirErr != nil { if homeDirErr != nil {
return nil return homeDirErr
} }
reader, openErr := os.Open(path.Join(home, ".mizu", "config.yaml")) reader, openErr := os.Open(path.Join(home, ".mizu", "config.yaml"))
if openErr != nil { if openErr != nil {
return nil return openErr
} }
buf, readErr := ioutil.ReadAll(reader) buf, readErr := ioutil.ReadAll(reader)
if readErr != nil { if readErr != nil {
return readErr 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 return err
} }
for k, v := range m {
addToConfig(k, v)
}
return nil return nil
} }
func addToConfig(prefix string, value interface{}) { func initFlag(f *pflag.Flag) {
typ := reflect.TypeOf(value).Kind() configElem := reflect.ValueOf(&Config).Elem()
if typ == reflect.Map {
for k1, v1 := range value.(map[string]interface{}) { sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
addToConfig(fmt.Sprintf("%s.%s", prefix, k1), v1) if !isSliceValue {
} mergeFlagValue(configElem, f.Name, f.Value.String())
} else { return
validateConfigFileKey(prefix)
configObj[prefix] = value
} }
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 { func mergeSetFlag(setValues []string) error {
Log.Debugf("Merging Command line flags") configElem := reflect.ValueOf(&Config).Elem()
for _, e := range commandLineValues {
if !strings.Contains(e, separator) { for _, setValue := range setValues {
return errors.New(fmt.Sprintf("invalid set argument %s", e)) 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 { if len(split) != 2 {
return errors.New(fmt.Sprintf("invalid set argument %s", e)) return errors.New(fmt.Sprintf("invalid set argument %s", setValue))
}
setFlagKey, argumentValue := split[0], split[1]
argumentNameInConfig, err := flagFromAllowed(setFlagKey)
if err != nil {
return err
} }
configObj[argumentNameInConfig] = argumentValue argumentKey, argumentValue := split[0], split[1]
mergeFlagValue(configElem, argumentKey, argumentValue)
} }
return nil return nil
} }
func flagFromAllowed(setFlagKey string) (string, error) { func mergeFlagValue(currentElem reflect.Value, flagKey string, flagValue string) {
for _, allowedFlag := range allowedSetFlags { for i := 0; i < currentElem.NumField(); i++ {
if strings.ToLower(allowedFlag.CommandLineName) == strings.ToLower(setFlagKey) { currentField := currentElem.Type().Field(i)
return allowedFlag.YamlHierarchyName, nil currentFieldByName := currentElem.FieldByName(currentField.Name)
}
}
return "", errors.New(fmt.Sprintf("invalid set argument %s", setFlagKey))
}
func validateConfigFileKey(configFileKey string) { if currentField.Type.Kind() == reflect.Struct {
for _, allowedFlag := range allowedSetFlags { mergeFlagValue(currentFieldByName, flagKey, flagValue)
if allowedFlag.YamlHierarchyName == configFileKey { 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 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{}) { func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []string) {
typ := reflect.TypeOf(value).Kind() for i := 0; i < currentElem.NumField(); i++ {
if typ != reflect.Map { currentField := currentElem.Type().Field(i)
if strings.Contains(key, ".") { currentFieldByName := currentElem.FieldByName(currentField.Name)
split := strings.SplitN(key, ".", 2)
firstLevelKey := split[0] if currentField.Type.Kind() == reflect.Struct {
if _, ok := configObj[firstLevelKey]; !ok { mergeFlagValues(currentFieldByName, flagKey, flagValues)
configObj[firstLevelKey] = map[string]interface{}{} continue
}
addToConfigObj(split[1], value, configObj[firstLevelKey].(map[string]interface{}))
} else {
configObj[key] = value
} }
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")
}

19
cli/mizu/configStruct.go Normal file
View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -0,0 +1,9 @@
package configStructs
const (
DebugInfoVersionName = "debug"
)
type VersionConfig struct {
DebugInfo bool `yaml:"debug" default:"false"`
}

View File

@@ -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"`
}

View File

@@ -10,7 +10,7 @@ import (
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry" const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
func ReportRun(cmd string, args interface{}) { func ReportRun(cmd string, args interface{}) {
if !GetBool(ConfigurationKeyTelemetry) { if !Config.Telemetry {
Log.Debugf("not reporting due to config value") Log.Debugf("not reporting due to config value")
return return
} }