// SPDX-FileCopyrightText: Copyright The Lima Authors1// SPDX-License-Identifier: Apache-2.023package sshutil45import (6"fmt"7"io"8"strings"910"github.com/lima-vm/lima/v2/pkg/instance/hostname"11)1213// FormatT specifies the format type.14type FormatT = string1516const (17// FormatCmd prints the full ssh command line.18//19// ssh -o IdentityFile="/Users/example/.lima/_config/user" -o User=example -o Hostname=127.0.0.1 -o Port=60022 lima-default20FormatCmd = FormatT("cmd")2122// FormatArgs is similar to FormatCmd but omits "ssh" and the destination address.23//24// -o IdentityFile="/Users/example/.lima/_config/user" -o User=example -o Hostname=127.0.0.1 -o Port=6002225FormatArgs = FormatT("args")2627// FormatOptions prints the ssh option key value pairs.28//29// IdentityFile="/Users/example/.lima/_config/user"30// User=example31// Hostname=127.0.0.132// Port=6002233FormatOptions = FormatT("options")3435// FormatConfig uses the ~/.ssh/config format36//37// Host lima-default38// IdentityFile "/Users/example/.lima/_config/user "39// User example40// Hostname 127.0.0.141// Port 6002242FormatConfig = FormatT("config")4344// TODO: consider supporting "url" format (ssh://USER@HOSTNAME:PORT).45//46// TODO: consider supporting "json" format.47// It is unclear whether we can just map ssh "config" into JSON, as "config" has duplicated keys.48// (JSON supports duplicated keys too, but not all JSON implementations expect JSON with duplicated keys).49)5051// Formats is the list of the supported formats.52var Formats = []FormatT{FormatCmd, FormatArgs, FormatOptions, FormatConfig}5354func quoteOption(o string) string {55// make sure the shell doesn't swallow quotes in option values56if strings.ContainsRune(o, '"') {57o = "'" + o + "'"58}59return o60}6162// Format formats the ssh options.63func Format(w io.Writer, sshPath, instName string, format FormatT, opts []string) error {64fakeHostname := hostname.FromInstName(instName) // TODO: support customization65switch format {66case FormatCmd:67args := []string{sshPath}68for _, o := range opts {69args = append(args, "-o", quoteOption(o))70}71args = append(args, fakeHostname)72// the args are similar to `limactl shell` but not exactly same. (e.g., lacks -t)73fmt.Fprintln(w, strings.Join(args, " ")) // no need to use shellescape.QuoteCommand74case FormatArgs:75var args []string76for _, o := range opts {77args = append(args, "-o", quoteOption(o))78}79fmt.Fprintln(w, strings.Join(args, " ")) // no need to use shellescape.QuoteCommand80case FormatOptions:81for _, o := range opts {82fmt.Fprintln(w, o)83}84case FormatConfig:85fmt.Fprintf(w, "Host %s\n", fakeHostname)86for _, o := range opts {87kv := strings.SplitN(o, "=", 2)88if len(kv) != 2 {89return fmt.Errorf("unexpected option %q", o)90}91fmt.Fprintf(w, " %s %s\n", kv[0], kv[1])92}93default:94return fmt.Errorf("unknown format: %q", format)95}96return nil97}9899100