Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat: project deployment versions
Browse files Browse the repository at this point in the history
  • Loading branch information
saitho committed May 28, 2023
1 parent 3e602c1 commit e2053dc
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 157 deletions.
21 changes: 18 additions & 3 deletions commands/project/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package project

import (
"fmt"
xfs "github.com/saitho/golang-extended-fs/v2"

"github.com/spf13/cobra"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -50,26 +51,40 @@ var DeployApplication = func() *cobra.Command {
}

if autoConfirm {
_ = taskRunner.RunTask(routines.CreateResources)
err = taskRunner.RunTask(routines.CreateResources)
} else {
// Confirm resource creation
fmt.Println("\nStackHead will try to create or update the following resources:")
for _, resourceGroup := range system.Context.Resources {
for _, resourceGroup := range system.Context.CurrentDeployment.ResourceGroups {
for _, resource := range resourceGroup.Resources {
fmt.Println(fmt.Sprintf("- %s", resource.ToString(false)))
}
}
fmt.Println("")
fmt.Print("Please confirm with \"y\" or \"yes\": ")
if askForConfirmation() {
_ = taskRunner.RunTask(routines.CreateResources)
err = taskRunner.RunTask(routines.CreateResources)
} else {
// Abort deployment -> delete
if err := xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true); err != nil {
fmt.Print("Unable to remove deployment directory.")
return
}
fmt.Print("Deployment aborted.")
return
}
}

if err != nil {
// ensure rollback is performed due to errors caused by StackHead module Run scripts not resource creation
routines.RollbackResources.Disabled = false
}

if !noRollback {
// Rollback may be skipped if CreateResources does not trigger a rollback
_ = taskRunner.RunTask(routines.RollbackResources)
}
_ = taskRunner.RunTask(routines.FinalizeDeployment)
},
}
command.PersistentFlags().BoolVar(&autoConfirm, "autoconfirm", false, "Whether to auto-confirm resource changes")
Expand Down
11 changes: 7 additions & 4 deletions modules/container/docker/definitions/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@ package container_docker_definitions
import (
"github.com/getstackhead/stackhead/project"
"github.com/getstackhead/stackhead/system"
"path"
)

type DockerPaths struct {
BaseDir string
DataDir string
DeploymentDir string
}

func GetDockerPaths() DockerPaths {
return DockerPaths{
BaseDir: system.Context.Project.GetRuntimeDataDirectoryPath() + "/container",
DataDir: path.Join(system.Context.Project.GetRuntimeDataDirectoryPath(), "container"),
DeploymentDir: path.Join(system.Context.CurrentDeployment.GetPath(), "container"),
}
}

func (p DockerPaths) GetHooksDir() string {
return p.BaseDir + "/hooks"
return path.Join(p.DeploymentDir, "hooks")
}

func (p DockerPaths) getDataDir() string {
return p.BaseDir + "/data"
return path.Join(p.DataDir, "data")
}

func (p DockerPaths) GetServiceDataDir(service project.ContainerService, volume project.ContainerServiceVolume) string {
Expand Down
89 changes: 58 additions & 31 deletions modules/container/docker/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,36 @@ func (m Module) Deploy(modulesSettings interface{}) error {
return err
}

composeFileRemotePath := system.Context.Project.GetDirectoryPath() + "/docker-compose.yaml"

hasRemoteFile, err := xfs.HasFile("ssh://" + composeFileRemotePath)
if err != nil && err.Error() == "file does not exist" {
hasRemoteFile = false
} else if err != nil {
return fmt.Errorf("Unable to check state of remote docker-compose.yaml from previous deployment: " + err.Error())
dockerComposeResource := system.Resource{
Type: system.TypeFile,
Operation: system.OperationCreate,
Name: "docker-compose.yaml",
}

oldComposeFilePath := ""
var remoteComposeObjMap map[string]interface{}
if hasRemoteFile {
remoteComposeObj := docker_compose.DockerCompose{}
remoteComposeContent, err := xfs.ReadFile("ssh://" + composeFileRemotePath)
if err := yaml.Unmarshal([]byte(remoteComposeContent), &remoteComposeObj); err != nil {
return fmt.Errorf("unable to read remote docker-compose.yaml file from previous deployment: " + err.Error())
}
remoteComposeObjMap, err = remoteComposeObj.Map()
if system.Context.LatestDeployment != nil {
dockerComposeFilePathOld, err := system.Context.LatestDeployment.GetResourcePath(dockerComposeResource)
if err != nil {
return fmt.Errorf("unable to process remote docker-compose.yaml file from previous deployment: " + err.Error())
return err
}
oldComposeFilePath = dockerComposeFilePathOld
hasRemoteFile, err := xfs.HasFile("ssh://" + oldComposeFilePath)
if err != nil && hasRemoteFile {
remoteComposeContent, err := xfs.ReadFile("ssh://" + oldComposeFilePath)
if err != nil {
return fmt.Errorf("unable to read remote docker-compose.yaml file from previous deployment: " + err.Error())
}
remoteComposeObj := docker_compose.DockerCompose{}
if err := yaml.Unmarshal([]byte(remoteComposeContent), &remoteComposeObj); err != nil {
return fmt.Errorf("unable to read remote docker-compose.yaml file from previous deployment: " + err.Error())
}
remoteComposeObjMap, err = remoteComposeObj.Map()
if err != nil {
return fmt.Errorf("unable to process remote docker-compose.yaml file from previous deployment: " + err.Error())
}
} else if err != nil && err.Error() != "file does not exist" {
return fmt.Errorf("Unable to check state of remote docker-compose.yaml from previous deployment: " + err.Error())
}
}

Expand All @@ -177,17 +188,13 @@ func (m Module) Deploy(modulesSettings interface{}) error {
if err != nil {
return err
}
dockerComposeResource.Content = composeFileContent

system.Context.Resources = append(system.Context.Resources, system.ResourceGroup{
Name: "container-docker-" + system.Context.Project.Name + "-composefile",
Resources: []system.Resource{
{
Type: system.TypeFile,
Operation: system.OperationCreate,
Name: composeFileRemotePath,
Content: composeFileContent,
},
},
dockerComposeFilePathNew, _ := system.Context.CurrentDeployment.GetResourcePath(dockerComposeResource)

system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
Name: "container-docker-" + system.Context.Project.Name + "-composefile",
Resources: []system.Resource{dockerComposeResource},
})

var containerResources []system.Resource
Expand All @@ -202,14 +209,24 @@ func (m Module) Deploy(modulesSettings interface{}) error {
})
}

system.Context.Resources = append(system.Context.Resources, system.ResourceGroup{
system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
Name: "container-docker-" + system.Context.Project.Name + "-containers",
Resources: containerResources,
ApplyResourceFunc: func() error {
if oldComposeFilePath != "" {
// Stop old Docker Compose containers
// todo: allow using either docker-compose or "docker compose" whichever is available (prefer "docker compose")
if _, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"down"}, WorkingDir: path.Dir(oldComposeFilePath)}); err != nil {
if stderr.Len() > 0 {
return fmt.Errorf("Unable to stop old Docker containers: " + stderr.String())
}
return fmt.Errorf("Unable to stop old Docker containers: " + err.Error())
}
}

// Start Docker Compose
// todo: allow using either docker-compose or "docker compose" whichever is available (prefer "docker compose")
_, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"up", "-d"}, WorkingDir: system.Context.Project.GetDirectoryPath()})
if err != nil {
if _, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"up", "-d"}, WorkingDir: path.Dir(dockerComposeFilePathNew)}); err != nil {
if stderr.Len() > 0 {
return fmt.Errorf("Unable to start Docker containers: " + stderr.String())
}
Expand All @@ -223,14 +240,24 @@ func (m Module) Deploy(modulesSettings interface{}) error {
return nil
},
RollbackResourceFunc: func() error {
// Start old containers again
if oldComposeFilePath != "" {
// todo: allow using either docker-compose or "docker compose" whichever is available (prefer "docker compose")
if _, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"up", "-d"}, WorkingDir: path.Dir(oldComposeFilePath)}); err != nil {
if stderr.Len() > 0 {
return fmt.Errorf("Unable to stop Docker containers: " + stderr.String())
}
return fmt.Errorf("Unable to start old Docker containers: " + err.Error())
}
}

// Stop Docker Compose
// todo: allow using either docker-compose or "docker compose" whichever is available (prefer "docker compose")
_, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"down"}, WorkingDir: system.Context.Project.GetDirectoryPath()})
if err != nil {
if _, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"down"}, WorkingDir: path.Dir(dockerComposeFilePathNew)}); err != nil {
if stderr.Len() > 0 {
return fmt.Errorf("Unable to stop Docker containers: " + stderr.String())
}
return fmt.Errorf("Unable to stop Docker containers: " + err.Error())
return fmt.Errorf("Unable to stop new Docker containers: " + err.Error())
}
return nil
},
Expand Down
2 changes: 1 addition & 1 deletion modules/container/docker/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (m Module) Destroy(modulesSettings interface{}) error {

// Stop and remove containers
// todo: allow using either docker-compose or "docker compose" whichever is available (prefer "docker compose")
_, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"down"}, WorkingDir: system.Context.Project.GetDirectoryPath()})
_, stderr, err := system.RemoteRun("docker compose", system.RemoteRunOpts{Args: []string{"down"}, WorkingDir: system.Context.CurrentDeployment.GetPath()})
if err != nil {
if stderr.Len() > 0 {
return fmt.Errorf("Unable to stop Docker containers: " + stderr.String())
Expand Down
9 changes: 4 additions & 5 deletions modules/container/docker/docker-compose/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func BuildDockerCompose(project *project.Project) (DockerCompose, error) {
dockerPaths := container_docker_definitions.GetDockerPaths()
compose := DockerCompose{
Version: "2.4",
Networks: map[string]Network{"stackhead-network-" + project.Name: {}},
Networks: map[string]Network{docker_system.NetworkName(project.Name, system.Context.CurrentDeployment): {}},
Services: map[string]Services{},
Volumes: map[string]Volume{},
}
Expand All @@ -36,7 +36,6 @@ func BuildDockerCompose(project *project.Project) (DockerCompose, error) {
continue
}
vol := Volume{}
serviceName := service.Name
vol.DriverOpts.Type = "none"
vol.DriverOpts.O = "bind"
if volume.Type == "local" {
Expand All @@ -46,7 +45,7 @@ func BuildDockerCompose(project *project.Project) (DockerCompose, error) {
} else if volume.Type == "custom" {
vol.DriverOpts.Device = volume.Src
}
compose.Volumes[GetVolumeSrcKey(project.Name, serviceName, volume)] = vol
compose.Volumes[GetVolumeSrcKey(project.Name, service.Name, volume)] = vol
}
}

Expand Down Expand Up @@ -89,12 +88,12 @@ func addService(compose *DockerCompose, project *project.Project, service projec
}

compose.Services[service.Name] = Services{
ContainerName: docker_system.ContainerName(project.Name, service.Name),
ContainerName: docker_system.ContainerName(project.Name, service.Name, system.Context.CurrentDeployment),
Image: service.Image,
Restart: "unless-stopped",
Labels: map[string]string{"stackhead.project": project.Name},
User: service.User,
Networks: map[string]ServiceNetwork{"stackhead-network-" + project.Name: {Aliases: []string{service.Name}}},
Networks: map[string]ServiceNetwork{docker_system.NetworkName(project.Name, system.Context.CurrentDeployment): {Aliases: []string{service.Name}}},
Volumes: volumes,
Ports: ports,
Environment: service.Environment,
Expand Down
8 changes: 4 additions & 4 deletions modules/container/docker/system/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ func ExecuteHook(hookName string) error {

// copy file onto container and run it.....
containerLocation := path.Join("/", file.File)
containerName := ContainerName(system.Context.Project.Name, file.Service)
containerName := ContainerName(system.Context.Project.Name, file.Service, system.Context.CurrentDeployment)
_, err := system.SimpleRemoteRun("docker", system.RemoteRunOpts{
Args: []string{
"cp",
filePath,
containerName + ":" + containerLocation,
},
WorkingDir: system.Context.Project.GetDirectoryPath(),
WorkingDir: system.Context.CurrentDeployment.GetPath(),
})
if err != nil {
return fmt.Errorf("Unable to copy file %s to container %s: \"%s\"", file.File, containerName, err.Error())
Expand All @@ -64,7 +64,7 @@ func ExecuteHook(hookName string) error {
containerName,
"chmod +x " + containerLocation,
},
WorkingDir: system.Context.Project.GetDirectoryPath(),
WorkingDir: system.Context.CurrentDeployment.GetPath(),
})
if err != nil {
return fmt.Errorf("Unable to copy file %s to container %s: \"%s\"", file.File, containerName, err.Error())
Expand All @@ -76,7 +76,7 @@ func ExecuteHook(hookName string) error {
containerName,
containerLocation,
},
WorkingDir: system.Context.Project.GetDirectoryPath(),
WorkingDir: system.Context.CurrentDeployment.GetPath(),
})
if err != nil {
return fmt.Errorf("Unable to run %s on container %s: \"%s\"", file, containerName, err.Error())
Expand Down
13 changes: 10 additions & 3 deletions modules/container/docker/system/naming.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package docker_system

import "fmt"
import (
"fmt"
"github.com/getstackhead/stackhead/system"
)

func ContainerName(projectName string, serviceName string) string {
return fmt.Sprintf("stackhead-%s-%s", projectName, serviceName)
func ContainerName(projectName string, serviceName string, deployment system.Deployment) string {
return fmt.Sprintf("stackhead-%s-%s-v%d", projectName, serviceName, deployment.Version)
}

func NetworkName(projectName string, deployment system.Deployment) string {
return fmt.Sprintf("stackhead-network-%s-v%d", projectName, deployment.Version)
}
2 changes: 1 addition & 1 deletion modules/container/docker/system/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func GetPortMap(project *project.Project) (map[string]int, error) {

// find ports for running containers
for _, service := range project.Container.Services {
res, _, err := system.RemoteRun("docker", system.RemoteRunOpts{Args: []string{"port", "stackhead-" + project.Name + "-" + service.Name}})
res, _, err := system.RemoteRun("docker", system.RemoteRunOpts{Args: []string{"port", ContainerName(project.Name, service.Name, system.Context.CurrentDeployment)}})
if err == nil { // ignore error (container not running)
// e.g. 80/tcp -> 0.0.0.0:49155
re := regexp.MustCompile(`(?P<Internal>\d+)\/tcp -> 0\.0\.0\.0:(?P<External>\d+)`)
Expand Down
17 changes: 0 additions & 17 deletions modules/container/docker/templates/project.tf.tmpl

This file was deleted.

27 changes: 15 additions & 12 deletions modules/proxy/caddy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@ func (Module) Deploy(modulesSettings interface{}) error {
return err
}

projectCaddyLocation := system.Context.Project.GetDirectoryPath() + "/Caddyfile"
caddyFileResource := system.Resource{
Type: system.TypeFile,
Operation: system.OperationCreate,
Name: "Caddyfile",
Content: caddyDirectives,
}

system.Context.Resources = append(system.Context.Resources, system.ResourceGroup{
Name: "proxy-caddy-" + system.Context.Project.Name + "-caddyfile",
Resources: []system.Resource{
{
Type: system.TypeFile,
Operation: system.OperationCreate,
Name: projectCaddyLocation,
Content: caddyDirectives,
},
},
caddyFilePath, err := system.Context.CurrentDeployment.GetResourcePath(caddyFileResource)
if err != nil {
return err
}

system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
Name: "proxy-caddy-" + system.Context.Project.Name + "-caddyfile",
Resources: []system.Resource{caddyFileResource},
ApplyResourceFunc: func() error {
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sf " + projectCaddyLocation + " /etc/caddy/conf.d/stackhead_" + system.Context.Project.Name + ".conf"}}); err != nil {
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sf " + caddyFilePath + " /etc/caddy/conf.d/stackhead_" + system.Context.Project.Name + ".conf"}}); err != nil {
return fmt.Errorf("Unable to symlink project Caddyfile: " + err.Error())
}
if _, err := system.SimpleRemoteRun("systemctl", system.RemoteRunOpts{Args: []string{"reload", "caddy"}, Sudo: true}); err != nil {
Expand Down
4 changes: 0 additions & 4 deletions modules/proxy/nginx/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ func GetSnakeoilPaths() (string, string) {
return path.Join(CertificatesDirectory, "fullchain_snakeoil.pem"), path.Join(CertificatesDirectory, "privkey_snakeoil.pem")
}

func GetCertificateDirectoryPath(p *project.Project) string {
return path.Join(config.ProjectsRootDirectory, p.Name, "certificates")
}

func GetCertificatesDirectory(p *project.Project) string {
return path.Join(CertificatesDirectory, p.Name)
}
Loading

0 comments on commit e2053dc

Please sign in to comment.