target

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

View Source
const PromptForArg = "<prompt>"

Variables

View Source
var TargetCmd = &cobra.Command{
	Use:                "target [flags]",
	DisableAutoGenTag:  true,
	DisableFlagParsing: true,
	Args:               cobra.ArbitraryArgs,
	Short:              "Switch to an MCP cluster",
	Long: `Switch to an MCP cluster.

This command can be used to switch the kubeconfig to a cluster belonging to an MCP landscape.

The following arguments specify the target cluster:
- --landscape/-l <name>: The MCP landscape to target.
- --project/-p <name>: The project (project namespace on the onboarding cluster) to target.
- --workspace/-w <name>: The workspace (workspace namespace on the onboarding cluster) to target.
- --mcp/-m <name>: The MCP cluster to target. Mutually exclusive with --platform and --onboarding.
- --platform: Target the landscape's platform cluster. Mutually exclusive with --mcp and --onboarding.
- --onboarding: Target the landscape's onboarding cluster. Mutually exclusive with --mcp and --platform.

Targeting a landscape does not have any requirements, except from the landscape being defined in the plugin configuration.
If neither --platform nor --onboarding is specified, the onboarding cluster is targeted by default.

Targeting a project requires the landscape to be either set via the corresponding argument or recoverable from the kubeswitcher state.
It results in the onboarding cluster being targeted, with the default namespace in the kubeconfig set to the project namespace.

Targeting a workspace requires the project to be either set via the corresponding argument or recoverable from the kubeswitcher state, and thus also the landscape.
It results in the onboarding cluster being targeted, with the default namespace in the kubeconfig set to the workspace namespace.

Targeting an MCP cluster requires landscape, project, and workspace to be either set via the corresponding arguments or recoverable from the kubeswitcher state.
The '--v1' and '--v2' flags can be used to specify which MCP version to target. If not specified, the default from the config (v2, if not explicitly set) is used.

If '--platform' is specified, the platform cluster of the landscape is targeted. This requires only the landscape to be known.

All of the '--landscape', '--project', '--workspace', and '--mcp' flags can be specified with or without an argument. If specified without, you will be prompted to select the value interactively.
If the argument is required, but not specified at all, the command fails if the value cannot be recovered from the current kubeswitcher state.

Examples:

	# Target the onboarding cluster of the 'live' landscape.
	kw mcp target --landscape live

	# Target the platform cluster of the landscape. Prompts for landscape selection.
	kw mcp target --platform

	# Target the project 'my-project' on the landscape which is currently active in the kubeswitcher state (= was selected by a previous 'kw mcp target' call).
	# Fails if the landscape cannot be recovered from the state.
	kw mcp target --project my-project

	# Target the cluster belonging to the v1 MCP 'foo' on the 'live' landscape, in the project 'my-project' and the workspace 'my-ws'.
	kw mcp target --landscape live --project my-project --workspace my-ws --mcp foo --v1

	# Target a cluster belonging to a v2 MCP. Prompts for landscape, project, workspace and MCP selection.
	# The '--v2' could be omitted, unless the default MCP version has been set to 'v1' in the plugin config.
	kw mcp target -l -p -w -m --v2`,
	Run: func(cmd *cobra.Command, args []string) {
		if slices.Contains(args, "--help") || slices.Contains(args, "-h") {
			if err := cmd.Usage(); err != nil {
				cmd.PrintErrf("unable to print usage info: %v", err)
			}
			return
		}

		parseArgs(cmd, args)

		validateArgs()

		debug.Debug("Loading kubeswitcher context from environment")
		con, err := libcontext.NewContextFromEnv()
		if err != nil {
			libutils.Fatal(1, "error creating kubeswitcher context from environment (this is a plugin, did you run it as standalone?): %w\n", err)
		}
		debug.Debug("Kubeswitcher context loaded:\n%s", con.String())
		debug.Debug("Loading plugin configuration")
		cfg, err := config.LoadFromBytes([]byte(con.PluginConfig))
		if err != nil {
			libutils.Fatal(1, "error loading plugin configuration: %w\n", err)
		}
		debug.Debug("Plugin configuration loaded:\n%s", cfg.String())

		cs = &callState{}
		if data, err := con.ReadInternalCallbackState(); err != nil {
			libutils.Fatal(1, "error reading internal callback data: %w\n", err)
		} else if data != nil {
			debug.Debug("Internal callback data found")
			if err := json.Unmarshal(data, cs); err != nil {
				libutils.Fatal(1, "error unmarshalling internal callback data: %w\n", err)
			}
		} else {
			debug.Debug("No internal callback data found, loading original state, if possible")
			cs.OriginalState = &state.MCPState{}
			loaded, err := cs.OriginalState.Load(con, cfg)
			if err != nil {
				libutils.Fatal(1, "error loading plugin state: %w\n", err)
			}
			if loaded {
				debug.Debug("Loaded original state")
				cs.IntermediateState = cs.OriginalState.DeepCopy()
				debug.Debug("Storing kubeconfig of original state, just in case")
				kcfgData, err := os.ReadFile(con.KubeconfigPath)
				if err != nil {
					libutils.Fatal(1, "error reading kubeconfig file from path '%s': %w\n", con.KubeconfigPath, err)
				}
				cs.OriginalStateKubeconfig = kcfgData
			} else {
				cs.OriginalState = nil
			}
		}

		debug.Debug("Current call state:\n")
		pData, err := yaml.Marshal(cs)
		if err != nil {
			debug.Debug("Error marshaling internal callback data to yaml: %v", err)
		} else {
			debug.Debug("%s", string(pData))
		}

		mcpVersionLog := mcpVersion(cfg)
		if mcpVersionLog == "" {
			mcpVersionLog = fmt.Sprintf("%s (defaulted from config)", cfg.DefaultMCPVersion)
		}
		debug.Debug("Command called with the following arguments:\n  --landscape: %s\n  --project: %s\n  --workspace: %s\n  --mcp: %s\n  --onboarding: %v\n  --platform: %v\n  MCP version: %s", landscapeArg, projectArg, workspaceArg, mcpArg, onboardingArg, platformArg, mcpVersionLog)

		req.Register(reqLandscape, satisfyLandscapeRequirement(cfg))
		req.Register(reqProject, satisfyProjectRequirement(cmd))
		req.Register(reqProjectNamespace, satisfyProjectNamespaceRequirement(cmd))
		req.Register(reqWorkspace, satisfyWorkspaceRequirement(cmd))
		req.Register(reqWorkspaceNamespace, satisfyWorkspaceNamespaceRequirement(cmd))
		req.Register(reqMCP, satisfyMCPRequirement(cmd, cfg))
		req.Register(reqPlatformCluster, satisfyClusterRequirement(con, cfg, reqPlatformCluster))
		req.Register(reqOnboardingCluster, satisfyClusterRequirement(con, cfg, reqOnboardingCluster))
		req.Register(reqMCPCluster, satisfyMCPClusterRequirement(cmd))

		if !cs.Final {

			if err := req.Require(reqLandscape); err != nil {
				libutils.Fatal(1, "error determining MCP landscape: %w\n", err)
			}
			if onboardingArg {
				targetNamespace := ""
				var adaptState func(*state.MCPState)
				if projectArg != "" {
					if err := req.Require(reqProject, reqProjectNamespace); err != nil {
						libutils.Fatal(1, "error determining MCP project and/or its namespace: %w\n", err)
					}
					if internalCall {
						return
					}
					if workspaceArg == "" {

						targetNamespace = cs.ProjectNamespace
						adaptState = func(s *state.MCPState) {
							s.Focus.ToProject(cs.ProjectName)
						}
					}
				}
				if workspaceArg != "" {
					if err := req.Require(reqWorkspace, reqWorkspaceNamespace); err != nil {
						libutils.Fatal(1, "error determining MCP workspace and/or its namespace: %w\n", err)
					}
					if internalCall {
						return
					}

					targetNamespace = cs.WorkspaceNamespace
					adaptState = func(s *state.MCPState) {
						s.Focus.ToProject(cs.ProjectName).ToWorkspace(cs.WorkspaceName)
					}
				}

				if cs.IntermediateState == nil || cs.IntermediateState.Focus.Landscape != cs.LandscapeName || !cs.IntermediateState.Focus.IsOnboardingCluster() {
					debug.Debug("cs.IntermediateState == nil: %v", cs.IntermediateState == nil)
					if cs.IntermediateState != nil {
						debug.Debug("cs.IntermediateState.Focus.Landscape (%s) != cs.LandscapeName (%s): %v", cs.IntermediateState.Focus.Landscape, cs.LandscapeName, cs.IntermediateState.Focus.Landscape != cs.LandscapeName)
						debug.Debug("cs.IntermediateState.Focus.IsOnboardingCluster(): %v", cs.IntermediateState.Focus.IsOnboardingCluster())
						debug.Debug("Focus type: %s (expected to be %s)", cs.IntermediateState.Focus.Focus(), state.FocusTypeLandscape)
						debug.Debug("Not targeting the onboarding cluster at the moment, issuing internal call to switch to it")
					}
					switchToOnboardingCluster(con, cfg, cs)
					return
				}

				if err := setDefaultNamespaceInKubeconfig(con, targetNamespace); err != nil {
					libutils.Fatal(1, "error setting default namespace in kubeconfig: %w\n", err)
				}

				cs.Final = true
				if adaptState != nil {
					adaptState(cs.IntermediateState)
					debug.Debug("Updated intermediate state focus to '%s'", cs.IntermediateState.Focus.String())
				}
			} else if platformArg {

				cs.Final = true
				if cs.IntermediateState == nil || cs.IntermediateState.Focus.Landscape != cs.LandscapeName || !cs.IntermediateState.Focus.IsPlatformCluster() {
					debug.Debug("Not targeting the platform cluster at the moment, issuing internal call to switch to it")
					switchToPlatformCluster(con, cfg, cs)
					return
				}
			} else if mcpArg != "" {
				if err := req.Require(reqMCP); err != nil {
					libutils.Fatal(1, "error determining MCP: %w\n", err)
				}
				if isMCPVersionV2(cfg) {

					if err := req.Require(reqPlatformCluster, reqMCPCluster); err != nil {
						libutils.Fatal(1, "error determining MCP Cluster: %w\n", err)
					}
					if internalCall {
						return
					}

					c := &mcpv2cluster.Cluster{}
					c.Name = cs.MCPClusterName
					c.Namespace = cs.MCPClusterNamespace
					if err := platformCluster.Client().Get(cmd.Context(), client.ObjectKeyFromObject(c), c); err != nil {
						libutils.Fatal(1, "unable to get Cluster '%s/%s' on platform cluster: %w\n", c.Namespace, c.Name, err)
					}

					p := &mcpv2cluster.ClusterProfile{}
					p.Name = c.Spec.Profile
					if err := platformCluster.Client().Get(cmd.Context(), client.ObjectKeyFromObject(p), p); err != nil {
						libutils.Fatal(1, "unable to get ClusterProfile '%s' on platform cluster: %w\n", p.Name, err)
					}

					cs.Final = true
					cs.IntermediateState = &state.MCPState{
						Focus: state.NewEmptyFocus().ToLandscape(cs.LandscapeName, "").ToProject(cs.ProjectName).ToWorkspace(cs.WorkspaceName).ToMCP(cs.MCPName),
					}
					switch p.Spec.ProviderRef.Name {
					case "gardener":
						shootName, shootNamespace, err := getGardenerShootName(c)
						if err != nil {
							libutils.Fatal(1, "error getting gardener shoot name from Cluster '%s/%s': %w\n", c.Namespace, c.Name, err)
						}
						switchToGardenerShoot(shootName, shootNamespace, con, cfg, cs)
						return
					case "kind":
						csData, err := json.Marshal(cs)
						if err != nil {
							libutils.Fatal(1, "error marshalling call state for internal call: %w\n", err)
						}
						kindClusterName := getKindClusterName(c)
						debug.Debug("Targeting kind cluster '%s' belonging to MCP '%s/%s'", kindClusterName, cs.WorkspaceNamespace, cs.MCPName)
						if err := con.WriteInternalCall(fmt.Sprintf("%s %s", cfg.KindPluginName, kindClusterName), csData); err != nil {
							libutils.Fatal(1, "error writing internal call data: %w\n", err)
						}
						return
					default:
						libutils.Fatal(1, "unsupported provider '%s' for Cluster '%s/%s'\n", p.Spec.ProviderRef.Name, c.Namespace, c.Name)
					}
				} else {

					if err := req.Require(reqOnboardingCluster, reqWorkspaceNamespace); err != nil {
						libutils.Fatal(1, "error determining MCP Cluster: %w\n", err)
					}
					if internalCall {
						return
					}

					as := &mcpv1.APIServer{}
					as.Name = cs.MCPName
					as.Namespace = cs.WorkspaceNamespace
					if err := onboardingCluster.Client().Get(cmd.Context(), client.ObjectKeyFromObject(as), as); err != nil {
						libutils.Fatal(1, "unable to get APIServer '%s/%s' from onboarding cluster: %w\n", as.Namespace, as.Name, err)
					}
					if as.Status.GardenerStatus == nil || as.Status.GardenerStatus.Shoot == nil {
						libutils.Fatal(1, "APIServer '%s/%s' does not contain Gardener shoot information in its status, cannot target MCP '%s/%s'\n", as.Namespace, as.Name, cs.WorkspaceNamespace, cs.MCPName)
					}

					shoot := &unstructured.Unstructured{}
					if err := yaml.Unmarshal(as.Status.GardenerStatus.Shoot.Raw, shoot); err != nil {
						libutils.Fatal(1, "unable to parse APIServer.status.gardener.shoot to unstructured: %s\n", err.Error())
					}

					cs.Final = true
					cs.IntermediateState = &state.MCPState{
						Focus: state.NewEmptyFocus().ToLandscape(cs.LandscapeName, "").ToProject(cs.ProjectName).ToWorkspace(cs.WorkspaceName).ToMCP(cs.MCPName),
					}
					switchToGardenerShoot(shoot.GetName(), shoot.GetNamespace(), con, cfg, cs)
					return
				}
			}
		}

		if err := con.WriteId(cs.IntermediateState.Id(con.CurrentPluginName)); err != nil {
			libutils.Fatal(1, "error writing state ID: %w\n", err)
		}
		if err := con.WriteNotificationMessage(cs.IntermediateState.Notification()); err != nil {
			libutils.Fatal(1, "error writing notification message: %w\n", err)
		}
		if err := con.WritePluginState(cs.IntermediateState); err != nil {
			libutils.Fatal(1, "error writing plugin state: %w\n", err)
		}
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL