github.com/openshift-online/ocm-sdk-go@v0.1.473/testing/database.go (about) 1 /* 2 Copyright (c) 2021 Red Hat, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package testing 18 19 import ( 20 "bytes" 21 "database/sql" 22 "fmt" 23 "net" 24 "os/exec" 25 "strings" 26 27 "github.com/google/uuid" 28 29 . "github.com/onsi/ginkgo/v2/dsl/core" // nolint 30 . "github.com/onsi/gomega" // nolint 31 32 _ "github.com/jackc/pgx/v4/stdlib" // nolint 33 ) 34 35 // DatabaseServer knows how to start a PostgreSQL database server inside a container, and how to 36 // create databases to be used for tests. 37 type DatabaseServer struct { 38 // Name of the tool used to create containers (podman or docker): 39 tool string 40 41 // Identifier of the container where the database server is running: 42 container string 43 44 // Host and port of the database server: 45 host string 46 port string 47 48 // Database handle: 49 handle *sql.DB 50 51 // Number of databases created: 52 count int 53 54 // List of databases created, so we don't forget to remove them: 55 dbs []*Database 56 } 57 58 // Database is a PostgreSQL database. 59 type Database struct { 60 // Reference to the database server that owns this database: 61 server *DatabaseServer 62 63 // Database name, user and password: 64 name string 65 user string 66 password string 67 } 68 69 // MakeDatabaseServer creates a new database server. 70 func MakeDatabaseServer() *DatabaseServer { 71 var err error 72 73 // Check if podman or docker are available: 74 tool, err := exec.LookPath("podman") 75 if err != nil { 76 tool, err = exec.LookPath("docker") 77 Expect(err).ToNot(HaveOccurred(), "Can't find 'podman' or 'docker'") 78 } 79 80 // Generate a random password for the database admnistrator: 81 password := uuid.NewString() 82 83 // Start the database server: 84 runOut := &bytes.Buffer{} 85 runCmd := exec.Command( 86 tool, "run", 87 "--env", "POSTGRES_PASSWORD="+password, 88 "--publish", "5432", 89 "--detach", 90 "docker.io/postgres:14", 91 "-c", "log_destination=stderr", 92 "-c", "log_statement=all", 93 "-c", "logging_collector=off", 94 ) // #nosec G204 95 runCmd.Stdout = runOut 96 runCmd.Stderr = GinkgoWriter 97 err = runCmd.Run() 98 Expect(err).ToNot(HaveOccurred()) 99 container := strings.TrimSpace(runOut.String()) 100 101 // Find out the port number assigned to the database server: 102 portOut := &bytes.Buffer{} 103 portCmd := exec.Command(tool, "port", container, "5432/tcp") // #nosec G204 104 portCmd.Stdout = portOut 105 portCmd.Stderr = GinkgoWriter 106 err = portCmd.Run() 107 Expect(err).ToNot(HaveOccurred()) 108 portLines := strings.Split(portOut.String(), "\n") 109 Expect(len(portLines)).To(BeNumerically(">=", 1)) 110 portLine := portLines[0] 111 hostPort := strings.TrimSpace(portLine) 112 host, port, err := net.SplitHostPort(hostPort) 113 Expect(err).ToNot(HaveOccurred()) 114 if host == "0.0.0.0" { 115 host = "127.0.0.1" 116 } 117 118 // Wait till the database server is responding: 119 url := fmt.Sprintf( 120 "postgres://postgres:%s@%s:%s/postgres?sslmode=disable", 121 password, host, port, 122 ) 123 handle, err := sql.Open("pgx", url) 124 Expect(err).ToNot(HaveOccurred()) 125 Eventually(handle.Ping, 10, 1).ShouldNot(HaveOccurred()) 126 127 // Create and populate the object: 128 return &DatabaseServer{ 129 tool: tool, 130 container: container, 131 host: host, 132 port: port, 133 handle: handle, 134 } 135 } 136 137 // Close stops the database server. 138 func (s *DatabaseServer) Close() { 139 var err error 140 141 // Delete all databases: 142 for _, db := range s.dbs { 143 db.Close() 144 } 145 146 // Get the logs of the database server: 147 logsCmd := exec.Command(s.tool, "logs", s.container) // #nosec G204 148 logsCmd.Stdout = GinkgoWriter 149 logsCmd.Stderr = GinkgoWriter 150 err = logsCmd.Run() 151 Expect(err).ToNot(HaveOccurred()) 152 153 // Stop the database server: 154 killCmd := exec.Command(s.tool, "kill", s.container) // #nosec G204 155 killCmd.Stdout = GinkgoWriter 156 killCmd.Stderr = GinkgoWriter 157 err = killCmd.Run() 158 Expect(err).ToNot(HaveOccurred()) 159 } 160 161 // MakeDatabase creates a new database. 162 func (s *DatabaseServer) MakeDatabase() *Database { 163 var err error 164 165 // Generate the database name and password: 166 name := fmt.Sprintf("test_%d", s.count) 167 user := fmt.Sprintf("test_%d", s.count) 168 password := uuid.NewString() 169 s.count++ 170 171 // Create the user: 172 _, err = s.handle.Exec(fmt.Sprintf( 173 "create user %s with password '%s'", 174 user, password, 175 )) 176 Expect(err).ToNot(HaveOccurred()) 177 178 // Create the database: 179 _, err = s.handle.Exec(fmt.Sprintf( 180 "create database %s owner %s;", 181 name, user, 182 )) 183 Expect(err).ToNot(HaveOccurred()) 184 185 // Create and populate the object: 186 db := &Database{ 187 server: s, 188 name: name, 189 user: user, 190 password: password, 191 } 192 193 // Remember to remove it: 194 s.dbs = append(s.dbs, db) 195 196 return db 197 } 198 199 // MakeHandle creates a new database handle for this database. 200 func (d *Database) MakeHandle() *sql.DB { 201 url := fmt.Sprintf( 202 "postgres://%s:%s@%s:%s/%s?sslmode=disable", 203 d.user, d.password, d.server.host, d.server.port, d.name, 204 ) 205 handle, err := sql.Open("pgx", url) 206 Expect(err).ToNot(HaveOccurred()) 207 return handle 208 } 209 210 // Close deletes the database. 211 func (d *Database) Close() { 212 var err error 213 214 // Drop the database: 215 _, err = d.server.handle.Exec(fmt.Sprintf( 216 `drop database if exists %s with (force)`, 217 d.name, 218 )) 219 Expect(err).ToNot(HaveOccurred()) 220 221 // Drop the user: 222 _, err = d.server.handle.Exec(fmt.Sprintf( 223 `drop user if exists %s`, 224 d.user, 225 )) 226 Expect(err).ToNot(HaveOccurred()) 227 }