Repository: microsoft/retina
File: cli/cmd/capture/download.go (Go)

Code before:
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to read from blobstore")
		}

		blobData, err := io.ReadAll(readCloser)
		readCloser.Close()
		if err != nil {
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to obtain blob from blobstore")
		}

		outputFile := filepath.Join(outputPath, blob.Name)
		err = os.WriteFile(outputFile, blobData, 0o600)
		if err != nil {
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to write file")
		}

		fmt.Println("Downloaded: ", outputFile)
	}
	return nil
}

func NewDownloadSubCommand() *cobra.Command {
	return downloadCapture
}


Review comment (security):
Removing the flags from here now means that `k retina capture download -h` does not show them.

e.g.

```
> k retina capture download -h
Download Retina Captures

Usage:
  kubectl-retina capture download [flags]

Examples:
  # List Retina capture jobs
  kubectl retina capture list

  # Download the capture file(s) created using the capture name
  kubectl retina capture download --name <capture-name>

  # Download the capture file(s) created using the capture name and define output location
  kubectl retina capture download --name <capture-name> -o <output-location>

  # Download capture file(s) from Blob Storage via Blob URL (Blob URL requires Read/List permissions)
  kubectl retina capture download --blob-url "<blob-url>"

Flags:
  -h, --help   help for download

Global Flags:
      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --as-uid string                  UID to impersonate for the operation.
      --cache-dir string               Default cache directory (default "/home/kamilp/.kube/cache")
      --certificate-authority string   Path to a cert file for the certificate authority
      --client-certificate string      Path to a client certificate file for TLS
      --client-key string              Path to a client key file for TLS
      --cluster string                 The name of the kubeconfig cluster to use
      --context string                 The name of the kubeconfig context to use
      --disable-compression            If true, opt-out of response compression for all requests to the server
      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.
      --name string                    The name of the Retina Capture (default "retina-capture")
  -n, --namespace string               If present, the namespace scope for this CLI request
      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
  -s, --server string                  The address and port of the Kubernetes API server
      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
      --token string                   Bearer token for authentication to the API server
      --user string                    The name of the kubeconfig user to use
```

Code after:
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to read from blobstore")
		}

		blobData, err := io.ReadAll(readCloser)
		readCloser.Close()
		if err != nil {
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to obtain blob from blobstore")
		}

		outputFile := filepath.Join(outputPath, blob.Name)
		err = os.WriteFile(outputFile, blobData, 0o600)
		if err != nil {
			retinacmd.Logger.Error("err: ", zap.Error(err))
			return errors.Wrap(err, "failed to write file")
		}

		fmt.Println("Downloaded: ", outputFile)
	}
	return nil
}

func init() {
	capture.AddCommand(downloadCapture)
	downloadCapture.Flags().StringVar(&blobURL, "blob-url", "", "Blob URL from which to download")
	downloadCapture.Flags().StringVar(&captureName, "name", "", "The name of a the capture")
	downloadCapture.Flags().StringVarP(&outputPath, "output", "o", DefaultOutputPath, "Path to save the downloaded capture")
}