File: ssh.go

package info (click to toggle)
golang-github-cli-go-gh-v2 2.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 496 kB
  • sloc: makefile: 2
file content (109 lines) | stat: -rw-r--r-- 2,330 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Package ssh resolves local SSH hostname aliases.
package ssh

import (
	"bufio"
	"net/url"
	"os/exec"
	"strings"
	"sync"

	"github.com/cli/safeexec"
)

type Translator struct {
	cacheMap   map[string]string
	cacheMu    sync.RWMutex
	sshPath    string
	sshPathErr error
	sshPathMu  sync.Mutex

	lookPath   func(string) (string, error)
	newCommand func(string, ...string) *exec.Cmd
}

// NewTranslator initializes a new Translator instance.
func NewTranslator() *Translator {
	return &Translator{}
}

// Translate applies applicable SSH hostname aliases to the specified URL and returns the resulting URL.
func (t *Translator) Translate(u *url.URL) *url.URL {
	if u.Scheme != "ssh" {
		return u
	}
	resolvedHost, err := t.resolve(u.Hostname())
	if err != nil {
		return u
	}
	if strings.EqualFold(resolvedHost, "ssh.github.com") {
		resolvedHost = "github.com"
	}
	newURL, _ := url.Parse(u.String())
	newURL.Host = resolvedHost
	return newURL
}

func (t *Translator) resolve(hostname string) (string, error) {
	t.cacheMu.RLock()
	cached, cacheFound := t.cacheMap[strings.ToLower(hostname)]
	t.cacheMu.RUnlock()
	if cacheFound {
		return cached, nil
	}

	var sshPath string
	t.sshPathMu.Lock()
	if t.sshPath == "" && t.sshPathErr == nil {
		lookPath := t.lookPath
		if lookPath == nil {
			lookPath = safeexec.LookPath
		}
		t.sshPath, t.sshPathErr = lookPath("ssh")
	}
	if t.sshPathErr != nil {
		defer t.sshPathMu.Unlock()
		return t.sshPath, t.sshPathErr
	}
	sshPath = t.sshPath
	t.sshPathMu.Unlock()

	t.cacheMu.Lock()
	defer t.cacheMu.Unlock()

	newCommand := t.newCommand
	if newCommand == nil {
		newCommand = exec.Command
	}
	sshCmd := newCommand(sshPath, "-G", hostname)
	stdout, err := sshCmd.StdoutPipe()
	if err != nil {
		return "", err
	}

	if err := sshCmd.Start(); err != nil {
		return "", err
	}

	var resolvedHost string
	s := bufio.NewScanner(stdout)
	for s.Scan() {
		line := s.Text()
		parts := strings.SplitN(line, " ", 2)
		if len(parts) == 2 && parts[0] == "hostname" {
			resolvedHost = parts[1]
		}
	}

	err = sshCmd.Wait()
	if err != nil || resolvedHost == "" {
		// handle failures by returning the original hostname unchanged
		resolvedHost = hostname
	}

	if t.cacheMap == nil {
		t.cacheMap = map[string]string{}
	}
	t.cacheMap[strings.ToLower(hostname)] = resolvedHost
	return resolvedHost, nil
}