项目文件夹

文件
Shelley c09695d4e2
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
feat: add micro build and micro deploy commands
micro build:
  - Generates Dockerfiles for services (if not present)
  - Builds container images for all services in micro.mu
  - Supports --tag, --registry, --push flags
  - --compose flag generates docker-compose.yml

micro deploy:
  - Default: deploys with docker-compose
  - --ssh user@host: deploys via SSH (rsync + build on remote)
  - --build: rebuild images before deploying

Complete workflow:
  micro run          # Develop locally
  micro build        # Build images
  micro deploy       # Deploy

Or for simple SSH deploys:
  micro deploy --ssh user@host

Co-authored-by: Shelley <shelley@exe.dev>
2026-01-27 12:39:43 +00:00

252 行
6.1 KiB
Go

// Package build provides the micro build command for building container images
package build
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
"go-micro.dev/v5/cmd"
"go-micro.dev/v5/cmd/micro/run/config"
)
const dockerfileTemplate = `# Auto-generated by micro build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /service %s
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /service /app/service
EXPOSE %d
CMD ["/app/service"]
`
// Build builds container images for services
func Build(c *cli.Context) error {
dir := c.Args().Get(0)
if dir == "" {
dir = "."
}
absDir, err := filepath.Abs(dir)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
// Load config
cfg, err := config.Load(absDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
tag := c.String("tag")
if tag == "" {
tag = "latest"
}
registry := c.String("registry")
push := c.Bool("push")
if cfg != nil && len(cfg.Services) > 0 {
// Build each service from config
for name, svc := range cfg.Services {
svcDir := filepath.Join(absDir, svc.Path)
if err := buildService(name, svcDir, svc.Port, tag, registry, push); err != nil {
return fmt.Errorf("failed to build %s: %w", name, err)
}
}
} else {
// Build single service from current directory
name := filepath.Base(absDir)
if err := buildService(name, absDir, 8080, tag, registry, push); err != nil {
return err
}
}
return nil
}
func buildService(name, dir string, port int, tag, registry string, push bool) error {
if port == 0 {
port = 8080
}
// Generate Dockerfile if not exists
dockerfilePath := filepath.Join(dir, "Dockerfile")
if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) {
fmt.Printf("Generating Dockerfile for %s...\n", name)
// Find the main package path
mainPath := "."
if _, err := os.Stat(filepath.Join(dir, "main.go")); os.IsNotExist(err) {
// Look for cmd/main.go or similar
if _, err := os.Stat(filepath.Join(dir, "cmd", "main.go")); err == nil {
mainPath = "./cmd"
}
}
dockerfile := fmt.Sprintf(dockerfileTemplate, mainPath, port)
if err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0644); err != nil {
return fmt.Errorf("failed to write Dockerfile: %w", err)
}
}
// Build image name
imageName := name + ":" + tag
if registry != "" {
imageName = registry + "/" + imageName
}
fmt.Printf("Building %s...\n", imageName)
// Run docker build
buildCmd := exec.Command("docker", "build", "-t", imageName, dir)
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("docker build failed: %w", err)
}
fmt.Printf("✓ Built %s\n", imageName)
// Push if requested
if push {
fmt.Printf("Pushing %s...\n", imageName)
pushCmd := exec.Command("docker", "push", imageName)
pushCmd.Stdout = os.Stdout
pushCmd.Stderr = os.Stderr
if err := pushCmd.Run(); err != nil {
return fmt.Errorf("docker push failed: %w", err)
}
fmt.Printf("✓ Pushed %s\n", imageName)
}
return nil
}
// GenerateDockerCompose generates a docker-compose.yml from micro.mu config
func GenerateDockerCompose(c *cli.Context) error {
dir := c.Args().Get(0)
if dir == "" {
dir = "."
}
absDir, err := filepath.Abs(dir)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
cfg, err := config.Load(absDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if cfg == nil || len(cfg.Services) == 0 {
return fmt.Errorf("no services found in micro.mu or micro.json")
}
registry := c.String("registry")
tag := c.String("tag")
if tag == "" {
tag = "latest"
}
// Generate docker-compose.yml
var sb strings.Builder
sb.WriteString("# Auto-generated by micro build --compose\n")
sb.WriteString("version: '3.8'\n\n")
sb.WriteString("services:\n")
// Sort by dependencies
sorted, err := cfg.TopologicalSort()
if err != nil {
return err
}
for _, svc := range sorted {
imageName := svc.Name + ":" + tag
if registry != "" {
imageName = registry + "/" + imageName
}
sb.WriteString(fmt.Sprintf(" %s:\n", svc.Name))
sb.WriteString(fmt.Sprintf(" image: %s\n", imageName))
if svc.Port > 0 {
sb.WriteString(fmt.Sprintf(" ports:\n"))
sb.WriteString(fmt.Sprintf(" - \"%d:%d\"\n", svc.Port, svc.Port))
}
if len(svc.Depends) > 0 {
sb.WriteString(" depends_on:\n")
for _, dep := range svc.Depends {
sb.WriteString(fmt.Sprintf(" - %s\n", dep))
}
}
sb.WriteString(" environment:\n")
sb.WriteString(" - MICRO_REGISTRY=mdns\n")
sb.WriteString("\n")
}
output := filepath.Join(absDir, "docker-compose.yml")
if err := os.WriteFile(output, []byte(sb.String()), 0644); err != nil {
return fmt.Errorf("failed to write docker-compose.yml: %w", err)
}
fmt.Printf("✓ Generated %s\n", output)
return nil
}
func init() {
cmd.Register(&cli.Command{
Name: "build",
Usage: "Build container images for services",
Description: `Build creates Docker container images for your services.
With a micro.mu config, builds all services. Without, builds the current directory.
Examples:
micro build # Build all services
micro build --tag v1.0.0 # Build with specific tag
micro build --push # Build and push to registry
micro build --compose # Generate docker-compose.yml`,
Action: func(c *cli.Context) error {
if c.Bool("compose") {
return GenerateDockerCompose(c)
}
return Build(c)
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tag",
Aliases: []string{"t"},
Usage: "Image tag (default: latest)",
Value: "latest",
},
&cli.StringFlag{
Name: "registry",
Aliases: []string{"r"},
Usage: "Container registry (e.g., docker.io/myuser)",
},
&cli.BoolFlag{
Name: "push",
Usage: "Push images after building",
},
&cli.BoolFlag{
Name: "compose",
Usage: "Generate docker-compose.yml instead of building",
},
},
})
}