Skip to content

Commit a2c76c1

Browse files
committed
Fix early project resolve
- Add deployment file writing - Add Sentry integration for deployments - Refactor command creation for Sentry and Git
1 parent 0152349 commit a2c76c1

15 files changed

Lines changed: 348 additions & 73 deletions

commands/deployment.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,27 @@ import (
1111
var deploymentCommand = &console.Command{
1212
Name: "deployment",
1313
Description: "Deploy the application",
14-
Action: func(c *console.Context) error {
14+
Args: []*console.Arg{
15+
{
16+
Name: "repository",
17+
Description: "The repository to read the release information from.",
18+
Optional: true,
19+
},
20+
},
21+
Action: func(ctx *console.Context) error {
1522
p := project.GetProject()
1623
env := p.GetEnvironment()
1724

1825
if !env.IsProd() {
1926
return errors.New("Deployment can only be run in production environment")
2027
}
2128

29+
repo := ctx.Args().Get("repository")
30+
31+
if repo != "" {
32+
macro.WriteDeploymentInfo(repo)
33+
}
34+
2235
macro.ComposerInstall()
2336
macro.CheckRequirements()
2437
macro.ComposerDumpEnv()
@@ -39,7 +52,11 @@ var deploymentCommand = &console.Command{
3952
// start
4053
macro.SoureCodeScreenStart()
4154

42-
shell.GetLogger().LogDuration()
55+
start, stop := shell.GetLogger().LogDuration()
56+
57+
if repo != "" {
58+
macro.SentryDeploysNew(repo, start, stop)
59+
}
4360

4461
return nil
4562
},

env/environment.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package env
22

33
import (
4+
"github.com/pkg/errors"
45
"os"
56
)
67

@@ -63,13 +64,13 @@ func NewEnvironment(directory string) (*Environment, error) {
6364
err := loadFile(envMap, directory, ".env")
6465

6566
if err != nil {
66-
return nil, err
67+
return nil, errors.Wrap(err, "error loading environment file")
6768
}
6869

6970
err = loadFile(envMap, directory, ".env.local")
7071

7172
if err != nil {
72-
return nil, err
73+
return nil, errors.Wrap(err, "failed to load local environment file")
7374
}
7475

7576
envName := os.Getenv("APP_ENV")
@@ -97,7 +98,7 @@ func NewEnvironment(directory string) (*Environment, error) {
9798
err = loadPHPFile(envMap, directory, ".env.local.php")
9899

99100
if err != nil {
100-
return nil, err
101+
return nil, errors.Wrap(err, "failed to load PHP environment file")
101102
}
102103

103104
return &Environment{

env/load_php_file.go

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,18 @@
11
package env
22

33
import (
4-
"fmt"
5-
"os"
6-
"os/exec"
4+
"github.com/SoureCode/kyx/internal/php"
75
"path/filepath"
86

9-
"github.com/joho/godotenv"
107
"github.com/pkg/errors"
118
)
129

1310
func loadPHPFile(envMap map[string]string, directory string, filename string) error {
1411
filePath := filepath.Join(directory, filename)
12+
parsedEnvMap, err := php.LoadFileDump(filePath)
1513

16-
info, err := os.Stat(filePath)
1714
if err != nil {
18-
if os.IsNotExist(err) {
19-
return nil
20-
}
21-
22-
return errors.Wrapf(err, "failed to stat file %s", filePath)
23-
}
24-
25-
if info.IsDir() {
26-
return errors.Errorf("expected file but found directory at %s", filePath)
27-
}
28-
29-
script := fmt.Sprintf(`$entries = include %q; if (!is_array($entries)) exit(1); foreach ($entries as $key => $value) { echo "$key=$value\n"; }`, filePath)
30-
cmd := exec.Command("php", "-r", script)
31-
cmd.Dir = directory
32-
33-
stdout, err := cmd.StdoutPipe()
34-
if err != nil {
35-
return errors.Wrapf(err, "failed to create stdout pipe for reading %s", filePath)
36-
}
37-
38-
if err := cmd.Start(); err != nil {
39-
return errors.Wrapf(err, "failed to start command for reading %s", filePath)
40-
}
41-
42-
parsedEnvMap, err := godotenv.Parse(stdout)
43-
44-
if err != nil {
45-
return errors.Wrapf(err, "failed to parse environment variables from %s", filePath)
46-
}
47-
48-
if err := cmd.Wait(); err != nil {
49-
return errors.Wrapf(err, "PHP script execution failed for %s", filePath)
15+
return errors.Wrapf(err, "failed to load PHP file: %s", filePath)
5016
}
5117

5218
mergeEnvMaps(envMap, parsedEnvMap)

internal/php/load_file_dump.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package php
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/joho/godotenv"
7+
"github.com/pkg/errors"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
)
13+
14+
func LoadFileDump(f string) (map[string]string, error) {
15+
info, err := os.Stat(f)
16+
17+
if err != nil {
18+
if os.IsNotExist(err) {
19+
return map[string]string{}, nil
20+
}
21+
22+
return nil, errors.Wrap(err, "failed to stat file")
23+
}
24+
25+
if info.IsDir() {
26+
return nil, errors.Errorf("expected file but found directory")
27+
}
28+
29+
dir := filepath.Dir(f)
30+
31+
var buf bytes.Buffer
32+
33+
script := fmt.Sprintf(`$entries = include %q; if (!is_array($entries)) exit(1); foreach ($entries as $key => $value) { echo "$key=$value\n"; }`, f)
34+
cmd := exec.Command("php", "-r", script)
35+
cmd.Dir = dir
36+
cmd.Stdout = &buf
37+
cmd.Stderr = &buf
38+
39+
if err := cmd.Start(); err != nil {
40+
return nil, errors.Wrap(err, "failed to start php command")
41+
}
42+
43+
if err := cmd.Wait(); err != nil {
44+
return nil, errors.Wrapf(err, "PHP script execution failed: %s", strings.TrimSpace(buf.String()))
45+
}
46+
47+
parsedEnvMap, err := godotenv.UnmarshalBytes(buf.Bytes())
48+
49+
if err != nil {
50+
return nil, errors.Wrap(err, "failed to parse environment variables")
51+
}
52+
53+
return parsedEnvMap, nil
54+
}

internal/php/write_file_dump.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package php
2+
3+
import (
4+
"os"
5+
"strings"
6+
)
7+
8+
func WriteFileDump(file string, data map[string]string) error {
9+
content := formatData(data)
10+
return os.WriteFile(file, []byte(content), 0644)
11+
}
12+
13+
func formatData(data map[string]string) string {
14+
lines := []string{
15+
"<?php",
16+
"",
17+
"return [",
18+
}
19+
20+
for key, value := range data {
21+
lines = append(lines, " '"+key+"' => '"+value+"',")
22+
}
23+
24+
lines = append(lines, "];")
25+
26+
return strings.Join(lines, "\n") + "\n"
27+
}

macro/sentry_deploys_new.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package macro
2+
3+
import (
4+
"fmt"
5+
"github.com/SoureCode/kyx/project"
6+
"github.com/SoureCode/kyx/shell"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
)
12+
13+
func SentryDeploysNew(repo string, startedAt, stoppedAt time.Time) {
14+
p := project.GetProject()
15+
dir := p.GetDirectory()
16+
logger := shell.GetLogger()
17+
18+
if !filepath.IsAbs(repo) {
19+
repo = filepath.Clean(filepath.Join(dir, repo))
20+
}
21+
22+
cmd := shell.NewGitCommand("-C", repo, "rev-parse", "HEAD").
23+
WithLogger(logger).
24+
WithLogLevel(3)
25+
26+
if err := cmd.Run(); err != nil {
27+
logger.Errorln("Error getting git commit hash:", err)
28+
os.Exit(1)
29+
}
30+
31+
hash := strings.TrimSpace(cmd.Stdout())
32+
env := p.GetEnvironment()
33+
34+
// Only run if Sentry is configured
35+
sentryUrl, ok := env.Lookup("SENTRY_URL")
36+
if !ok {
37+
logger.Infoln("Sentry url not configured, skipping sentry integration")
38+
return
39+
}
40+
41+
if strings.HasSuffix(sentryUrl, "/") {
42+
sentryUrl = strings.TrimSuffix(sentryUrl, "/")
43+
}
44+
45+
sentryOrg, ok := env.Lookup("SENTRY_ORG")
46+
if !ok {
47+
logger.Infoln("Sentry organization not configured, skipping sentry integration")
48+
return
49+
}
50+
51+
sentryProject, ok := env.Lookup("SENTRY_PROJECT")
52+
if !ok {
53+
logger.Infoln("Sentry project not configured, skipping sentry integration")
54+
return
55+
}
56+
57+
_, ok = env.Lookup("SENTRY_AUTH_TOKEN")
58+
if !ok {
59+
logger.Infoln("Sentry auth token not configured, skipping sentry integration")
60+
return
61+
}
62+
63+
appEnv, ok := env.Lookup("APP_ENV")
64+
if !ok {
65+
logger.Infoln("APP_ENV not configured, skipping sentry integration")
66+
return
67+
}
68+
69+
cmd = shell.NewSentryCommand(
70+
"--url="+sentryUrl,
71+
"releases", "list",
72+
"--raw",
73+
"--org="+sentryOrg,
74+
"--project="+sentryProject,
75+
).
76+
WithLogger(logger).
77+
WithLogLevel(0)
78+
79+
if err := cmd.Run(); err != nil {
80+
logger.Errorln("Error listing Sentry releases:", err)
81+
os.Exit(1)
82+
}
83+
84+
releases := strings.TrimSpace(cmd.Stdout())
85+
86+
if !strings.Contains(releases, hash) {
87+
cmd = shell.NewSentryCommand(
88+
"releases", "new",
89+
"--url="+sentryUrl,
90+
"--org="+sentryOrg,
91+
"--project="+sentryProject,
92+
hash,
93+
).
94+
WithLogger(logger).
95+
WithLogLevel(0)
96+
97+
if err := cmd.Run(); err != nil {
98+
logger.Errorln("Error creating new Sentry release:", err)
99+
os.Exit(1)
100+
}
101+
}
102+
103+
cmd = shell.NewSentryCommand(
104+
"--url="+sentryUrl,
105+
"deploys", "new",
106+
// "--url="+filepath.Base(filepath.Dir(dir)), // @todo strategy to get the URL
107+
"--org="+sentryOrg,
108+
"--project="+sentryProject,
109+
"--started="+startedAt.Format(time.RFC3339),
110+
"--finished="+stoppedAt.Format(time.RFC3339),
111+
"--release="+hash,
112+
"--env="+appEnv,
113+
).
114+
WithLogger(logger).
115+
WithLogLevel(0)
116+
117+
if err := cmd.Run(); err != nil {
118+
fmt.Fprintf(os.Stderr, "Error executing sentry-cli: %v\n", err)
119+
os.Exit(cmd.ExitCode())
120+
}
121+
}

macro/write_deployment_info.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package macro
2+
3+
import (
4+
"github.com/SoureCode/kyx/internal/php"
5+
"github.com/SoureCode/kyx/project"
6+
"github.com/SoureCode/kyx/shell"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
func WriteDeploymentInfo(repo string) {
13+
p := project.GetProject()
14+
dir := p.GetDirectory()
15+
logger := shell.GetLogger()
16+
17+
if !filepath.IsAbs(repo) {
18+
repo = filepath.Clean(filepath.Join(dir, repo))
19+
}
20+
21+
deploymentFile := filepath.Join(dir, "deployment.php")
22+
deploymentInfo, err := php.LoadFileDump(deploymentFile)
23+
24+
if err != nil {
25+
logger.Errorln("Error loading deployment info:", err)
26+
// fail as the deployment info is required
27+
os.Exit(1)
28+
}
29+
30+
cmd := shell.NewGitCommand("-C", repo, "rev-parse", "HEAD").
31+
WithLogger(logger).
32+
WithLogLevel(0)
33+
34+
if err := cmd.Run(); err != nil {
35+
logger.Errorln("Error getting git commit hash:", err)
36+
os.Exit(1)
37+
}
38+
39+
deploymentInfo["git_commit"] = strings.TrimSpace(cmd.Stdout())
40+
41+
if err := php.WriteFileDump(deploymentFile, deploymentInfo); err != nil {
42+
logger.Errorln("Error writing deployment info:", err)
43+
os.Exit(1)
44+
} else {
45+
logger.Infoln("Deployment info written to", deploymentFile)
46+
}
47+
}

0 commit comments

Comments
 (0)