micro--go-micro
c09695d4e2
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
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>
252 行
6.1 KiB
Go
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",
|
|
},
|
|
},
|
|
})
|
|
}
|