069db2777dfbba94e2c93c37275632037cd64bec

Author
Jonatan Wallmander <jonatan@vovoid.com>
Committer
GitHub <noreply@github.com>
Date

Message

feat: support toggling servers on/off in configuration (#594) (#612)

To test this, tests added the new function:

  `ensureservernotrunning [SERVICE_NAME]`

Co-authored-by: Jonatan Wallmander <jonatan.wallmander@kdab.com>

Diff

  1diff --git a/cmd/soft/serve/server.go b/cmd/soft/serve/server.go
  2index 69d5db5a8fcf40c3c1f09786599303a61cb47c65..76e55f581234203600edebb4f7b7bcb5d1d155f7 100644
  3--- a/cmd/soft/serve/server.go
  4+++ b/cmd/soft/serve/server.go
  5@@ -93,34 +93,51 @@ func NewServer(ctx context.Context) (*Server, error) {
  6 // Start starts the SSH server.
  7 func (s *Server) Start() error {
  8 	errg, _ := errgroup.WithContext(s.ctx)
  9-	errg.Go(func() error {
 10-		s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
 11-		if err := s.GitDaemon.ListenAndServe(); !errors.Is(err, daemon.ErrServerClosed) {
 12-			return err
 13-		}
 14-		return nil
 15-	})
 16-	errg.Go(func() error {
 17-		s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
 18-		if err := s.HTTPServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 19-			return err
 20-		}
 21-		return nil
 22-	})
 23-	errg.Go(func() error {
 24-		s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
 25-		if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {
 26-			return err
 27-		}
 28-		return nil
 29-	})
 30-	errg.Go(func() error {
 31-		s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
 32-		if err := s.StatsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 33-			return err
 34-		}
 35-		return nil
 36-	})
 37+
 38+	// optionally start the SSH server
 39+	if s.Config.SSH.Enabled {
 40+		errg.Go(func() error {
 41+			s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
 42+			if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {
 43+				return err
 44+			}
 45+			return nil
 46+		})
 47+	}
 48+
 49+	// optionally start the git daemon
 50+	if s.Config.Git.Enabled {
 51+		errg.Go(func() error {
 52+			s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
 53+			if err := s.GitDaemon.ListenAndServe(); !errors.Is(err, daemon.ErrServerClosed) {
 54+				return err
 55+			}
 56+			return nil
 57+		})
 58+	}
 59+
 60+	// optionally start the HTTP server
 61+	if s.Config.HTTP.Enabled {
 62+		errg.Go(func() error {
 63+			s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
 64+			if err := s.HTTPServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 65+				return err
 66+			}
 67+			return nil
 68+		})
 69+	}
 70+
 71+	// optionally start the Stats server
 72+	if s.Config.Stats.Enabled {
 73+		errg.Go(func() error {
 74+			s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
 75+			if err := s.StatsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
 76+				return err
 77+			}
 78+			return nil
 79+		})
 80+	}
 81+
 82 	errg.Go(func() error {
 83 		s.Cron.Start()
 84 		return nil
 85diff --git a/pkg/config/config.go b/pkg/config/config.go
 86index 2b650984c2d401f12c579fa702f32c7828ae5841..9d8395d4546720580e51bb2afc31925a54b2000a 100644
 87--- a/pkg/config/config.go
 88+++ b/pkg/config/config.go
 89@@ -18,6 +18,9 @@ var binPath = "soft"
 90 
 91 // SSHConfig is the configuration for the SSH server.
 92 type SSHConfig struct {
 93+	// Enabled toggles the SSH server on/off
 94+	Enabled bool `env:"ENABLED" yaml:"enabled"`
 95+
 96 	// ListenAddr is the address on which the SSH server will listen.
 97 	ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
 98 
 99@@ -39,6 +42,9 @@ type SSHConfig struct {
100 
101 // GitConfig is the Git daemon configuration for the server.
102 type GitConfig struct {
103+	// Enabled toggles the Git daemon on/off
104+	Enabled bool `env:"ENABLED" yaml:"enabled"`
105+
106 	// ListenAddr is the address on which the Git daemon will listen.
107 	ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
108 
109@@ -57,6 +63,9 @@ type GitConfig struct {
110 
111 // HTTPConfig is the HTTP configuration for the server.
112 type HTTPConfig struct {
113+	// Enabled toggles the HTTP server on/off
114+	Enabled bool `env:"ENABLED" yaml:"enabled"`
115+
116 	// ListenAddr is the address on which the HTTP server will listen.
117 	ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
118 
119@@ -72,6 +81,9 @@ type HTTPConfig struct {
120 
121 // StatsConfig is the configuration for the stats server.
122 type StatsConfig struct {
123+	// Enabled toggles the Stats server on/off
124+	Enabled bool `env:"ENABLED" yaml:"enabled"`
125+
126 	// ListenAddr is the address on which the stats server will listen.
127 	ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
128 }
129@@ -165,21 +177,25 @@ func (c *Config) Environ() []string {
130 		fmt.Sprintf("SOFT_SERVE_DATA_PATH=%s", c.DataPath),
131 		fmt.Sprintf("SOFT_SERVE_NAME=%s", c.Name),
132 		fmt.Sprintf("SOFT_SERVE_INITIAL_ADMIN_KEYS=%s", strings.Join(c.InitialAdminKeys, "\n")),
133+		fmt.Sprintf("SOFT_SERVE_SSH_ENABLED=%t", c.SSH.Enabled),
134 		fmt.Sprintf("SOFT_SERVE_SSH_LISTEN_ADDR=%s", c.SSH.ListenAddr),
135 		fmt.Sprintf("SOFT_SERVE_SSH_PUBLIC_URL=%s", c.SSH.PublicURL),
136 		fmt.Sprintf("SOFT_SERVE_SSH_KEY_PATH=%s", c.SSH.KeyPath),
137 		fmt.Sprintf("SOFT_SERVE_SSH_CLIENT_KEY_PATH=%s", c.SSH.ClientKeyPath),
138 		fmt.Sprintf("SOFT_SERVE_SSH_MAX_TIMEOUT=%d", c.SSH.MaxTimeout),
139 		fmt.Sprintf("SOFT_SERVE_SSH_IDLE_TIMEOUT=%d", c.SSH.IdleTimeout),
140+		fmt.Sprintf("SOFT_SERVE_GIT_ENABLED=%t", c.Git.Enabled),
141 		fmt.Sprintf("SOFT_SERVE_GIT_LISTEN_ADDR=%s", c.Git.ListenAddr),
142 		fmt.Sprintf("SOFT_SERVE_GIT_PUBLIC_URL=%s", c.Git.PublicURL),
143 		fmt.Sprintf("SOFT_SERVE_GIT_MAX_TIMEOUT=%d", c.Git.MaxTimeout),
144 		fmt.Sprintf("SOFT_SERVE_GIT_IDLE_TIMEOUT=%d", c.Git.IdleTimeout),
145 		fmt.Sprintf("SOFT_SERVE_GIT_MAX_CONNECTIONS=%d", c.Git.MaxConnections),
146+		fmt.Sprintf("SOFT_SERVE_HTTP_ENABLED=%t", c.HTTP.Enabled),
147 		fmt.Sprintf("SOFT_SERVE_HTTP_LISTEN_ADDR=%s", c.HTTP.ListenAddr),
148 		fmt.Sprintf("SOFT_SERVE_HTTP_TLS_KEY_PATH=%s", c.HTTP.TLSKeyPath),
149 		fmt.Sprintf("SOFT_SERVE_HTTP_TLS_CERT_PATH=%s", c.HTTP.TLSCertPath),
150 		fmt.Sprintf("SOFT_SERVE_HTTP_PUBLIC_URL=%s", c.HTTP.PublicURL),
151+		fmt.Sprintf("SOFT_SERVE_STATS_ENABLED=%t", c.Stats.Enabled),
152 		fmt.Sprintf("SOFT_SERVE_STATS_LISTEN_ADDR=%s", c.Stats.ListenAddr),
153 		fmt.Sprintf("SOFT_SERVE_LOG_FORMAT=%s", c.Log.Format),
154 		fmt.Sprintf("SOFT_SERVE_LOG_TIME_FORMAT=%s", c.Log.TimeFormat),
155@@ -318,6 +334,7 @@ func DefaultConfig() *Config {
156 		Name:     "Soft Serve",
157 		DataPath: DefaultDataPath(),
158 		SSH: SSHConfig{
159+			Enabled:       true,
160 			ListenAddr:    ":23231",
161 			PublicURL:     "ssh://localhost:23231",
162 			KeyPath:       filepath.Join("ssh", "soft_serve_host_ed25519"),
163@@ -326,6 +343,7 @@ func DefaultConfig() *Config {
164 			IdleTimeout:   10 * 60, // 10 minutes
165 		},
166 		Git: GitConfig{
167+			Enabled:        true,
168 			ListenAddr:     ":9418",
169 			PublicURL:      "git://localhost",
170 			MaxTimeout:     0,
171@@ -333,10 +351,12 @@ func DefaultConfig() *Config {
172 			MaxConnections: 32,
173 		},
174 		HTTP: HTTPConfig{
175+			Enabled:    true,
176 			ListenAddr: ":23232",
177 			PublicURL:  "http://localhost:23232",
178 		},
179 		Stats: StatsConfig{
180+			Enabled:    true,
181 			ListenAddr: "localhost:23233",
182 		},
183 		Log: LogConfig{
184diff --git a/testscript/script_test.go b/testscript/script_test.go
185index 564372808f2e8f6b7eacc86a33f348a48428e070..8f3a6af43497752cae064723faf0ccc1da02c8b1 100644
186--- a/testscript/script_test.go
187+++ b/testscript/script_test.go
188@@ -79,20 +79,21 @@ func TestScript(t *testing.T) {
189 		UpdateScripts:       *update,
190 		RequireExplicitExec: true,
191 		Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
192-			"soft":                cmdSoft("admin", admin1.Signer()),
193-			"usoft":               cmdSoft("user1", user1.Signer()),
194-			"git":                 cmdGit(admin1Key),
195-			"ugit":                cmdGit(user1Key),
196-			"curl":                cmdCurl,
197-			"mkfile":              cmdMkfile,
198-			"envfile":             cmdEnvfile,
199-			"readfile":            cmdReadfile,
200-			"dos2unix":            cmdDos2Unix,
201-			"new-webhook":         cmdNewWebhook,
202-			"ensureserverrunning": cmdEnsureServerRunning,
203-			"stopserver":          cmdStopserver,
204-			"ui":                  cmdUI(admin1.Signer()),
205-			"uui":                 cmdUI(user1.Signer()),
206+			"soft":                   cmdSoft("admin", admin1.Signer()),
207+			"usoft":                  cmdSoft("user1", user1.Signer()),
208+			"git":                    cmdGit(admin1Key),
209+			"ugit":                   cmdGit(user1Key),
210+			"curl":                   cmdCurl,
211+			"mkfile":                 cmdMkfile,
212+			"envfile":                cmdEnvfile,
213+			"readfile":               cmdReadfile,
214+			"dos2unix":               cmdDos2Unix,
215+			"new-webhook":            cmdNewWebhook,
216+			"ensureserverrunning":    cmdEnsureServerRunning,
217+			"ensureservernotrunning": cmdEnsureServerNotRunning,
218+			"stopserver":             cmdStopserver,
219+			"ui":                     cmdUI(admin1.Signer()),
220+			"uui":                    cmdUI(user1.Signer()),
221 		},
222 		Setup: func(e *testscript.Env) error {
223 			// Add binPath to PATH
224@@ -112,6 +113,8 @@ func TestScript(t *testing.T) {
225 			e.Setenv("DATA_PATH", data)
226 			e.Setenv("SSH_PORT", fmt.Sprintf("%d", sshPort))
227 			e.Setenv("HTTP_PORT", fmt.Sprintf("%d", httpPort))
228+			e.Setenv("STATS_PORT", fmt.Sprintf("%d", statsPort))
229+			e.Setenv("GIT_PORT", fmt.Sprintf("%d", gitPort))
230 			e.Setenv("ADMIN1_AUTHORIZED_KEY", admin1.AuthorizedKey())
231 			e.Setenv("ADMIN2_AUTHORIZED_KEY", admin2.AuthorizedKey())
232 			e.Setenv("USER1_AUTHORIZED_KEY", user1.AuthorizedKey())
233@@ -496,6 +499,32 @@ func cmdEnsureServerRunning(ts *testscript.TestScript, neg bool, args []string)
234 	}
235 }
236 
237+func cmdEnsureServerNotRunning(ts *testscript.TestScript, neg bool, args []string) {
238+	if len(args) < 1 {
239+		ts.Fatalf("Must supply a TCP port of one of the services to connect to. " +
240+			"These are set as env vars as they are randomized. " +
241+			"Example usage: \"cmdensureservernotrunning SSH_PORT\"\n" +
242+			"Valid values for the env var: SSH_PORT|HTTP_PORT|GIT_PORT|STATS_PORT")
243+	}
244+
245+	port := ts.Getenv(args[0])
246+
247+	// verify that the server is not up
248+	addr := net.JoinHostPort("localhost", port)
249+	for {
250+		conn, _ := net.DialTimeout(
251+			"tcp",
252+			addr,
253+			time.Second,
254+		)
255+		if conn != nil {
256+			ts.Fatalf("server is running on port %s while it should not be running", port)
257+			conn.Close()
258+		}
259+		break
260+	}
261+}
262+
263 func cmdStopserver(ts *testscript.TestScript, neg bool, args []string) {
264 	// stop the server
265 	resp, err := http.DefaultClient.Head(fmt.Sprintf("%s/__stop", ts.Getenv("SOFT_SERVE_HTTP_PUBLIC_URL")))
266diff --git a/testscript/testdata/config-servers-git_disabled.txtar b/testscript/testdata/config-servers-git_disabled.txtar
267new file mode 100644
268index 0000000000000000000000000000000000000000..7fe34eda1e28c3430ac7214ca6b16ef0867d23fb
269--- /dev/null
270+++ b/testscript/testdata/config-servers-git_disabled.txtar
271@@ -0,0 +1,18 @@
272+# vi: set ft=conf
273+
274+# disable git listening
275+env SOFT_SERVE_SSH_ENABLED=true
276+env SOFT_SERVE_GIT_ENABLED=false
277+env SOFT_SERVE_HTTP_ENABLED=true
278+env SOFT_SERVE_STATS_ENABLED=true
279+
280+# start soft serve
281+exec soft serve --sync-hooks &
282+
283+# wait for the ssh + other servers to come up
284+ensureserverrunning SSH_PORT
285+ensureserverrunning HTTP_PORT
286+ensureserverrunning STATS_PORT
287+
288+# ensure that the disabled server is not running
289+ensureservernotrunning GIT_PORT
290diff --git a/testscript/testdata/config-servers-http_disabled.txtar b/testscript/testdata/config-servers-http_disabled.txtar
291new file mode 100644
292index 0000000000000000000000000000000000000000..7eee3ed5d52673a4f2b3d1ebb57c36a4f0f7d06b
293--- /dev/null
294+++ b/testscript/testdata/config-servers-http_disabled.txtar
295@@ -0,0 +1,19 @@
296+# vi: set ft=conf
297+
298+# disable http listening
299+env SOFT_SERVE_SSH_ENABLED=true
300+env SOFT_SERVE_GIT_ENABLED=true
301+env SOFT_SERVE_HTTP_ENABLED=false
302+env SOFT_SERVE_STATS_ENABLED=true
303+
304+# start soft serve
305+exec soft serve --sync-hooks &
306+
307+# wait for the ssh + other servers to come up
308+ensureserverrunning SSH_PORT
309+ensureserverrunning GIT_PORT
310+ensureserverrunning STATS_PORT
311+
312+# ensure that the disabled server is not running
313+ensureservernotrunning HTTP_PORT
314+
315diff --git a/testscript/testdata/config-servers-ssh_disabled.txtar b/testscript/testdata/config-servers-ssh_disabled.txtar
316new file mode 100644
317index 0000000000000000000000000000000000000000..30b7492deae79d7688409341d4db256b7a603b38
318--- /dev/null
319+++ b/testscript/testdata/config-servers-ssh_disabled.txtar
320@@ -0,0 +1,18 @@
321+# vi: set ft=conf
322+
323+# disable ssh listening
324+env SOFT_SERVE_SSH_ENABLED=false
325+env SOFT_SERVE_GIT_ENABLED=true
326+env SOFT_SERVE_HTTP_ENABLED=true
327+env SOFT_SERVE_STATS_ENABLED=true
328+
329+# start soft serve
330+exec soft serve --sync-hooks &
331+
332+# wait for the git + other servers to come up
333+ensureserverrunning GIT_PORT
334+ensureserverrunning HTTP_PORT
335+ensureserverrunning STATS_PORT
336+
337+# ensure that the disabled server is not running
338+ensureservernotrunning SSH_PORT
339diff --git a/testscript/testdata/config-servers-stats_disabled.txtar b/testscript/testdata/config-servers-stats_disabled.txtar
340new file mode 100644
341index 0000000000000000000000000000000000000000..2b8031752933733274760701411eb587c176e3c0
342--- /dev/null
343+++ b/testscript/testdata/config-servers-stats_disabled.txtar
344@@ -0,0 +1,18 @@
345+# vi: set ft=conf
346+
347+# disable stats listening
348+env SOFT_SERVE_SSH_ENABLED=true
349+env SOFT_SERVE_GIT_ENABLED=true
350+env SOFT_SERVE_HTTP_ENABLED=true
351+env SOFT_SERVE_STATS_ENABLED=false
352+
353+# start soft serve
354+exec soft serve --sync-hooks &
355+
356+# wait for the ssh + other servers to come up
357+ensureserverrunning SSH_PORT
358+ensureserverrunning GIT_PORT
359+ensureserverrunning HTTP_PORT
360+
361+# ensure that the disabled server is not running
362+ensureservernotrunning STATS_PORT