-
Notifications
You must be signed in to change notification settings - Fork 44
/
configure_port.go
146 lines (126 loc) · 3.33 KB
/
configure_port.go
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package net contains some helper wrapping functions for the http and net
// golang libraries that meet Packer-specific needs.
package net
import (
"context"
"fmt"
"log"
"math/rand"
"net"
"os"
"strconv"
"time"
"github.com/hashicorp/packer-plugin-sdk/filelock"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
)
var _ net.Listener = &Listener{}
// Listener wraps a net.Lister with some Packer-specific capabilies. For
// example, until you call Listener.Close, any call to ListenRangeConfig.Listen
// cannot bind to a Port. Packer tries to tell moving parts which port they can
// use, but often the port has to be released before a 3rd party is started,
// like a VNC server.
type Listener struct {
// Listener can be closed but Port will be file locked by packer until
// Close is called.
net.Listener
Port int
Address string
lock *filelock.Flock
cleanupFunc func() error
}
func (l *Listener) Close() error {
err := l.lock.Unlock()
if err != nil {
log.Printf("cannot unlock lockfile %#v: %v", l, err)
}
err = l.Listener.Close()
if err != nil {
return err
}
if l.cleanupFunc != nil {
err := l.cleanupFunc()
if err != nil {
log.Printf("cannot cleanup: %#v", err)
}
}
return nil
}
// ListenRangeConfig contains options for listening to a free address [Min,Max)
// range. ListenRangeConfig wraps a net.ListenConfig.
type ListenRangeConfig struct {
// like "tcp" or "udp". defaults to "tcp".
Network string
Addr string
Min, Max int
net.ListenConfig
}
// Listen tries to Listen to a random open TCP port in the [min, max) range
// until ctx is cancelled.
// Listen uses net.ListenConfig.Listen internally.
func (lc ListenRangeConfig) Listen(ctx context.Context) (*Listener, error) {
if lc.Network == "" {
lc.Network = "tcp"
}
portRange := lc.Max - lc.Min
var listener *Listener
err := retry.Config{
RetryDelay: func() time.Duration { return 1 * time.Millisecond },
}.Run(ctx, func(context.Context) error {
port := lc.Min
if portRange > 0 {
port += rand.Intn(portRange)
}
lockFilePath, err := packersdk.CachePath("port", strconv.Itoa(port))
if err != nil {
return err
}
lock := filelock.New(lockFilePath)
locked, err := lock.TryLock()
if err != nil {
return err
}
if !locked {
return ErrPortFileLocked(port)
}
l, err := lc.ListenConfig.Listen(ctx, lc.Network, fmt.Sprintf("%s:%d", lc.Addr, port))
if err != nil {
if err := lock.Unlock(); err != nil {
log.Fatalf("Could not unlock file lock for port %d: %v", port, err)
}
return &ErrPortBusy{
Port: port,
Err: err,
}
}
cleanupFunc := func() error {
return os.Remove(lockFilePath)
}
log.Printf("Found available port: %d on IP: %s", port, lc.Addr)
listener = &Listener{
Address: lc.Addr,
Port: port,
Listener: l,
lock: lock,
cleanupFunc: cleanupFunc,
}
return nil
})
return listener, err
}
type ErrPortFileLocked int
func (port ErrPortFileLocked) Error() string {
return fmt.Sprintf("Port %d is file locked", port)
}
type ErrPortBusy struct {
Port int
Err error
}
func (err *ErrPortBusy) Error() string {
if err == nil {
return "<nil>"
}
return fmt.Sprintf("port %d cannot be opened: %v", err.Port, err.Err)
}