295 lines
7.3 KiB
Go
295 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func getContainerForCurrentDirectory() (string, error) {
|
|
// Get all hako containers
|
|
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Names}}")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
containers := strings.Split(strings.TrimSpace(output), "\n")
|
|
if len(containers) == 0 || containers[0] == "" {
|
|
return "", fmt.Errorf("no hako containers found")
|
|
}
|
|
|
|
// Try to find containers that match any supported language for the current directory
|
|
supportedLangs := []string{"go", "py", "python"}
|
|
|
|
for _, lang := range supportedLangs {
|
|
expectedName, err := getProjectContainerName(lang)
|
|
if err != nil {
|
|
continue // Skip if we can't generate a name for this language
|
|
}
|
|
|
|
// Check if this expected container name exists in our list
|
|
for _, container := range containers {
|
|
if strings.TrimSpace(container) == expectedName {
|
|
return expectedName, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no exact match found, return the first container as fallback
|
|
return strings.TrimSpace(containers[0]), nil
|
|
}
|
|
|
|
func initCommand(lang string) error {
|
|
if lang == "" {
|
|
fmt.Println("Building base hako image...")
|
|
return buildBaseImage()
|
|
}
|
|
|
|
if !imageExists("hako-userland") {
|
|
fmt.Println("Base image not found, building it first...")
|
|
if err := buildBaseImage(); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Check base image version
|
|
checkVersionMismatch("hako-userland")
|
|
}
|
|
|
|
fmt.Printf("Building %s language image...\n", lang)
|
|
return buildLanguageImage(lang)
|
|
}
|
|
|
|
func upCommand(lang string) error {
|
|
containerName, err := getProjectContainerName(lang)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
imageName := fmt.Sprintf("hako-userland-%s", lang)
|
|
if !imageExists(imageName) {
|
|
return fmt.Errorf("image %s not found, run 'hako init %s' first", imageName, lang)
|
|
}
|
|
|
|
// Check for version mismatch
|
|
checkVersionMismatch(imageName)
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if containerExists(containerName) {
|
|
if !containerIsRunning(containerName) {
|
|
fmt.Printf("Starting existing container %s...\n", containerName)
|
|
if err := startContainer(containerName); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
fmt.Printf("Container %s is already running\n", containerName)
|
|
}
|
|
} else {
|
|
fmt.Printf("Creating new container %s...\n", containerName)
|
|
if err := createContainer(containerName, imageName, cwd); err != nil {
|
|
return err
|
|
}
|
|
if err := startContainer(containerName); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Copying workspace to container...\n")
|
|
if err := copyWorkspaceToContainer(containerName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Entering container %s...\n", containerName)
|
|
return execContainer(containerName)
|
|
}
|
|
|
|
func downCommand(container string) error {
|
|
var containerName string
|
|
|
|
if container != "" {
|
|
containerName = container
|
|
} else {
|
|
// Find the appropriate hako container for current directory
|
|
var err error
|
|
containerName, err = getContainerForCurrentDirectory()
|
|
if err != nil {
|
|
return fmt.Errorf("no hako containers found for current project: %v", err)
|
|
}
|
|
}
|
|
|
|
if !containerExists(containerName) {
|
|
return fmt.Errorf("container %s does not exist", containerName)
|
|
}
|
|
|
|
fmt.Printf("Stopping container %s...\n", containerName)
|
|
if containerIsRunning(containerName) {
|
|
if err := stopContainer(containerName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Removing container %s...\n", containerName)
|
|
return removeContainer(containerName)
|
|
}
|
|
|
|
func psCommand() error {
|
|
fmt.Println("Hako containers:")
|
|
if err := listHakoContainers(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check for version mismatches in running containers
|
|
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Image}}")
|
|
if err == nil {
|
|
images := strings.Split(strings.TrimSpace(output), "\n")
|
|
checkedImages := make(map[string]bool)
|
|
for _, image := range images {
|
|
if image != "" && !checkedImages[image] {
|
|
checkVersionMismatch(image)
|
|
checkedImages[image] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func syncCommand() error {
|
|
// Find the appropriate hako container for current directory
|
|
containerName, err := getContainerForCurrentDirectory()
|
|
if err != nil {
|
|
return fmt.Errorf("no hako containers found for current project: %v", err)
|
|
}
|
|
|
|
if !containerExists(containerName) {
|
|
return fmt.Errorf("container %s does not exist", containerName)
|
|
}
|
|
|
|
fmt.Printf("Syncing files from container %s...\n", containerName)
|
|
|
|
tempDir, err := os.MkdirTemp("", "hako-sync-*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
if err := copyFromContainer(containerName, "/workspace/.", tempDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
return syncFiles(tempDir, ".")
|
|
}
|
|
|
|
func syncFiles(srcDir, destDir string) error {
|
|
gitignore, err := NewGitIgnore()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse .gitignore: %v", err)
|
|
}
|
|
|
|
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return fmt.Errorf("failed to walk %q: %v", path, err)
|
|
}
|
|
|
|
relPath, err := filepath.Rel(srcDir, path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to calculate relative path for %q: %v", path, err)
|
|
}
|
|
|
|
// Skip .git directory and its contents
|
|
if relPath == ".git" {
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
// Skip files that start with .git (like .gitignore, .gitmodules, etc.)
|
|
if strings.HasPrefix(filepath.Base(relPath), ".git") {
|
|
return nil
|
|
}
|
|
|
|
// Check if the file should be ignored according to .gitignore
|
|
if gitignore.IsIgnored(relPath) {
|
|
if info.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
destPath := filepath.Join(destDir, relPath)
|
|
|
|
if info.IsDir() {
|
|
return os.MkdirAll(destPath, info.Mode())
|
|
}
|
|
|
|
fmt.Println("cp", path, destPath)
|
|
srcFile, err := os.Open(path)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to open %s: %v\n", path, err)
|
|
return nil
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
destFile, err := os.Create(destPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to create %s: %v\n", destPath, err)
|
|
return nil
|
|
}
|
|
defer destFile.Close()
|
|
|
|
_, err = io.Copy(destFile, srcFile)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to copy %s to %s: %v\n", path, destPath, err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func copyWorkspaceToContainer(containerName string) error {
|
|
// Get all files that should be copied (tracked + untracked, excluding ignored)
|
|
trackedFiles, err := runCommandOutput("git", "ls-files")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
untrackedFiles, err := runCommandOutput("git", "ls-files", "--others", "--exclude-standard")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allFiles := []string{}
|
|
if trackedFiles != "" {
|
|
allFiles = append(allFiles, strings.Split(trackedFiles, "\n")...)
|
|
}
|
|
if untrackedFiles != "" {
|
|
allFiles = append(allFiles, strings.Split(untrackedFiles, "\n")...)
|
|
}
|
|
|
|
if len(allFiles) == 0 {
|
|
return fmt.Errorf("no files to copy")
|
|
}
|
|
|
|
// Copy files one by one to preserve directory structure
|
|
for _, file := range allFiles {
|
|
if file == "" {
|
|
continue
|
|
}
|
|
|
|
// Create directory structure in container if needed
|
|
dir := filepath.Dir(file)
|
|
if dir != "." {
|
|
if err := runCommand("docker", "exec", containerName, "mkdir", "-p", "/workspace/"+dir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Copy the file
|
|
if err := copyToContainer(containerName, file, "/workspace/"+file); err != nil {
|
|
return fmt.Errorf("failed to copy %s: %v", file, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|