2021-07-07 12:06:17 +01:00
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
2022-08-05 10:26:59 +01:00
"io"
2022-12-02 10:44:20 +00:00
"io/ioutil"
2021-07-07 12:06:17 +01:00
"log"
"net/http"
"os"
"path"
"regexp"
"runtime"
"sort"
2021-07-07 14:20:07 +01:00
"strconv"
2021-07-07 12:06:17 +01:00
"strings"
"sync"
"time"
"github.com/Masterminds/semver/v3"
"github.com/codeclysm/extract"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
2021-07-07 14:20:07 +01:00
"github.com/docker/docker/api/types/filters"
2021-07-07 12:06:17 +01:00
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
)
var (
flagTempDir = flag . String ( "tmp" , "tmp" , "Path to temporary directory to dump tarballs to" )
2021-07-07 14:20:07 +01:00
flagFrom = flag . String ( "from" , "HEAD-1" , "The version to start from e.g '0.3.1'. If 'HEAD-N' then starts N versions behind HEAD." )
2021-07-07 12:06:17 +01:00
flagTo = flag . String ( "to" , "HEAD" , "The version to end on e.g '0.3.3'." )
flagBuildConcurrency = flag . Int ( "build-concurrency" , runtime . NumCPU ( ) , "The amount of build concurrency when building images" )
2021-07-08 12:28:04 +01:00
flagHead = flag . String ( "head" , "" , "Location to a dendrite repository to treat as HEAD instead of Github" )
flagDockerHost = flag . String ( "docker-host" , "localhost" , "The hostname of the docker client. 'localhost' if running locally, 'host.docker.internal' if running in Docker." )
2022-07-25 10:39:22 +01:00
flagDirect = flag . Bool ( "direct" , false , "If a direct upgrade from the defined FROM version to TO should be done" )
2022-11-11 11:21:16 +00:00
flagSqlite = flag . Bool ( "sqlite" , false , "Test SQLite instead of PostgreSQL" )
2021-07-07 12:06:17 +01:00
alphaNumerics = regexp . MustCompile ( "[^a-zA-Z0-9]+" )
)
2021-07-08 12:28:04 +01:00
const HEAD = "HEAD"
2023-02-14 11:47:47 +00:00
// The binary was renamed after v0.11.1, so everything after that should use the new name
var binaryChangeVersion , _ = semver . NewVersion ( "v0.11.1" )
var latest , _ = semver . NewVersion ( "v6.6.6" ) // Dummy version, used as "HEAD"
2021-07-07 12:06:17 +01:00
// Embed the Dockerfile to use when building dendrite versions.
// We cannot use the dockerfile associated with the repo with each version sadly due to changes in
// Docker versions. Specifically, earlier Dendrite versions are incompatible with newer Docker clients
// due to the error:
2022-08-05 10:12:41 +01:00
// When using COPY with more than one source file, the destination must be a directory and end with a /
2021-07-07 12:06:17 +01:00
// We need to run a postgres anyway, so use the dockerfile associated with Complement instead.
2023-08-28 12:28:22 +01:00
const DockerfilePostgreSQL = ` FROM golang : 1.20 - bookworm as build
2021-07-07 12:06:17 +01:00
RUN apt - get update && apt - get install - y postgresql
WORKDIR / build
2023-02-14 11:47:47 +00:00
ARG BINARY
2021-07-07 12:06:17 +01:00
# Copy the build context to the repo as this is the right dendrite code . This is different to the
# Complement Dockerfile which wgets a branch .
COPY . .
2023-02-14 11:47:47 +00:00
RUN go build . / cmd / $ { BINARY }
2021-07-07 12:06:17 +01:00
RUN go build . / cmd / generate - keys
RUN go build . / cmd / generate - config
2022-12-02 10:44:20 +00:00
RUN go build . / cmd / create - account
2021-07-07 12:06:17 +01:00
RUN . / generate - config -- ci > dendrite . yaml
RUN . / generate - keys -- private - key matrix_key . pem -- tls - cert server . crt -- tls - key server . key
# Replace the connection string with a single postgres DB , using user / db = ' postgres ' and no password
2023-04-24 11:50:37 +01:00
RUN sed - i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite . yaml
2021-07-07 12:06:17 +01:00
# No password when connecting over localhost
2023-08-28 12:28:22 +01:00
RUN sed - i "s%127.0.0.1/32 scram-sha-256%127.0.0.1/32 trust%g" / etc / postgresql / 15 / main / pg_hba . conf
2021-07-07 12:06:17 +01:00
# Bump up max conns for moar concurrency
2023-08-28 12:28:22 +01:00
RUN sed - i ' s / max_connections = 100 / max_connections = 2000 / g ' / etc / postgresql / 15 / main / postgresql . conf
2021-07-07 12:06:17 +01:00
RUN sed - i ' s / max_open_conns : . * $ / max_open_conns : 100 / g ' dendrite . yaml
# This entry script starts postgres , waits for it to be up then starts dendrite
RUN echo ' \
# ! / bin / bash - eu \ n \
pg_lsclusters \ n \
2023-08-28 12:28:22 +01:00
pg_ctlcluster 15 main start \ n \
2021-07-07 12:06:17 +01:00
\ n \
until pg_isready \ n \
do \ n \
echo "Waiting for postgres" ; \ n \
sleep 1 ; \ n \
done \ n \
\ n \
sed - i "s/server_name: localhost/server_name: ${SERVER_NAME}/g" dendrite . yaml \ n \
2022-04-29 08:31:11 +01:00
PARAMS = "--tls-cert server.crt --tls-key server.key --config dendrite.yaml" \ n \
2023-02-14 11:47:47 +00:00
. / $ { BINARY } -- really - enable - open - registration $ { PARAMS } || . / $ { BINARY } $ { PARAMS } \ n \
2021-07-07 12:06:17 +01:00
' > run_dendrite . sh && chmod + x run_dendrite . sh
ENV SERVER_NAME = localhost
2023-02-14 11:47:47 +00:00
ENV BINARY = dendrite
2021-07-07 12:06:17 +01:00
EXPOSE 8008 8448
2023-02-14 11:47:47 +00:00
CMD / build / run_dendrite . sh `
2021-07-07 12:06:17 +01:00
2023-08-28 12:28:22 +01:00
const DockerfileSQLite = ` FROM golang : 1.20 - bookworm as build
2022-11-11 11:21:16 +00:00
RUN apt - get update && apt - get install - y postgresql
WORKDIR / build
2023-02-14 11:47:47 +00:00
ARG BINARY
2022-11-11 11:21:16 +00:00
# Copy the build context to the repo as this is the right dendrite code . This is different to the
# Complement Dockerfile which wgets a branch .
COPY . .
2023-02-14 11:47:47 +00:00
RUN go build . / cmd / $ { BINARY }
2022-11-11 11:21:16 +00:00
RUN go build . / cmd / generate - keys
RUN go build . / cmd / generate - config
2022-12-02 10:44:20 +00:00
RUN go build . / cmd / create - account
2022-11-11 11:21:16 +00:00
RUN . / generate - config -- ci > dendrite . yaml
RUN . / generate - keys -- private - key matrix_key . pem -- tls - cert server . crt -- tls - key server . key
# Make sure the SQLite databases are in a persistent location , we ' re already mapping
# the postgresql folder so let ' s just use that for simplicity
2023-08-28 12:28:22 +01:00
RUN sed - i "s%connection_string:.file:%connection_string: file:\/var\/lib\/postgresql\/15\/main\/%g" dendrite . yaml
2022-11-11 11:21:16 +00:00
# This entry script starts postgres , waits for it to be up then starts dendrite
RUN echo ' \
sed - i "s/server_name: localhost/server_name: ${SERVER_NAME}/g" dendrite . yaml \ n \
PARAMS = "--tls-cert server.crt --tls-key server.key --config dendrite.yaml" \ n \
2023-02-14 11:47:47 +00:00
. / $ { BINARY } -- really - enable - open - registration $ { PARAMS } || . / $ { BINARY } $ { PARAMS } \ n \
2022-11-11 11:21:16 +00:00
' > run_dendrite . sh && chmod + x run_dendrite . sh
ENV SERVER_NAME = localhost
2023-02-14 11:47:47 +00:00
ENV BINARY = dendrite
2022-11-11 11:21:16 +00:00
EXPOSE 8008 8448
CMD / build / run_dendrite . sh `
func dockerfile ( ) [ ] byte {
if * flagSqlite {
return [ ] byte ( DockerfileSQLite )
}
return [ ] byte ( DockerfilePostgreSQL )
}
2021-07-07 14:20:07 +01:00
const dendriteUpgradeTestLabel = "dendrite_upgrade_test"
2021-07-07 12:06:17 +01:00
// downloadArchive downloads an arbitrary github archive of the form:
2022-08-05 10:12:41 +01:00
//
// https://github.com/matrix-org/dendrite/archive/v0.3.11.tar.gz
//
2021-07-07 12:06:17 +01:00
// and re-tarballs it without the top-level directory which contains branch information. It inserts
// the contents of `dockerfile` as a root file `Dockerfile` in the re-tarballed directory such that
// you can directly feed the retarballed archive to `ImageBuild` to have it run said dockerfile.
// Returns the tarball buffer on success.
func downloadArchive ( cli * http . Client , tmpDir , archiveURL string , dockerfile [ ] byte ) ( * bytes . Buffer , error ) {
resp , err := cli . Get ( archiveURL )
if err != nil {
return nil , err
}
// nolint:errcheck
defer resp . Body . Close ( )
if resp . StatusCode != 200 {
return nil , fmt . Errorf ( "got HTTP %d" , resp . StatusCode )
}
2021-07-08 10:07:39 +01:00
_ = os . RemoveAll ( tmpDir )
2021-07-07 12:06:17 +01:00
if err = os . Mkdir ( tmpDir , os . ModePerm ) ; err != nil {
return nil , fmt . Errorf ( "failed to make temporary directory: %s" , err )
}
// nolint:errcheck
defer os . RemoveAll ( tmpDir )
// dump the tarball temporarily, stripping the top-level directory
err = extract . Archive ( context . Background ( ) , resp . Body , tmpDir , func ( inPath string ) string {
// remove top level
segments := strings . Split ( inPath , "/" )
return strings . Join ( segments [ 1 : ] , "/" )
} )
if err != nil {
return nil , err
}
// add top level Dockerfile
2022-08-05 10:26:59 +01:00
err = os . WriteFile ( path . Join ( tmpDir , "Dockerfile" ) , dockerfile , os . ModePerm )
2021-07-07 12:06:17 +01:00
if err != nil {
return nil , fmt . Errorf ( "failed to inject /Dockerfile: %w" , err )
}
// now re-tarball it :/
var tarball bytes . Buffer
err = compress ( tmpDir , & tarball )
if err != nil {
return nil , err
}
return & tarball , nil
}
// buildDendrite builds Dendrite on the branchOrTagName given. Returns the image ID or an error
2023-02-14 11:47:47 +00:00
func buildDendrite ( httpClient * http . Client , dockerClient * client . Client , tmpDir string , branchOrTagName , binary string ) ( string , error ) {
2021-07-08 12:28:04 +01:00
var tarball * bytes . Buffer
var err error
// If a custom HEAD location is given, use that, else pull from github. Mostly useful for CI
// where we want to use the working directory.
if branchOrTagName == HEAD && * flagHead != "" {
log . Printf ( "%s: Using %s as HEAD" , branchOrTagName , * flagHead )
// add top level Dockerfile
2022-11-11 11:21:16 +00:00
err = os . WriteFile ( path . Join ( * flagHead , "Dockerfile" ) , dockerfile ( ) , os . ModePerm )
2021-07-08 12:28:04 +01:00
if err != nil {
2021-09-08 17:31:03 +01:00
return "" , fmt . Errorf ( "custom HEAD: failed to inject /Dockerfile: %w" , err )
2021-07-08 12:28:04 +01:00
}
// now tarball it
var buffer bytes . Buffer
err = compress ( * flagHead , & buffer )
if err != nil {
return "" , fmt . Errorf ( "failed to tarball custom HEAD %s : %s" , * flagHead , err )
}
tarball = & buffer
} else {
log . Printf ( "%s: Downloading version %s to %s\n" , branchOrTagName , branchOrTagName , tmpDir )
// pull an archive, this contains a top-level directory which screws with the build context
// which we need to fix up post download
u := fmt . Sprintf ( "https://github.com/matrix-org/dendrite/archive/%s.tar.gz" , branchOrTagName )
2022-11-11 11:21:16 +00:00
tarball , err = downloadArchive ( httpClient , tmpDir , u , dockerfile ( ) )
2021-07-08 12:28:04 +01:00
if err != nil {
return "" , fmt . Errorf ( "failed to download archive %s: %w" , u , err )
}
log . Printf ( "%s: %s => %d bytes\n" , branchOrTagName , u , tarball . Len ( ) )
2021-07-07 12:06:17 +01:00
}
2021-07-08 12:28:04 +01:00
2021-07-07 12:06:17 +01:00
log . Printf ( "%s: Building version %s\n" , branchOrTagName , branchOrTagName )
res , err := dockerClient . ImageBuild ( context . Background ( ) , tarball , types . ImageBuildOptions {
Tags : [ ] string { "dendrite-upgrade" } ,
2023-02-14 11:47:47 +00:00
BuildArgs : map [ string ] * string {
"BINARY" : & binary ,
} ,
2021-07-07 12:06:17 +01:00
} )
if err != nil {
return "" , fmt . Errorf ( "failed to start building image: %s" , err )
}
// nolint:errcheck
defer res . Body . Close ( )
decoder := json . NewDecoder ( res . Body )
// {"aux":{"ID":"sha256:247082c717963bc2639fc2daed08838d67811ea12356cd4fda43e1ffef94f2eb"}}
var imageID string
for decoder . More ( ) {
var dl struct {
Stream string ` json:"stream" `
Aux map [ string ] interface { } ` json:"aux" `
}
if err := decoder . Decode ( & dl ) ; err != nil {
return "" , fmt . Errorf ( "failed to decode build image output line: %w" , err )
}
2022-01-21 09:56:06 +00:00
if len ( strings . TrimSpace ( dl . Stream ) ) > 0 {
log . Printf ( "%s: %s" , branchOrTagName , dl . Stream )
}
2021-07-07 12:06:17 +01:00
if dl . Aux != nil {
imgID , ok := dl . Aux [ "ID" ]
if ok {
imageID = imgID . ( string )
}
}
}
return imageID , nil
}
func getAndSortVersionsFromGithub ( httpClient * http . Client ) ( semVers [ ] * semver . Version , err error ) {
u := "https://api.github.com/repos/matrix-org/dendrite/tags"
2023-03-22 08:21:32 +00:00
var res * http . Response
for i := 0 ; i < 3 ; i ++ {
res , err = httpClient . Get ( u )
if err != nil {
return nil , err
}
if res . StatusCode == 200 {
break
}
log . Printf ( "Github API returned HTTP %d, retrying\n" , res . StatusCode )
time . Sleep ( time . Second * 5 )
2021-07-07 12:06:17 +01:00
}
2023-03-22 08:21:32 +00:00
2021-07-07 12:06:17 +01:00
if res . StatusCode != 200 {
return nil , fmt . Errorf ( "%s returned HTTP %d" , u , res . StatusCode )
}
resp := [ ] struct {
Name string ` json:"name" `
} { }
if err = json . NewDecoder ( res . Body ) . Decode ( & resp ) ; err != nil {
return nil , err
}
for _ , r := range resp {
v , err := semver . NewVersion ( r . Name )
if err != nil {
continue // not a semver, that's ok and isn't an error, we allow tags that aren't semvers
}
semVers = append ( semVers , v )
}
sort . Sort ( semver . Collection ( semVers ) )
return semVers , nil
}
2023-02-14 11:47:47 +00:00
func calculateVersions ( cli * http . Client , from , to string , direct bool ) [ ] * semver . Version {
2021-07-07 12:06:17 +01:00
semvers , err := getAndSortVersionsFromGithub ( cli )
if err != nil {
log . Fatalf ( "failed to collect semvers from github: %s" , err )
}
// snip the lower bound depending on --from
if from != "" {
2021-07-07 14:20:07 +01:00
if strings . HasPrefix ( from , "HEAD-" ) {
var headN int
headN , err = strconv . Atoi ( strings . TrimPrefix ( from , "HEAD-" ) )
if err != nil {
log . Fatalf ( "invalid --from, try 'HEAD-1'" )
2021-07-07 12:06:17 +01:00
}
2021-07-07 14:20:07 +01:00
if headN >= len ( semvers ) {
log . Fatalf ( "only have %d versions, but asked to go to HEAD-%d" , len ( semvers ) , headN )
}
if headN > 0 {
semvers = semvers [ len ( semvers ) - headN : ]
}
} else {
fromVer , err := semver . NewVersion ( from )
if err != nil {
log . Fatalf ( "invalid --from: %s" , err )
}
i := 0
for i = 0 ; i < len ( semvers ) ; i ++ {
if semvers [ i ] . LessThan ( fromVer ) {
continue
}
break
}
semvers = semvers [ i : ]
2021-07-07 12:06:17 +01:00
}
}
2021-07-08 12:28:04 +01:00
if to != "" && to != HEAD {
2021-07-07 12:06:17 +01:00
toVer , err := semver . NewVersion ( to )
if err != nil {
log . Fatalf ( "invalid --to: %s" , err )
}
var i int
for i = len ( semvers ) - 1 ; i >= 0 ; i -- {
if semvers [ i ] . GreaterThan ( toVer ) {
continue
}
break
}
semvers = semvers [ : i + 1 ]
}
2023-02-14 11:47:47 +00:00
2021-07-08 12:28:04 +01:00
if to == HEAD {
2023-02-14 11:47:47 +00:00
semvers = append ( semvers , latest )
2021-07-07 12:06:17 +01:00
}
2022-07-25 10:39:22 +01:00
if direct {
2023-02-14 11:47:47 +00:00
semvers = [ ] * semver . Version { semvers [ 0 ] , semvers [ len ( semvers ) - 1 ] }
2022-07-25 10:39:22 +01:00
}
2023-02-14 11:47:47 +00:00
return semvers
2021-07-07 12:06:17 +01:00
}
2023-02-14 11:47:47 +00:00
func buildDendriteImages ( httpClient * http . Client , dockerClient * client . Client , baseTempDir string , concurrency int , versions [ ] * semver . Version ) map [ string ] string {
2021-07-07 12:06:17 +01:00
// concurrently build all versions, this can be done in any order. The mutex protects the map
branchToImageID := make ( map [ string ] string )
var mu sync . Mutex
var wg sync . WaitGroup
wg . Add ( concurrency )
2023-02-14 11:47:47 +00:00
ch := make ( chan * semver . Version , len ( versions ) )
for _ , branchName := range versions {
2021-07-07 12:06:17 +01:00
ch <- branchName
}
close ( ch )
for i := 0 ; i < concurrency ; i ++ {
go func ( ) {
defer wg . Done ( )
2023-02-14 11:47:47 +00:00
for version := range ch {
branchName , binary := versionToBranchAndBinary ( version )
log . Printf ( "Building version %s with binary %s" , branchName , binary )
2021-07-07 12:06:17 +01:00
tmpDir := baseTempDir + alphaNumerics . ReplaceAllString ( branchName , "" )
2023-02-14 11:47:47 +00:00
imgID , err := buildDendrite ( httpClient , dockerClient , tmpDir , branchName , binary )
2021-07-07 12:06:17 +01:00
if err != nil {
2023-02-14 11:47:47 +00:00
log . Fatalf ( "%s: failed to build dendrite image: %s" , version , err )
2021-07-07 12:06:17 +01:00
}
mu . Lock ( )
branchToImageID [ branchName ] = imgID
mu . Unlock ( )
}
} ( )
}
wg . Wait ( )
return branchToImageID
}
2023-02-14 11:47:47 +00:00
func runImage ( dockerClient * client . Client , volumeName string , branchNameToImageID map [ string ] string , version * semver . Version ) ( csAPIURL , containerID string , err error ) {
branchName , binary := versionToBranchAndBinary ( version )
imageID := branchNameToImageID [ branchName ]
2021-07-07 12:06:17 +01:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 3 * time . Minute )
defer cancel ( )
body , err := dockerClient . ContainerCreate ( ctx , & container . Config {
Image : imageID ,
2023-02-14 11:47:47 +00:00
Env : [ ] string { "SERVER_NAME=hs1" , fmt . Sprintf ( "BINARY=%s" , binary ) } ,
2021-07-07 14:20:07 +01:00
Labels : map [ string ] string {
dendriteUpgradeTestLabel : "yes" ,
} ,
2021-07-07 12:06:17 +01:00
} , & container . HostConfig {
PublishAllPorts : true ,
Mounts : [ ] mount . Mount {
{
Type : mount . TypeVolume ,
Source : volumeName ,
2023-08-28 12:28:22 +01:00
Target : "/var/lib/postgresql/15/main" ,
2021-07-07 12:06:17 +01:00
} ,
} ,
2023-02-14 11:47:47 +00:00
} , nil , nil , "dendrite_upgrade_test_" + branchName )
2021-07-07 12:06:17 +01:00
if err != nil {
return "" , "" , fmt . Errorf ( "failed to ContainerCreate: %s" , err )
}
containerID = body . ID
err = dockerClient . ContainerStart ( ctx , containerID , types . ContainerStartOptions { } )
if err != nil {
return "" , "" , fmt . Errorf ( "failed to ContainerStart: %s" , err )
}
inspect , err := dockerClient . ContainerInspect ( ctx , containerID )
if err != nil {
return "" , "" , err
}
csapiPortInfo , ok := inspect . NetworkSettings . Ports [ nat . Port ( "8008/tcp" ) ]
if ! ok {
return "" , "" , fmt . Errorf ( "port 8008 not exposed - exposed ports: %v" , inspect . NetworkSettings . Ports )
}
2021-07-08 12:28:04 +01:00
baseURL := fmt . Sprintf ( "http://%s:%s" , * flagDockerHost , csapiPortInfo [ 0 ] . HostPort )
2021-07-07 12:06:17 +01:00
versionsURL := fmt . Sprintf ( "%s/_matrix/client/versions" , baseURL )
// hit /versions to check it is up
var lastErr error
for i := 0 ; i < 500 ; i ++ {
2022-11-10 10:16:56 +00:00
var res * http . Response
res , err = http . Get ( versionsURL )
2021-07-07 12:06:17 +01:00
if err != nil {
lastErr = fmt . Errorf ( "GET %s => error: %s" , versionsURL , err )
time . Sleep ( 50 * time . Millisecond )
continue
}
if res . StatusCode != 200 {
lastErr = fmt . Errorf ( "GET %s => HTTP %s" , versionsURL , res . Status )
time . Sleep ( 50 * time . Millisecond )
continue
}
lastErr = nil
break
}
2022-11-10 10:07:19 +00:00
logs , err := dockerClient . ContainerLogs ( context . Background ( ) , containerID , types . ContainerLogsOptions {
ShowStdout : true ,
ShowStderr : true ,
Follow : true ,
} )
// ignore errors when cannot get logs, it's just for debugging anyways
if err == nil {
go func ( ) {
for {
if body , err := io . ReadAll ( logs ) ; err == nil && len ( body ) > 0 {
log . Printf ( "%s: %s" , version , string ( body ) )
} else {
return
}
2021-07-07 12:06:17 +01:00
}
2022-11-10 10:07:19 +00:00
} ( )
2021-07-07 12:06:17 +01:00
}
return baseURL , containerID , lastErr
}
func destroyContainer ( dockerClient * client . Client , containerID string ) {
err := dockerClient . ContainerRemove ( context . TODO ( ) , containerID , types . ContainerRemoveOptions {
Force : true ,
} )
if err != nil {
log . Printf ( "failed to remove container %s : %s" , containerID , err )
}
}
2023-02-14 11:47:47 +00:00
func loadAndRunTests ( dockerClient * client . Client , volumeName string , v * semver . Version , branchToImageID map [ string ] string ) error {
csAPIURL , containerID , err := runImage ( dockerClient , volumeName , branchToImageID , v )
2021-07-07 12:06:17 +01:00
if err != nil {
return fmt . Errorf ( "failed to run container for branch %v: %v" , v , err )
}
defer destroyContainer ( dockerClient , containerID )
log . Printf ( "URL %s -> %s \n" , csAPIURL , containerID )
if err = runTests ( csAPIURL , v ) ; err != nil {
return fmt . Errorf ( "failed to run tests on version %s: %s" , v , err )
}
2022-12-02 10:44:20 +00:00
err = testCreateAccount ( dockerClient , v , containerID )
if err != nil {
return err
}
return nil
}
// test that create-account is working
2023-02-14 11:47:47 +00:00
func testCreateAccount ( dockerClient * client . Client , version * semver . Version , containerID string ) error {
branchName , _ := versionToBranchAndBinary ( version )
createUser := strings . ToLower ( "createaccountuser-" + branchName )
log . Printf ( "%s: Creating account %s with create-account\n" , branchName , createUser )
2022-12-02 10:44:20 +00:00
respID , err := dockerClient . ContainerExecCreate ( context . Background ( ) , containerID , types . ExecConfig {
AttachStderr : true ,
AttachStdout : true ,
Cmd : [ ] string {
"/build/create-account" ,
"-username" , createUser ,
"-password" , "someRandomPassword" ,
} ,
} )
if err != nil {
return fmt . Errorf ( "failed to ContainerExecCreate: %w" , err )
}
response , err := dockerClient . ContainerExecAttach ( context . Background ( ) , respID . ID , types . ExecStartCheck { } )
if err != nil {
return fmt . Errorf ( "failed to attach to container: %w" , err )
}
defer response . Close ( )
data , err := ioutil . ReadAll ( response . Reader )
if err != nil {
return err
}
if ! bytes . Contains ( data , [ ] byte ( "AccessToken" ) ) {
return fmt . Errorf ( "failed to create-account: %s" , string ( data ) )
}
2021-07-07 12:06:17 +01:00
return nil
}
2023-02-14 11:47:47 +00:00
func versionToBranchAndBinary ( version * semver . Version ) ( branchName , binary string ) {
binary = "dendrite-monolith-server"
branchName = version . Original ( )
if version . GreaterThan ( binaryChangeVersion ) {
binary = "dendrite"
if version . Equal ( latest ) {
branchName = HEAD
}
}
return
}
func verifyTests ( dockerClient * client . Client , volumeName string , versions [ ] * semver . Version , branchToImageID map [ string ] string ) error {
2021-07-07 12:06:17 +01:00
lastVer := versions [ len ( versions ) - 1 ]
2023-02-14 11:47:47 +00:00
csAPIURL , containerID , err := runImage ( dockerClient , volumeName , branchToImageID , lastVer )
2021-07-07 12:06:17 +01:00
if err != nil {
return fmt . Errorf ( "failed to run container for branch %v: %v" , lastVer , err )
}
defer destroyContainer ( dockerClient , containerID )
return verifyTestsRan ( csAPIURL , versions )
}
2021-07-07 14:20:07 +01:00
// cleanup old containers/volumes from a previous run
func cleanup ( dockerClient * client . Client ) {
// ignore all errors, we are just cleaning up and don't want to fail just because we fail to cleanup
containers , _ := dockerClient . ContainerList ( context . Background ( ) , types . ContainerListOptions {
Filters : label ( dendriteUpgradeTestLabel ) ,
2022-01-21 09:56:06 +00:00
All : true ,
2021-07-07 14:20:07 +01:00
} )
for _ , c := range containers {
2022-01-21 09:56:06 +00:00
log . Printf ( "Removing container: %v %v\n" , c . ID , c . Names )
2023-08-28 12:28:22 +01:00
timeout := 1
_ = dockerClient . ContainerStop ( context . Background ( ) , c . ID , container . StopOptions { Timeout : & timeout } )
2021-07-07 14:20:07 +01:00
_ = dockerClient . ContainerRemove ( context . Background ( ) , c . ID , types . ContainerRemoveOptions {
Force : true ,
} )
}
_ = dockerClient . VolumeRemove ( context . Background ( ) , "dendrite_upgrade_test" , true )
}
func label ( in string ) filters . Args {
f := filters . NewArgs ( )
f . Add ( "label" , in )
return f
}
2021-07-07 12:06:17 +01:00
func main ( ) {
flag . Parse ( )
httpClient := & http . Client {
Timeout : 60 * time . Second ,
}
dockerClient , err := client . NewClientWithOpts ( client . FromEnv )
if err != nil {
log . Fatalf ( "failed to make docker client: %s" , err )
}
if * flagFrom == "" {
flag . Usage ( )
os . Exit ( 1 )
}
2021-07-07 14:20:07 +01:00
cleanup ( dockerClient )
2022-07-25 10:39:22 +01:00
versions := calculateVersions ( httpClient , * flagFrom , * flagTo , * flagDirect )
2021-07-07 12:06:17 +01:00
log . Printf ( "Testing dendrite versions: %v\n" , versions )
branchToImageID := buildDendriteImages ( httpClient , dockerClient , * flagTempDir , * flagBuildConcurrency , versions )
// make a shared postgres volume
2023-08-28 12:28:22 +01:00
volume , err := dockerClient . VolumeCreate ( context . Background ( ) , volume . CreateOptions {
2021-07-07 12:06:17 +01:00
Name : "dendrite_upgrade_test" ,
2021-07-07 14:20:07 +01:00
Labels : map [ string ] string {
dendriteUpgradeTestLabel : "yes" ,
} ,
2021-07-07 12:06:17 +01:00
} )
if err != nil {
log . Fatalf ( "failed to make docker volume: %s" , err )
}
failed := false
defer func ( ) {
perr := recover ( )
log . Println ( "removing postgres volume" )
verr := dockerClient . VolumeRemove ( context . Background ( ) , volume . Name , true )
if perr == nil {
perr = verr
}
if perr != nil {
panic ( perr )
}
if failed {
os . Exit ( 1 )
}
} ( )
// run through images sequentially
for _ , v := range versions {
if err = loadAndRunTests ( dockerClient , volume . Name , v , branchToImageID ) ; err != nil {
log . Printf ( "failed to run tests for %v: %s\n" , v , err )
failed = true
break
}
}
if err := verifyTests ( dockerClient , volume . Name , versions , branchToImageID ) ; err != nil {
log . Printf ( "failed to verify test results: %s" , err )
failed = true
}
}