Skip to content

Commit 34e821a

Browse files
committed
integration: read prebuilt-image env knobs via one envknob helper
Replace the repeated os.Getenv(HEADSCALE_INTEGRATION_*_IMAGE) + strings.Cut parsing in hsic/dsic/scenario with integrationutil.PrebuiltImage (built on tailscale envknob), and switch the remaining raw reads in tsic/tsric to envknob.String. One documented place for the prebuilt-image knobs.
1 parent 5b2a9ff commit 34e821a

6 files changed

Lines changed: 55 additions & 43 deletions

File tree

integration/dsic/dsic.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"log"
88
"net"
99
"net/http"
10-
"os"
1110
"strconv"
1211
"strings"
1312
"time"
@@ -27,12 +26,7 @@ const (
2726
dockerExecuteTimeout = 60 * time.Second
2827
)
2928

30-
var (
31-
errDERPerStatusCodeNotOk = errors.New("DERPer status code not OK")
32-
errInvalidDerperImageFormat = errors.New(
33-
"invalid HEADSCALE_INTEGRATION_DERPER_IMAGE format, expected repository:tag",
34-
)
35-
)
29+
var errDERPerStatusCodeNotOk = errors.New("DERPer status code not OK")
3630

3731
// DERPServerInContainer represents DERP Server in Container (DSIC).
3832
type DERPServerInContainer struct {
@@ -233,13 +227,12 @@ func New(
233227
// In CI / nix checks a prebuilt derper image is provided so we neither build
234228
// the image nor clone tailscale at runtime, mirroring the tailscale and
235229
// headscale image injection. Only the head image is prebuilt.
236-
prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_DERPER_IMAGE")
237-
if prebuiltImage != "" && version == "head" {
238-
repo, tag, ok := strings.Cut(prebuiltImage, ":")
239-
if !ok {
240-
return nil, errInvalidDerperImageFormat
241-
}
230+
repo, tag, prebuilt, err := integrationutil.PrebuiltImage("HEADSCALE_INTEGRATION_DERPER_IMAGE")
231+
if err != nil {
232+
return nil, err
233+
}
242234

235+
if prebuilt && version == "head" {
243236
runOptions.Repository = repo
244237
runOptions.Tag = tag
245238

integration/hsic/hsic.go

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,8 @@ const (
5555
)
5656

5757
var (
58-
errHeadscaleStatusCodeNotOk = errors.New("headscale status code not ok")
59-
errInvalidHeadscaleImageFormat = errors.New("invalid HEADSCALE_INTEGRATION_HEADSCALE_IMAGE format, expected repository:tag")
60-
errHeadscaleImageRequiredInCI = errors.New("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE must be set in CI")
61-
errInvalidPostgresImageFormat = errors.New("invalid HEADSCALE_INTEGRATION_POSTGRES_IMAGE format, expected repository:tag")
58+
errHeadscaleStatusCodeNotOk = errors.New("headscale status code not ok")
59+
errHeadscaleImageRequiredInCI = errors.New("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE must be set in CI")
6260
)
6361

6462
type fileInContainer struct {
@@ -404,12 +402,12 @@ func New(
404402
pgRepo := "postgres"
405403
pgTag := "latest"
406404

407-
if prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_POSTGRES_IMAGE"); prebuiltImage != "" {
408-
repo, tag, found := strings.Cut(prebuiltImage, ":")
409-
if !found {
410-
return nil, errInvalidPostgresImageFormat
411-
}
405+
repo, tag, prebuilt, err := integrationutil.PrebuiltImage("HEADSCALE_INTEGRATION_POSTGRES_IMAGE")
406+
if err != nil {
407+
return nil, err
408+
}
412409

410+
if prebuilt {
413411
pgRepo = repo
414412
pgTag = tag
415413
}
@@ -508,16 +506,13 @@ func New(
508506
var container *dockertest.Resource
509507

510508
// Check if a pre-built image is available via environment variable
511-
prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE")
512-
513-
if prebuiltImage != "" {
514-
log.Printf("Using pre-built headscale image: %s", prebuiltImage) //nolint:gosec // G706: integration-only log of trusted env value
515-
// Parse image into repository and tag
516-
repo, tag, ok := strings.Cut(prebuiltImage, ":")
517-
if !ok {
518-
return nil, errInvalidHeadscaleImageFormat
519-
}
509+
repo, tag, prebuilt, err := integrationutil.PrebuiltImage("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE")
510+
if err != nil {
511+
return nil, err
512+
}
520513

514+
if prebuilt {
515+
log.Printf("Using pre-built headscale image: %s:%s", repo, tag) //nolint:gosec // G706: integration-only log of trusted env value
521516
runOptions.Repository = repo
522517
runOptions.Tag = tag
523518

@@ -528,7 +523,7 @@ func New(
528523
dockertestutil.DockerAllowNetworkAdministration,
529524
)
530525
if err != nil {
531-
return nil, fmt.Errorf("running pre-built headscale container %q: %w", prebuiltImage, err)
526+
return nil, fmt.Errorf("running pre-built headscale container %s:%s: %w", repo, tag, err)
532527
}
533528
} else if util.IsCI() {
534529
return nil, errHeadscaleImageRequiredInCI

integration/integrationutil/util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,45 @@ import (
88
"crypto/x509"
99
"crypto/x509/pkix"
1010
"encoding/pem"
11+
"errors"
1112
"fmt"
1213
"io"
1314
"math/big"
1415
"path/filepath"
16+
"strings"
1517
"time"
1618

1719
"github.com/juanfont/headscale/hscontrol/types"
1820
"github.com/juanfont/headscale/hscontrol/util"
1921
"github.com/juanfont/headscale/integration/dockertestutil"
2022
"github.com/ory/dockertest/v3"
2123
"github.com/ory/dockertest/v3/docker"
24+
"tailscale.com/envknob"
2225
"tailscale.com/tailcfg"
2326
)
2427

28+
var errInvalidImageFormat = errors.New("integration image env must be in repository:tag format")
29+
30+
// PrebuiltImage reads a HEADSCALE_INTEGRATION_*_IMAGE knob (the full var name,
31+
// e.g. "HEADSCALE_INTEGRATION_HEADSCALE_IMAGE") and splits it into repository
32+
// and tag. The bool is false when the knob is unset — the suite then builds the
33+
// image itself; it errors when the value is set but is not "repository:tag".
34+
// This is the one place the prebuilt-image knobs (used by the CI / nix-check
35+
// path) are read, via tailscale's envknob.
36+
func PrebuiltImage(envVar string) (string, string, bool, error) {
37+
image := envknob.String(envVar)
38+
if image == "" {
39+
return "", "", false, nil
40+
}
41+
42+
repo, tag, found := strings.Cut(image, ":")
43+
if !found {
44+
return "", "", false, fmt.Errorf("%s=%q: %w", envVar, image, errInvalidImageFormat)
45+
}
46+
47+
return repo, tag, true, nil
48+
}
49+
2550
// PeerSyncTimeout returns the timeout for peer synchronization based on environment:
2651
// 60s for dev, 120s for CI.
2752
func PeerSyncTimeout() time.Duration {

integration/scenario.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,21 +1585,19 @@ const (
15851585

15861586
var errStatusCodeNotOK = errors.New("status code not OK")
15871587

1588-
var errInvalidHeadscaleImageFormat = errors.New("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE must be in 'repository:tag' format")
1589-
15901588
// runHeadscaleImageContainer starts runOptions from the prebuilt headscale
15911589
// image when HEADSCALE_INTEGRATION_HEADSCALE_IMAGE is set, otherwise it builds
15921590
// Dockerfile.integration. The mock OIDC provider (headscale mockoidc) and the
15931591
// webservice (python3 -m http.server) both run on the headscale image, so they
15941592
// share the same prebuilt-or-build path hsic uses for headscale itself — which
15951593
// is what lets them come up offline (e.g. in the nix VM checks).
15961594
func (s *Scenario) runHeadscaleImageContainer(runOptions *dockertest.RunOptions) (*dockertest.Resource, error) {
1597-
if prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE"); prebuiltImage != "" {
1598-
repo, tag, ok := strings.Cut(prebuiltImage, ":")
1599-
if !ok {
1600-
return nil, errInvalidHeadscaleImageFormat
1601-
}
1595+
repo, tag, prebuilt, err := integrationutil.PrebuiltImage("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE")
1596+
if err != nil {
1597+
return nil, err
1598+
}
16021599

1600+
if prebuilt {
16031601
runOptions.Repository = repo
16041602
runOptions.Tag = tag
16051603

integration/tsic/tsic.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/juanfont/headscale/integration/integrationutil"
2727
"github.com/ory/dockertest/v3"
2828
"github.com/ory/dockertest/v3/docker"
29+
"tailscale.com/envknob"
2930
"tailscale.com/ipn"
3031
"tailscale.com/ipn/ipnstate"
3132
"tailscale.com/ipn/store/mem"
@@ -432,7 +433,7 @@ func New(
432433
switch version {
433434
case VersionHead:
434435
// Check if a pre-built image is available via environment variable
435-
prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_TAILSCALE_IMAGE")
436+
prebuiltImage := envknob.String("HEADSCALE_INTEGRATION_TAILSCALE_IMAGE")
436437

437438
// If custom build tags are required (e.g., for websocket DERP), the
438439
// default pre-built image won't have the necessary code compiled in. A
@@ -441,7 +442,7 @@ func New(
441442
// building the image with the tags.
442443
hasBuildTags := len(tsic.buildConfig.tags) > 0
443444
if hasBuildTags && prebuiltImage != "" {
444-
wsImage := os.Getenv("HEADSCALE_INTEGRATION_TAILSCALE_WEBSOCKET_IMAGE")
445+
wsImage := envknob.String("HEADSCALE_INTEGRATION_TAILSCALE_WEBSOCKET_IMAGE")
445446
if tsic.withWebsocketDERP && wsImage != "" {
446447
prebuiltImage = wsImage
447448
} else {

integration/tsric/tsric.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import (
1212
"fmt"
1313
"io"
1414
"log"
15-
"os"
1615
"strings"
1716

1817
"github.com/juanfont/headscale/integration/dockertestutil"
1918
"github.com/juanfont/headscale/integration/integrationutil"
2019
"github.com/ory/dockertest/v3"
2120
"github.com/ory/dockertest/v3/docker"
21+
"tailscale.com/envknob"
2222
"tailscale.com/util/rands"
2323
)
2424

@@ -35,7 +35,7 @@ const (
3535

3636
// getPrebuiltImage returns the pre-built tailscale-rs Docker image name if set.
3737
func getPrebuiltImage() string {
38-
return os.Getenv("HEADSCALE_INTEGRATION_TAILSCALE_RS_IMAGE")
38+
return envknob.String("HEADSCALE_INTEGRATION_TAILSCALE_RS_IMAGE")
3939
}
4040

4141
// TailscaleRustInContainer runs the tailscale-rs axum example as an

0 commit comments

Comments
 (0)