From 04f92e0f501f4098acdd1dd9040504348cde7df1 Mon Sep 17 00:00:00 2001 From: benpresnell <76139888+benpresnell@users.noreply.github.com> Date: Wed, 17 Mar 2021 14:29:59 -0400 Subject: [PATCH] Enable tridentctl to show a list of its required container images --- .gitignore | 1 + Makefile | 1 + cli/cmd/images.go | 187 ++++++++++++++++++++++++++++++ cli/cmd/images_test.go | 29 +++++ cli/cmd/root.go | 1 + docs/Required-Container-Images.md | 59 ++++++++++ 6 files changed, 278 insertions(+) create mode 100644 cli/cmd/images.go create mode 100644 cli/cmd/images_test.go create mode 100644 docs/Required-Container-Images.md diff --git a/.gitignore b/.gitignore index 68a819dba..9cb5de5e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ ############################################################################### bin/trident* +trident-required-images.md coverage/** vendor/ operator/trident-operator diff --git a/Makefile b/Makefile index b5b7bd47c..724488a00 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,7 @@ trident_build: @${GO_LINUX} ${BUILD} -ldflags $(BUILD_FLAGS) -o ${TRIDENT_VOLUME_PATH}/bin/${CLI_BIN} ${CLI_PKG} @${GO_LINUX} ${BUILD} -ldflags $(BUILD_FLAGS) -o ${TRIDENT_VOLUME_PATH}/bin/chwrap chwrap/chwrap.go cp ${BIN_DIR}/${BIN} ${BIN_DIR}/${CLI_BIN} . + ${BIN_DIR}/${CLI_BIN} images -o markdown > trident-required-images.md chwrap/make-tarball.sh ${BIN_DIR}/chwrap chwrap.tar docker build --build-arg PORT=${PORT} --build-arg BIN=${BIN} --build-arg CLI_BIN=${CLI_BIN} --build-arg K8S=${K8S} -t ${TRIDENT_TAG} --rm . ifdef REGISTRY_ADDR diff --git a/cli/cmd/images.go b/cli/cmd/images.go new file mode 100644 index 000000000..f384808b5 --- /dev/null +++ b/cli/cmd/images.go @@ -0,0 +1,187 @@ +// Copyright 2021 NetApp, Inc. All Rights Reserved. + +package cmd + +import ( + "errors" + "fmt" + "os" + "regexp" + "strings" + + "github.com/olekukonko/tablewriter" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + k8sclient "github.com/netapp/trident/cli/k8s_client" + tridentconfig "github.com/netapp/trident/config" + "github.com/netapp/trident/utils" +) + +var K8sVersion string +var versionNotSupported = "The provided Kubernetes version is not supported: " +var versionNotSemantic = "The provided Kubernetes version was not given in semantic versioning: " + +type ImageSet struct { + Images []string `json:"images"` + K8sVersion string `json:"k8sVersion"` +} + +type ImageList struct { + ImageSets []ImageSet `json:"imageSets"` +} + +func init() { + getImageCmd.Flags().StringVarP(&K8sVersion, "k8s-version", "v", "all", + "Semantic version of Kubernetes cluster") + RootCmd.AddCommand(getImageCmd) +} + +var getImageCmd = &cobra.Command{ + Use: "images", + Short: "Print a table of the container images Trident needs", + Aliases: []string{}, + RunE: func(cmd *cobra.Command, args []string) error { + if OperatingMode == ModeTunnel { + command := []string{"images"} + TunnelCommand(append(command, args...)) + return nil + } else { + return printImages() + } + }, +} + +func printImages() error { + err := listImages() + if err != nil { + if err.Error() == versionNotSupported { + log.Fatal(fmt.Sprintln(versionNotSupported, K8sVersion)) + } else { + log.Fatal(fmt.Sprintln(versionNotSemantic, K8sVersion)) + } + } + + return nil +} + +func listImages() error { + var err error + var semVersion *utils.Version + k8sVersions := make([]string, 0) + imageMap := make(map[string][]string) + var installYaml string + var yamlErr error + + if K8sVersion == "all" { + minMinorVersion := utils.MustParseSemantic(tridentconfig.KubernetesVersionMin).MinorVersion() + maxMinorVersion := utils.MustParseSemantic(tridentconfig.KubernetesVersionMax).MinorVersion() + for i := minMinorVersion; i <= maxMinorVersion; i++ { + semVersion := fmt.Sprintf("v1.%d.0", i) + k8sVersions = append(k8sVersions, semVersion) + installYaml, yamlErr = getInstallYaml(utils.MustParseSemantic(semVersion)) + if yamlErr != nil { + return yamlErr + } + imageMap[semVersion] = getImageNames(installYaml) + } + } else { + semVersion, err = utils.ParseSemantic(K8sVersion) + if err != nil { + return err + } + k8sVersions = append(k8sVersions, K8sVersion) + installYaml, yamlErr = getInstallYaml(semVersion) + if yamlErr != nil { + return yamlErr + } + imageMap[K8sVersion] = getImageNames(installYaml) + } + writeImages(k8sVersions, imageMap) + return nil +} + +func getInstallYaml(semVersion *utils.Version) (string, error) { + var yaml string + minorVersion := semVersion.ToMajorMinorVersion().MinorVersion() + isSupportedVersion := minorVersion <= utils.MustParseSemantic(tridentconfig.KubernetesVersionMax).MinorVersion() && + minorVersion >= utils.MustParseSemantic(tridentconfig.KubernetesVersionMin).MinorVersion() + + if isSupportedVersion { + if semVersion.LessThan(utils.MustParseSemantic(tridentconfig.KubernetesCSIVersionMinForced)) { + csi = false + } else { + csi = true + } + } else { + return "", errors.New(fmt.Sprintln(K8sVersion, "The provided Kubernetes version is not supported")) + } + + // Get Deployment and Daemonset YAML and collect the names of the container images Trident needs to run. + if csi { + yaml = k8sclient.GetCSIDeploymentYAML(getDeploymentName(true), + tridentconfig.BuildImage, tridentconfig.DefaultAutosupportImage, "", "", + "", "", "", "", []string{}, nil, + nil, false, false, true, semVersion, false) + // trident image here is an empty string because we are already going to get it from the deployment yaml + yaml += k8sclient.GetCSIDaemonSetYAML("", "", "", "", + "", []string{}, nil, nil, false, false, semVersion) + } else { + yaml = k8sclient.GetDeploymentYAML("", tridentconfig.BuildImage, "", []string{}, nil, + nil, false) + } + return yaml, nil +} + +func getImageNames(yaml string) []string { + var images []string + lines := strings.Split(yaml, "\n") + // don't get images that are empty strings + imageRegex := regexp.MustCompile(`\s*image:\s*(.*)`) + for i := 0; i < len(lines); i++ { + imageLine := lines[i] + image := strings.TrimSpace(imageRegex.ReplaceAllString(imageLine, "$1")) + if matches := imageRegex.MatchString(imageLine); matches { + // strip out " image:" prefix + if image != "" { + images = append(images, image) + } + } + } + return images +} + +func writeImages(k8sVersions []string, imageMap map[string][]string) { + + var imageSets []ImageSet + for _, k8sVersion := range k8sVersions { + var images []string + for _, image := range imageMap[k8sVersion] { + images = append(images, image) + } + imageSets = append(imageSets, ImageSet{Images: images, K8sVersion: k8sVersion}) + } + switch OutputFormat { + case FormatJSON: + WriteJSON(ImageList{ImageSets: imageSets}) + case FormatYAML: + WriteYAML(ImageList{ImageSets: imageSets}) + default: + writeImageTable(k8sVersions, imageMap) + } +} + +func writeImageTable(k8sVersions []string, imageMap map[string][]string) { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Kubernetes Version", "Container Image"}) + if OutputFormat == FormatMarkdown { + // print in markdown table format + table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + } + table.SetRowLine(true) + for _, k8sVersion := range k8sVersions { + table.Append([]string{k8sVersion, strings.Join(imageMap[k8sVersion], "\n")}) + } + table.Render() +} diff --git a/cli/cmd/images_test.go b/cli/cmd/images_test.go new file mode 100644 index 000000000..a19e32bd8 --- /dev/null +++ b/cli/cmd/images_test.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 NetApp, Inc. All Rights Reserved. + */ + +package cmd + +import ( + "testing" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestImages(t *testing.T) { + log.Debug("Running TestImages...") + + unsupportedVersion := "1.9.0" + invalidVersion := "1.10.12.2240" + supportedVersion := "1.17.0" + + K8sVersion = unsupportedVersion + assert.Error(t, listImages(), "Unsupported version %s should return an error.", K8sVersion) + + K8sVersion = invalidVersion + assert.Error(t, listImages(), "Invalid version %s should return an error.", K8sVersion) + + K8sVersion = supportedVersion + assert.NoError(t, listImages(), "Supported version %s should not return an error.", K8sVersion) +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go index ed402d502..9ad90a057 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -31,6 +31,7 @@ const ( FormatName = "name" FormatWide = "wide" FormatYAML = "yaml" + FormatMarkdown = "markdown" ModeDirect = "direct" ModeTunnel = "tunnel" diff --git a/docs/Required-Container-Images.md b/docs/Required-Container-Images.md new file mode 100644 index 000000000..f8f251e20 --- /dev/null +++ b/docs/Required-Container-Images.md @@ -0,0 +1,59 @@ +| KUBERNETES VERSION | CONTAINER IMAGE | +|--------------------|---------------------------------------------------------| +| v1.11.0 | netapp/trident:21.04.0-custom | +|--------------------|---------------------------------------------------------| +| v1.12.0 | netapp/trident:21.04.0-custom | +|--------------------|---------------------------------------------------------| +| v1.13.0 | netapp/trident:21.04.0-custom | +|--------------------|---------------------------------------------------------| +| v1.14.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | quay.io/k8scsi/csi-provisioner:v1.6.1 | +| | quay.io/k8scsi/csi-attacher:v2.2.1 | +| | quay.io/k8scsi/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.15.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | quay.io/k8scsi/csi-provisioner:v1.6.1 | +| | quay.io/k8scsi/csi-attacher:v2.2.1 | +| | quay.io/k8scsi/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.16.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | quay.io/k8scsi/csi-provisioner:v1.6.1 | +| | quay.io/k8scsi/csi-attacher:v2.2.1 | +| | quay.io/k8scsi/csi-resizer:v1.1.0 | +| | quay.io/k8scsi/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.17.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | k8s.gcr.io/sig-storage/csi-provisioner:v2.1.0 | +| | k8s.gcr.io/sig-storage/csi-attacher:v3.1.0 | +| | k8s.gcr.io/sig-storage/csi-resizer:v1.1.0 | +| | k8s.gcr.io/sig-storage/csi-snapshotter:v3.0.3 | +| | k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.18.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | k8s.gcr.io/sig-storage/csi-provisioner:v2.1.0 | +| | k8s.gcr.io/sig-storage/csi-attacher:v3.1.0 | +| | k8s.gcr.io/sig-storage/csi-resizer:v1.1.0 | +| | k8s.gcr.io/sig-storage/csi-snapshotter:v3.0.3 | +| | k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.19.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | k8s.gcr.io/sig-storage/csi-provisioner:v2.1.0 | +| | k8s.gcr.io/sig-storage/csi-attacher:v3.1.0 | +| | k8s.gcr.io/sig-storage/csi-resizer:v1.1.0 | +| | k8s.gcr.io/sig-storage/csi-snapshotter:v3.0.3 | +| | k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------| +| v1.20.0 | netapp/trident:21.04.0-custom | +| | netapp/trident-autosupport:21.01 | +| | k8s.gcr.io/sig-storage/csi-provisioner:v2.1.0 | +| | k8s.gcr.io/sig-storage/csi-attacher:v3.1.0 | +| | k8s.gcr.io/sig-storage/csi-resizer:v1.1.0 | +| | k8s.gcr.io/sig-storage/csi-snapshotter:v3.0.3 | +| | k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.1.0 | +|--------------------|---------------------------------------------------------|