Skip to content

Commit

Permalink
chore: wip, first steps towards #1
Browse files Browse the repository at this point in the history
Add the `Target` entity and refactor a lot of stuff.
Also closes #43 by updating the `update_app` PATCH behavior.
  • Loading branch information
YuukanOO committed Jan 23, 2024
1 parent 406947d commit 4a3ade9
Show file tree
Hide file tree
Showing 46 changed files with 881 additions and 601 deletions.
2 changes: 1 addition & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Key aspects of seelf are:
- Reliable
- Easy to understand

_Althought Docker is the only backend supported at the moment, I would like to investigate to enable other ones too. Remote Docker or Podman for example._
_Althought Docker is the only provider supported at the moment, I would like to investigate to enable other ones too. Remote Docker or Podman for example._

## Installation

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ _The seelf initial public version has some limitation (only local Docker engine,

Got an already working docker compose file for your project ? Just send it to your [seelf](https://github.com/YuukanOO/seelf) instance and _boom_, that's live on your own infrastructure with all services correctly deployed and exposed on nice urls as needed! See [the documentation](DOCUMENTATION.md) for more information.

_Althought Docker is the only backend supported at the moment, I would like to investigate to enable other ones too. Remote Docker or Podman for example._
_Althought Docker is the only provider supported at the moment, I would like to investigate to enable other ones too. Remote Docker or Podman for example._

## Prerequisites

Expand Down
6 changes: 3 additions & 3 deletions internal/deployment/app/cleanup_app/cleanup_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Handler(
reader domain.AppsReader,
writer domain.AppsWriter,
artifactManager domain.ArtifactManager,
backend domain.Backend,
provider domain.Provider,
) bus.RequestHandler[bus.UnitType, Command] {
return func(ctx context.Context, cmd Command) (bus.UnitType, error) {
app, err := reader.GetByID(ctx, domain.AppID(cmd.ID))
Expand All @@ -50,12 +50,12 @@ func Handler(
return bus.Unit, err
}

// Before calling the backend cleanup, make sure the application can be safely deleted.
// Before calling the provider cleanup, make sure the application can be safely deleted.
if err = app.Delete(count); err != nil {
return bus.Unit, err
}

if err = backend.Cleanup(ctx, app); err != nil {
if err = provider.Cleanup(ctx, app); err != nil {
return bus.Unit, err
}

Expand Down
14 changes: 7 additions & 7 deletions internal/deployment/app/cleanup_app/cleanup_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func Test_CleanupApp(t *testing.T) {
os.RemoveAll(opts.DataDir())
})

return cleanup_app.Handler(deploymentsStore, appsStore, appsStore, artifactManager, &dummyBackend{})
return cleanup_app.Handler(deploymentsStore, appsStore, appsStore, artifactManager, &dummyProvider{})
}

t.Run("should returns no error if the application does not exist", func(t *testing.T) {
Expand All @@ -50,7 +50,7 @@ func Test_CleanupApp(t *testing.T) {
})

t.Run("should fail if the application cleanup as not been requested", func(t *testing.T) {
a := domain.NewApp("my-app", "uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "uid")
uc := sut(initialData{
existingApps: []*domain.App{&a},
})
Expand All @@ -64,7 +64,7 @@ func Test_CleanupApp(t *testing.T) {
})

t.Run("should fail if there are still pending or running deployments", func(t *testing.T) {
a := domain.NewApp("my-app", "uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "uid")
depl, _ := a.NewDeployment(1, raw.Data(""), domain.Production, "uid")
a.RequestCleanup("uid")

Expand All @@ -82,7 +82,7 @@ func Test_CleanupApp(t *testing.T) {
})

t.Run("should succeed if everything is good", func(t *testing.T) {
a := domain.NewApp("my-app", "uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "uid")
a.RequestCleanup("uid")

uc := sut(initialData{
Expand All @@ -99,10 +99,10 @@ func Test_CleanupApp(t *testing.T) {
})
}

type dummyBackend struct {
domain.Backend
type dummyProvider struct {
domain.Provider
}

func (d *dummyBackend) Cleanup(ctx context.Context, app domain.App) error {
func (d *dummyProvider) Cleanup(ctx context.Context, app domain.App) error {
return nil
}
43 changes: 32 additions & 11 deletions internal/deployment/app/create_app/create_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ type (
Command struct {
bus.Command[string]

Name string `json:"name"`
VCS monad.Maybe[VCSConfig] `json:"vcs"`
Env monad.Maybe[map[string]map[string]map[string]string] `json:"env"` // This is not so sweet but hey!
Name string `json:"name"`
VCS monad.Maybe[VCSConfig] `json:"vcs"`
Production EnvironmentConfig `json:"production"`
Staging EnvironmentConfig `json:"staging"`
}

EnvironmentConfig struct {
Target string `json:"target"`
Vars monad.Maybe[map[string]map[string]string] `json:"vars"`
}

VCSConfig struct {
Expand All @@ -36,7 +42,6 @@ func Handler(
return func(ctx context.Context, cmd Command) (string, error) {
var (
appname domain.AppName
envs domain.EnvironmentsEnv
url domain.Url
)

Expand All @@ -50,20 +55,29 @@ func Handler(
}),
})
}),
"env": validation.Maybe(cmd.Env, func(envmap map[string]map[string]map[string]string) error {
return validation.Value(envmap, &envs, domain.EnvironmentsEnvFrom)
"production": validation.Check(validation.Of{
"target": validation.Is(cmd.Production.Target, strings.Required), // TODO: check target existence
}),
"staging": validation.Check(validation.Of{
"target": validation.Is(cmd.Staging.Target, strings.Required), // TODO: check target existence
}),
}); err != nil {
return "", err
}

// TODO: maybe the validation can be done when checking above
uniqueName, err := reader.IsNameUnique(ctx, appname)

if err != nil {
return "", validation.WrapIfAppErr(err, "name")
}

app := domain.NewApp(uniqueName, auth.CurrentUser(ctx).MustGet())
app := domain.NewApp(
uniqueName,
BuildEnvironmentConfig(domain.TargetID(cmd.Production.Target), cmd.Production.Vars),
BuildEnvironmentConfig(domain.TargetID(cmd.Staging.Target), cmd.Staging.Vars),
auth.CurrentUser(ctx).MustGet(),
)

if cmdVCS, isSet := cmd.VCS.TryGet(); isSet {
vcs := domain.NewVCSConfig(url)
Expand All @@ -75,14 +89,21 @@ func Handler(
app.UseVersionControl(vcs)
}

if cmd.Env.HasValue() {
app.HasEnvironmentVariables(envs)
}

if err := writer.Write(ctx, &app); err != nil {
return "", err
}

return string(app.ID()), nil
}
}

// Helper method to build a domain.EnvironmentConfig from a raw command value.
func BuildEnvironmentConfig(target domain.TargetID, env monad.Maybe[map[string]map[string]string]) domain.EnvironmentConfig {
config := domain.NewEnvironmentConfig(target)

if vars, hasVars := env.TryGet(); hasVars {
config = config.WithEnvironmentVariables(domain.ServicesEnvFrom(vars))
}

return config
}
14 changes: 13 additions & 1 deletion internal/deployment/app/create_app/create_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ func Test_CreateApp(t *testing.T) {
})

t.Run("should fail if the name is already taken", func(t *testing.T) {
a := domain.NewApp("my-app", "uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "uid")
uc := sut(&a)

id, err := uc(ctx, create_app.Command{
Name: "my-app",
Production: create_app.EnvironmentConfig{
Target: "production-target",
},
Staging: create_app.EnvironmentConfig{
Target: "staging-target",
},
})

validationErr, ok := apperr.As[validation.Error](err)
Expand All @@ -47,6 +53,12 @@ func Test_CreateApp(t *testing.T) {
uc := sut()
id, err := uc(ctx, create_app.Command{
Name: "my-app",
Production: create_app.EnvironmentConfig{
Target: "production-target",
},
Staging: create_app.EnvironmentConfig{
Target: "staging-target",
},
})

testutil.IsNil(t, err)
Expand Down
17 changes: 8 additions & 9 deletions internal/deployment/app/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Handler(
writer domain.DeploymentsWriter,
artifactManager domain.ArtifactManager,
source domain.Source,
backend domain.Backend,
provider domain.Provider,
) bus.RequestHandler[bus.UnitType, Command] {
return func(ctx context.Context, cmd Command) (result bus.UnitType, finalErr error) {
result = bus.Unit
Expand All @@ -54,9 +54,8 @@ func Handler(
}

var (
buildDirectory string
logger domain.DeploymentLogger
services domain.Services
deploymentCtx domain.DeploymentContext
services domain.Services
)

// This one is a special case to avoid to avoid many branches
Expand Down Expand Up @@ -85,19 +84,19 @@ func Handler(
}()

// Prepare the build directory
if buildDirectory, logger, finalErr = artifactManager.PrepareBuild(ctx, depl); finalErr != nil {
if deploymentCtx, finalErr = artifactManager.PrepareBuild(ctx, depl); finalErr != nil {
return
}

defer logger.Close()
defer deploymentCtx.Logger().Close()

// Fetch deployment files
if finalErr = source.Fetch(ctx, buildDirectory, logger, depl); finalErr != nil {
if finalErr = source.Fetch(ctx, deploymentCtx, depl); finalErr != nil {
return
}

// Ask the backend to actually deploy the app
if services, finalErr = backend.Run(ctx, buildDirectory, logger, depl); finalErr != nil {
// Ask the provider to actually deploy the app
if services, finalErr = provider.Run(ctx, deploymentCtx, depl); finalErr != nil {
return
}

Expand Down
46 changes: 25 additions & 21 deletions internal/deployment/app/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (

func Test_Deploy(t *testing.T) {
ctx := auth.WithUserID(context.Background(), "some-uid")
a := domain.NewApp("my-app", "some-uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "some-uid")
logger, _ := log.NewLogger()

sut := func(
source domain.Source,
backend domain.Backend,
provider domain.Provider,
existingDeployments ...*domain.Deployment,
) bus.RequestHandler[bus.UnitType, deploy.Command] {
opts := config.Default(config.WithTestDefaults())
Expand All @@ -37,11 +37,11 @@ func Test_Deploy(t *testing.T) {
os.RemoveAll(opts.DataDir())
})

return deploy.Handler(store, store, artifactManager, source, backend)
return deploy.Handler(store, store, artifactManager, source, provider)
}

t.Run("should fail if the deployment does not exists", func(t *testing.T) {
uc := sut(source(nil), backend(nil))
uc := sut(source(nil), provider(nil))
r, err := uc(ctx, deploy.Command{})

testutil.ErrorIs(t, apperr.ErrNotFound, err)
Expand All @@ -51,9 +51,9 @@ func Test_Deploy(t *testing.T) {
t.Run("should mark the deployment has failed if source does not succeed", func(t *testing.T) {
srcErr := errors.New("source_failed")
src := source(srcErr)
meta, _ := src.Prepare(a, 42)
meta, _ := src.Prepare(ctx, a, 42)
depl, _ := a.NewDeployment(1, meta, domain.Production, "some-uid")
uc := sut(src, backend(nil), &depl)
uc := sut(src, provider(nil), &depl)

r, err := uc(ctx, deploy.Command{
AppID: string(a.ID()),
Expand All @@ -70,11 +70,11 @@ func Test_Deploy(t *testing.T) {
testutil.Equals(t, domain.DeploymentStatusFailed, evt.State.Status())
})

t.Run("should mark the deployment has failed if backend does not run the deployment successfuly", func(t *testing.T) {
backendErr := errors.New("run_failed")
be := backend(backendErr)
t.Run("should mark the deployment has failed if provider does not run the deployment successfuly", func(t *testing.T) {
providerErr := errors.New("run_failed")
be := provider(providerErr)
src := source(nil)
meta, _ := src.Prepare(a, 42)
meta, _ := src.Prepare(ctx, a, 42)
depl, _ := a.NewDeployment(1, meta, domain.Production, "some-uid")
uc := sut(src, be, &depl)

Expand All @@ -83,20 +83,20 @@ func Test_Deploy(t *testing.T) {
DeploymentNumber: 1,
})

testutil.ErrorIs(t, backendErr, err)
testutil.ErrorIs(t, providerErr, err)
testutil.Equals(t, bus.Unit, r)
evt := testutil.EventIs[domain.DeploymentStateChanged](t, &depl, 2)
testutil.IsTrue(t, evt.State.StartedAt().HasValue())
testutil.IsTrue(t, evt.State.FinishedAt().HasValue())
testutil.Equals(t, backendErr.Error(), evt.State.ErrCode().MustGet())
testutil.Equals(t, providerErr.Error(), evt.State.ErrCode().MustGet())
testutil.Equals(t, domain.DeploymentStatusFailed, evt.State.Status())
})

t.Run("should mark the deployment has succeeded if all is good", func(t *testing.T) {
src := source(nil)
meta, _ := src.Prepare(a, 42)
meta, _ := src.Prepare(ctx, a, 42)
depl, _ := a.NewDeployment(1, meta, domain.Production, "some-uid")
uc := sut(src, backend(nil), &depl)
uc := sut(src, provider(nil), &depl)

r, err := uc(ctx, deploy.Command{
AppID: string(a.ID()),
Expand All @@ -120,26 +120,30 @@ func source(failedWithErr error) domain.Source {
return &dummySource{failedWithErr}
}

func (*dummySource) Prepare(domain.App, any) (domain.SourceData, error) {
func (*dummySource) Prepare(context.Context, domain.App, any) (domain.SourceData, error) {
return raw.Data(""), nil
}

func (t *dummySource) Fetch(context.Context, string, domain.DeploymentLogger, domain.Deployment) error {
func (t *dummySource) Fetch(context.Context, domain.DeploymentContext, domain.Deployment) error {
return t.err
}

type dummyBackend struct {
type dummyProvider struct {
err error
}

func backend(failedWithErr error) domain.Backend {
return &dummyBackend{failedWithErr}
func provider(failedWithErr error) domain.Provider {
return &dummyProvider{failedWithErr}
}

func (b *dummyBackend) Run(context.Context, string, domain.DeploymentLogger, domain.Deployment) (domain.Services, error) {
func (b *dummyProvider) Prepare(context.Context, any) (domain.ProviderConfig, error) {
return nil, nil
}

func (b *dummyProvider) Run(context.Context, domain.DeploymentContext, domain.Deployment) (domain.Services, error) {
return domain.Services{}, b.err
}

func (b *dummyBackend) Cleanup(context.Context, domain.App) error {
func (b *dummyProvider) Cleanup(context.Context, domain.App) error {
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

func Test_FailRunningDeployments(t *testing.T) {
ctx := auth.WithUserID(context.Background(), "some-uid")
a := domain.NewApp("my-app", "some-uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "some-uid")

sut := func(existingDeployments ...*domain.Deployment) bus.RequestHandler[bus.UnitType, fail_running_deployments.Command] {
deploymentsStore := memory.NewDeploymentsStore(existingDeployments...)
Expand Down
2 changes: 1 addition & 1 deletion internal/deployment/app/promote/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

func Test_Promote(t *testing.T) {
ctx := auth.WithUserID(context.Background(), "some-uid")
a := domain.NewApp("my-app", "some-uid")
a := domain.NewApp("my-app", domain.NewEnvironmentConfig("1"), domain.NewEnvironmentConfig("1"), "some-uid")
appsStore := memory.NewAppsStore(&a)

sut := func(existingDeployments ...*domain.Deployment) bus.RequestHandler[int, promote.Command] {
Expand Down
Loading

0 comments on commit 4a3ade9

Please sign in to comment.