mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
add upgrade tests (#6596)
* add upgrade tests * fix lint issues go.mod * remove req.cnf * revert unrelated changes * make version configurable in test * fix golangci-lint ruleguard issue Related to https://github.com/go-critic/go-critic/issues/1152 Need to have github.com/quasilyte/go-ruleguard/dsl * fix lint issues * fix * clean up docker-compose.yml * fix http request * add readme * fix lint issues * address feedback * fix * add platform * address feedback * run go fmt
This commit is contained in:
parent
51e505c3f6
commit
d4be5ad2a1
16 changed files with 684 additions and 14 deletions
|
|
@ -33,7 +33,7 @@ linters-settings:
|
|||
settings:
|
||||
ruleguard:
|
||||
rules: "./tools/ci/rules.go"
|
||||
failOn: all # NEW
|
||||
failOn: all
|
||||
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
|
|
|
|||
|
|
@ -977,14 +977,14 @@ func getSoftwareCommand() *cli.Command {
|
|||
return errors.New("Can't specify both yaml and json flags.")
|
||||
}
|
||||
|
||||
var teamID *uint
|
||||
query := url.Values{}
|
||||
|
||||
teamIDFlag := c.Uint(teamFlagName)
|
||||
if teamIDFlag != 0 {
|
||||
teamID = &teamIDFlag
|
||||
teamID := c.Uint(teamFlagName)
|
||||
if teamID != 0 {
|
||||
query.Set("team_id", strconv.FormatUint(uint64(teamID), 10))
|
||||
}
|
||||
|
||||
software, err := client.ListSoftware(teamID)
|
||||
software, err := client.ListSoftware(query.Encode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list software: %w", err)
|
||||
}
|
||||
|
|
|
|||
10
go.mod
10
go.mod
|
|
@ -14,9 +14,11 @@ require (
|
|||
github.com/aws/aws-sdk-go v1.43.16
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/briandowns/spinner v1.13.0
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgraph-io/badger/v2 v2.2007.2
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/doug-martin/goqu/v9 v9.18.0
|
||||
github.com/e-dard/netbug v0.0.0-20151029172837-e64d308a0b20
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
|
|
@ -150,7 +152,6 @@ require (
|
|||
github.com/caarlos0/env/v6 v6.7.0 // indirect
|
||||
github.com/caarlos0/go-shellwords v1.0.12 // indirect
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
|
|
@ -160,6 +161,9 @@ require (
|
|||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/elastic/go-licenser v0.3.1 // indirect
|
||||
github.com/elastic/go-sysinfo v1.1.1 // indirect
|
||||
|
|
@ -185,6 +189,7 @@ require (
|
|||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
|
|
@ -230,6 +235,8 @@ require (
|
|||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||
|
|
@ -246,6 +253,7 @@ require (
|
|||
github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/slack-go/slack v0.9.4 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
|
|
|||
11
go.sum
11
go.sum
|
|
@ -92,6 +92,7 @@ github.com/Azure/go-amqp v0.13.11/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNN
|
|||
github.com/Azure/go-amqp v0.13.12/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
|
|
@ -567,18 +568,22 @@ github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop
|
|||
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
|
||||
github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
|
||||
github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
|
|
@ -1293,12 +1298,14 @@ github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn
|
|||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||
github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0=
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
|
|
@ -1363,11 +1370,13 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1
|
|||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
|
|
@ -2488,8 +2497,10 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
)
|
||||
|
|
@ -13,6 +15,13 @@ func (c *Client) GetHosts(query string) ([]HostResponse, error) {
|
|||
return responseBody.Hosts, err
|
||||
}
|
||||
|
||||
func (c *Client) GetHost(id uint) (*HostDetailResponse, error) {
|
||||
verb, path := "GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", id)
|
||||
var responseBody getHostResponse
|
||||
err := c.authenticatedRequest(nil, verb, path, &responseBody)
|
||||
return responseBody.Host, err
|
||||
}
|
||||
|
||||
// HostByIdentifier retrieves a host by the uuid, osquery_host_id, hostname, or
|
||||
// node_key.
|
||||
func (c *Client) HostByIdentifier(identifier string) (*HostDetailResponse, error) {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
// ListSoftware retrieves the software running across hosts.
|
||||
func (c *Client) ListSoftware(teamID *uint) ([]fleet.Software, error) {
|
||||
func (c *Client) ListSoftware(query string) ([]fleet.Software, error) {
|
||||
verb, path := "GET", "/api/latest/fleet/software"
|
||||
query := ""
|
||||
if teamID != nil {
|
||||
query = fmt.Sprintf("team_id=%d", *teamID)
|
||||
}
|
||||
var responseBody listSoftwareResponse
|
||||
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query)
|
||||
if err != nil {
|
||||
|
|
|
|||
16
test/upgrade/README.md
Normal file
16
test/upgrade/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Upgrade Tests
|
||||
|
||||
The tests located in `test/upgrade` are intended to test fleet upgrades with online migrations as proposed in [#6376](https://github.com/fleetdm/fleet/pull/6376).
|
||||
To run the tests, you need to specify the from and to versions. For example
|
||||
|
||||
```
|
||||
$ FLEET_VERSION_A=v4.16.0 FLEET_VERSION_B=v4.17.0 go test ./test/upgrade
|
||||
```
|
||||
|
||||
Ensure that Docker is installed with Compose V2.
|
||||
To check if you have the correct version, run the following command
|
||||
|
||||
```
|
||||
$ docker compose version
|
||||
Docker Compose version v2.6.0
|
||||
```
|
||||
67
test/upgrade/docker-compose.yaml
Normal file
67
test/upgrade/docker-compose.yaml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
services:
|
||||
mysql:
|
||||
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: toor
|
||||
MYSQL_DATABASE: fleet
|
||||
MYSQL_USER: fleet
|
||||
MYSQL_PASSWORD: fleet
|
||||
ports:
|
||||
- "3306"
|
||||
|
||||
redis:
|
||||
image: redis:6
|
||||
|
||||
# reverse proxy and tls termination for fleet-a and fleet-b
|
||||
fleet:
|
||||
image: nginx
|
||||
volumes:
|
||||
# don't mount the config. These will be copied manually so that
|
||||
# we can reload nginx without recreating containers and getting a new public port each time.
|
||||
# - ./nginx/fleet-a.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./fleet.crt:/etc/nginx/fleet.crt
|
||||
- ./fleet.key:/etc/nginx/fleet.key
|
||||
ports:
|
||||
- "443"
|
||||
|
||||
fleet-a: &default-fleet
|
||||
image: fleetdm/fleet:${FLEET_VERSION_A:-latest}
|
||||
environment:
|
||||
FLEET_MYSQL_ADDRESS: mysql:3306
|
||||
FLEET_MYSQL_DATABASE: fleet
|
||||
FLEET_MYSQL_USERNAME: fleet
|
||||
FLEET_MYSQL_PASSWORD: fleet
|
||||
FLEET_REDIS_ADDRESS: redis:6379
|
||||
FLEET_SERVER_ADDRESS: 0.0.0.0:8080
|
||||
FLEET_SERVER_TLS: 'false'
|
||||
FLEET_LOGGING_JSON: 'true'
|
||||
FLEET_BETA_SOFTWARE_INVENTORY: 1
|
||||
FLEET_LICENSE_KEY: ${FLEET_LICENSE_KEY}
|
||||
FLEET_OSQUERY_LABEL_UPDATE_INTERVAL: 1m
|
||||
FLEET_VULNERABILITIES_CURRENT_INSTANCE_CHECKS: "yes"
|
||||
FLEET_VULNERABILITIES_DATABASES_PATH: /fleet/vulndb
|
||||
FLEET_VULNERABILITIES_PERIODICITY: 5m
|
||||
FLEET_LOGGING_DEBUG: 'true'
|
||||
# This can be configured for testing purposes but otherwise uses the
|
||||
# typical default of provided.
|
||||
FLEET_OSQUERY_HOST_IDENTIFIER: ${FLEET_OSQUERY_HOST_IDENTIFIER:-provided}
|
||||
ports:
|
||||
- "8080"
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
|
||||
# Uses a different version than fleet-a
|
||||
fleet-b:
|
||||
<<: *default-fleet
|
||||
image: fleetdm/fleet:${FLEET_VERSION_B:-latest}
|
||||
|
||||
osquery:
|
||||
image: "osquery/osquery:4.6.0-ubuntu20.04"
|
||||
volumes:
|
||||
- ./fleet.crt:/etc/osquery/fleet.crt
|
||||
- ./osquery.flags:/etc/osquery/osquery.flags
|
||||
environment:
|
||||
ENROLL_SECRET: "${ENROLL_SECRET}"
|
||||
command: osqueryd --flagfile=/etc/osquery/osquery.flags
|
||||
28
test/upgrade/fleet.crt
Normal file
28
test/upgrade/fleet.crt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEyDCCArCgAwIBAgIJALXDyAqDa1hCMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
|
||||
BAMMBWZsZWV0MB4XDTIyMDcwNDE4MTUwN1oXDTMyMDcwMTE4MTUwN1owEDEOMAwG
|
||||
A1UEAwwFZmxlZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCt7sQJ
|
||||
XtNFuP9URxv2mSYYb9ePvPHNQENGkEDvpJUT3BRXmPHrdaCAbi2kDM+/f+M0smMn
|
||||
TQdVn9/PWRrIFQjGpW+AKtnvhR6O6MgfflJonIewRYertfyjwpPWzGGre12jB9Og
|
||||
3T4fzlGy+GnYE9dKvVUaS+Tm+DH0yCle0YRfjJFEaMrmqTAIKxkz9rUDqp6iF3mr
|
||||
vkonhwI1D9VsMDcECvgw8MG5A3N0Yte1MV4OrFD9f88xrmx8b8F5HaozQVLBT0eA
|
||||
0hQ5RUFZC9mGD5hs0B6qvvfOKHO8RJjdydHMTD/w7RpyU+PzTE2qT3ofrc1Ecaow
|
||||
CAAPwSwlPc00qPw9Wfdyw/GzkMCkTgf6XmkdSAahdc69YfjA1OpR/nlUziQTM60Y
|
||||
MjKn96lNuOP87AbgxEPyG8rTkuNUXSEjQeGNmntE9yLmYKLVQcbetzviDgJXM4Xw
|
||||
YlDRMwCsqwJYJmKrY+XYC3nqdVoyJSgWIMPh5SCUTzq5gUoT3jl1I7J4rCZmM3jy
|
||||
BMITbDElv3p9HTKv/RIUVMr9N3i/NyRf00SgO6guIqz1vNjB2YhmtVpQiDw+wLxa
|
||||
wfW5p9vrB7XQNM5/CVPDh2ScVq2ZZam6dQEdltSwooLqHbL2dv0iIGTo4mX31PQz
|
||||
LUmC9RbB3k6WkplpvH+oq31mC/Hmst1BvQc4XwIDAQABoyUwIzAhBgNVHREEGjAY
|
||||
ggdmbGVldC1hggdmbGVldC1ihwQKAAABMA0GCSqGSIb3DQEBCwUAA4ICAQBijPir
|
||||
B3ePlqiWvyOJo4OyxURLG8mBzH/xNabpUY3BFBwgauEKGm/LzAgeQzbt5B5g9V9q
|
||||
waVt7meRKEC+HJi+vEjVLXx7rk40z9m67GEfhC/DvSiGeWty3pQFzNEHlyHZmi9f
|
||||
Z1zR6AVgPSdTCooPPWgi5cBWjq/kG4gxJT7Wvrw9IuWzlgwlkkux1gwVuWZ6GLil
|
||||
pq5T5PGdsBg0W3S3ssxayQ3Nl2qYeKqRiN/0ynR+RIf4VqZGySXwIECHROC4fpsA
|
||||
UoJxmABYdBjjvfIgLJyM+bbLVXRLAM6I7wabgaICDPsFc4Ax+TjOdfao/SkTRDUk
|
||||
zsSuc5SpfVUEQxcpVxwpyRpRlO4YGrCrgozzx7Sal/PGp+Hs3L2uC/JguUaVRUGS
|
||||
yZhaiFQjLNXeND9ARwtEQkpMWrjAnICVwX80mlcwfx5JK2zFQe5yJusMynu+Ecv4
|
||||
PuJRl6LMd+m7hof1oWWMHYKbzj7VaefM0C0GvFCoX2YeBHpr7FmqaEOVtehDgizK
|
||||
Nrz2c92VUFn77vmIphNeR8cnJou5AB1L9pSelNYO0wcABhLktsyOaQyftjN3aCA/
|
||||
cUn73RXyud8jg63W4dcsT2O5eaANc0rlZefjy57l+PDg8joIdFDIN4Ul2B6f7tfY
|
||||
yuYXq7cV8AZFRKVEOcpmLUOexq1AtEvslZqC3Q==
|
||||
-----END CERTIFICATE-----
|
||||
52
test/upgrade/fleet.key
Normal file
52
test/upgrade/fleet.key
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
RTtLhMIZYvzUXRhi7Hwm2KCjG3kVxg==
|
||||
-----END PRIVATE KEY-----
|
||||
369
test/upgrade/fleet_test.go
Normal file
369
test/upgrade/fleet_test.go
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
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)
|
||||
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
|
||||
}
|
||||
10
test/upgrade/generate_cert.sh
Executable file
10
test/upgrade/generate_cert.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
|
||||
-keyout fleet.key -out fleet.crt -extensions san -config \
|
||||
<(echo "[req]";
|
||||
echo distinguished_name=req;
|
||||
echo "[san]";
|
||||
echo subjectAltName=DNS:fleet-a,DNS:fleet-b
|
||||
) \
|
||||
-subj "/CN=fleet"
|
||||
11
test/upgrade/nginx/fleet-a.conf
Normal file
11
test/upgrade/nginx/fleet-a.conf
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
server_name fleet;
|
||||
ssl_certificate fleet.crt;
|
||||
ssl_certificate_key fleet.key;
|
||||
|
||||
location / {
|
||||
proxy_pass http://fleet-a:8080;
|
||||
}
|
||||
}
|
||||
|
||||
11
test/upgrade/nginx/fleet-b.conf
Normal file
11
test/upgrade/nginx/fleet-b.conf
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
server_name fleet;
|
||||
ssl_certificate fleet.crt;
|
||||
ssl_certificate_key fleet.key;
|
||||
|
||||
location / {
|
||||
proxy_pass http://fleet-b:8080;
|
||||
}
|
||||
}
|
||||
|
||||
27
test/upgrade/osquery.flags
Normal file
27
test/upgrade/osquery.flags
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
--verbose=true
|
||||
--debug
|
||||
|
||||
--tls_hostname=fleet
|
||||
--tls_server_certs=/etc/osquery/fleet.crt
|
||||
|
||||
--enroll_secret_env=ENROLL_SECRET
|
||||
--enroll_tls_endpoint=/api/v1/osquery/enroll
|
||||
|
||||
--config_plugin=tls
|
||||
--config_tls_endpoint=/api/v1/osquery/config
|
||||
--config_refresh=10
|
||||
|
||||
--disable_distributed=false
|
||||
--distributed_plugin=tls
|
||||
--distributed_interval=10
|
||||
--distributed_tls_max_attempts=3
|
||||
--distributed_tls_read_endpoint=/api/v1/osquery/distributed/read
|
||||
--distributed_tls_write_endpoint=/api/v1/osquery/distributed/write
|
||||
|
||||
--logger_plugin=tls
|
||||
--logger_tls_endpoint=/api/v1/osquery/log
|
||||
--logger_tls_period=10
|
||||
|
||||
--disable_carver=false
|
||||
--carver_start_endpoint=/api/v1/osquery/carve/begin
|
||||
--carver_continue_endpoint=/api/v1/osquery/carve/block
|
||||
57
test/upgrade/upgrade_test.go
Normal file
57
test/upgrade/upgrade_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package upgrade
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func enrollHost(t *testing.T, f *Fleet) string {
|
||||
client, err := f.Client()
|
||||
require.NoError(t, err)
|
||||
|
||||
// enroll a host
|
||||
hostname, err := f.StartHost()
|
||||
require.NoError(t, err)
|
||||
|
||||
// wait until host is enrolled and software is listed
|
||||
require.Eventually(t, func() bool {
|
||||
host, err := client.HostByIdentifier(hostname)
|
||||
if err != nil {
|
||||
t.Logf("get host: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(host.Software) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, 5*time.Minute, 5*time.Second)
|
||||
|
||||
return hostname
|
||||
}
|
||||
|
||||
func TestUpgradeAToB(t *testing.T) {
|
||||
versionA := os.Getenv("FLEET_VERSION_A")
|
||||
if versionA == "" {
|
||||
t.Skip("Missing environment variable FLEET_VERSION_A")
|
||||
}
|
||||
|
||||
versionB := os.Getenv("FLEET_VERSION_B")
|
||||
if versionB == "" {
|
||||
t.Skip("Missing environment variable FLEET_VERSION_B")
|
||||
}
|
||||
|
||||
f := NewFleet(t, versionA)
|
||||
|
||||
enrollHost(t, f)
|
||||
|
||||
err := f.Upgrade(versionB)
|
||||
require.NoError(t, err)
|
||||
|
||||
// enroll another host with the new version
|
||||
enrollHost(t, f)
|
||||
}
|
||||
Loading…
Reference in a new issue