fleet/test/upgrade/fleet_test.go
dependabot[bot] 23a2964eef
Bump github.com/docker/docker from 20.10.23+incompatible to 23.0.4+incompatible (#11259)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from
20.10.23+incompatible to 23.0.4+incompatible.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/docker/releases">github.com/docker/docker's
releases</a>.</em></p>
<blockquote>
<h2>v23.0.4</h2>
<h2>23.0.4</h2>
<p>For a full list of pull requests and changes in this release, refer
to the relevant GitHub milestones:</p>
<ul>
<li><a
href="https://github.com/docker/cli/milestone/77?closed=1">docker/cli,
23.0.4 milestone</a></li>
<li><a
href="https://github.com/moby/moby/milestone/117?closed=1">moby/moby,
23.0.4 milestone</a></li>
</ul>
<h3>Bug fixes and enhancements</h3>
<ul>
<li>Fix a performance regression in Docker CLI 23.0.0 <a
href="https://redirect.github.com/docker/cli/pull/4141">docker/cli#4141</a>.</li>
<li>Fix progress indicator on <code>docker cp</code> not functioning as
intended <a
href="https://redirect.github.com/docker/cli/pull/4157">docker/cli#4157</a>.</li>
<li>Fix shell completion for <code>docker compose --file</code> <a
href="https://redirect.github.com/docker/cli/pull/4177">docker/cli#4177</a>.</li>
<li>Fix an error caused by incorrect handling of
&quot;default-address-pools&quot; in <code>daemon.json</code> <a
href="https://redirect.github.com/moby/moby/pull/45246">moby/moby#45246</a>.</li>
</ul>
<h3>Packaging Updates</h3>
<ul>
<li>Fix missing packages for CentOS 9 Stream.</li>
<li>Upgrade Go to <code>1.19.8</code>. <a
href="https://redirect.github.com/docker/docker-ce-packaging/pull/878">docker/docker-ce-packaging#878</a>,
<a
href="https://redirect.github.com/docker/cli/pull/4164">docker/cli#4164</a>,
<a
href="https://redirect.github.com/moby/moby/pull/45277">moby/moby#45277</a>,
which contains fixes for <a
href="https://github.com/advisories/GHSA-fp86-2355-v99r">CVE-2023-24537</a>,
<a
href="https://github.com/advisories/GHSA-v4m2-x4rp-hv22">CVE-2023-24538</a>,
<a
href="https://github.com/advisories/GHSA-8v5j-pwr7-w5f8">CVE-2023-24534</a>,
and <a
href="https://github.com/advisories/GHSA-9f7g-gqwh-jpf5">CVE-2023-24536</a></li>
</ul>
<h2>v23.0.3</h2>
<h2>23.0.3</h2>
<blockquote>
<p><strong>Note</strong></p>
<p>Due to an issue with CentOS 9 Stream's package repositories, packages
for
CentOS 9 are currently unavailable. Packages for CentOS 9 may be added
later,
or as part of the next (23.0.4) patch release.</p>
</blockquote>
<h3>Bug fixes and enhancements</h3>
<ul>
<li>Fixed a number of issues that can cause Swarm encrypted overlay
networks
to fail to uphold their guarantees, addressing <a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28841">CVE-2023-28841</a>,
<a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28840">CVE-2023-28840</a>,
and
<a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28842">CVE-2023-28842</a>.
<ul>
<li>A lack of kernel support for encrypted overlay networks now reports
as an error.</li>
<li>Encrypted overlay networks are eagerly set up, rather than waiting
for
multiple nodes to attach.</li>
<li>Encrypted overlay networks are now usable on Red Hat Enterprise
Linux 9
through the use of the <code>xt_bpf</code> kernel module.</li>
<li>Users of Swarm overlay networks should review <a
href="https://github.com/moby/moby/security/advisories/GHSA-vwm3-crmr-xfxw">GHSA-vwm3-crmr-xfxw</a>
to ensure that unintentional exposure has not occurred.</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cbce331930"><code>cbce331</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/docker/issues/45330">#45330</a>
from kevingentile/buildkit-3770</li>
<li><a
href="5f684cb072"><code>5f684cb</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/docker/issues/45331">#45331</a>
from thaJeztah/23.0_backport_rootless_script_bugs</li>
<li><a
href="3731ce10d4"><code>3731ce1</code></a>
Fix argument quoting bugs in dockerd-rootless.sh</li>
<li><a
href="23774ada04"><code>23774ad</code></a>
vendor: github.com/moby/buildkit
v0.10.7-0.20230412161310-d52b2d584242</li>
<li><a
href="90e8a0bbf5"><code>90e8a0b</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/docker/issues/45323">#45323</a>
from thaJeztah/23.0_backport_vendor_sctp</li>
<li><a
href="9277e64444"><code>9277e64</code></a>
vendor: github.com/ishidawataru/sctp
v0.0.0-20230406120618-7ff4192f6ff2</li>
<li><a
href="cdb6200887"><code>cdb6200</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/docker/issues/45293">#45293</a>
from AkihiroSuda/backport-45283-23</li>
<li><a
href="09fbbd5677"><code>09fbbd5</code></a>
docker-rootless-setuptools.sh: improve readability of messages</li>
<li><a
href="4ca4705bf7"><code>4ca4705</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/docker/issues/45277">#45277</a>
from thaJeztah/23.0_bump_go1.19.8</li>
<li><a
href="d3e52936c3"><code>d3e5293</code></a>
[23.0] update go to go1.19.8</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/docker/compare/v20.10.23...v23.0.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/docker/docker&package-manager=go_modules&previous-version=20.10.23+incompatible&new-version=23.0.4+incompatible)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zach Wasserman <zach@fleetdm.com>
2023-04-21 14:53:55 -07:00

368 lines
8.7 KiB
Go

package upgrade
import (
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/cenkalti/backoff"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/fleetdm/fleet/v4/server/service"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
// Slots correspond to docker-compose fleet services, either fleet-a or fleet-b
const (
slotA = "a"
slotB = "b"
)
func init() {
rand.Seed(time.Now().Unix())
}
// Fleet represents the fleet server and its dependencies used for testing.
type Fleet struct {
// ProjectName is the docker compose project name
ProjectName string
// FilePath is the path to the docker-compose.yml
FilePath string
// Version is the active fleet version.
Version string
// Token is the fleet token used for authentication
Token string
dockerClient client.ContainerAPIClient
}
// NewFleet starts fleet and it's dependencies with the specified version.
func NewFleet(t *testing.T, version string) *Fleet {
// don't use test name because it will be normalized
//nolint:gosec // does not need to be secure for tests
projectName := "fleet-test-" + strconv.FormatUint(rand.Uint64(), 16)
dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
t.Fatalf("create docker client: %v", err)
}
f := &Fleet{
ProjectName: projectName,
FilePath: "docker-compose.yaml",
Version: version,
dockerClient: dockerClient,
}
t.Cleanup(f.cleanup)
if err := f.Start(); err != nil {
t.Fatalf("start fleet: %v", err)
}
return f
}
func (f *Fleet) Start() error {
env := map[string]string{
"FLEET_VERSION_A": f.Version,
}
_, err := f.execCompose(env, "pull", "--parallel")
if err != nil {
return err
}
// start mysql and wait until ready
_, err = f.execCompose(env, "up", "-d", "mysql")
if err != nil {
return err
}
if err := f.waitMYSQL(); err != nil {
return err
}
// run the migrations using the fleet-a service
_, err = f.execCompose(env, "run", "-T", "fleet-a", "fleet", "prepare", "db", "--no-prompt")
if err != nil {
return err
}
// start fleet-a
_, err = f.execCompose(env, "up", "-d", "fleet-a", "fleet")
if err != nil {
return err
}
// copy the nginx conf and reload nginx without creating a new container
srcPath := filepath.Join("nginx", "fleet-a.conf")
_, err = f.execCompose(env, "cp", srcPath, "fleet:/etc/nginx/conf.d/default.conf")
if err != nil {
return err
}
_, err = f.execCompose(env, "exec", "-T", "fleet", "nginx", "-s", "reload")
if err != nil {
return err
}
if err := f.waitFleet(slotA); err != nil {
return err
}
if err := f.setupFleet(); err != nil {
return err
}
return nil
}
// Client returns a fleet client that uses the fleet API.
func (f *Fleet) Client() (*service.Client, error) {
port, err := f.getPublicPort("fleet", 443)
if err != nil {
return nil, fmt.Errorf("get fleet port: %v", err)
}
address := fmt.Sprintf("https://localhost:%d", port)
client, err := service.NewClient(address, true, "", "")
if err != nil {
return nil, err
}
client.SetToken(f.Token)
return client, nil
}
func (f *Fleet) setupFleet() error {
client, err := f.Client()
if err != nil {
return err
}
token, err := client.Setup("admin@example.com", "Admin", "password123#", "Fleet Test")
if err != nil {
return err
}
f.Token = token
return nil
}
func (f *Fleet) waitMYSQL() error {
// get the random mysql host port assigned by docker
port, err := f.getPublicPort("mysql", 3306)
if err != nil {
return err
}
dsn := fmt.Sprintf("fleet:fleet@tcp(localhost:%d)/fleet", port)
retryInterval := 5 * time.Second
timeout := 1 * time.Minute
ticker := time.NewTicker(retryInterval)
defer ticker.Stop()
timeoutChan := time.After(timeout)
for {
select {
case <-timeoutChan:
return fmt.Errorf("db connection failed after %s", timeout)
case <-ticker.C:
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to connect to db: %v\n", err)
} else {
db.Close()
return nil
}
}
}
}
func (f *Fleet) getPublicPort(serviceName string, privatePort uint16) (uint16, error) {
containerName := fmt.Sprintf("%s-%s-1", f.ProjectName, serviceName)
// get the random fleet host port assigned by docker
argsName := filters.Arg("name", containerName)
containers, err := f.dockerClient.ContainerList(context.TODO(), types.ContainerListOptions{Filters: filters.NewArgs(argsName), All: true})
if err != nil {
return 0, err
}
if len(containers) == 0 {
return 0, errors.New("no containers found")
}
for _, port := range containers[0].Ports {
if port.PrivatePort == privatePort {
return port.PublicPort, nil
}
}
return 0, errors.New("private port not found")
}
func (f *Fleet) waitFleet(slot string) error {
containerName := fmt.Sprintf("%s-fleet-%s-1", f.ProjectName, slot)
// get the random fleet host port assigned by docker
argsName := filters.Arg("name", containerName)
containers, err := f.dockerClient.ContainerList(context.TODO(), types.ContainerListOptions{Filters: filters.NewArgs(argsName), All: true})
if err != nil {
return err
}
if len(containers) == 0 {
return errors.New("no fleet container found")
}
port := containers[0].Ports[0].PublicPort
healthURL := fmt.Sprintf("http://localhost:%d/healthz", port)
retryStrategy := backoff.NewExponentialBackOff()
retryStrategy.MaxInterval = 1 * time.Second
if err := backoff.Retry(
func() error {
//nolint:gosec // G107: Ok to trust docker here
resp, err := http.Get(healthURL)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non-200 status code: %d", resp.StatusCode)
}
return nil
},
retryStrategy,
); err != nil {
return fmt.Errorf("check health: %v", err)
}
return nil
}
func (f *Fleet) cleanup() {
output, err := f.execCompose(nil, "down", "-v", "--remove-orphans")
if err != nil {
fmt.Fprintf(os.Stderr, "stop fleet: %v %s", err, output)
}
}
func (f *Fleet) execCompose(env map[string]string, args ...string) (string, error) {
// docker compose variables via environment eg FLEET_VERSION_A
e := os.Environ()
for k, v := range env {
e = append(e, fmt.Sprintf("%s=%s", k, v))
}
// prepend default args
args = append([]string{
"compose",
"--project-name", f.ProjectName,
"--file", f.FilePath,
}, args...)
var stdout, stderr bytes.Buffer
cmd := exec.Command("docker", args...)
cmd.Env = e
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("docker: %v %s", err, stderr.String())
}
return stdout.String(), nil
}
// StartHost starts an osquery host using docker-compose and enrolls it with fleet.
// Returns the container ID which is also the hostname and osquery host ID.
func (f *Fleet) StartHost() (string, error) {
// get the enroll secret
client, err := f.Client()
if err != nil {
return "", err
}
enrollSecretSpec, err := client.GetEnrollSecretSpec()
if err != nil {
return "", err
}
if len(enrollSecretSpec.Secrets) == 0 {
return "", errors.New("no enroll secret found")
}
enrollSecret := enrollSecretSpec.Secrets[0].Secret
env := map[string]string{
"ENROLL_SECRET": enrollSecret,
}
output, err := f.execCompose(env, "run", "-d", "-T", "osquery")
if err != nil {
return "", err
}
// get the container id
containerID := output[:len(output)-1] // strip the newline from output
// inspect the container to get the hostname
containerJSON, err := f.dockerClient.ContainerInspect(context.Background(), containerID)
if err != nil {
return "", fmt.Errorf("inspect container: %v", err)
}
hostname := containerJSON.Config.Hostname
return hostname, nil
}
// Upgrade upgrades fleet to a specified version.
func (f *Fleet) Upgrade(toVersion string) error {
env := map[string]string{
"FLEET_VERSION_B": toVersion,
}
// run migrations using fleet-b
serviceName := "fleet-b"
_, err := f.execCompose(env, "run", "-T", serviceName, "fleet", "prepare", "db", "--no-prompt")
if err != nil {
return fmt.Errorf("run migrations: %v", err)
}
// start the service
_, err = f.execCompose(env, "up", "-d", serviceName)
if err != nil {
return fmt.Errorf("start fleet: %v", err)
}
// wait until healthy
if err := f.waitFleet(slotB); err != nil {
return fmt.Errorf("wait for fleet to be healthy: %v", err)
}
// copy the nginx conf and reload nginx without creating a new container
srcPath := filepath.Join("nginx", "fleet-b.conf")
_, err = f.execCompose(env, "cp", srcPath, "fleet:/etc/nginx/conf.d/default.conf")
if err != nil {
return err
}
_, err = f.execCompose(env, "exec", "-T", "fleet", "nginx", "-s", "reload")
if err != nil {
return err
}
f.Version = toVersion
return nil
}