Skip to content

Commit

Permalink
refactor: revamps
Browse files Browse the repository at this point in the history
  • Loading branch information
m-adawi committed Aug 2, 2024
1 parent 66ac8ac commit a148ea2
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 23 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ A declarative GitOps and Continuous Deployment tool for Docker Swarm.

Inspired by [ArgoCD](https://argo-cd.readthedocs.io/en/stable/).

Features:
- [**GitOps:**](#basic-usage) Host your Swarm stacks, configs, and secrets in a Git repo and SwarmCD will automatically deploy new changes
- [**Encrypted Secrets:**](#encrypt-secrets-using-sops) Encrypt your secrets using [SOPS](https://github.com/getsops/sops) and configure SwarmCD to decrypt them before deployment
- [**Automatic Rotation of Configs and Secrets:**](#automatically-rotate-configs-and-secrets) You don't need to rename your configs or secrets every time you change them
- [**Templating:**](#templating) Define stack templates and fill them with different values for different environments, similar to [helm](https://helm.sh/)

# Usage


## Basic Usage
In this example, we use SwarmCD to deploy the stack in the repo
[swarm-cd-example](https://github.com/m-adawi/swarm-cd-example) to a docker swarm cluster.

Expand Down Expand Up @@ -55,7 +62,7 @@ This will start SwarmCD, it will periodically check the stack repo
for new changes, pulling them and updating the stack.


# Manage Encrypted Secrets Using SOPS
## Encrypt Secrets Using SOPS
You can use [sops](https://github.com/getsops/sops) to encrypt secrets in git repos and
have SwarmCD decrypt them before deploying or updating your stacks.

Expand Down Expand Up @@ -109,6 +116,17 @@ This way, SwarmCD will decrypt the files each time before it updates
the stack.


# Documentation
## Automatically Rotate Configs and Secrets

In the official docker swarm docs, the recommended method to rotate a config or a secret is by creating a new object with a new name, deleting the old one and making the docker service use the new one, as in [this](https://docs.docker.com/engine/swarm/configs/#example-rotate-a-config) and [this](https://docs.docker.com/engine/swarm/secrets/#example-rotate-a-secret). SwarmCD does this automatically by renaming the config or secret object by appending a short hash to it. This hash is calculated using md5 and its value will be different for different file contents. You can disable this behavior be setting `auto_rotate` property in [config.yaml](docs/config.yaml) file to `false`, which means you would have to manually rename the object in stack defenition every time you change it.




## Templating



## Documentation
See [docs](https://github.com/m-adawi/swarm-cd/blob/main/docs).

100 changes: 80 additions & 20 deletions swarmcd/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,39 +93,66 @@ func (swarmStack *swarmStack) deployStack() error {
}

func (swarmStack *swarmStack) rotateConfigsAndSecrets() error {
composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath)
composeFileBytes, err := os.ReadFile(composeFile)
if err != nil {
return fmt.Errorf("could not read compose file %s: %w", composeFile, err)
}
var composeMap map[string]any
err = yaml.Unmarshal(composeFileBytes, &composeMap)
composeMap, err := swarmStack.readComposeFile()
if err != nil {
return fmt.Errorf("could not parse yaml file %s: %w", composeFile, err)
return err
}

if configs, ok := composeMap["configs"].(map[string]any); ok {
err = swarmStack.rotateObjects(configs)

_, ok := composeMap["configs"]
// if configs are defined rotate them
if ok {
configsMap, ok := composeMap["configs"].(map[string]any)
if !ok {
return fmt.Errorf("could not read %s stack configs: should be a map", swarmStack.name, err)
}
err = swarmStack.rotateObjects(configsMap)
if err != nil{
return fmt.Errorf("could not rotate one or more config files of stack %s: %w", swarmStack.name, err)
return fmt.Errorf("could not rotate one or more configs of stack %s: %w", swarmStack.name, err)
}
}
if secrets, ok := composeMap["secrets"].(map[string]any); ok {
err = swarmStack.rotateObjects(secrets)

_, ok = composeMap["secrets"]
// if secrets are defined rotate them
if ok {
secretsMap, ok := composeMap["secrets"].(map[string]any)
if !ok {
return fmt.Errorf("could not read %s stack secrets: should be a map", swarmStack.name, err)
}
err = swarmStack.rotateObjects(secretsMap)
if err != nil{
return fmt.Errorf("could not rotate one or more secret files of stack %s: %w", swarmStack.name, err)
return fmt.Errorf("could not rotate one or more secrets of stack %s: %w", swarmStack.name, err)
}
}
composeFileBytes, err = yaml.Marshal(composeMap)

composeFileBytes, err := yaml.Marshal(composeMap)
if err != nil {
return fmt.Errorf("could not store comopse file as yaml after calculating hashes for stack %s", swarmStack.name)
}
composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath)
fileInfo, _ := os.Stat(composeFile)
os.WriteFile(composeFile, composeFileBytes, fileInfo.Mode())
return nil
}

func (swarmStack *swarmStack) readComposeFile() (map[string]any, error) {
composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath)
composeFileBytes, err := os.ReadFile(composeFile)
if err != nil {
return nil, fmt.Errorf("could not read compose file %s: %w", composeFile, err)
}
var composeMap map[string]any
err = yaml.Unmarshal(composeFileBytes, &composeMap)
if err != nil {
return nil, fmt.Errorf("could not parse yaml file %s: %w", composeFile, err)
}
return composeMap, nil
// value, ok := composeMap[key]
// if !ok {
// return nil, fmt.Errorf("key %s does not exist in %s stack compose file", key, swarmStack.name)
// }
// return value, nil
}

func (swarmStack *swarmStack) rotateObjects(objects map[string]any) error {
objectsDir := path.Dir(path.Join(swarmStack.repo.path, swarmStack.composePath))
for objectName, object := range objects {
Expand All @@ -148,20 +175,53 @@ func (swarmStack *swarmStack) rotateObjects(objects map[string]any) error {
return nil
}

func (swarmStack *swarmStack) renderComposeTemplate() error {

func (swarmStack *swarmStack) rednerAllTemplates() error {
composeFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.composePath)
err := swarmStack.renderTemplate(composeFile)
if err != nil {
return err
}

}

func (swarmStack *swarmStack) renderTemplate(filepath string) error {
valuesFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.valuesFile)
valuesBytes, err := os.ReadFile(valuesFile)
if err != nil {
return fmt.Errorf("could not read %s stack values file: %w", swarmStack.name, err)
}
var valuesMap map[string]any
yaml.Unmarshal(valuesBytes, &valuesMap)
templ, err := template.New(path.Base(filepath)).ParseFiles(filepath)
if err != nil {
return fmt.Errorf("could not parse %s stack file %s as a Go template: %w", swarmStack.name, filepath, err)
}
composeFileWriter, err := os.Create(filepath)
if err != nil {
return fmt.Errorf("could not open %s stack file %s: %w", swarmStack.name, filepath, err)
}
err = templ.Execute(composeFileWriter, map[string]map[string]any{"Values": valuesMap})
if err != nil {
return fmt.Errorf("error rending %s stack %s template: %w", swarmStack.name, filepath, err)
}
return nil
}

func (swarmStack *swarmStack) renderComposeTemplate() error {
filepath := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.composePath)
valuesFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.valuesFile)
valuesBytes, err := os.ReadFile(valuesFile)
if err != nil {
return fmt.Errorf("could not read %s stack values file: %w", swarmStack.name, err)
}
var valuesMap map[string]any
yaml.Unmarshal(valuesBytes, &valuesMap)
templ, err := template.New(path.Base(composeFile)).ParseFiles(composeFile)
templ, err := template.New(path.Base(filepath)).ParseFiles(filepath)
if err != nil {
return fmt.Errorf("could not parse %s stack compose file as a Go template: %w", swarmStack.name, err)
}
composeFileWriter, err := os.Create(composeFile)
composeFileWriter, err := os.Create(filepath)
if err != nil {
return fmt.Errorf("could not open %s stack compose file: %w", swarmStack.name, err)
}
Expand Down

0 comments on commit a148ea2

Please sign in to comment.