/* Copyright 2021 The Dapr Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "bytes" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strconv" "github.com/dapr/dapr/pkg/runtime" "k8s.io/apimachinery/pkg/api/resource" "github.com/spf13/cobra" "github.com/dapr/cli/pkg/kubernetes" "github.com/dapr/cli/pkg/print" ) var ( annotateTargetResource string annotateTargetNamespace string annotateAppID string annotateAppPort int annotateConfig string annotateAppProtocol string annotateEnableProfile bool annotateLogLevel string annotateAPITokenSecret string annotateAppTokenSecret string annotateLogAsJSON bool annotateAppMaxConcurrency int annotateEnableMetrics bool annotateMetricsPort int annotateEnableDebug bool annotateEnv string annotateCPULimit string annotateMemoryLimit string annotateCPURequest string annotateMemoryRequest string annotateListenAddresses string annotateLivenessProbeDelay int annotateLivenessProbeTimeout int annotateLivenessProbePeriod int annotateLivenessProbeThreshold int annotateReadinessProbeDelay int annotateReadinessProbeTimeout int annotateReadinessProbePeriod int annotateReadinessProbeThreshold int annotateDaprImage string annotateAppSSL bool annotateMaxRequestBodySize string annotateReadBufferSize string annotateHTTPStreamRequestBody bool annotateGracefulShutdownSeconds int annotateEnableAPILogging bool annotateUnixDomainSocketPath string annotateVolumeMountsReadOnly string annotateVolumeMountsReadWrite string annotateDisableBuiltinK8sSecretStore bool annotatePlacementHostAddress string ) var AnnotateCmd = &cobra.Command{ Use: "annotate [flags] CONFIG-FILE", Short: "Add dapr annotations to a Kubernetes configuration. Supported platforms: Kubernetes", Example: ` # Annotate the first deployment found in the input kubectl get deploy -l app=node -o yaml | dapr annotate -k - | kubectl apply -f - # Annotate multiple deployments by name in a chain kubectl get deploy -o yaml | dapr annotate -k -r nodeapp - | dapr annotate -k -r pythonapp - | kubectl apply -f - # Annotate deployment in a specific namespace from file or directory by name dapr annotate -k -r nodeapp -n namespace mydeploy.yaml | kubectl apply -f - # Annotate deployment from url by name dapr annotate -k -r nodeapp --log-level debug https://raw.githubusercontent.com/dapr/quickstarts/master/tutorials/hello-kubernetes/deploy/node.yaml | kubectl apply -f - -------------------------------------------------------------------------------- WARNING: If an app id is not provided, we will generate one using the format '--'. -------------------------------------------------------------------------------- `, Run: func(cmd *cobra.Command, args []string) { if !kubernetesMode { print.FailureStatusEvent(os.Stderr, "annotate command is only supported for Kubernetes, please provide the -k flag") os.Exit(1) } if len(args) < 1 { print.FailureStatusEvent(os.Stderr, "please specify a Kubernetes resource file") os.Exit(1) } input, err := readInput(args[0]) if err != nil { print.FailureStatusEvent(os.Stderr, err.Error()) os.Exit(1) } var config kubernetes.K8sAnnotatorConfig if annotateTargetResource != "" { config = kubernetes.K8sAnnotatorConfig{ TargetResource: &annotateTargetResource, } //nolint:exhaustivestruct if annotateTargetNamespace != "" { config.TargetNamespace = &annotateTargetNamespace } } else { if annotateTargetNamespace != "" { // The resource is empty but namespace is set, this // is invalid as we cannot search for a resource // if the identifier isn't provided. print.FailureStatusEvent(os.Stderr, "--resource is required when --namespace is provided.") os.Exit(1) } } annotator := kubernetes.NewK8sAnnotator(config) opts := getOptionsFromFlags() if err := annotator.Annotate(input, os.Stdout, opts); err != nil { print.FailureStatusEvent(os.Stderr, err.Error()) os.Exit(1) } }, } func readInput(arg string) ([]io.Reader, error) { var inputs []io.Reader var err error if arg == "-" { // input is from stdin. inputs = append(inputs, os.Stdin) } else if isURL(arg) { inputs, err = readInputsFromURL(arg) if err != nil { return nil, err } } else { // input is from file or dir. inputs, err = readInputsFromFS(arg) if err != nil { return nil, err } } return inputs, nil } func readInputsFromURL(url string) ([]io.Reader, error) { resp, err := http.Get(url) // #nosec if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unable to read from %s: %d - %s", url, resp.StatusCode, resp.Status) } var b []byte b, err = io.ReadAll(resp.Body) if err != nil { return nil, err } reader := bytes.NewReader(b) return []io.Reader{reader}, nil } func isURL(maybeURL string) bool { url, err := url.ParseRequestURI(maybeURL) if err != nil { return false } return url.Host != "" && url.Scheme != "" } func readInputsFromFS(path string) ([]io.Reader, error) { stat, err := os.Stat(path) if err != nil { return nil, err } if !stat.IsDir() { // input is a file. var file *os.File file, err = os.Open(path) if err != nil { return nil, err } return []io.Reader{file}, nil } // input is a directory. var inputs []io.Reader err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } file, err := os.Open(path) if err != nil { return err } inputs = append(inputs, file) return nil }) if err != nil { return nil, err } return inputs, nil } func getOptionsFromFlags() kubernetes.AnnotateOptions { // TODO: Use a pointer for int flag where zero is nil not -1. o := []kubernetes.AnnoteOption{} if annotateAppID != "" { o = append(o, kubernetes.WithAppID(annotateAppID)) } if annotateConfig != "" { o = append(o, kubernetes.WithConfig(annotateConfig)) } if annotateAppPort != -1 { o = append(o, kubernetes.WithAppPort(annotateAppPort)) } if annotateAppProtocol != "" { o = append(o, kubernetes.WithAppProtocol(annotateAppProtocol)) } if annotateEnableProfile { o = append(o, kubernetes.WithProfileEnabled()) } if annotateLogLevel != "" { o = append(o, kubernetes.WithLogLevel(annotateLogLevel)) } if annotateAPITokenSecret != "" { o = append(o, kubernetes.WithAPITokenSecret(annotateAPITokenSecret)) } if annotateAppTokenSecret != "" { o = append(o, kubernetes.WithAppTokenSecret(annotateAppTokenSecret)) } if annotateLogAsJSON { o = append(o, kubernetes.WithLogAsJSON()) } if annotateAppMaxConcurrency != -1 { o = append(o, kubernetes.WithAppMaxConcurrency(annotateAppMaxConcurrency)) } if annotateEnableMetrics { o = append(o, kubernetes.WithMetricsEnabled()) } if annotateMetricsPort != -1 { o = append(o, kubernetes.WithMetricsPort(annotateMetricsPort)) } if annotateEnableDebug { o = append(o, kubernetes.WithDebugEnabled()) } if annotateEnv != "" { o = append(o, kubernetes.WithEnv(annotateEnv)) } if annotateCPULimit != "" { o = append(o, kubernetes.WithCPULimit(annotateCPULimit)) } if annotateMemoryLimit != "" { o = append(o, kubernetes.WithMemoryLimit(annotateMemoryLimit)) } if annotateCPURequest != "" { o = append(o, kubernetes.WithCPURequest(annotateCPURequest)) } if annotateMemoryRequest != "" { o = append(o, kubernetes.WithMemoryRequest(annotateMemoryRequest)) } if annotateListenAddresses != "" { o = append(o, kubernetes.WithListenAddresses(annotateListenAddresses)) } if annotateLivenessProbeDelay != -1 { o = append(o, kubernetes.WithLivenessProbeDelay(annotateLivenessProbeDelay)) } if annotateLivenessProbeTimeout != -1 { o = append(o, kubernetes.WithLivenessProbeTimeout(annotateLivenessProbeTimeout)) } if annotateLivenessProbePeriod != -1 { o = append(o, kubernetes.WithLivenessProbePeriod(annotateLivenessProbePeriod)) } if annotateLivenessProbeThreshold != -1 { o = append(o, kubernetes.WithLivenessProbeThreshold(annotateLivenessProbeThreshold)) } if annotateReadinessProbeDelay != -1 { o = append(o, kubernetes.WithReadinessProbeDelay(annotateReadinessProbeDelay)) } if annotateReadinessProbeTimeout != -1 { o = append(o, kubernetes.WithReadinessProbeTimeout(annotateReadinessProbeTimeout)) } if annotateReadinessProbePeriod != -1 { o = append(o, kubernetes.WithReadinessProbePeriod(annotateReadinessProbePeriod)) } if annotateReadinessProbeThreshold != -1 { o = append(o, kubernetes.WithReadinessProbeThreshold(annotateReadinessProbeThreshold)) } if annotateDaprImage != "" { o = append(o, kubernetes.WithDaprImage(annotateDaprImage)) } if annotateAppSSL { o = append(o, kubernetes.WithAppSSL()) } if annotateMaxRequestBodySize != "-1" { if q, err := resource.ParseQuantity(annotateMaxRequestBodySize); err != nil { print.FailureStatusEvent(os.Stderr, "error parsing value of max-body-size: %s, error: %s", annotateMaxRequestBodySize, err.Error()) os.Exit(1) } else { o = append(o, kubernetes.WithMaxRequestBodySize(int(q.Value()))) } } if annotateReadBufferSize != "-1" { if q, err := resource.ParseQuantity(annotateReadBufferSize); err != nil { print.FailureStatusEvent(os.Stderr, "error parsing value of read-buffer-size: %s, error: %s", annotateMaxRequestBodySize, err.Error()) os.Exit(1) } else { o = append(o, kubernetes.WithReadBufferSize(int(q.Value()))) } } if annotateHTTPStreamRequestBody { o = append(o, kubernetes.WithHTTPStreamRequestBody()) } if annotateGracefulShutdownSeconds != -1 { o = append(o, kubernetes.WithGracefulShutdownSeconds(annotateGracefulShutdownSeconds)) } if annotateEnableAPILogging { o = append(o, kubernetes.WithEnableAPILogging()) } if annotateUnixDomainSocketPath != "" { o = append(o, kubernetes.WithUnixDomainSocketPath(annotateUnixDomainSocketPath)) } if annotateVolumeMountsReadOnly != "" { o = append(o, kubernetes.WithVolumeMountsReadOnly(annotateVolumeMountsReadOnly)) } if annotateVolumeMountsReadWrite != "" { o = append(o, kubernetes.WithVolumeMountsReadWrite(annotateVolumeMountsReadWrite)) } if annotateDisableBuiltinK8sSecretStore { o = append(o, kubernetes.WithDisableBuiltinK8sSecretStore()) } if annotatePlacementHostAddress != "" { o = append(o, kubernetes.WithPlacementHostAddress(annotatePlacementHostAddress)) } return kubernetes.NewAnnotateOptions(o...) } func init() { AnnotateCmd.Flags().BoolVarP(&kubernetesMode, "kubernetes", "k", false, "Apply annotations to Kubernetes resources") AnnotateCmd.Flags().StringVarP(&annotateTargetResource, "resource", "r", "", "The resource to target to annotate") AnnotateCmd.Flags().StringVarP(&annotateTargetNamespace, "namespace", "n", "", "The namespace the resource target is in (can only be set if --resource is also set)") AnnotateCmd.Flags().StringVarP(&annotateAppID, "app-id", "a", "", "The app id to annotate") AnnotateCmd.Flags().IntVarP(&annotateAppPort, "app-port", "p", -1, "The port to expose the app on") AnnotateCmd.Flags().StringVarP(&annotateConfig, "config", "c", "", "The config file to annotate") AnnotateCmd.Flags().StringVar(&annotateAppProtocol, "app-protocol", "", "The protocol to use for the app. Allowed values http, https, h2c, grpc, grpcs") AnnotateCmd.Flags().BoolVar(&annotateEnableProfile, "enable-profile", false, "Enable profiling") AnnotateCmd.Flags().StringVar(&annotateLogLevel, "log-level", "", "The log level to use") AnnotateCmd.Flags().StringVar(&annotateAPITokenSecret, "api-token-secret", "", "The secret to use for the API token") AnnotateCmd.Flags().StringVar(&annotateAppTokenSecret, "app-token-secret", "", "The secret to use for the app token") AnnotateCmd.Flags().BoolVar(&annotateLogAsJSON, "log-as-json", false, "Log as JSON") AnnotateCmd.Flags().IntVar(&annotateAppMaxConcurrency, "app-max-concurrency", -1, "The maximum number of concurrent requests to allow") AnnotateCmd.Flags().BoolVar(&annotateEnableMetrics, "enable-metrics", false, "Enable metrics") AnnotateCmd.Flags().IntVar(&annotateMetricsPort, "metrics-port", -1, "The port to expose the metrics on") AnnotateCmd.Flags().BoolVar(&annotateEnableDebug, "enable-debug", false, "Enable debug") AnnotateCmd.Flags().StringVar(&annotateEnv, "env", "", "Environment variables to set (key value pairs, comma separated)") AnnotateCmd.Flags().StringVar(&annotateCPULimit, "cpu-limit", "", "The CPU limit to set") AnnotateCmd.Flags().StringVar(&annotateMemoryLimit, "memory-limit", "", "The memory limit to set") AnnotateCmd.Flags().StringVar(&annotateCPURequest, "cpu-request", "", "The CPU request to set") AnnotateCmd.Flags().StringVar(&annotateMemoryRequest, "memory-request", "", "The memory request to set") AnnotateCmd.Flags().StringVar(&annotateListenAddresses, "listen-addresses", "", "The addresses to listen on") AnnotateCmd.Flags().IntVar(&annotateLivenessProbeDelay, "liveness-probe-delay", -1, "The delay to use for the liveness probe") AnnotateCmd.Flags().IntVar(&annotateLivenessProbeTimeout, "liveness-probe-timeout", -1, "The timeout to use for the liveness probe") AnnotateCmd.Flags().IntVar(&annotateLivenessProbePeriod, "liveness-probe-period", -1, "The period to use for the liveness probe") AnnotateCmd.Flags().IntVar(&annotateLivenessProbeThreshold, "liveness-probe-threshold", -1, "The threshold to use for the liveness probe") AnnotateCmd.Flags().IntVar(&annotateReadinessProbeDelay, "readiness-probe-delay", -1, "The delay to use for the readiness probe") AnnotateCmd.Flags().IntVar(&annotateReadinessProbeTimeout, "readiness-probe-timeout", -1, "The timeout to use for the readiness probe") AnnotateCmd.Flags().IntVar(&annotateReadinessProbePeriod, "readiness-probe-period", -1, "The period to use for the readiness probe") AnnotateCmd.Flags().IntVar(&annotateReadinessProbeThreshold, "readiness-probe-threshold", -1, "The threshold to use for the readiness probe") AnnotateCmd.Flags().StringVar(&annotateDaprImage, "dapr-image", "", "The image to use for the dapr sidecar container") AnnotateCmd.Flags().BoolVar(&annotateAppSSL, "app-ssl", false, "Enable SSL for the app") AnnotateCmd.Flags().MarkDeprecated("app-ssl", "This flag is deprecated and will be removed in a future release. Use \"app-protocol\" flag with https or grpcs instead") AnnotateCmd.Flags().StringVar(&annotateMaxRequestBodySize, "max-body-size", strconv.Itoa(runtime.DefaultMaxRequestBodySize>>20)+"Mi", "The maximum request body size to use") AnnotateCmd.Flags().StringVar(&annotateReadBufferSize, "read-buffer-size", strconv.Itoa(runtime.DefaultReadBufferSize>>10)+"Ki", "The maximum size of HTTP header read buffer in kilobytes") AnnotateCmd.Flags().BoolVar(&annotateHTTPStreamRequestBody, "http-stream-request-body", false, "Enable streaming request body for HTTP") AnnotateCmd.Flags().IntVar(&annotateGracefulShutdownSeconds, "graceful-shutdown-seconds", -1, "The number of seconds to wait for the app to shutdown") AnnotateCmd.Flags().BoolVar(&annotateEnableAPILogging, "enable-api-logging", false, "Enable API logging for the Dapr sidecar") AnnotateCmd.Flags().StringVar(&annotateUnixDomainSocketPath, "unix-domain-socket-path", "", "Linux domain socket path to use for communicating with the Dapr sidecar") AnnotateCmd.Flags().StringVar(&annotateVolumeMountsReadOnly, "volume-mounts", "", "List of pod volumes to be mounted to the sidecar container in read-only mode") AnnotateCmd.Flags().StringVar(&annotateVolumeMountsReadWrite, "volume-mounts-rw", "", "List of pod volumes to be mounted to the sidecar container in read-write mode") AnnotateCmd.Flags().BoolVar(&annotateDisableBuiltinK8sSecretStore, "disable-builtin-k8s-secret-store", false, "Disable the built-in k8s secret store") AnnotateCmd.Flags().StringVar(&annotatePlacementHostAddress, "placement-host-address", "", "Comma separated list of addresses for Dapr actor placement servers") RootCmd.AddCommand(AnnotateCmd) }