build: upgrade dependencies and build with go 1.21

This commit is contained in:
2023-08-21 23:04:28 +02:00
parent ddc5ee91e5
commit 3942b32843
1201 changed files with 129198 additions and 39613 deletions

View File

@ -0,0 +1,67 @@
package testcontainersdocker
import (
"context"
"path/filepath"
"github.com/docker/docker/client"
"github.com/testcontainers/testcontainers-go/internal"
"github.com/testcontainers/testcontainers-go/internal/config"
"github.com/testcontainers/testcontainers-go/internal/testcontainerssession"
)
// NewClient returns a new docker client extracting the docker host from the different alternatives
func NewClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) {
tcConfig := config.Read()
dockerHost := ExtractDockerHost(ctx)
opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}
if dockerHost != "" {
opts = append(opts, client.WithHost(dockerHost))
// for further information, read https://docs.docker.com/engine/security/protect-access/
if tcConfig.TLSVerify == 1 {
cacertPath := filepath.Join(tcConfig.CertPath, "ca.pem")
certPath := filepath.Join(tcConfig.CertPath, "cert.pem")
keyPath := filepath.Join(tcConfig.CertPath, "key.pem")
opts = append(opts, client.WithTLSClientConfig(cacertPath, certPath, keyPath))
}
}
opts = append(opts, client.WithHTTPHeaders(
map[string]string{
"x-tc-sid": testcontainerssession.String(),
"User-Agent": "tc-go/" + internal.Version,
}),
)
// passed options have priority over the default ones
opts = append(opts, ops...)
cli, err := client.NewClientWithOpts(opts...)
if err != nil {
return nil, err
}
if _, err = cli.Ping(context.Background()); err != nil {
// Fallback to environment, including the original options
cli, err = defaultClient(context.Background(), ops...)
if err != nil {
return nil, err
}
}
defer cli.Close()
return cli, nil
}
// defaultClient returns a plain, new docker client with the default options
func defaultClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) {
if len(ops) == 0 {
ops = []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}
}
return client.NewClientWithOpts(ops...)
}

View File

@ -0,0 +1,269 @@
package testcontainersdocker
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"sync"
"github.com/docker/docker/client"
"github.com/testcontainers/testcontainers-go/internal/config"
)
type dockerHostContext string
var DockerHostContextKey = dockerHostContext("docker_host")
var (
ErrDockerHostNotSet = errors.New("DOCKER_HOST is not set")
ErrDockerSocketOverrideNotSet = errors.New("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE is not set")
ErrDockerSocketNotSetInContext = errors.New("socket not set in context")
ErrDockerSocketNotSetInProperties = errors.New("socket not set in ~/.testcontainers.properties")
ErrNoUnixSchema = errors.New("URL schema is not unix")
ErrSocketNotFound = errors.New("socket not found")
ErrSocketNotFoundInPath = errors.New("docker socket not found in " + DockerSocketPath)
// ErrTestcontainersHostNotSetInProperties this error is specific to Testcontainers
ErrTestcontainersHostNotSetInProperties = errors.New("tc.host not set in ~/.testcontainers.properties")
)
var dockerHostCache string
var dockerHostOnce sync.Once
var dockerSocketPathCache string
var dockerSocketPathOnce sync.Once
// deprecated
// see https://github.com/testcontainers/testcontainers-java/blob/main/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L46
func DefaultGatewayIP() (string, error) {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
cmd := exec.Command("sh", "-c", "ip route|awk '/default/ { print $3 }'")
stdout, err := cmd.Output()
if err != nil {
return "", errors.New("failed to detect docker host")
}
ip := strings.TrimSpace(string(stdout))
if len(ip) == 0 {
return "", errors.New("failed to parse default gateway IP")
}
return ip, nil
}
// ExtractDockerHost Extracts the docker host from the different alternatives, caching the result to avoid unnecessary
// calculations. Use this function to get the actual Docker host. This function does not consider Windows containers at the moment.
// The possible alternatives are:
//
// 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file.
// 2. DOCKER_HOST environment variable.
// 3. Docker host from context.
// 4. Docker host from the default docker socket path, without the unix schema.
// 5. Docker host from the "docker.host" property in the ~/.testcontainers.properties file.
// 6. Rootless docker socket path.
// 7. Else, the default Docker socket including schema will be returned.
func ExtractDockerHost(ctx context.Context) string {
dockerHostOnce.Do(func() {
dockerHostCache = extractDockerHost(ctx)
})
return dockerHostCache
}
// ExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema and
// caching the result to avoid unnecessary calculations. Use this function to get the docker socket path,
// not the host (e.g. mounting the socket in a container). This function does not consider Windows containers at the moment.
// The possible alternatives are:
//
// 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file.
// 2. The TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable.
// 3. Using a Docker client, check if the Info().OperativeSystem is "Docker Desktop" and return the default docker socket path for rootless docker.
// 4. Else, Get the current Docker Host from the existing strategies: see ExtractDockerHost.
// 5. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock)
// 6. Else, the default location of the docker socket is used (/var/run/docker.sock)
//
// In any case, if the docker socket schema is "tcp://", the default docker socket path will be returned.
func ExtractDockerSocket(ctx context.Context) string {
dockerSocketPathOnce.Do(func() {
dockerSocketPathCache = extractDockerSocket(ctx)
})
return dockerSocketPathCache
}
// extractDockerHost Extracts the docker host from the different alternatives, without caching the result.
// This internal method is handy for testing purposes.
func extractDockerHost(ctx context.Context) string {
dockerHostFns := []func(context.Context) (string, error){
testcontainersHostFromProperties,
dockerHostFromEnv,
dockerHostFromContext,
dockerSocketPath,
dockerHostFromProperties,
rootlessDockerSocketPath,
}
outerErr := ErrSocketNotFound
for _, dockerHostFn := range dockerHostFns {
dockerHost, err := dockerHostFn(ctx)
if err != nil {
outerErr = fmt.Errorf("%w: %v", outerErr, err)
continue
}
return dockerHost
}
// We are not supporting Windows containers at the moment
return DockerSocketPathWithSchema
}
// extractDockerHost Extracts the docker socket from the different alternatives, without caching the result.
// It will internally use the default Docker client, calling the internal method extractDockerSocketFromClient with it.
// This internal method is handy for testing purposes.
// If a Docker client cannot be created, the program will panic.
func extractDockerSocket(ctx context.Context) string {
cli, err := NewClient(ctx)
if err != nil {
panic(err) // a Docker client is required to get the Docker info
}
defer cli.Close()
return extractDockerSocketFromClient(ctx, cli)
}
// extractDockerSocketFromClient Extracts the docker socket from the different alternatives, without caching the result,
// and receiving an instance of the Docker API client interface.
// This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour.
func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) string {
// check that the socket is not a tcp or unix socket
checkDockerSocketFn := func(socket string) string {
// this use case will cover the case when the docker host is a tcp socket
if strings.HasPrefix(socket, TCPSchema) {
return DockerSocketPath
}
if strings.HasPrefix(socket, DockerSocketSchema) {
return strings.Replace(socket, DockerSocketSchema, "", 1)
}
return socket
}
tcHost, err := testcontainersHostFromProperties(ctx)
if err == nil {
return checkDockerSocketFn(tcHost)
}
testcontainersDockerSocket, err := dockerSocketOverridePath(ctx)
if err == nil {
return checkDockerSocketFn(testcontainersDockerSocket)
}
info, err := cli.Info(ctx)
if err != nil {
panic(err) // Docker Info is required to get the Operating System
}
// Because Docker Desktop runs in a VM, we need to use the default docker path for rootless docker
if info.OperatingSystem == "Docker Desktop" {
if IsWindows() {
return WindowsDockerSocketPath
}
return DockerSocketPath
}
dockerHost := extractDockerHost(ctx)
return checkDockerSocketFn(dockerHost)
}
// dockerHostFromEnv returns the docker host from the DOCKER_HOST environment variable, if it's not empty
func dockerHostFromEnv(ctx context.Context) (string, error) {
if dockerHostPath := os.Getenv("DOCKER_HOST"); dockerHostPath != "" {
return dockerHostPath, nil
}
return "", ErrDockerHostNotSet
}
// dockerHostFromContext returns the docker host from the Go context, if it's not empty
func dockerHostFromContext(ctx context.Context) (string, error) {
if socketPath, ok := ctx.Value(DockerHostContextKey).(string); ok && socketPath != "" {
parsed, err := parseURL(socketPath)
if err != nil {
return "", err
}
return parsed, nil
}
return "", ErrDockerSocketNotSetInContext
}
// dockerHostFromProperties returns the docker host from the ~/.testcontainers.properties file, if it's not empty
func dockerHostFromProperties(ctx context.Context) (string, error) {
cfg := config.Read()
socketPath := cfg.Host
if socketPath != "" {
parsed, err := parseURL(socketPath)
if err != nil {
return "", err
}
return parsed, nil
}
return "", ErrDockerSocketNotSetInProperties
}
// dockerSocketOverridePath returns the docker socket from the TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable,
// if it's not empty
func dockerSocketOverridePath(ctx context.Context) (string, error) {
if dockerHostPath, exists := os.LookupEnv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"); exists {
return dockerHostPath, nil
}
return "", ErrDockerSocketOverrideNotSet
}
// dockerSocketPath returns the docker socket from the default docker socket path, if it's not empty
// and the socket exists
func dockerSocketPath(ctx context.Context) (string, error) {
if fileExists(DockerSocketPath) {
return DockerSocketPathWithSchema, nil
}
return "", ErrSocketNotFoundInPath
}
// testcontainersHostFromProperties returns the testcontainers host from the ~/.testcontainers.properties file, if it's not empty
func testcontainersHostFromProperties(ctx context.Context) (string, error) {
cfg := config.Read()
testcontainersHost := cfg.TestcontainersHost
if testcontainersHost != "" {
parsed, err := parseURL(testcontainersHost)
if err != nil {
return "", err
}
return parsed, nil
}
return "", ErrTestcontainersHostNotSetInProperties
}
// InAContainer returns true if the code is running inside a container
// See https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25
func InAContainer() bool {
return inAContainer("/.dockerenv")
}
func inAContainer(path string) bool {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L15
if _, err := os.Stat(path); err == nil {
return true
}
return false
}

View File

@ -0,0 +1,146 @@
package testcontainersdocker
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
)
var (
ErrRootlessDockerNotFound = errors.New("rootless Docker not found")
ErrRootlessDockerNotFoundHomeDesktopDir = errors.New("checked path: ~/.docker/desktop/docker.sock")
ErrRootlessDockerNotFoundHomeRunDir = errors.New("checked path: ~/.docker/run/docker.sock")
ErrRootlessDockerNotFoundRunDir = errors.New("checked path: /run/user/${uid}/docker.sock")
ErrRootlessDockerNotFoundXDGRuntimeDir = errors.New("checked path: $XDG_RUNTIME_DIR")
ErrRootlessDockerNotSupportedWindows = errors.New("rootless Docker is not supported on Windows")
ErrXDGRuntimeDirNotSet = errors.New("XDG_RUNTIME_DIR is not set")
)
// baseRunDir is the base directory for the "/run/user/${uid}" directory.
// It is a variable so it can be modified for testing.
var baseRunDir = "/run"
// IsWindows returns if the current OS is Windows. For that it checks the GOOS environment variable or the runtime.GOOS constant.
func IsWindows() bool {
return os.Getenv("GOOS") == "windows" || runtime.GOOS == "windows"
}
// rootlessDockerSocketPath returns if the path to the rootless Docker socket exists.
// The rootless socket path is determined by the following order:
//
// 1. XDG_RUNTIME_DIR environment variable.
// 2. ~/.docker/run/docker.sock file.
// 3. ~/.docker/desktop/docker.sock file.
// 4. /run/user/${uid}/docker.sock file.
// 5. Else, return ErrRootlessDockerNotFound, wrapping secific errors for each of the above paths.
//
// It should include the Docker socket schema (unix://) in the returned path.
func rootlessDockerSocketPath(_ context.Context) (string, error) {
// adding a manner to test it on non-windows machines, setting the GOOS env var to windows
// This is needed because runtime.GOOS is a constant that returns the OS of the machine running the test
if IsWindows() {
return "", ErrRootlessDockerNotSupportedWindows
}
socketPathFns := []func() (string, error){
rootlessSocketPathFromEnv,
rootlessSocketPathFromHomeRunDir,
rootlessSocketPathFromHomeDesktopDir,
rootlessSocketPathFromRunDir,
}
outerErr := ErrRootlessDockerNotFound
for _, socketPathFn := range socketPathFns {
s, err := socketPathFn()
if err != nil {
outerErr = fmt.Errorf("%w: %v", outerErr, err)
continue
}
return DockerSocketSchema + s, nil
}
return "", outerErr
}
func fileExists(f string) bool {
_, err := os.Stat(f)
return err == nil
}
func parseURL(s string) (string, error) {
var hostURL *url.URL
if u, err := url.Parse(s); err != nil {
return "", err
} else {
hostURL = u
}
switch hostURL.Scheme {
case "unix", "npipe":
return hostURL.Path, nil
case "tcp":
// return the original URL, as it is a valid TCP URL
return s, nil
default:
return "", ErrNoUnixSchema
}
}
// rootlessSocketPathFromEnv returns the path to the rootless Docker socket from the XDG_RUNTIME_DIR environment variable.
// It should include the Docker socket schema (unix://) in the returned path.
func rootlessSocketPathFromEnv() (string, error) {
xdgRuntimeDir, exists := os.LookupEnv("XDG_RUNTIME_DIR")
if exists {
f := filepath.Join(xdgRuntimeDir, "docker.sock")
if fileExists(f) {
return f, nil
}
return "", ErrRootlessDockerNotFoundXDGRuntimeDir
}
return "", ErrXDGRuntimeDirNotSet
}
// rootlessSocketPathFromHomeRunDir returns the path to the rootless Docker socket from the ~/.docker/run/docker.sock file.
func rootlessSocketPathFromHomeRunDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
f := filepath.Join(home, ".docker", "run", "docker.sock")
if fileExists(f) {
return f, nil
}
return "", ErrRootlessDockerNotFoundHomeRunDir
}
// rootlessSocketPathFromHomeDesktopDir returns the path to the rootless Docker socket from the ~/.docker/desktop/docker.sock file.
func rootlessSocketPathFromHomeDesktopDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
f := filepath.Join(home, ".docker", "desktop", "docker.sock")
if fileExists(f) {
return f, nil
}
return "", ErrRootlessDockerNotFoundHomeDesktopDir
}
// rootlessSocketPathFromRunDir returns the path to the rootless Docker socket from the /run/user/<uid>/docker.sock file.
func rootlessSocketPathFromRunDir() (string, error) {
uid := os.Getuid()
f := filepath.Join(baseRunDir, "user", fmt.Sprintf("%d", uid), "docker.sock")
if fileExists(f) {
return f, nil
}
return "", ErrRootlessDockerNotFoundRunDir
}

View File

@ -0,0 +1,49 @@
package testcontainersdocker
import (
"net/url"
"strings"
"github.com/docker/docker/client"
)
// DockerSocketSchema is the unix schema.
var DockerSocketSchema = "unix://"
// DockerSocketPath is the path to the docker socket under unix systems.
var DockerSocketPath = "/var/run/docker.sock"
// DockerSocketPathWithSchema is the path to the docker socket under unix systems with the unix schema.
var DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath
// TCPSchema is the tcp schema.
var TCPSchema = "tcp://"
// WindowsDockerSocketPath is the path to the docker socket under windows systems.
var WindowsDockerSocketPath = "//var/run/docker.sock"
func init() {
const DefaultDockerHost = client.DefaultDockerHost
u, err := url.Parse(DefaultDockerHost)
if err != nil {
// unsupported default host specified by the docker client package,
// so revert to the default unix docker socket path
return
}
switch u.Scheme {
case "unix", "npipe":
DockerSocketSchema = u.Scheme + "://"
DockerSocketPath = u.Path
if !strings.HasPrefix(DockerSocketPath, "/") {
// seeing as the code in this module depends on DockerSocketPath having
// a slash (`/`) prefix, we add it here if it is missing.
// for the known environments, we do not foresee how the socket-path
// should miss the slash, however this extra if-condition is worth to
// save future pain from innocent users.
DockerSocketPath = "/" + DockerSocketPath
}
DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath
}
}

View File

@ -0,0 +1,126 @@
package testcontainersdocker
import (
"bufio"
"net/url"
"os"
"regexp"
"strings"
"unicode/utf8"
)
const (
IndexDockerIO = "https://index.docker.io/v1/"
maxURLRuneCount = 2083
minURLRuneCount = 3
URLSchema = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername = `(\S+(:\S*)?@)`
URLIP = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
IP = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSubdomain = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
URLPath = `((\/|\?|#)[^\s]*)`
URLPort = `(:(\d{1,5}))`
URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
)
var rxURL = regexp.MustCompile(URL)
func ExtractImagesFromDockerfile(dockerfile string, buildArgs map[string]*string) ([]string, error) {
var images []string
file, err := os.Open(dockerfile)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
// extract images from dockerfile
for _, line := range lines {
line = strings.TrimSpace(line)
if !strings.HasPrefix(strings.ToUpper(line), "FROM") {
continue
}
// remove FROM
line = strings.TrimPrefix(line, "FROM")
parts := strings.Split(strings.TrimSpace(line), " ")
if len(parts) == 0 {
continue
}
// interpolate build args
for k, v := range buildArgs {
if v != nil {
parts[0] = strings.Replace(parts[0], "${"+k+"}", *v, -1)
}
}
images = append(images, parts[0])
}
return images, nil
}
// ExtractRegistry extracts the registry from the image name, using a regular expression to extract the registry from the image name.
// regular expression to extract the registry from the image name
// the regular expression is based on the grammar defined in
// - image:tag
// - image
// - repository/image:tag
// - repository/image
// - registry/image:tag
// - registry/image
// - registry/repository/image:tag
// - registry/repository/image
// - registry:port/repository/image:tag
// - registry:port/repository/image
// - registry:port/image:tag
// - registry:port/image
// Once extracted the registry, it is validated to check if it is a valid URL or an IP address.
func ExtractRegistry(image string, fallback string) string {
exp := regexp.MustCompile(`^(?:(?P<registry>(https?://)?[^/]+)(?::(?P<port>\d+))?/)?(?:(?P<repository>[^/]+)/)?(?P<image>[^:]+)(?::(?P<tag>.+))?$`).FindStringSubmatch(image)
if len(exp) == 0 {
return ""
}
registry := exp[1]
if IsURL(registry) {
return registry
}
return fallback
}
// IsURL checks if the string is an URL.
// Extracted from https://github.com/asaskevich/govalidator/blob/f21760c49a8d/validator.go#L104
func IsURL(str string) bool {
if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") {
return false
}
strTemp := str
if strings.Contains(str, ":") && !strings.Contains(str, "://") {
// support no indicated urlscheme but with colon for port number
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
strTemp = "http://" + str
}
u, err := url.Parse(strTemp)
if err != nil {
return false
}
if strings.HasPrefix(u.Host, ".") {
return false
}
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
return false
}
return rxURL.MatchString(str)
}

View File

@ -0,0 +1,19 @@
package testcontainersdocker
import "github.com/testcontainers/testcontainers-go/internal"
const (
LabelBase = "org.testcontainers"
LabelLang = LabelBase + ".lang"
LabelReaper = LabelBase + ".reaper"
LabelSessionID = LabelBase + ".sessionId"
LabelVersion = LabelBase + ".version"
)
func DefaultLabels() map[string]string {
return map[string]string{
LabelBase: "true",
LabelLang: "go",
LabelVersion: internal.Version,
}
}