diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bae9b30..ea38d75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24" + go-version: "1.25" - name: Run tests run: go test -v ./... @@ -35,7 +35,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24" + go-version: "1.25" - name: Build binaries for all platforms run: | @@ -57,6 +57,10 @@ jobs: echo "Building Linux amd64..." GOOS=linux GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o dist/devenv-linux-amd64 ./cmd/devenv + # Linux arm64 + echo "Building Linux arm64..." + GOOS=linux GOARCH=arm64 go build -ldflags="${LDFLAGS}" -o dist/devenv-linux-arm64 ./cmd/devenv + # macOS amd64 (Intel) echo "Building macOS amd64..." GOOS=darwin GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o dist/devenv-darwin-amd64 ./cmd/devenv @@ -72,14 +76,21 @@ jobs: echo "✅ All binaries built successfully" ls -lh dist/ + - name: Generate checksums + run: | + cd dist + sha256sum * > checksums.txt + - name: Create Release uses: softprops/action-gh-release@v2 with: files: | dist/devenv-linux-amd64 + dist/devenv-linux-arm64 dist/devenv-darwin-amd64 dist/devenv-darwin-arm64 dist/devenv-windows-amd64.exe + dist/checksums.txt generate_release_notes: true draft: false prerelease: false diff --git a/internal/config/types.go b/internal/config/types.go index 1b2c7e5..6609cf4 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -17,6 +17,9 @@ type BaseConfig struct { // Package management Packages PackageConfig `yaml:"packages,omitempty"` + // Git repos to be cloned + GitRepos []GitRepo `yaml:"gitRepos,omitempty" validate:"dive"` + // Storage configuration Volumes []VolumeMount `yaml:"volumes,omitempty" validate:"dive"` @@ -68,9 +71,18 @@ type GitConfig struct { type PackageConfig struct { Python []string `yaml:"python,omitempty" validate:"dive,min=1"` APT []string `yaml:"apt,omitempty" validate:"dive,min=1"` + Brew []string `yaml:"brew,omitempty" validate:"dive,min=1"` // Consider adding other package managers such as NPM, Yarn, etc. } +type GitRepo struct { + URL string `yaml:"url" validate:"required,min=1,url"` + Branch string `yaml:"branch,omitempty" validate:"omitempty,min=1"` + Tag string `yaml:"tag,omitempty" validate:"omitempty,min=1"` + CommitHash string `yaml:"commitHash,omitempty" validate:"omitempty,min=1"` + Directory string `yaml:"directory,omitempty" validate:"omitempty,min=1,filepath"` +} + // ResourceConfig represents resource allocation type ResourceConfig struct { CPU any `yaml:"cpu,omitempty" validate:"omitempty,k8s_cpu"` @@ -112,7 +124,9 @@ func NewBaseConfigWithDefaults() BaseConfig { Packages: PackageConfig{ Python: []string{}, // Empty slice - no default packages APT: []string{}, // Empty slice - no default packages + Brew: []string{}, // Empty slice - no default packages }, + GitRepos: []GitRepo{}, // Empty slice - no default git repositories Volumes: []VolumeMount{}, // Empty slice - no default volumes Namespace: "devenv", // Default namespace EnvironmentName: "development", // Default environment name diff --git a/internal/config/validation.go b/internal/config/validation.go index 989cdad..9df4428 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -56,6 +56,7 @@ func init() { if err := validate.RegisterValidation("k8s_memory", validateKubernetesMemory); err != nil { panic(fmt.Errorf("register validator k8s_memory: %w", err)) } + validate.RegisterStructValidation(validateGitRepo, GitRepo{}) } // validateSSHKeys implements the "ssh_keys" tag. @@ -79,6 +80,35 @@ func validateSSHKeys(fl validator.FieldLevel) bool { return true } +// validateGitRepo implements the "git_repo" tag. +// Ensures that if both Branch and CommitHash are specified, an error is raised. +func validateGitRepo(sl validator.StructLevel) { + repo := sl.Current().Interface().(GitRepo) + // Both Ref and CommitHash cannot be specified simultaneously. + targets := []string{} + if repo.Branch != "" { + targets = append(targets, repo.Branch) + } + if repo.Tag != "" { + targets = append(targets, repo.Tag) + } + if repo.CommitHash != "" { + targets = append(targets, repo.CommitHash) + } + + if len(targets) > 1 { + if repo.Branch != "" { + sl.ReportError(repo.Branch, "branch", "Branch", "too many target specifications", "") + } + if repo.Tag != "" { + sl.ReportError(repo.Tag, "tag", "Tag", "too many target specifications", "") + } + if repo.CommitHash != "" { + sl.ReportError(repo.CommitHash, "commitHash", "CommitHash", "too many target specifications", "") + } + } +} + // validateKubernetesCPU implements the "k8s_cpu" tag for *raw* CPU fields. // Accepts: // - Strings: "", "unlimited", plain number ("2", "2.5"), or millicores ("500m") diff --git a/internal/templates/template_files/dev/scripts/templated/startup.sh b/internal/templates/template_files/dev/scripts/templated/startup.sh index d72e25c..48aa35b 100644 --- a/internal/templates/template_files/dev/scripts/templated/startup.sh +++ b/internal/templates/template_files/dev/scripts/templated/startup.sh @@ -145,6 +145,11 @@ echo "Installing Python packages: {{range $i, $pkg := .Packages.Python}}{{if gt /bin/bash /scripts/run_with_git.sh ${DEV_USERNAME} ${PYTHON_PATH} -m pip install --no-user --no-cache-dir{{range .Packages.Python}} {{.}}{{end}} {{- end}} +{{- if gt (len .Packages.Brew) 0}} +echo "Installing Homebrew packages: {{range $i, $pkg := .Packages.Brew}}{{if gt $i 0}} {{end}}{{$pkg}}{{end}}" +sudo -u ${DEV_USERNAME} brew install{{range .Packages.Brew}} {{.}}{{end}} +{{- end}} + echo "Section 6: Package installation complete" # === USER ENVIRONMENT SETUP === @@ -181,6 +186,47 @@ chown -R ${DEV_USERNAME}:${DEV_USERNAME} /home/${DEV_USERNAME}/.vscode-server echo "Section 8: VSCode configuration complete" +# === GIT REPO CLONING === +{{- if gt (len .GitRepos) 0}} +echo "Cloning Git repositories" +{{- range .GitRepos}} +echo "Cloning repository: {{.URL}}" + +{{- /* Determine target directory */ -}} +{{- $targetDir := "" -}} +{{- if .Directory -}} + {{- $targetDir = .Directory -}} +{{- else -}} + {{- $targetDir = printf "/home/%s" $.Name -}} +{{- end }} + +# Clone the complete repository +git clone {{.URL}} {{$targetDir}} +cd {{$targetDir}} + +{{- /* Checkout specific reference */ -}} +{{- if .Tag}} +echo "Checking out tag: {{.Tag}}" +git checkout tags/{{.Tag}} +{{- else if .CommitHash}} +echo "Checking out commit: {{.CommitHash}}" +git checkout {{.CommitHash}} +{{- else if .Branch}} +echo "Checking out branch: {{.Branch}}" +git checkout {{.Branch}} +{{- else}} +echo "Staying on default branch" +{{- end}} + +echo "Repository cloned successfully to: {{$targetDir}}" +echo "Current commit: $(git rev-parse --short HEAD)" +echo "Current branch/ref: $(git branch --show-current 2>/dev/null || git describe --tags --exact-match 2>/dev/null || echo 'detached HEAD')" + +{{- end}} +{{- else}} +echo "No Git repositories to clone" +{{- end}} + # === SSH SERVER LAUNCH === echo "Starting SSH server" /usr/sbin/sshd -D \ No newline at end of file diff --git a/internal/templates/template_files/system/manifests/namespace.tmpl b/internal/templates/template_files/system/manifests/namespace.tmpl index b0ac03b..19fb4cd 100644 --- a/internal/templates/template_files/system/manifests/namespace.tmpl +++ b/internal/templates/template_files/system/manifests/namespace.tmpl @@ -2,6 +2,6 @@ apiVersion: v1 kind: Namespace metadata: name: {{.Namespace}} - environment: {{.EnvironmentName}} annotations: - description: "Namespace for DevENV resources" \ No newline at end of file + description: "Namespace for DevENV resources" + environment: {{.EnvironmentName}} diff --git a/internal/templates/testdata/golden/startup-scripts.yaml b/internal/templates/testdata/golden/startup-scripts.yaml index b55ae86..c7eac05 100644 --- a/internal/templates/testdata/golden/startup-scripts.yaml +++ b/internal/templates/testdata/golden/startup-scripts.yaml @@ -151,6 +151,9 @@ data: echo "Section 8: VSCode configuration complete" + # === GIT REPO CLONING === + echo "No Git repositories to clone" + # === SSH SERVER LAUNCH === echo "Starting SSH server" /usr/sbin/sshd -D