8000 Merge pull request #313 from symfony-cli/db-version-during-build · glengemann/symfony-cli@40644ef · GitHub
[go: up one dir, main page]

Skip to content

Commit 40644ef

Browse files
authored
Merge pull request symfony-cli#313 from symfony-cli/db-version-during-build
Check that the DB version is set before deploying
2 parents 1c9e612 + df11feb commit 40644ef

File tree

20 files changed

+356
-4
lines changed

20 files changed

+356
-4
lines changed

commands/db_versions.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package commands
2+
3+
import (
4+
"errors"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"gopkg.in/yaml.v2"
11+
)
12+
13+
func readDBVersionFromPlatformServiceYAML(projectDir string) (string, string, error) {
14+
servicesYAML, err := ioutil.ReadFile(filepath.Join(projectDir, ".platform", "services.yaml"))
15+
if err != nil {
16+
// no services.yaml or unreadable
17+
return "", "", err
18+
}
19+
var services map[string]struct {
20+
Type string `yaml:"type"`
21+
}
22+
if err := yaml.Unmarshal(servicesYAML, &services); err != nil {
23+
// services.yaml format is wrong
24+
return "", "", err
25+
}
26+
27+
dbName := ""
28+
dbVersion := ""
29+
for _, service := range services {
30+
if strings.HasPrefix(service.Type, "mysql") || strings.HasPrefix(service.Type, "mariadb") || strings.HasPrefix(service.Type, "postgresql") {
31+
if dbName != "" {
32+
// give up as there are multiple DBs
33+
return "", "", nil
34+
}
35+
36+
parts := strings.Split(service.Type, ":")
37+
dbName = parts[0]
38+
dbVersion = parts[1]
39+
}
40+
}
41+
return dbName, dbVersion, nil
42+
}
43+
44+
func readDBVersionFromDotEnv(projectDir string) (string, error) {
45+
path := filepath.Join(projectDir, ".env")
46+
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
47+
return "", nil
48+
}
49+
50+
dotEnv, err := ioutil.ReadFile(path)
51+
if err != nil {
52+
return "", err
53+
}
54+
55+
lines := strings.Split(string(dotEnv), "\n")
56+
for _, line := range lines {
57+
if !strings.HasPrefix(line, "DATABASE_URL=") {
58+
continue
59+
}
60+
if !strings.Contains(line, "serverVersion=") {
61+
return "", nil
62+
}
63+
return strings.TrimRight(strings.Split(strings.Split(line, "serverVersion=")[1], "&")[0], "\""), nil
64+
}
65+
return "", nil
66+
}
67+
68+
func readDBVersionFromDoctrineConfigYAML(projectDir string) (string, error) {
69+
path := filepath.Join(projectDir, "config", "packages", "doctrine.yaml")
70+
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
71+
return "", nil
72+
}
73+
74+
doctrineConfigYAML, err := ioutil.ReadFile(path)
75+
if err != nil {
76+
return "", err
77+
}
78+
79+
var doctrineConfig struct {
80+
Doctrine struct {
81+
Dbal struct {
82+
ServerVersion string `yaml:"server_version"`
83+
} `yaml:"dbal"`
84+
} `yaml:"doctrine"`
85+
}
86+
if err := yaml.Unmarshal(doctrineConfigYAML, &doctrineConfig); err != nil {
87+
// format is wrong
88+
return "", err
89+
}
90+
return doctrineConfig.Doctrine.Dbal.ServerVersion, nil
91+
}

commands/local_new_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ func TestParseDockerComposeServices(t *testing.T) {
3838
}
3939

4040
for dir, expected := range map[string]CloudService{
41-
"testdata/postgresql/noversion/": {
41+
"testdata/docker/postgresql/noversion/": {
4242
Name: "database",
4343
Type: "postgresql",
4444
Version: lastVersion,
4545
},
46-
"testdata/postgresql/10/": {
46+
"testdata/docker/postgresql/10/": {
4747
Name: "database",
4848
Type: "postgresql",
4949
Version: "10",
5050
},
51-
"testdata/postgresql/next/": {
51+
"testdata/docker/postgresql/next/": {
5252
Name: "database",
5353
Type: "postgresql",
5454
Version: lastVersion,

commands/platformsh_hooks.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,94 @@
1+
/*
2+
* Copyright (c) 2021-present Fabien Potencier <fabien@symfony.com>
3+
*
4+
* This file is part of Symfony CLI project
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
120
package commands
221

322
import (
23+
"fmt"
24+
425
"github.com/symfony-cli/console"
526
"github.com/symfony-cli/symfony-cli/envs"
627
"github.com/symfony-cli/symfony-cli/local/platformsh"
728
"github.com/symfony-cli/terminal"
829
)
930

1031
var platformshBeforeHooks = map[string]console.BeforeFunc{
32+
"cloud:environment:push": func(c *console.Context) error {
33+
// check that project has a DB and that server version is set properly
34+
projectDir, err := getProjectDir(c.String("dir"))
35+
if err != nil {
36+
return err
37+
}
38+
if len(platformsh.FindLocalApplications(projectDir)) > 1 {
39+
// not implemented yet as more complex
40+
return nil
41+
}
42+
43+
dbName, dbVersion, err := readDBVersionFromPlatformServiceYAML(projectDir)
44+
if err != nil {
45+
return nil
46+
}
47+
if dbName == "" {
48+
// no DB
49+
return nil
50+
}
51+
52+
errorTpl := fmt.Sprintf(`
53+
The ".platform/services.yaml" file defines
54+
a "%s" version %s database service
55+
but %%s.
56+
57+
Before deploying, fix the version mismatch.
58+
`, dbName, dbVersion)
59+
60+
dotEnvVersion, err := readDBVersionFromDotEnv(projectDir)
61+
if err != nil {
62+
return nil
63+
}
64+
if dotEnvVersion != "" && dotEnvVersion != dbVersion {
65+
return fmt.Errorf(errorTpl, fmt.Sprintf("the \".env\" file requires version %s", dotEnvVersion))
66+
}
67+
68+
doctrineConfigVersion, err := readDBVersionFromDoctrineConfigYAML(projectDir)
69+
if err != nil {
70+
fmt.Printf("%+v", err)
71+
return nil
72+
}
73+
if doctrineConfigVersion != "" && doctrineConfigVersion != dbVersion {
74+
return fmt.Errorf(errorTpl, fmt.Sprintf("the \"config/packages/doctrine.yaml\" file requires version %s", doctrineConfigVersion))
75+
}
76+
77+
if dotEnvVersion == "" && doctrineConfigVersion == "" {
78+
return fmt.Errorf(`
79+
The ".platform/services.yaml" file defines a "%s" database service.
80+
81+
When deploying, Doctrine needs to know the database version to determine the supported SQL syntax.
82+
83+
As the database is not available when Doctrine is warming up its cache on Platform.sh,
84+
you need to explicitly set the database version in the ".env" or "config/packages/doctrine.yaml" file.
85+
86+
Set the "server_version" parameter to "%s" in "config/packages/doctrine.yaml".
87+
`, dbName, dbVersion)
88+
}
89+
90+
return nil
91+
},
1192
"cloud:tunnel:close": func(c *console.Context) error {
1293
terminal.Eprintln("Stop exposing tunnel service environment variables")
1394

commands/platformsh_hooks_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2023-present Fabien Potencier <fabien@symfony.com>
3+
*
4+
* This file is part of Symfony CLI project
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package commands
21+
22+
import (
23+
"flag"
24+
"strings"
25+
"testing"
26+
27+
"github.com/symfony-cli/console"
28+
)
29+
30+
func TestDeployHook(t *testing.T) {
31+
flags := flag.NewFlagSet("test", 0)
32+
flags.String("dir", "", "")
33+
c := console.NewContext(nil, flags, nil)
34+
35+
for dir, expected := range map[string]string{
36+
"testdata/platformsh/version-mismatch-env/": `The ".platform/services.yaml" file defines a "postgresql" version 14 database service but the ".env" file requires version 13.`,
37+
"testdata/platformsh/version-mismatch-config/": `The ".platform/services.yaml" file defines a "postgresql" version 14 database service but the "config/packages/doctrine.yaml" file requires version 13.`,
38+
"testdata/platformsh/ok/": ``,
39+
"testdata/platformsh/missing-version/": `Set the "server_version" parameter to "14" in "config/packages/doctrine.yaml".`,
40+
} {
41+
flags.Set("dir", dir)
42+
err := platformshBeforeHooks["cloud:environment:push"](c)
43+
if err == nil {
44+
if expected != "" {
45+
t.Errorf("TestDeployHook(%q): got %v, expected %v", dir, err, expected)
46+
}
47+
continue
48+
}
49+
errString := strings.ReplaceAll(err.Error(), "\n", " ")
50+
if !strings.Contains(errString, expected) {
51+
t.Errorf("TestDeployHook(%q): got %s, expected %s", dir, errString, expected)
52+
}
53+
}
54+
}

commands/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ Environment variables to use Platform.sh relationships or Docker services are au
181181
}
182182

183183
func getProjectDir(dir string) (string, error) {
184-
185184
var err error
186185
if dir, err = filepath.Abs(dir); err != nil {
187186
return "", err
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
###> doctrine/doctrine-bundle ###
2+
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
3+
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
4+
#
5+
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
6+
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
7+
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?charset=utf8"
8+
###< doctrine/doctrine-bundle ###
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pgsqldb:
2+
type: postgresql:14
3+
disk: 512

0 commit comments

Comments
 (0)
0