Skip to content

Commit

Permalink
[skip ci] wip
Browse files Browse the repository at this point in the history
  • Loading branch information
daichitakahashi committed Feb 13, 2023
1 parent 30737ae commit ce2b440
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 71 deletions.
141 changes: 130 additions & 11 deletions compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ package confort
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/daichitakahashi/confort/compose"
"github.com/daichitakahashi/confort/internal/beacon"
"github.com/daichitakahashi/confort/internal/beacon/proto"
"github.com/daichitakahashi/confort/internal/exclusion"
"github.com/daichitakahashi/confort/internal/logging"
"github.com/daichitakahashi/confort/wait"
"github.com/docker/docker/client"
"github.com/lestrrat-go/option"
Expand All @@ -23,15 +31,46 @@ type (
option.Interface
composeIdent
}
// TODO: identOptionProjectDir struct{}
// TODO: identOptionProjectName struct{}
identOptionComposeBackend struct{}
composeOption struct {
identOptionProjectDir struct{}
identOptionProjectName struct{}
identOptionProfiles struct{}
identOptionEnvFile struct{}
identOptionScalingStrategy struct{} // TODO: add option
identOptionComposeBackend struct{}
composeOption struct {
option.Interface
composeIdent
}
)

// ModDir is a special value that indicates the location of go.mod of the test
// target module. Use with WithProjectDir option.
const ModDir = "\000mod\000"

func WithProjectDir(dir ...string) ComposeOption {
return composeOption{
Interface: option.New(identOptionProjectDir{}, dir),
}
}

func WithProjectName(name string) ComposeOption {
return composeOption{
Interface: option.New(identOptionProjectName{}, name),
}
}

func WithProfiles(profiles ...string) ComposeOption {
return composeOption{
Interface: option.New(identOptionProfiles{}, profiles),
}
}

func WithEnvFile(filename string) ComposeOption {
return composeOption{
Interface: option.New(identOptionEnvFile{}, filename),
}
}

func WithComposeBackend(b compose.Backend) ComposeOption {
return composeOption{
Interface: option.New(identOptionComposeBackend{}, b),
Expand All @@ -40,19 +79,77 @@ func WithComposeBackend(b compose.Backend) ComposeOption {

func Compose(ctx context.Context, configFiles []string, opts ...ComposeOption) (*ComposeProject, error) {
var (
be compose.Backend = &composeBackend{}
clientOpts = []client.Opt{
projectDir string
projectName string
profiles []string
envFile string
policy = ResourcePolicyReuse
be compose.Backend = &composeBackend{}
clientOpts = []client.Opt{
client.FromEnv,
}
timeout time.Duration
beaconConn *beacon.Connection
ex = exclusion.NewControl()
skipDeletion bool
)

for _, opt := range opts {
switch opt.Ident() {
case identOptionProjectDir{}:
dir := opt.Value().([]string)
for i := range dir {
// ModDir is a special value.
// Retrieve module file path and use its parent directory as a project directory.
if dir[i] == ModDir {
val, err := resolveGoModDir(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get module directory: %w", err)
}
dir[i] = val
}
}
projectDir = filepath.Join(dir...)
case identOptionProjectName{}:
projectName = opt.Value().(string)
case identOptionProfiles{}:
profiles = opt.Value().([]string)
case identOptionEnvFile{}:
envFile = opt.Value().(string)
case identOptionComposeBackend{}:
be = opt.Value().(compose.Backend)
case identOptionClientOptions{}:
clientOpts = opt.Value().([]client.Opt)
case identOptionDefaultTimeout{}:
timeout = opt.Value().(time.Duration)
case identOptionResourcePolicy{}:
newPolicy := opt.Value().(ResourcePolicy)
if policy != "" && policy != newPolicy {
logging.Infof("resource policy is overwritten by WithResourcePolicy: %q -> %q", policy, newPolicy)
}
policy = newPolicy
case identOptionBeacon{}:
conn, err := beacon.Connect(ctx)
if err != nil {
return nil, err
}
if conn.Enabled() {
ex = exclusion.NewBeaconControl(
proto.NewBeaconServiceClient(conn.Conn),
)
skipDeletion = true
beaconConn = conn
}
}
}

ctx, cancel := applyTimeout(ctx, timeout)
defer cancel()

if !beacon.ValidResourcePolicy(string(policy)) {
return nil, fmt.Errorf("confort: invalid resource policy: %s", policy)
}

// create docker API client
apiClient, err := client.NewClientWithOpts(clientOpts...)
if err != nil {
Expand All @@ -61,11 +158,12 @@ func Compose(ctx context.Context, configFiles []string, opts ...ComposeOption) (
apiClient.NegotiateAPIVersion(ctx)

composer, err := be.Load(ctx, compose.LoadOptions{
ProjectDir: "",
ConfigFiles: nil,
Profiles: nil,
EnvFile: "",
Policy: compose.ResourcePolicy{},
ProjectDir: projectDir,
ProjectName: projectName,
ConfigFiles: configFiles,
Profiles: profiles,
EnvFile: envFile,
Policy: compose.ConvertResourcePolicy(string(policy)), // TODO: 不要ではないか?
})
if err != nil {
return nil, fmt.Errorf("compose: %w", err)
Expand All @@ -74,13 +172,34 @@ func Compose(ctx context.Context, configFiles []string, opts ...ComposeOption) (
return &ComposeProject{
composer: composer,
cli: apiClient,
ex: ex,
}, nil
}

func (c *ComposeProject) Close() error {
return nil
}

func resolveGoModDir(ctx context.Context) (string, error) {
out, err := exec.CommandContext(ctx, "go", "env", "GOMOD").Output()
if err != nil {
return "", fmt.Errorf("failed to get module directory: %w", err)
}
v := strings.TrimSpace(string(out))
if v == os.DevNull {
// If go.mod doesn't exist, use current directory.
return ".", nil
}
_, err = os.Stat(v)
if err != nil {
if os.IsNotExist(err) {
return "", fmt.Errorf("go.mod not found: %s", v)
}
return "", fmt.Errorf("failed to check go.mod: %w", err)
}
return filepath.Dir(v), nil
}

type (
upIdent interface{ up() }
UpOption interface {
Expand Down
48 changes: 36 additions & 12 deletions compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package compose

import (
"context"

"github.com/daichitakahashi/confort/internal/beacon"
)

type (
Backend interface {
Load(ctx context.Context, opts LoadOptions) (Composer, error)
Load(ctx context.Context, config string, opts LoadOptions) (Composer, error)
}

ResourcePolicy struct {
Expand All @@ -15,18 +17,20 @@ type (
Takeover bool
}
LoadOptions struct {
ProjectDir string
ConfigFiles []string
Profiles []string
EnvFile string
Policy ResourcePolicy
ProjectDir string
ProjectName string
OverrideConfigFiles []string
Profiles []string
EnvFile string
Policy ResourcePolicy
ResourceLabel string
ResourceLabelValue string
}

Composer interface {
ProjectName() string
Up(ctx context.Context, service string, opts UpOptions) (*Service, error)
RemoveCreated(ctx context.Context, opts RemoveOptions) error
Down(ctx context.Context, opts DownOptions) error
}

// UpOptions
Expand All @@ -48,18 +52,38 @@ type (
RemoveAnonymousVolumes bool
}

DownOptions struct {
RemoveOrphans bool
RemoveVolumes bool
}

Service struct {
Name string
ContainerIDs []string
Env map[string]string
}
)

func ConvertResourcePolicy(p string) ResourcePolicy {
switch p {
case beacon.ResourcePolicyReuse:
return ResourcePolicy{
AllowReuse: true,
Remove: true,
}
case beacon.ResourcePolicyReusable:
return ResourcePolicy{
AllowReuse: true,
}
case beacon.ResourcePolicyTakeOver:
return ResourcePolicy{
AllowReuse: true,
Remove: true,
Takeover: true,
}
case beacon.ResourcePolicyError:
return ResourcePolicy{
Remove: true,
}
}
panic("unknown policy")
}

type ScalingStrategy int

const (
Expand Down

0 comments on commit ce2b440

Please sign in to comment.