Skip to content

Commit ca01979

Browse files
committed
Rewrite in go initial work
1 parent 2ca8e5a commit ca01979

11 files changed

Lines changed: 522 additions & 0 deletions

File tree

.gitte-env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
REMOTE_GIT_FILE="gitte.yml"
2+
REMOTE_GIT_REPO="git@gitlab.cego.dk:cego/local-helper-configs.git"
3+
REMOTE_GIT_REF="master"

cmd.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package executor
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"os/exec"
7+
)
8+
9+
type ExecuteResult struct {
10+
ExitCode int8
11+
Stdout []byte
12+
Stderr []byte
13+
}
14+
15+
func ExecuteSync(command string, args ...string) (*ExecuteResult, error) {
16+
cmd := exec.Command(command, args...)
17+
var stdout bytes.Buffer
18+
var stderr bytes.Buffer
19+
20+
cmd.Stdout = &stdout
21+
cmd.Stderr = &stderr
22+
23+
err := cmd.Run()
24+
25+
// if the error is of type *exec.ExitError, we can get the exit code
26+
if err != nil {
27+
var exitCode int8
28+
var exitError *exec.ExitError
29+
if errors.As(err, &exitError) {
30+
exitCode = int8(exitError.ExitCode())
31+
}
32+
return &ExecuteResult{
33+
ExitCode: exitCode,
34+
Stdout: stdout.Bytes(),
35+
Stderr: stderr.Bytes(),
36+
}, nil
37+
}
38+
39+
return &ExecuteResult{
40+
ExitCode: 0,
41+
Stdout: stdout.Bytes(),
42+
Stderr: stderr.Bytes(),
43+
}, nil
44+
}

cmd/gitops.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"gitte/config"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// gitopsCmd represents the gitops command
12+
var gitopsCmd = &cobra.Command{
13+
Use: "gitops",
14+
Short: "GitOps refer to GitOperations and will ensure that the enabled projects are cloned and up to date if possible.",
15+
Run: func(cmd *cobra.Command, args []string) {
16+
fmt.Println("gitops called")
17+
fs := os.DirFS(".")
18+
fd, err := config.ResolveGitteDir(fs)
19+
if err != nil {
20+
fmt.Println("Error resolving .gitte dir:", err)
21+
return
22+
}
23+
24+
gitteConfig, err := config.LoadConfig(fd)
25+
if err != nil {
26+
fmt.Println("Error loading gitte config:", err)
27+
return
28+
}
29+
30+
fmt.Println("Loaded Gitte Config:", gitteConfig)
31+
},
32+
}
33+
34+
func init() {
35+
rootCmd.AddCommand(gitopsCmd)
36+
}

cmd/root.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
// rootCmd represents the base command when called without any subcommands
10+
var rootCmd = &cobra.Command{
11+
Use: "gitte",
12+
Short: "Tool for managing monorepo-like structured git repositories.",
13+
Long: `Gitte is a tool for managing monorepo-like structured git repositories with inter-project dependencies.
14+
15+
For example in a microservice environment
16+
- Service A requires a database
17+
- Service B requires service A and kafka
18+
- Gitte will make sure that kafka and database starts first, then service a, then service b. Ensuring maximum parallelity in the dependency resolvement.
19+
20+
Gitte also contain other sub commands for utilities managing such a setup.
21+
`}
22+
23+
// Execute adds all child commands to the root command and sets flags appropriately.
24+
// This is called by main.main(). It only needs to happen once to the rootCmd.
25+
func Execute() {
26+
err := rootCmd.Execute()
27+
if err != nil {
28+
os.Exit(1)
29+
}
30+
}
31+
32+
func init() {
33+
// Here you will define your flags and configuration settings.
34+
// Cobra supports persistent flags, which, if defined here,
35+
// will be global for your application.
36+
37+
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.gitte.yaml)")
38+
39+
// Cobra also supports local flags, which will only run
40+
// when this action is called directly.
41+
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
42+
}

config/loader.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package config
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"errors"
7+
"fmt"
8+
"gitte/executor"
9+
"io"
10+
"io/fs"
11+
"os"
12+
"strings"
13+
14+
"github.com/joho/godotenv"
15+
)
16+
17+
type GitteConfig struct {
18+
}
19+
20+
type GitteContext struct {
21+
config *GitteConfig
22+
cwd string
23+
}
24+
25+
const ConfigPath = ".gitte.yml"
26+
const DotEnvPath = ".gitte-env"
27+
const OverridePath = ".gitte.override.yml"
28+
const CacheDir = ".gitte" // TODO maybe make migration from .gitte-cache.json
29+
30+
func LoadConfig(fd FileDefinition) (*GitteConfig, error) {
31+
content := fd.ConfigContent
32+
if fd.IsEnv {
33+
dotenv, err := godotenv.Parse(strings.NewReader(content))
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
// Assert required fields
39+
remoteGitRepo, ok := dotenv["REMOTE_GIT_REPO"]
40+
if !ok || remoteGitRepo == "" {
41+
return nil, errors.New("REMOTE_GIT_REPO isn't defined in .gitte.env")
42+
}
43+
remoteGitFile, ok := dotenv["REMOTE_GIT_FILE"]
44+
if !ok || remoteGitFile == "" {
45+
return nil, errors.New("REMOTE_GIT_FILE isn't defined in .gitte.env")
46+
}
47+
remoteGitRef, ok := dotenv["REMOTE_GIT_REF"]
48+
if !ok || remoteGitRef == "" {
49+
return nil, errors.New("REMOTE_GIT_REF isn't defined in .gitte.env")
50+
}
51+
52+
fmt.Println("Executing command to fetch remote gitte config:", "git", "archive", "--remote="+remoteGitRepo, remoteGitRef, remoteGitFile)
53+
54+
res, err := executor.ExecuteSync("git", "archive", "--remote="+remoteGitRepo, remoteGitRef, remoteGitFile)
55+
56+
if err != nil {
57+
return nil, fmt.Errorf("failed to execute git archive command: %w", err)
58+
}
59+
60+
// Untar the result to extract the file content
61+
tarReader := tar.NewReader(bytes.NewReader(res.Stdout))
62+
var fileContent []byte
63+
64+
for {
65+
header, err := tarReader.Next()
66+
if err == io.EOF {
67+
break
68+
}
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to read tar archive: %w", err)
71+
}
72+
73+
// Extract the file matching remoteGitFile
74+
if header.Name == remoteGitFile {
75+
fileContent, err = io.ReadAll(tarReader)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to read file from tar archive: %w", err)
78+
}
79+
break
80+
}
81+
}
82+
83+
if fileContent == nil {
84+
return nil, fmt.Errorf("file %s not found in tar archive", remoteGitFile)
85+
}
86+
87+
content = string(fileContent)
88+
}
89+
90+
return &GitteConfig{}, nil // TODO parse and validate
91+
}
92+
93+
type FileDefinition struct {
94+
ConfigContent string
95+
IsEnv bool
96+
}
97+
98+
var (
99+
ErrGitteConfigNotFound = errors.New("Gitte configuration file not found")
100+
)
101+
102+
func ResolveGitteDir(fs fs.FS) (FileDefinition, error) {
103+
if f, err := fs.Open(DotEnvPath); err == nil {
104+
defer f.Close()
105+
content, err := io.ReadAll(f)
106+
if err != nil {
107+
return FileDefinition{}, err
108+
}
109+
return FileDefinition{ConfigContent: string(content), IsEnv: true}, nil
110+
}
111+
112+
if f, err := fs.Open(ConfigPath); err == nil {
113+
defer f.Close()
114+
content, err := io.ReadAll(f)
115+
if err != nil {
116+
return FileDefinition{}, err
117+
}
118+
return FileDefinition{ConfigContent: string(content), IsEnv: false}, nil
119+
}
120+
121+
parentDir := os.DirFS("..")
122+
if parentDir != fs {
123+
return ResolveGitteDir(parentDir)
124+
}
125+
126+
return FileDefinition{}, ErrGitteConfigNotFound
127+
}

executor/cmd.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package executor
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"os/exec"
7+
)
8+
9+
type ExecuteResult struct {
10+
ExitCode int8
11+
Stdout []byte
12+
Stderr []byte
13+
}
14+
15+
func ExecuteSync(command string, args ...string) (*ExecuteResult, error) {
16+
cmd := exec.Command(command, args...)
17+
var stdout bytes.Buffer
18+
var stderr bytes.Buffer
19+
20+
cmd.Stdout = &stdout
21+
cmd.Stderr = &stderr
22+
23+
err := cmd.Run()
24+
25+
// if the error is of type *exec.ExitError, we can get the exit code
26+
if err != nil {
27+
var exitCode int8
28+
var exitError *exec.ExitError
29+
if errors.As(err, &exitError) {
30+
exitCode = int8(exitError.ExitCode())
31+
}
32+
return &ExecuteResult{
33+
ExitCode: exitCode,
34+
Stdout: stdout.Bytes(),
35+
Stderr: stderr.Bytes(),
36+
}, nil
37+
}
38+
39+
return &ExecuteResult{
40+
ExitCode: 0,
41+
Stdout: stdout.Bytes(),
42+
Stderr: stderr.Bytes(),
43+
}, nil
44+
}

gitops.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"gitte/config"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// gitopsCmd represents the gitops command
12+
var gitopsCmd = &cobra.Command{
13+
Use: "gitops",
14+
Short: "GitOps refer to GitOperations and will ensure that the enabled projects are cloned and up to date if possible.",
15+
Run: func(cmd *cobra.Command, args []string) {
16+
fmt.Println("gitops called")
17+
fs := os.DirFS(".")
18+
fd, err := config.ResolveGitteDir(fs)
19+
if err != nil {
20+
fmt.Println("Error resolving .gitte dir:", err)
21+
return
22+
}
23+
24+
gitteConfig, err := config.LoadConfig(fd)
25+
if err != nil {
26+
fmt.Println("Error loading gitte config:", err)
27+
return
28+
}
29+
30+
fmt.Println("Loaded Gitte Config:", gitteConfig)
31+
},
32+
}
33+
34+
func init() {
35+
rootCmd.AddCommand(gitopsCmd)
36+
}

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module gitte
2+
3+
go 1.25.3
4+
5+
require (
6+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
7+
github.com/joho/godotenv v1.5.1 // indirect
8+
github.com/spf13/cobra v1.10.2 // indirect
9+
github.com/spf13/pflag v1.0.9 // indirect
10+
)

0 commit comments

Comments
 (0)