Remove package-scoped vars in cmd package (#975)

* Remove package-scoped vars in cmd package
* Run gofmt on all cmd package files
* Re-add missing Args setting on check command

Signed-off-by: Kevin Lingerfelt <kl@buoyant.io>
This commit is contained in:
Kevin Lingerfelt 2018-05-21 18:15:39 -07:00 committed by GitHub
parent d256377eea
commit 2baeaacbc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 607 additions and 491 deletions

View File

@ -23,44 +23,61 @@ const (
versionCheckURL = "https://versioncheck.conduit.io/version.json"
)
var versionOverride string
type checkOptions struct {
versionOverride string
}
var checkCmd = &cobra.Command{
Use: "check",
Short: "Check your Conduit installation for potential problems.",
Long: `Check your Conduit installation for potential problems. The check command will perform various checks of your
func newCheckOptions() *checkOptions {
return &checkOptions{
versionOverride: "",
}
}
func newCmdCheck() *cobra.Command {
options := newCheckOptions()
cmd := &cobra.Command{
Use: "check",
Short: "Check your Conduit installation for potential problems.",
Long: `Check your Conduit installation for potential problems. The check command will perform various checks of your
local system, the Conduit control plane, and connectivity between those. The process will exit with non-zero check if
problems were found.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
kubeApi, err := k8s.NewAPI(kubeconfigPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Kubernetes API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}
kubeApi, err := k8s.NewAPI(kubeconfigPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Kubernetes API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}
var conduitApi pb.ApiClient
if apiAddr != "" {
conduitApi, err = public.NewInternalClient(apiAddr)
} else {
conduitApi, err = public.NewExternalClient(controlPlaneNamespace, kubeApi)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Conduit API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}
var conduitApi pb.ApiClient
if apiAddr != "" {
conduitApi, err = public.NewInternalClient(apiAddr)
} else {
conduitApi, err = public.NewExternalClient(controlPlaneNamespace, kubeApi)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error with Conduit API: %s\n", err.Error())
statusCheckResultWasError(os.Stdout)
os.Exit(2)
}
grpcStatusChecker := healthcheck.NewGrpcStatusChecker(public.ConduitApiSubsystemName, conduitApi)
versionStatusChecker := version.NewVersionStatusChecker(versionCheckURL, versionOverride, conduitApi)
grpcStatusChecker := healthcheck.NewGrpcStatusChecker(public.ConduitApiSubsystemName, conduitApi)
versionStatusChecker := version.NewVersionStatusChecker(versionCheckURL, options.versionOverride, conduitApi)
err = checkStatus(os.Stdout, kubeApi, grpcStatusChecker, versionStatusChecker)
if err != nil {
os.Exit(2)
}
},
err = checkStatus(os.Stdout, kubeApi, grpcStatusChecker, versionStatusChecker)
if err != nil {
os.Exit(2)
}
},
}
cmd.Args = cobra.NoArgs
cmd.PersistentFlags().StringVar(&options.versionOverride, "expected-version", options.versionOverride, "Overrides the version used when checking if Conduit is running the latest version (mostly for testing)")
return cmd
}
func checkStatus(w io.Writer, checkers ...healthcheck.StatusChecker) error {
@ -119,9 +136,3 @@ func statusCheckResultWasError(w io.Writer) error {
fmt.Fprintln(w, "Status check results are [ERROR]")
return errors.New("error during status check")
}
func init() {
RootCmd.AddCommand(checkCmd)
checkCmd.Args = cobra.NoArgs
checkCmd.PersistentFlags().StringVar(&versionOverride, "expected-version", "", "Overrides the version used when checking if Conduit is running the latest version (mostly for testing)")
}

View File

@ -8,7 +8,8 @@ import (
"github.com/spf13/cobra"
)
var example = ` # bash <= 3.2
func newCmdCompletion() *cobra.Command {
example := ` # bash <= 3.2
source /dev/stdin <<< "$(conduit completion bash)"
# bash >= 4.0
@ -28,37 +29,36 @@ var example = ` # bash <= 3.2
# zsh on osx / oh-my-zsh
conduit completion zsh > "${fpath[1]}/_conduit"`
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh]",
Short: "Shell completion",
Long: "Output completion code for the specified shell (bash or zsh).",
Example: example,
Args: cobra.ExactArgs(1),
ValidArgs: []string{"bash", "zsh"},
RunE: func(cmd *cobra.Command, args []string) error {
out, err := getCompletion(args[0])
if err != nil {
return err
}
cmd := &cobra.Command{
Use: "completion [bash|zsh]",
Short: "Shell completion",
Long: "Output completion code for the specified shell (bash or zsh).",
Example: example,
Args: cobra.ExactArgs(1),
ValidArgs: []string{"bash", "zsh"},
RunE: func(cmd *cobra.Command, args []string) error {
out, err := getCompletion(args[0], cmd.Parent())
if err != nil {
return err
}
fmt.Print(out)
return nil
},
fmt.Print(out)
return nil
},
}
return cmd
}
func init() {
RootCmd.AddCommand(completionCmd)
}
func getCompletion(sh string) (string, error) {
func getCompletion(sh string, parent *cobra.Command) (string, error) {
var err error
var buf bytes.Buffer
switch sh {
case "bash":
err = RootCmd.GenBashCompletion(&buf)
err = parent.GenBashCompletion(&buf)
case "zsh":
err = RootCmd.GenZshCompletion(&buf)
err = parent.GenZshCompletion(&buf)
default:
err = errors.New("unsupported shell type (must be bash or zsh): " + sh)
}

View File

@ -8,12 +8,12 @@ import (
func TestCompletion(t *testing.T) {
t.Run("Returns completion code", func(t *testing.T) {
bash, err := getCompletion("bash")
bash, err := getCompletion("bash", RootCmd)
if err != nil {
t.Fatalf("Unexpected error: %+v", err)
}
zsh, err := getCompletion("zsh")
zsh, err := getCompletion("zsh", RootCmd)
if err != nil {
t.Fatalf("Unexpected error: %+v", err)
}
@ -28,7 +28,7 @@ func TestCompletion(t *testing.T) {
})
t.Run("Fails with invalid shell type", func(t *testing.T) {
out, err := getCompletion("foo")
out, err := getCompletion("foo", RootCmd)
if err == nil {
t.Fatalf("Unexpected success for invalid shell type: %+v", out)
}

View File

@ -25,89 +25,110 @@ const (
showURL = "url"
)
var dashboardProxyPort int
var dashboardShow string
type dashboardOptions struct {
dashboardProxyPort int
dashboardShow string
}
var dashboardCmd = &cobra.Command{
Use: "dashboard [flags]",
Short: "Open the Conduit dashboard in a web browser",
RunE: func(cmd *cobra.Command, args []string) error {
if dashboardProxyPort < 0 {
return fmt.Errorf("port must be greater than or equal to zero, was %d", dashboardProxyPort)
}
func newDashboardOptions() *dashboardOptions {
return &dashboardOptions{
dashboardProxyPort: 0,
dashboardShow: "conduit",
}
}
if dashboardShow != showConduit && dashboardShow != showGrafana && dashboardShow != showURL {
return fmt.Errorf("unknown value for 'show' param, was: %s, must be one of: %s, %s, %s", dashboardShow, showConduit, showGrafana, showURL)
}
func newCmdDashboard() *cobra.Command {
options := newDashboardOptions()
kubernetesProxy, err := k8s.NewProxy(kubeconfigPath, dashboardProxyPort)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize proxy: %s\n", err)
os.Exit(1)
}
cmd := &cobra.Command{
Use: "dashboard [flags]",
Short: "Open the Conduit dashboard in a web browser",
RunE: func(cmd *cobra.Command, args []string) error {
if options.dashboardProxyPort < 0 {
return fmt.Errorf("port must be greater than or equal to zero, was %d", options.dashboardProxyPort)
}
url, err := kubernetesProxy.URLFor(controlPlaneNamespace, "/services/web:http/proxy/")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to generate URL for dashboard: %s\n", err)
os.Exit(1)
}
if options.dashboardShow != showConduit && options.dashboardShow != showGrafana && options.dashboardShow != showURL {
return fmt.Errorf("unknown value for 'show' param, was: %s, must be one of: %s, %s, %s",
options.dashboardShow, showConduit, showGrafana, showURL)
}
grafanaUrl, err := kubernetesProxy.URLFor(controlPlaneNamespace, "/services/grafana:http/proxy/")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to generate URL for Grafana: %s\n", err)
os.Exit(1)
}
client, err := newPublicAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize Conduit API client: %+v\n", err)
os.Exit(1)
}
dashboardAvailable, err := isDashboardAvailable(client)
if err != nil {
log.Debugf("Error checking dashboard availability: %s", err)
}
if err != nil || !dashboardAvailable {
fmt.Fprintf(os.Stderr, "Conduit is not running in the \"%s\" namespace\n", controlPlaneNamespace)
fmt.Fprintf(os.Stderr, "Install with: conduit install --conduit-namespace %s | kubectl apply -f -\n", controlPlaneNamespace)
os.Exit(1)
}
fmt.Printf("Conduit dashboard available at:\n%s\n", url.String())
fmt.Printf("Grafana dashboard available at:\n%s\n", grafanaUrl.String())
switch dashboardShow {
case showConduit:
fmt.Println("Opening Conduit dashboard in the default browser")
err = browser.OpenURL(url.String())
kubernetesProxy, err := k8s.NewProxy(kubeconfigPath, options.dashboardProxyPort)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open Conduit URL %s in the default browser: %s", url, err)
fmt.Fprintf(os.Stderr, "Failed to initialize proxy: %s\n", err)
os.Exit(1)
}
case showGrafana:
fmt.Println("Opening Grafana dashboard in the default browser")
err = browser.OpenURL(grafanaUrl.String())
url, err := kubernetesProxy.URLFor(controlPlaneNamespace, "/services/web:http/proxy/")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open Grafana URL %s in the default browser: %s", grafanaUrl, err)
fmt.Fprintf(os.Stderr, "Failed to generate URL for dashboard: %s\n", err)
os.Exit(1)
}
case showURL:
// no-op, we already printed the URLs
}
// blocks until killed
err = kubernetesProxy.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error running proxy: %s", err)
os.Exit(1)
}
grafanaUrl, err := kubernetesProxy.URLFor(controlPlaneNamespace, "/services/grafana:http/proxy/")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to generate URL for Grafana: %s\n", err)
os.Exit(1)
}
return nil
},
client, err := newPublicAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize Conduit API client: %+v\n", err)
os.Exit(1)
}
dashboardAvailable, err := isDashboardAvailable(client)
if err != nil {
log.Debugf("Error checking dashboard availability: %s", err)
}
if err != nil || !dashboardAvailable {
fmt.Fprintf(os.Stderr, "Conduit is not running in the \"%s\" namespace\n", controlPlaneNamespace)
fmt.Fprintf(os.Stderr, "Install with: conduit install --conduit-namespace %s | kubectl apply -f -\n", controlPlaneNamespace)
os.Exit(1)
}
fmt.Printf("Conduit dashboard available at:\n%s\n", url.String())
fmt.Printf("Grafana dashboard available at:\n%s\n", grafanaUrl.String())
switch options.dashboardShow {
case showConduit:
fmt.Println("Opening Conduit dashboard in the default browser")
err = browser.OpenURL(url.String())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open Conduit URL %s in the default browser: %s", url, err)
os.Exit(1)
}
case showGrafana:
fmt.Println("Opening Grafana dashboard in the default browser")
err = browser.OpenURL(grafanaUrl.String())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open Grafana URL %s in the default browser: %s", grafanaUrl, err)
os.Exit(1)
}
case showURL:
// no-op, we already printed the URLs
}
// blocks until killed
err = kubernetesProxy.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error running proxy: %s", err)
os.Exit(1)
}
return nil
},
}
cmd.Args = cobra.NoArgs
// This is identical to what `kubectl proxy --help` reports, `--port 0` indicates a random port.
cmd.PersistentFlags().IntVarP(&options.dashboardProxyPort, "port", "p", options.dashboardProxyPort, "The port on which to run the proxy (when set to 0, a random port will be used)")
cmd.PersistentFlags().StringVar(&options.dashboardShow, "show", options.dashboardShow, "Open a dashboard in a browser or show URLs in the CLI (one of: conduit, grafana, url)")
return cmd
}
func isDashboardAvailable(client pb.ApiClient) (bool, error) {
@ -123,13 +144,3 @@ func isDashboardAvailable(client pb.ApiClient) (bool, error) {
}
return true, nil
}
func init() {
RootCmd.AddCommand(dashboardCmd)
dashboardCmd.Args = cobra.NoArgs
// This is identical to what `kubectl proxy --help` reports, `--port 0`
// indicates a random port.
dashboardCmd.PersistentFlags().IntVarP(&dashboardProxyPort, "port", "p", 0, "The port on which to run the proxy (when set to 0, a random port will be used)")
dashboardCmd.PersistentFlags().StringVar(&dashboardShow, "show", "conduit", "Open a dashboard in a browser or show URLs in the CLI (one of: conduit, grafana, url)")
}

View File

@ -10,49 +10,49 @@ import (
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get [flags] pods",
Short: "Display one or many mesh resources",
Long: `Display one or many mesh resources.
func newCmdGet() *cobra.Command {
cmd := &cobra.Command{
Use: "get [flags] pods",
Short: "Display one or many mesh resources",
Long: `Display one or many mesh resources.
Only pod resources (aka pods, po) are supported.`,
Example: ` # get all pods
Example: ` # get all pods
conduit get pods`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("please specify a resource type")
}
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("please specify a resource type")
}
if len(args) > 1 {
return errors.New("please specify only one resource type")
}
if len(args) > 1 {
return errors.New("please specify only one resource type")
}
friendlyName := args[0]
resourceType, err := k8s.CanonicalKubernetesNameFromFriendlyName(friendlyName)
friendlyName := args[0]
resourceType, err := k8s.CanonicalKubernetesNameFromFriendlyName(friendlyName)
if err != nil || resourceType != k8s.Pods {
return fmt.Errorf("invalid resource type %s, only %s are allowed as resource types", friendlyName, k8s.Pods)
}
client, err := newPublicAPIClient()
if err != nil {
return err
}
if err != nil || resourceType != k8s.Pods {
return fmt.Errorf("invalid resource type %s, only %s are allowed as resource types", friendlyName, k8s.Pods)
}
client, err := newPublicAPIClient()
if err != nil {
return err
}
podNames, err := getPods(client)
if err != nil {
return err
}
podNames, err := getPods(client)
if err != nil {
return err
}
for _, podName := range podNames {
fmt.Println(podName)
}
for _, podName := range podNames {
fmt.Println(podName)
}
return nil
},
}
return nil
},
}
func init() {
RootCmd.AddCommand(getCmd)
return cmd
}
func getPods(apiClient pb.ApiClient) ([]string, error) {

View File

@ -11,7 +11,6 @@ import (
"github.com/ghodss/yaml"
"github.com/runconduit/conduit/pkg/k8s"
"github.com/runconduit/conduit/pkg/version"
"github.com/spf13/cobra"
appsV1 "k8s.io/api/apps/v1"
batchV1 "k8s.io/api/batch/v1"
@ -29,54 +28,73 @@ const (
ControlPlanePodName = "controller"
)
var (
type injectOptions struct {
initImage string
proxyImage string
proxyUID int64
inboundPort uint
outboundPort uint
ignoreInboundPorts []uint
ignoreOutboundPorts []uint
proxyControlPort uint
proxyMetricsPort uint
proxyAPIPort uint
proxyLogLevel string
)
*proxyConfigOptions
}
var injectCmd = &cobra.Command{
Use: "inject [flags] CONFIG-FILE",
Short: "Add the Conduit proxy to a Kubernetes config",
Long: `Add the Conduit proxy to a Kubernetes config.
func newInjectOptions() *injectOptions {
return &injectOptions{
initImage: "gcr.io/runconduit/proxy-init",
inboundPort: 4143,
outboundPort: 4140,
ignoreInboundPorts: nil,
ignoreOutboundPorts: nil,
proxyConfigOptions: newProxyConfigOptions(),
}
}
func newCmdInject() *cobra.Command {
options := newInjectOptions()
cmd := &cobra.Command{
Use: "inject [flags] CONFIG-FILE",
Short: "Add the Conduit proxy to a Kubernetes config",
Long: `Add the Conduit proxy to a Kubernetes config.
You can use a config file from stdin by using the '-' argument
with 'conduit inject'. e.g. curl http://url.to/yml | conduit inject -
`,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("please specify a kubernetes resource file")
}
var in io.Reader
var err error
if args[0] == "-" {
in = os.Stdin
} else {
if in, err = os.Open(args[0]); err != nil {
return err
if len(args) < 1 {
return fmt.Errorf("please specify a kubernetes resource file")
}
}
exitCode := runInjectCmd(in, os.Stderr, os.Stdout, conduitVersion)
os.Exit(exitCode)
return nil
},
var in io.Reader
var err error
if args[0] == "-" {
in = os.Stdin
} else {
if in, err = os.Open(args[0]); err != nil {
return err
}
}
exitCode := runInjectCmd(in, os.Stderr, os.Stdout, options)
os.Exit(exitCode)
return nil
},
}
addProxyConfigFlags(cmd, options.proxyConfigOptions)
cmd.PersistentFlags().StringVar(&options.initImage, "init-image", options.initImage, "Conduit init container image name")
cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
cmd.PersistentFlags().UintSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports that should skip the proxy and send directly to the application")
cmd.PersistentFlags().UintSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports that should skip the proxy")
return cmd
}
// Returns the integer representation of os.Exit code; 0 on success and 1 on failure.
func runInjectCmd(input io.Reader, errWriter, outWriter io.Writer, version string) int {
func runInjectCmd(input io.Reader, errWriter, outWriter io.Writer, options *injectOptions) int {
postInjectBuf := &bytes.Buffer{}
err := InjectYAML(input, postInjectBuf, version)
err := InjectYAML(input, postInjectBuf, options)
if err != nil {
fmt.Fprintf(errWriter, "Error injecting conduit proxy: %v\n", err)
return 1
@ -93,7 +111,7 @@ func runInjectCmd(input io.Reader, errWriter, outWriter io.Writer, version strin
* and init-container injected. If the pod is unsuitable for having them
* injected, return null.
*/
func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, version string, k8sLabels map[string]string) bool {
func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride string, k8sLabels map[string]string, options *injectOptions) bool {
// Pods with `hostNetwork=true` share a network namespace with the host. The
// init-container would destroy the iptables configuration on the host, so
// skip the injection in this case.
@ -102,21 +120,21 @@ func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, v
}
f := false
inboundSkipPorts := append(ignoreInboundPorts, proxyControlPort, proxyMetricsPort)
inboundSkipPorts := append(options.ignoreInboundPorts, options.proxyControlPort, options.proxyMetricsPort)
inboundSkipPortsStr := make([]string, len(inboundSkipPorts))
for i, p := range inboundSkipPorts {
inboundSkipPortsStr[i] = strconv.Itoa(int(p))
}
outboundSkipPortsStr := make([]string, len(ignoreOutboundPorts))
for i, p := range ignoreOutboundPorts {
outboundSkipPortsStr := make([]string, len(options.ignoreOutboundPorts))
for i, p := range options.ignoreOutboundPorts {
outboundSkipPortsStr[i] = strconv.Itoa(int(p))
}
initArgs := []string{
"--incoming-proxy-port", fmt.Sprintf("%d", inboundPort),
"--outgoing-proxy-port", fmt.Sprintf("%d", outboundPort),
"--proxy-uid", fmt.Sprintf("%d", proxyUID),
"--incoming-proxy-port", fmt.Sprintf("%d", options.inboundPort),
"--outgoing-proxy-port", fmt.Sprintf("%d", options.outboundPort),
"--proxy-uid", fmt.Sprintf("%d", options.proxyUID),
}
if len(inboundSkipPortsStr) > 0 {
@ -131,8 +149,8 @@ func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, v
initContainer := v1.Container{
Name: "conduit-init",
Image: fmt.Sprintf("%s:%s", initImage, version),
ImagePullPolicy: v1.PullPolicy(imagePullPolicy),
Image: fmt.Sprintf("%s:%s", options.initImage, options.conduitVersion),
ImagePullPolicy: v1.PullPolicy(options.imagePullPolicy),
Args: initArgs,
SecurityContext: &v1.SecurityContext{
Capabilities: &v1.Capabilities{
@ -148,31 +166,31 @@ func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, v
sidecar := v1.Container{
Name: "conduit-proxy",
Image: fmt.Sprintf("%s:%s", proxyImage, version),
ImagePullPolicy: v1.PullPolicy(imagePullPolicy),
Image: fmt.Sprintf("%s:%s", options.proxyImage, options.conduitVersion),
ImagePullPolicy: v1.PullPolicy(options.imagePullPolicy),
SecurityContext: &v1.SecurityContext{
RunAsUser: &proxyUID,
RunAsUser: &options.proxyUID,
},
Ports: []v1.ContainerPort{
{
Name: "conduit-proxy",
ContainerPort: int32(inboundPort),
ContainerPort: int32(options.inboundPort),
},
{
Name: "conduit-metrics",
ContainerPort: int32(proxyMetricsPort),
ContainerPort: int32(options.proxyMetricsPort),
},
},
Env: []v1.EnvVar{
{Name: "CONDUIT_PROXY_LOG", Value: proxyLogLevel},
{Name: "CONDUIT_PROXY_LOG", Value: options.proxyLogLevel},
{
Name: "CONDUIT_PROXY_CONTROL_URL",
Value: fmt.Sprintf("tcp://%s:%d", controlPlaneDNS, proxyAPIPort),
Value: fmt.Sprintf("tcp://%s:%d", controlPlaneDNS, options.proxyAPIPort),
},
{Name: "CONDUIT_PROXY_CONTROL_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", proxyControlPort)},
{Name: "CONDUIT_PROXY_METRICS_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", proxyMetricsPort)},
{Name: "CONDUIT_PROXY_PRIVATE_LISTENER", Value: fmt.Sprintf("tcp://127.0.0.1:%d", outboundPort)},
{Name: "CONDUIT_PROXY_PUBLIC_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", inboundPort)},
{Name: "CONDUIT_PROXY_CONTROL_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", options.proxyControlPort)},
{Name: "CONDUIT_PROXY_METRICS_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", options.proxyMetricsPort)},
{Name: "CONDUIT_PROXY_PRIVATE_LISTENER", Value: fmt.Sprintf("tcp://127.0.0.1:%d", options.outboundPort)},
{Name: "CONDUIT_PROXY_PUBLIC_LISTENER", Value: fmt.Sprintf("tcp://0.0.0.0:%d", options.inboundPort)},
{
Name: "CONDUIT_PROXY_POD_NAMESPACE",
ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.namespace"}},
@ -184,7 +202,7 @@ func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, v
t.Annotations = make(map[string]string)
}
t.Annotations[k8s.CreatedByAnnotation] = k8s.CreatedByAnnotationValue()
t.Annotations[k8s.ProxyVersionAnnotation] = version
t.Annotations[k8s.ProxyVersionAnnotation] = options.conduitVersion
if t.Labels == nil {
t.Labels = make(map[string]string)
@ -201,7 +219,7 @@ func injectPodTemplateSpec(t *v1.PodTemplateSpec, controlPlaneDNSNameOverride, v
}
// InjectYAML takes an input stream of YAML, outputting injected YAML to out.
func InjectYAML(in io.Reader, out io.Writer, version string) error {
func InjectYAML(in io.Reader, out io.Writer, options *injectOptions) error {
reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(in, 4096))
// Iterate over all YAML objects in the input
@ -215,7 +233,7 @@ func InjectYAML(in io.Reader, out io.Writer, version string) error {
return err
}
result, err := injectResource(bytes, version)
result, err := injectResource(bytes, options)
if err != nil {
return err
}
@ -227,7 +245,7 @@ func InjectYAML(in io.Reader, out io.Writer, version string) error {
return nil
}
func injectList(b []byte, version string) ([]byte, error) {
func injectList(b []byte, options *injectOptions) ([]byte, error) {
var sourceList v1.List
if err := yaml.Unmarshal(b, &sourceList); err != nil {
return nil, err
@ -236,7 +254,7 @@ func injectList(b []byte, version string) ([]byte, error) {
items := []runtime.RawExtension{}
for _, item := range sourceList.Items {
result, err := injectResource(item.Raw, version)
result, err := injectResource(item.Raw, options)
if err != nil {
return nil, err
}
@ -256,7 +274,7 @@ func injectList(b []byte, version string) ([]byte, error) {
return yaml.Marshal(sourceList)
}
func injectResource(bytes []byte, version string) ([]byte, error) {
func injectResource(bytes []byte, options *injectOptions) ([]byte, error) {
// The Kuberentes API is versioned and each version has an API modeled
// with its own distinct Go types. If we tell `yaml.Unmarshal()` which
// version we support then it will provide a representation of that
@ -356,14 +374,14 @@ func injectResource(bytes []byte, version string) ([]byte, error) {
// Lists are a little different than the other types. There's no immediate
// pod template. Because of this, we do a recursive call for each element
// in the list (instead of just marshaling the injected pod template).
return injectList(bytes, version)
return injectList(bytes, options)
}
// If we don't inject anything into the pod template then output the
// original serialization of the original object. Otherwise, output the
// serialization of the modified object.
output := bytes
if podTemplateSpec != nil && injectPodTemplateSpec(podTemplateSpec, DNSNameOverride, version, k8sLabels) {
if podTemplateSpec != nil && injectPodTemplateSpec(podTemplateSpec, DNSNameOverride, k8sLabels, options) {
var err error
output, err = yaml.Marshal(obj)
if err != nil {
@ -373,24 +391,3 @@ func injectResource(bytes []byte, version string) ([]byte, error) {
return output, nil
}
func init() {
RootCmd.AddCommand(injectCmd)
addProxyConfigFlags(injectCmd)
injectCmd.PersistentFlags().StringVar(&initImage, "init-image", "gcr.io/runconduit/proxy-init", "Conduit init container image name")
injectCmd.PersistentFlags().UintVar(&inboundPort, "inbound-port", 4143, "Proxy port to use for inbound traffic")
injectCmd.PersistentFlags().UintVar(&outboundPort, "outbound-port", 4140, "Proxy port to use for outbound traffic")
injectCmd.PersistentFlags().UintSliceVar(&ignoreInboundPorts, "skip-inbound-ports", nil, "Ports that should skip the proxy and send directly to the application")
injectCmd.PersistentFlags().UintSliceVar(&ignoreOutboundPorts, "skip-outbound-ports", nil, "Outbound ports that should skip the proxy")
}
func addProxyConfigFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&conduitVersion, "conduit-version", "v", version.Version, "Tag to be used for Conduit images")
cmd.PersistentFlags().StringVar(&proxyImage, "proxy-image", "gcr.io/runconduit/proxy", "Conduit proxy container image name")
cmd.PersistentFlags().StringVar(&imagePullPolicy, "image-pull-policy", "IfNotPresent", "Docker image pull policy")
cmd.PersistentFlags().Int64Var(&proxyUID, "proxy-uid", 2102, "Run the proxy under this user ID")
cmd.PersistentFlags().StringVar(&proxyLogLevel, "proxy-log-level", "warn,conduit_proxy=info", "Log level for the proxy")
cmd.PersistentFlags().UintVar(&proxyAPIPort, "api-port", 8086, "Port where the Conduit controller is running")
cmd.PersistentFlags().UintVar(&proxyControlPort, "control-port", 4190, "Proxy port to use for control")
cmd.PersistentFlags().UintVar(&proxyMetricsPort, "metrics-port", 4191, "Proxy port to serve metrics on")
}

View File

@ -10,7 +10,8 @@ import (
)
func TestInjectYAML(t *testing.T) {
testInjectVersion := "testinjectversion"
testInjectOptions := newInjectOptions()
testInjectOptions.conduitVersion = "testinjectversion"
testCases := []struct {
inputFileName string
goldenFileName string
@ -34,7 +35,7 @@ func TestInjectYAML(t *testing.T) {
output := new(bytes.Buffer)
err = InjectYAML(read, output, testInjectVersion)
err = InjectYAML(read, output, testInjectOptions)
if err != nil {
t.Errorf("Unexpected error injecting YAML: %v\n", err)
}
@ -52,7 +53,8 @@ func TestInjectYAML(t *testing.T) {
}
func TestRunInjectCmd(t *testing.T) {
testInjectVersion := "testinjectversion"
testInjectOptions := newInjectOptions()
testInjectOptions.conduitVersion = "testinjectversion"
testCases := []struct {
inputFileName string
stdErrGoldenFileName string
@ -81,7 +83,7 @@ func TestRunInjectCmd(t *testing.T) {
t.Fatalf("Unexpected error: %v", err)
}
exitCode := runInjectCmd(in, errBuffer, outBuffer, testInjectVersion)
exitCode := runInjectCmd(in, errBuffer, outBuffer, testInjectOptions)
if exitCode != tc.exitCode {
t.Fatalf("Expected exit code to be %d but got: %d", tc.exitCode, exitCode)
}

View File

@ -32,52 +32,75 @@ type installConfig struct {
CreatedByAnnotation string
}
var (
conduitVersion string
type installOptions struct {
dockerRegistry string
controllerReplicas uint
webReplicas uint
prometheusReplicas uint
imagePullPolicy string
controllerLogLevel string
)
var installCmd = &cobra.Command{
Use: "install [flags]",
Short: "Output Kubernetes configs to install Conduit",
Long: "Output Kubernetes configs to install Conduit.",
RunE: func(cmd *cobra.Command, args []string) error {
config, err := validateAndBuildConfig()
if err != nil {
return err
}
return render(*config, os.Stdout)
},
*proxyConfigOptions
}
func validateAndBuildConfig() (*installConfig, error) {
if err := validate(); err != nil {
func newInstallOptions() *installOptions {
return &installOptions{
dockerRegistry: "gcr.io/runconduit",
controllerReplicas: 1,
webReplicas: 1,
prometheusReplicas: 1,
controllerLogLevel: "info",
proxyConfigOptions: newProxyConfigOptions(),
}
}
func newCmdInstall() *cobra.Command {
options := newInstallOptions()
cmd := &cobra.Command{
Use: "install [flags]",
Short: "Output Kubernetes configs to install Conduit",
Long: "Output Kubernetes configs to install Conduit.",
RunE: func(cmd *cobra.Command, args []string) error {
config, err := validateAndBuildConfig(options)
if err != nil {
return err
}
return render(*config, os.Stdout, options)
},
}
addProxyConfigFlags(cmd, options.proxyConfigOptions)
cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
cmd.PersistentFlags().UintVar(&options.controllerReplicas, "controller-replicas", options.controllerReplicas, "Replicas of the controller to deploy")
cmd.PersistentFlags().UintVar(&options.webReplicas, "web-replicas", options.webReplicas, "Replicas of the web server to deploy")
cmd.PersistentFlags().UintVar(&options.prometheusReplicas, "prometheus-replicas", options.prometheusReplicas, "Replicas of prometheus to deploy")
cmd.PersistentFlags().StringVar(&options.controllerLogLevel, "controller-log-level", options.controllerLogLevel, "Log level for the controller and web components")
return cmd
}
func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
if err := validate(options); err != nil {
return nil, err
}
return &installConfig{
Namespace: controlPlaneNamespace,
ControllerImage: fmt.Sprintf("%s/controller:%s", dockerRegistry, conduitVersion),
WebImage: fmt.Sprintf("%s/web:%s", dockerRegistry, conduitVersion),
ControllerImage: fmt.Sprintf("%s/controller:%s", options.dockerRegistry, options.conduitVersion),
WebImage: fmt.Sprintf("%s/web:%s", options.dockerRegistry, options.conduitVersion),
PrometheusImage: "prom/prometheus:v2.2.1",
GrafanaImage: fmt.Sprintf("%s/grafana:%s", dockerRegistry, conduitVersion),
ControllerReplicas: controllerReplicas,
WebReplicas: webReplicas,
PrometheusReplicas: prometheusReplicas,
ImagePullPolicy: imagePullPolicy,
GrafanaImage: fmt.Sprintf("%s/grafana:%s", options.dockerRegistry, options.conduitVersion),
ControllerReplicas: options.controllerReplicas,
WebReplicas: options.webReplicas,
PrometheusReplicas: options.prometheusReplicas,
ImagePullPolicy: options.imagePullPolicy,
UUID: uuid.NewV4().String(),
CliVersion: k8s.CreatedByAnnotationValue(),
ControllerLogLevel: controllerLogLevel,
ControllerLogLevel: options.controllerLogLevel,
ControllerComponentLabel: k8s.ControllerComponentLabel,
CreatedByAnnotation: k8s.CreatedByAnnotation,
}, nil
}
func render(config installConfig, w io.Writer) error {
func render(config installConfig, w io.Writer, options *installOptions) error {
template, err := template.New("conduit").Parse(install.Template)
if err != nil {
return err
@ -87,40 +110,32 @@ func render(config installConfig, w io.Writer) error {
if err != nil {
return err
}
return InjectYAML(buf, w, conduitVersion)
injectOptions := newInjectOptions()
injectOptions.proxyConfigOptions = options.proxyConfigOptions
return InjectYAML(buf, w, injectOptions)
}
var alphaNumDash = regexp.MustCompile("^[a-zA-Z0-9-]+$")
var alphaNumDashDot = regexp.MustCompile("^[\\.a-zA-Z0-9-]+$")
var alphaNumDashDotSlash = regexp.MustCompile("^[\\./a-zA-Z0-9-]+$")
func validate() error {
func validate(options *installOptions) error {
// These regexs are not as strict as they could be, but are a quick and dirty
// sanity check against illegal characters.
if !alphaNumDash.MatchString(controlPlaneNamespace) {
return fmt.Errorf("%s is not a valid namespace", controlPlaneNamespace)
}
if !alphaNumDashDot.MatchString(conduitVersion) {
return fmt.Errorf("%s is not a valid version", conduitVersion)
if !alphaNumDashDot.MatchString(options.conduitVersion) {
return fmt.Errorf("%s is not a valid version", options.conduitVersion)
}
if !alphaNumDashDotSlash.MatchString(dockerRegistry) {
return fmt.Errorf("%s is not a valid Docker registry", dockerRegistry)
if !alphaNumDashDotSlash.MatchString(options.dockerRegistry) {
return fmt.Errorf("%s is not a valid Docker registry", options.dockerRegistry)
}
if imagePullPolicy != "Always" && imagePullPolicy != "IfNotPresent" && imagePullPolicy != "Never" {
if options.imagePullPolicy != "Always" && options.imagePullPolicy != "IfNotPresent" && options.imagePullPolicy != "Never" {
return fmt.Errorf("--image-pull-policy must be one of: Always, IfNotPresent, Never")
}
if _, err := log.ParseLevel(controllerLogLevel); err != nil {
if _, err := log.ParseLevel(options.controllerLogLevel); err != nil {
return fmt.Errorf("--controller-log-level must be one of: panic, fatal, error, warn, info, debug")
}
return nil
}
func init() {
RootCmd.AddCommand(installCmd)
addProxyConfigFlags(installCmd)
installCmd.PersistentFlags().StringVar(&dockerRegistry, "registry", "gcr.io/runconduit", "Docker registry to pull images from")
installCmd.PersistentFlags().UintVar(&controllerReplicas, "controller-replicas", 1, "Replicas of the controller to deploy")
installCmd.PersistentFlags().UintVar(&webReplicas, "web-replicas", 1, "Replicas of the web server to deploy")
installCmd.PersistentFlags().UintVar(&prometheusReplicas, "prometheus-replicas", 1, "Replicas of prometheus to deploy")
installCmd.PersistentFlags().StringVar(&controllerLogLevel, "controller-log-level", "info", "Log level for the controller and web components")
}

View File

@ -3,7 +3,6 @@ package cmd
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
)
@ -12,7 +11,8 @@ func TestRender(t *testing.T) {
// The default configuration, with the random UUID overridden with a fixed
// value to facilitate testing.
defaultControlPlaneNamespace := controlPlaneNamespace
defaultConfig, err := validateAndBuildConfig()
defaultOptions := newInstallOptions()
defaultConfig, err := validateAndBuildConfig(defaultOptions)
if err != nil {
t.Fatalf("Unexpected error from validateAndBuildConfig(): %v", err)
}
@ -51,7 +51,7 @@ func TestRender(t *testing.T) {
controlPlaneNamespace = tc.controlPlaneNamespace
var buf bytes.Buffer
err := render(tc.config, &buf)
err := render(tc.config, &buf, defaultOptions)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@ -4,6 +4,7 @@ import (
"github.com/runconduit/conduit/controller/api/public"
pb "github.com/runconduit/conduit/controller/gen/public"
"github.com/runconduit/conduit/pkg/k8s"
"github.com/runconduit/conduit/pkg/version"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -32,6 +33,16 @@ func init() {
RootCmd.PersistentFlags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests")
RootCmd.PersistentFlags().StringVar(&apiAddr, "api-addr", "", "Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)")
RootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Turn on debug logging")
RootCmd.AddCommand(newCmdCheck())
RootCmd.AddCommand(newCmdCompletion())
RootCmd.AddCommand(newCmdDashboard())
RootCmd.AddCommand(newCmdGet())
RootCmd.AddCommand(newCmdInject())
RootCmd.AddCommand(newCmdInstall())
RootCmd.AddCommand(newCmdStat())
RootCmd.AddCommand(newCmdTap())
RootCmd.AddCommand(newCmdVersion())
}
func newPublicAPIClient() (pb.ApiClient, error) {
@ -44,3 +55,38 @@ func newPublicAPIClient() (pb.ApiClient, error) {
}
return public.NewExternalClient(controlPlaneNamespace, kubeAPI)
}
type proxyConfigOptions struct {
conduitVersion string
proxyImage string
imagePullPolicy string
proxyUID int64
proxyLogLevel string
proxyAPIPort uint
proxyControlPort uint
proxyMetricsPort uint
}
func newProxyConfigOptions() *proxyConfigOptions {
return &proxyConfigOptions{
conduitVersion: version.Version,
proxyImage: "gcr.io/runconduit/proxy",
imagePullPolicy: "IfNotPresent",
proxyUID: 2102,
proxyLogLevel: "warn,conduit_proxy=info",
proxyAPIPort: 8086,
proxyControlPort: 4190,
proxyMetricsPort: 4191,
}
}
func addProxyConfigFlags(cmd *cobra.Command, options *proxyConfigOptions) {
cmd.PersistentFlags().StringVarP(&options.conduitVersion, "conduit-version", "v", options.conduitVersion, "Tag to be used for Conduit images")
cmd.PersistentFlags().StringVar(&options.proxyImage, "proxy-image", options.proxyImage, "Conduit proxy container image name")
cmd.PersistentFlags().StringVar(&options.imagePullPolicy, "image-pull-policy", options.imagePullPolicy, "Docker image pull policy")
cmd.PersistentFlags().Int64Var(&options.proxyUID, "proxy-uid", options.proxyUID, "Run the proxy under this user ID")
cmd.PersistentFlags().StringVar(&options.proxyLogLevel, "proxy-log-level", options.proxyLogLevel, "Log level for the proxy")
cmd.PersistentFlags().UintVar(&options.proxyAPIPort, "api-port", options.proxyAPIPort, "Port where the Conduit controller is running")
cmd.PersistentFlags().UintVar(&options.proxyControlPort, "control-port", options.proxyControlPort, "Proxy port to use for control")
cmd.PersistentFlags().UintVar(&options.proxyMetricsPort, "metrics-port", options.proxyMetricsPort, "Proxy port to serve metrics on")
}

View File

@ -18,18 +18,35 @@ import (
"k8s.io/api/core/v1"
)
var (
timeWindow string
namespace string
toNamespace, toResource string
fromNamespace, fromResource string
allNamespaces bool
)
type statOptions struct {
namespace string
timeWindow string
toNamespace string
toResource string
fromNamespace string
fromResource string
allNamespaces bool
}
var statCmd = &cobra.Command{
Use: "stat [flags] (RESOURCE)",
Short: "Display traffic stats about one or many resources",
Long: `Display traffic stats about one or many resources.
func newStatOptions() *statOptions {
return &statOptions{
namespace: "default",
timeWindow: "1m",
toNamespace: "",
toResource: "",
fromNamespace: "",
fromResource: "",
allNamespaces: false,
}
}
func newCmdStat() *cobra.Command {
options := newStatOptions()
cmd := &cobra.Command{
Use: "stat [flags] (RESOURCE)",
Short: "Display traffic stats about one or many resources",
Long: `Display traffic stats about one or many resources.
The RESOURCE argument specifies the target resource(s) to aggregate stats over:
(TYPE [NAME] | TYPE/NAME)
@ -52,7 +69,7 @@ Valid resource types include:
This command will hide resources that have completed, such as pods that are in the Succeeded or Failed phases.
If no resource name is specified, displays stats about all resources of the specified RESOURCETYPE`,
Example: ` # Get all deployments in the test namespace.
Example: ` # Get all deployments in the test namespace.
conduit stat deployments -n test
# Get the hello1 replication controller in the test namespace.
@ -72,47 +89,42 @@ If no resource name is specified, displays stats about all resources of the spec
# Get all services in all namespaces that receive calls from hello1 deployment in the test namesapce.
conduit stat services --from deploy/hello1 --from-namespace test --all-namespaces`,
Args: cobra.RangeArgs(1, 2),
ValidArgs: util.ValidTargets,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := newPublicAPIClient()
if err != nil {
return fmt.Errorf("error creating api client while making stats request: %v", err)
}
Args: cobra.RangeArgs(1, 2),
ValidArgs: util.ValidTargets,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := newPublicAPIClient()
if err != nil {
return fmt.Errorf("error creating api client while making stats request: %v", err)
}
req, err := buildStatSummaryRequest(
timeWindow, allNamespaces,
args, namespace,
toResource, toNamespace,
fromResource, fromNamespace,
)
if err != nil {
return fmt.Errorf("error creating metrics request while making stats request: %v", err)
}
req, err := buildStatSummaryRequest(args, options)
if err != nil {
return fmt.Errorf("error creating metrics request while making stats request: %v", err)
}
output, err := requestStatsFromAPI(client, req, options)
if err != nil {
return err
}
_, err = fmt.Print(output)
output, err := requestStatsFromAPI(client, req)
if err != nil {
return err
}
},
}
_, err = fmt.Print(output)
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace of the specified resource")
cmd.PersistentFlags().StringVarP(&options.timeWindow, "time-window", "t", options.timeWindow, "Stat window (for example: \"10s\", \"1m\", \"10m\", \"1h\")")
cmd.PersistentFlags().StringVar(&options.toResource, "to", options.toResource, "If present, restricts outbound stats to the specified resource name")
cmd.PersistentFlags().StringVar(&options.toNamespace, "to-namespace", options.toNamespace, "Sets the namespace used to lookup the \"--to\" resource; by default the current \"--namespace\" is used")
cmd.PersistentFlags().StringVar(&options.fromResource, "from", options.fromResource, "If present, restricts outbound stats from the specified resource name")
cmd.PersistentFlags().StringVar(&options.fromNamespace, "from-namespace", options.fromNamespace, "Sets the namespace used from lookup the \"--from\" resource; by default the current \"--namespace\" is used")
cmd.PersistentFlags().BoolVar(&options.allNamespaces, "all-namespaces", options.allNamespaces, "If present, returns stats across all namespaces, ignoring the \"--namespace\" flag")
return err
},
return cmd
}
func init() {
RootCmd.AddCommand(statCmd)
statCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "Namespace of the specified resource")
statCmd.PersistentFlags().StringVarP(&timeWindow, "time-window", "t", "1m", "Stat window (for example: \"10s\", \"1m\", \"10m\", \"1h\")")
statCmd.PersistentFlags().StringVar(&toResource, "to", "", "If present, restricts outbound stats to the specified resource name")
statCmd.PersistentFlags().StringVar(&toNamespace, "to-namespace", "", "Sets the namespace used to lookup the \"--to\" resource; by default the current \"--namespace\" is used")
statCmd.PersistentFlags().StringVar(&fromResource, "from", "", "If present, restricts outbound stats from the specified resource name")
statCmd.PersistentFlags().StringVar(&fromNamespace, "from-namespace", "", "Sets the namespace used from lookup the \"--from\" resource; by default the current \"--namespace\" is used")
statCmd.PersistentFlags().BoolVar(&allNamespaces, "all-namespaces", false, "If present, returns stats across all namespaces, ignoring the \"--namespace\" flag")
}
func requestStatsFromAPI(client pb.ApiClient, req *pb.StatSummaryRequest) (string, error) {
func requestStatsFromAPI(client pb.ApiClient, req *pb.StatSummaryRequest, options *statOptions) (string, error) {
resp, err := client.StatSummary(context.Background(), req)
if err != nil {
return "", fmt.Errorf("StatSummary API error: %v", err)
@ -121,13 +133,13 @@ func requestStatsFromAPI(client pb.ApiClient, req *pb.StatSummaryRequest) (strin
return "", fmt.Errorf("StatSummary API response error: %v", e.Error)
}
return renderStats(resp, req.Selector.Resource.Type), nil
return renderStats(resp, req.Selector.Resource.Type, options), nil
}
func renderStats(resp *pb.StatSummaryResponse, resourceType string) string {
func renderStats(resp *pb.StatSummaryResponse, resourceType string, options *statOptions) string {
var buffer bytes.Buffer
w := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', tabwriter.AlignRight)
writeStatsToBuffer(resp, resourceType, w)
writeStatsToBuffer(resp, resourceType, w, options)
w.Flush()
// strip left padding on the first column
@ -157,7 +169,7 @@ var (
namespaceHeader = "NAMESPACE"
)
func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w *tabwriter.Writer) {
func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w *tabwriter.Writer, options *statOptions) {
maxNameLength := len(nameHeader)
maxNamespaceLength := len(namespaceHeader)
statTables := make(map[string]map[string]*row)
@ -216,16 +228,16 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w
}
lastDisplayedStat = false
if reqResourceType == k8s.All {
printStatTable(stats, resourceType, w, maxNameLength, maxNamespaceLength)
printStatTable(stats, resourceType, w, maxNameLength, maxNamespaceLength, options)
} else {
printStatTable(stats, "", w, maxNameLength, maxNamespaceLength)
printStatTable(stats, "", w, maxNameLength, maxNamespaceLength, options)
}
}
}
func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Writer, maxNameLength int, maxNamespaceLength int) {
func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Writer, maxNameLength int, maxNamespaceLength int, options *statOptions) {
headers := make([]string, 0)
if allNamespaces {
if options.allNamespaces {
headers = append(headers,
namespaceHeader+strings.Repeat(" ", maxNamespaceLength-len(namespaceHeader)))
}
@ -252,7 +264,7 @@ func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Wri
templateString := "%s\t%s\t%.2f%%\t%.1frps\t%dms\t%dms\t%dms\t\n"
templateStringEmpty := "%s\t%s\t-\t-\t-\t-\t-\t\n"
if allNamespaces {
if options.allNamespaces {
values = append(values,
namespace+strings.Repeat(" ", maxNamespaceLength-len(namespace)))
templateString = "%s\t" + templateString
@ -287,16 +299,11 @@ func getNamePrefix(resourceType string) string {
}
}
func buildStatSummaryRequest(
timeWindow string, allNamespaces bool,
resource []string, namespace string,
toResource, toNamespace string,
fromResource, fromNamespace string,
) (*pb.StatSummaryRequest, error) {
targetNamespace := namespace
if allNamespaces {
func buildStatSummaryRequest(resource []string, options *statOptions) (*pb.StatSummaryRequest, error) {
targetNamespace := options.namespace
if options.allNamespaces {
targetNamespace = ""
} else if namespace == "" {
} else if options.namespace == "" {
targetNamespace = v1.NamespaceDefault
}
@ -306,30 +313,30 @@ func buildStatSummaryRequest(
}
var toRes, fromRes pb.Resource
if toResource != "" {
toRes, err = util.BuildResource(toNamespace, toResource)
if options.toResource != "" {
toRes, err = util.BuildResource(options.toNamespace, options.toResource)
if err != nil {
return nil, err
}
}
if fromResource != "" {
fromRes, err = util.BuildResource(fromNamespace, fromResource)
if options.fromResource != "" {
fromRes, err = util.BuildResource(options.fromNamespace, options.fromResource)
if err != nil {
return nil, err
}
}
requestParams := util.StatSummaryRequestParams{
TimeWindow: timeWindow,
TimeWindow: options.timeWindow,
ResourceName: target.Name,
ResourceType: target.Type,
Namespace: targetNamespace,
ToName: toRes.Name,
ToType: toRes.Type,
ToNamespace: toNamespace,
ToNamespace: options.toNamespace,
FromName: fromRes.Name,
FromType: fromRes.Type,
FromNamespace: fromNamespace,
FromNamespace: options.fromNamespace,
}
return util.BuildStatSummaryRequest(requestParams)

View File

@ -16,18 +16,37 @@ import (
"google.golang.org/grpc/codes"
)
var (
maxRps float32
scheme string
method string
authority string
path string
)
type tapOptions struct {
namespace string
toResource string
toNamespace string
maxRps float32
scheme string
method string
authority string
path string
}
var tapCmd = &cobra.Command{
Use: "tap [flags] (RESOURCE)",
Short: "Listen to a traffic stream",
Long: `Listen to a traffic stream.
func newTapOptions() *tapOptions {
return &tapOptions{
namespace: "default",
toResource: "",
toNamespace: "",
maxRps: 1.0,
scheme: "",
method: "",
authority: "",
path: "",
}
}
func newCmdTap() *cobra.Command {
options := newTapOptions()
cmd := &cobra.Command{
Use: "tap [flags] (RESOURCE)",
Short: "Listen to a traffic stream",
Long: `Listen to a traffic stream.
The RESOURCE argument specifies the target resource(s) to tap:
(TYPE [NAME] | TYPE/NAME)
@ -45,7 +64,7 @@ var tapCmd = &cobra.Command{
* pods
* replicationcontrollers
* services (only supported as a "--to" resource)`,
Example: ` # tap the web deployment in the default namespace
Example: ` # tap the web deployment in the default namespace
conduit tap deploy/web
# tap the web-dlbvj pod in the default namespace
@ -53,56 +72,49 @@ var tapCmd = &cobra.Command{
# tap the test namespace, filter by request to prod namespace
conduit tap ns/test --to ns/prod`,
Args: cobra.RangeArgs(1, 2),
ValidArgs: apiUtil.ValidTargets,
RunE: func(cmd *cobra.Command, args []string) error {
req, err := buildTapByResourceRequest(
args, namespace,
toResource, toNamespace,
maxRps,
scheme, method, authority, path,
)
if err != nil {
return err
}
Args: cobra.RangeArgs(1, 2),
ValidArgs: apiUtil.ValidTargets,
RunE: func(cmd *cobra.Command, args []string) error {
req, err := buildTapByResourceRequest(args, options)
if err != nil {
return err
}
client, err := newPublicAPIClient()
if err != nil {
return err
}
client, err := newPublicAPIClient()
if err != nil {
return err
}
return requestTapByResourceFromAPI(os.Stdout, client, req)
},
}
return requestTapByResourceFromAPI(os.Stdout, client, req)
},
}
func init() {
RootCmd.AddCommand(tapCmd)
tapCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default",
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace,
"Namespace of the specified resource")
tapCmd.PersistentFlags().StringVar(&toResource, "to", "",
cmd.PersistentFlags().StringVar(&options.toResource, "to", options.toResource,
"Display requests to this resource")
tapCmd.PersistentFlags().StringVar(&toNamespace, "to-namespace", "",
cmd.PersistentFlags().StringVar(&options.toNamespace, "to-namespace", options.toNamespace,
"Sets the namespace used to lookup the \"--to\" resource; by default the current \"--namespace\" is used")
tapCmd.PersistentFlags().Float32Var(&maxRps, "max-rps", 1.0,
cmd.PersistentFlags().Float32Var(&options.maxRps, "max-rps", options.maxRps,
"Maximum requests per second to tap.")
tapCmd.PersistentFlags().StringVar(&scheme, "scheme", "",
cmd.PersistentFlags().StringVar(&options.scheme, "scheme", options.scheme,
"Display requests with this scheme")
tapCmd.PersistentFlags().StringVar(&method, "method", "",
cmd.PersistentFlags().StringVar(&options.method, "method", options.method,
"Display requests with this HTTP method")
tapCmd.PersistentFlags().StringVar(&authority, "authority", "",
cmd.PersistentFlags().StringVar(&options.authority, "authority", options.authority,
"Display requests with this :authority")
tapCmd.PersistentFlags().StringVar(&path, "path", "",
cmd.PersistentFlags().StringVar(&options.path, "path", options.path,
"Display requests with paths that start with this prefix")
return cmd
}
func buildTapByResourceRequest(
resource []string, namespace string,
toResource, toNamespace string,
maxRps float32,
scheme, method, authority, path string,
resource []string,
options *tapOptions,
) (*pb.TapByResourceRequest, error) {
target, err := apiUtil.BuildResource(namespace, resource...)
target, err := apiUtil.BuildResource(options.namespace, resource...)
if err != nil {
return nil, fmt.Errorf("target resource invalid: %s", err)
}
@ -112,8 +124,8 @@ func buildTapByResourceRequest(
matches := []*pb.TapByResourceRequest_Match{}
if toResource != "" {
destination, err := apiUtil.BuildResource(toNamespace, toResource)
if options.toResource != "" {
destination, err := apiUtil.BuildResource(options.toNamespace, options.toResource)
if err != nil {
return nil, fmt.Errorf("destination resource invalid: %s", err)
}
@ -131,27 +143,27 @@ func buildTapByResourceRequest(
matches = append(matches, &match)
}
if scheme != "" {
if options.scheme != "" {
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
Match: &pb.TapByResourceRequest_Match_Http_Scheme{Scheme: scheme},
Match: &pb.TapByResourceRequest_Match_Http_Scheme{Scheme: options.scheme},
})
matches = append(matches, &match)
}
if method != "" {
if options.method != "" {
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
Match: &pb.TapByResourceRequest_Match_Http_Method{Method: method},
Match: &pb.TapByResourceRequest_Match_Http_Method{Method: options.method},
})
matches = append(matches, &match)
}
if authority != "" {
if options.authority != "" {
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
Match: &pb.TapByResourceRequest_Match_Http_Authority{Authority: authority},
Match: &pb.TapByResourceRequest_Match_Http_Authority{Authority: options.authority},
})
matches = append(matches, &match)
}
if path != "" {
if options.path != "" {
match := buildMatchHTTP(&pb.TapByResourceRequest_Match_Http{
Match: &pb.TapByResourceRequest_Match_Http_Path{Path: path},
Match: &pb.TapByResourceRequest_Match_Http_Path{Path: options.path},
})
matches = append(matches, &match)
}
@ -160,7 +172,7 @@ func buildTapByResourceRequest(
Target: &pb.ResourceSelection{
Resource: &target,
},
MaxRps: maxRps,
MaxRps: options.maxRps,
Match: &pb.TapByResourceRequest_Match{
Match: &pb.TapByResourceRequest_Match_All{
All: &pb.TapByResourceRequest_Match_Seq{

View File

@ -19,15 +19,16 @@ func TestRequestTapByResourceFromAPI(t *testing.T) {
t.Run("Should render busy response if everything went well", func(t *testing.T) {
resourceType := k8s.Pods
targetName := "pod-666"
scheme := "https"
method := "GET"
authority := "localhost"
path := "/some/path"
options := &tapOptions{
scheme: "https",
method: "GET",
authority: "localhost",
path: "/some/path",
}
req, err := buildTapByResourceRequest(
[]string{resourceType, targetName},
"", "", "", 0,
scheme, method, authority, path,
options,
)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -40,8 +41,8 @@ func TestRequestTapByResourceFromAPI(t *testing.T) {
Id: &common.TapEvent_Http_StreamId{
Base: 1,
},
Authority: authority,
Path: path,
Authority: options.authority,
Path: options.path,
},
},
},
@ -94,15 +95,16 @@ func TestRequestTapByResourceFromAPI(t *testing.T) {
t.Run("Should render empty response if no events returned", func(t *testing.T) {
resourceType := k8s.Pods
targetName := "pod-666"
scheme := "https"
method := "GET"
authority := "localhost"
path := "/some/path"
options := &tapOptions{
scheme: "https",
method: "GET",
authority: "localhost",
path: "/some/path",
}
req, err := buildTapByResourceRequest(
[]string{resourceType, targetName},
"", "", "", 0,
scheme, method, authority, path,
options,
)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -134,15 +136,16 @@ func TestRequestTapByResourceFromAPI(t *testing.T) {
t.SkipNow()
resourceType := k8s.Pods
targetName := "pod-666"
scheme := "https"
method := "GET"
authority := "localhost"
path := "/some/path"
options := &tapOptions{
scheme: "https",
method: "GET",
authority: "localhost",
path: "/some/path",
}
req, err := buildTapByResourceRequest(
[]string{resourceType, targetName},
"", "", "", 0,
scheme, method, authority, path,
options,
)
if err != nil {
t.Fatalf("Unexpected error: %v", err)

View File

@ -12,42 +12,54 @@ import (
const DefaultVersionString = "unavailable"
var shortVersion bool
var onlyClientVersion bool
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the client and server version information",
Run: func(cmd *cobra.Command, args []string) {
clientVersion := version.Version
if shortVersion {
fmt.Println(clientVersion)
} else {
fmt.Printf("Client version: %s\n", clientVersion)
}
conduitApiClient, err := newPublicAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Error connecting to server: %s\n", err)
os.Exit(1)
}
if !onlyClientVersion {
serverVersion := getServerVersion(conduitApiClient)
if shortVersion {
fmt.Println(serverVersion)
} else {
fmt.Printf("Server version: %s\n", serverVersion)
}
}
},
type versionOptions struct {
shortVersion bool
onlyClientVersion bool
}
func init() {
RootCmd.AddCommand(versionCmd)
versionCmd.Args = cobra.NoArgs
versionCmd.PersistentFlags().BoolVar(&shortVersion, "short", false, "Print the version number(s) only, with no additional output")
versionCmd.PersistentFlags().BoolVar(&onlyClientVersion, "client", false, "Print the client version only")
func newVersionOptions() *versionOptions {
return &versionOptions{
shortVersion: false,
onlyClientVersion: false,
}
}
func newCmdVersion() *cobra.Command {
options := newVersionOptions()
cmd := &cobra.Command{
Use: "version",
Short: "Print the client and server version information",
Run: func(cmd *cobra.Command, args []string) {
clientVersion := version.Version
if options.shortVersion {
fmt.Println(clientVersion)
} else {
fmt.Printf("Client version: %s\n", clientVersion)
}
conduitApiClient, err := newPublicAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Error connecting to server: %s\n", err)
os.Exit(1)
}
if !options.onlyClientVersion {
serverVersion := getServerVersion(conduitApiClient)
if options.shortVersion {
fmt.Println(serverVersion)
} else {
fmt.Printf("Server version: %s\n", serverVersion)
}
}
},
}
cmd.Args = cobra.NoArgs
cmd.PersistentFlags().BoolVar(&options.shortVersion, "short", options.shortVersion, "Print the version number(s) only, with no additional output")
cmd.PersistentFlags().BoolVar(&options.onlyClientVersion, "client", options.onlyClientVersion, "Print the client version only")
return cmd
}
func getServerVersion(client pb.ApiClient) string {