2023-03-24 06:51:18 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-07-07 11:02:01 -04:00
|
|
|
"errors"
|
2023-03-24 06:51:18 -04:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2023-07-07 11:02:01 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
2023-03-24 06:51:18 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
2023-12-06 04:01:39 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constellation/helm"
|
2023-12-05 10:23:31 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constellation/kubecmd"
|
2023-03-24 06:51:18 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/spf13/cobra"
|
2023-07-07 11:02:01 -04:00
|
|
|
"gopkg.in/yaml.v3"
|
2023-03-24 06:51:18 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// NewStatusCmd returns a new cobra.Command for the statuus command.
|
|
|
|
func NewStatusCmd() *cobra.Command {
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "status",
|
2023-07-05 10:44:57 -04:00
|
|
|
Short: "Show status of a Constellation cluster",
|
|
|
|
Long: "Show the status of a constellation cluster.\n\n" +
|
|
|
|
"Shows microservice, image, and Kubernetes versions installed in the cluster. Also shows status of current version upgrades.",
|
2023-03-24 06:51:18 -04:00
|
|
|
Args: cobra.NoArgs,
|
|
|
|
RunE: runStatus,
|
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
// runStatus runs the terminate command.
|
2023-04-05 12:40:35 -04:00
|
|
|
func runStatus(cmd *cobra.Command, _ []string) error {
|
2023-03-24 06:51:18 -04:00
|
|
|
log, err := newCLILogger(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("creating logger: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
|
|
|
|
2023-12-01 03:00:44 -05:00
|
|
|
kubeConfig, err := fileHandler.Read(constants.AdminConfFilename)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("reading kubeconfig: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
helmClient, err := helm.NewReleaseVersionClient(kubeConfig, log)
|
2023-03-24 06:51:18 -04:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("setting up helm client: %w", err)
|
|
|
|
}
|
2023-07-27 10:14:36 -04:00
|
|
|
helmVersionGetter := func() (fmt.Stringer, error) {
|
|
|
|
return helmClient.Versions()
|
|
|
|
}
|
2023-03-24 06:51:18 -04:00
|
|
|
|
2023-07-07 11:02:01 -04:00
|
|
|
fetcher := attestationconfigapi.NewFetcher()
|
2023-12-05 10:23:31 -05:00
|
|
|
kubeClient, err := kubecmd.New(kubeConfig, log)
|
2023-08-08 07:03:23 -04:00
|
|
|
if err != nil {
|
2023-08-21 10:15:32 -04:00
|
|
|
return fmt.Errorf("setting up kubernetes client: %w", err)
|
2023-08-08 07:03:23 -04:00
|
|
|
}
|
2023-08-21 10:15:32 -04:00
|
|
|
|
2023-10-16 09:05:29 -04:00
|
|
|
s := statusCmd{log: log, fileHandler: fileHandler}
|
|
|
|
if err := s.flags.parse(cmd.Flags()); err != nil {
|
|
|
|
return err
|
2023-03-24 06:51:18 -04:00
|
|
|
}
|
2023-10-16 09:05:29 -04:00
|
|
|
return s.status(cmd, helmVersionGetter, kubeClient, fetcher)
|
|
|
|
}
|
2023-03-24 06:51:18 -04:00
|
|
|
|
2023-10-16 09:05:29 -04:00
|
|
|
type statusCmd struct {
|
|
|
|
log debugLog
|
|
|
|
fileHandler file.Handler
|
|
|
|
flags rootFlags
|
2023-03-24 06:51:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// status queries the cluster for the relevant status information and returns the output string.
|
2023-10-16 09:05:29 -04:00
|
|
|
func (s *statusCmd) status(
|
|
|
|
cmd *cobra.Command, getHelmVersions func() (fmt.Stringer, error),
|
|
|
|
kubeClient kubeCmd, fetcher attestationconfigapi.Fetcher,
|
|
|
|
) error {
|
|
|
|
conf, err := config.New(s.fileHandler, constants.ConfigFilename, fetcher, s.flags.force)
|
|
|
|
var configValidationErr *config.ValidationError
|
|
|
|
if errors.As(err, &configValidationErr) {
|
|
|
|
cmd.PrintErrln(configValidationErr.LongMessage())
|
|
|
|
}
|
2023-11-22 02:31:37 -05:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("loading config file: %w", err)
|
|
|
|
}
|
2023-10-16 09:05:29 -04:00
|
|
|
|
|
|
|
nodeVersion, err := kubeClient.GetConstellationVersion(cmd.Context())
|
2023-03-24 06:51:18 -04:00
|
|
|
if err != nil {
|
2023-10-16 09:05:29 -04:00
|
|
|
return fmt.Errorf("getting constellation version: %w", err)
|
2023-03-24 06:51:18 -04:00
|
|
|
}
|
|
|
|
|
2023-10-16 09:05:29 -04:00
|
|
|
attestationConfig, err := kubeClient.GetClusterAttestationConfig(cmd.Context(), conf.GetAttestationConfig().GetVariant())
|
2023-07-07 11:02:01 -04:00
|
|
|
if err != nil {
|
2023-10-16 09:05:29 -04:00
|
|
|
return fmt.Errorf("getting attestation config: %w", err)
|
2023-07-07 11:02:01 -04:00
|
|
|
}
|
|
|
|
prettyYAML, err := yaml.Marshal(attestationConfig)
|
|
|
|
if err != nil {
|
2023-10-16 09:05:29 -04:00
|
|
|
return fmt.Errorf("marshalling attestation config: %w", err)
|
2023-07-07 11:02:01 -04:00
|
|
|
}
|
|
|
|
|
2023-07-27 10:14:36 -04:00
|
|
|
serviceVersions, err := getHelmVersions()
|
2023-03-24 06:51:18 -04:00
|
|
|
if err != nil {
|
2023-10-16 09:05:29 -04:00
|
|
|
return fmt.Errorf("getting service versions: %w", err)
|
2023-03-24 06:51:18 -04:00
|
|
|
}
|
|
|
|
|
2023-10-16 09:05:29 -04:00
|
|
|
status, err := kubeClient.ClusterStatus(cmd.Context())
|
2023-03-24 06:51:18 -04:00
|
|
|
if err != nil {
|
2023-10-16 09:05:29 -04:00
|
|
|
return fmt.Errorf("getting cluster status: %w", err)
|
2023-03-24 06:51:18 -04:00
|
|
|
}
|
|
|
|
|
2023-10-16 09:05:29 -04:00
|
|
|
cmd.Print(statusOutput(nodeVersion, serviceVersions, status, string(prettyYAML)))
|
|
|
|
return nil
|
2023-08-08 07:03:23 -04:00
|
|
|
}
|
|
|
|
|
2023-03-24 06:51:18 -04:00
|
|
|
// statusOutput creates the status cmd output string by formatting the received information.
|
2023-07-27 10:14:36 -04:00
|
|
|
func statusOutput(
|
2023-08-21 10:15:32 -04:00
|
|
|
nodeVersion kubecmd.NodeVersion, serviceVersions fmt.Stringer,
|
|
|
|
status map[string]kubecmd.NodeStatus, rawAttestationConfig string,
|
2023-07-27 10:14:36 -04:00
|
|
|
) string {
|
2023-03-24 06:51:18 -04:00
|
|
|
builder := strings.Builder{}
|
|
|
|
|
2023-08-21 10:15:32 -04:00
|
|
|
builder.WriteString(targetVersionsString(nodeVersion))
|
2023-07-27 10:14:36 -04:00
|
|
|
builder.WriteString(serviceVersions.String())
|
2023-08-21 10:15:32 -04:00
|
|
|
builder.WriteString(fmt.Sprintf("Cluster status: %s\n", nodeVersion.ClusterStatus()))
|
|
|
|
builder.WriteString(nodeStatusString(status, nodeVersion))
|
2023-07-07 11:02:01 -04:00
|
|
|
builder.WriteString(fmt.Sprintf("Attestation config:\n%s", indentEntireStringWithTab(rawAttestationConfig)))
|
2023-03-24 06:51:18 -04:00
|
|
|
return builder.String()
|
|
|
|
}
|
|
|
|
|
2023-07-07 11:02:01 -04:00
|
|
|
func indentEntireStringWithTab(input string) string {
|
|
|
|
lines := strings.Split(input, "\n")
|
|
|
|
for i, line := range lines[:len(lines)-1] {
|
|
|
|
lines[i] = "\t" + line
|
|
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
|
|
}
|
|
|
|
|
2023-03-24 06:51:18 -04:00
|
|
|
// nodeStatusString creates the node status part of the output string.
|
2023-08-21 10:15:32 -04:00
|
|
|
func nodeStatusString(status map[string]kubecmd.NodeStatus, targetVersions kubecmd.NodeVersion) string {
|
2023-03-24 06:51:18 -04:00
|
|
|
var upToDateImages int
|
|
|
|
var upToDateK8s int
|
|
|
|
for _, node := range status {
|
2023-08-21 10:15:32 -04:00
|
|
|
if node.KubeletVersion() == targetVersions.KubernetesVersion() {
|
2023-03-24 06:51:18 -04:00
|
|
|
upToDateK8s++
|
|
|
|
}
|
2023-08-21 10:15:32 -04:00
|
|
|
if node.ImageVersion() == targetVersions.ImageReference() {
|
2023-03-24 06:51:18 -04:00
|
|
|
upToDateImages++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
builder := strings.Builder{}
|
|
|
|
if upToDateImages != len(status) || upToDateK8s != len(status) {
|
|
|
|
builder.WriteString(fmt.Sprintf("\tImage: %d/%d\n", upToDateImages, len(status)))
|
|
|
|
builder.WriteString(fmt.Sprintf("\tKubernetes: %d/%d\n", upToDateK8s, len(status)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// targetVersionsString creates the target versions part of the output string.
|
2023-08-21 10:15:32 -04:00
|
|
|
func targetVersionsString(target kubecmd.NodeVersion) string {
|
2023-03-24 06:51:18 -04:00
|
|
|
builder := strings.Builder{}
|
|
|
|
builder.WriteString("Target versions:\n")
|
2023-08-21 10:15:32 -04:00
|
|
|
builder.WriteString(fmt.Sprintf("\tImage: %s\n", target.ImageVersion()))
|
|
|
|
builder.WriteString(fmt.Sprintf("\tKubernetes: %s\n", target.KubernetesVersion()))
|
2023-03-24 06:51:18 -04:00
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
}
|
|
|
|
|
2023-08-21 10:15:32 -04:00
|
|
|
type kubeCmd interface {
|
|
|
|
ClusterStatus(ctx context.Context) (map[string]kubecmd.NodeStatus, error)
|
|
|
|
GetConstellationVersion(ctx context.Context) (kubecmd.NodeVersion, error)
|
|
|
|
GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, error)
|
2023-07-07 11:02:01 -04:00
|
|
|
}
|