Skip to content

Commit

Permalink
support ignoring undeployable apps
Browse files Browse the repository at this point in the history
We have some apps that can not be deployed but have dependencies
defined, e.g. the meta-apps.
Those apps are included in the generated deployment orders.
In multiple places we have code snippets to remove these again from the
deploy-order.
To get rid of those snippets, support to figure out if an app is
deployable and store the information for it in the dependency-tree.

An app is marked as undeployable if it does not have a deploy directory.

The deployorder command is changed to not include undeployable apps by
default. This can be changed by passing the new --include-undeployable
parameter.
  • Loading branch information
fho committed Dec 6, 2023
1 parent e70bc3d commit 67bb6a6
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 62 deletions.
16 changes: 11 additions & 5 deletions internal/cmd/deployorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ ROOT-DIR to generate a dependency-tree.
type deployOrder struct {
*cobra.Command

format string
apps []string
format string
apps []string
includeAppsWithoutDeployDir bool

src string
Env string
Expand Down Expand Up @@ -80,6 +81,11 @@ func newDeployOrder() *deployOrder {
"comma-separated list of apps to generate the deploy order for,\n"+
"if unset, the deploy-order is generated for all found apps.",
)
cmd.Flags().BoolVar(
&cmd.includeAppsWithoutDeployDir,
"include-undeployable", false,
"include apps that do not have a deploy directory",
)

cmd.PreRunE = func(_ *cobra.Command, args []string) error {
if !slices.Contains(supportedFormats, cmd.format) {
Expand Down Expand Up @@ -140,7 +146,7 @@ func (c *deployOrder) run(cc *cobra.Command, _ []string) error {

switch c.format {
case "text":
secondsorted, err := depsfrom.DeploymentOrder()
secondsorted, err := depsfrom.DeploymentOrder(c.includeAppsWithoutDeployDir)
if err != nil {
return fmt.Errorf("could not generate graph: %w", err)
}
Expand All @@ -150,14 +156,14 @@ func (c *deployOrder) run(cc *cobra.Command, _ []string) error {
}
case "dot":
cc.Printf("###########\n# dot of %s\n##########\n", strings.Join(c.apps, ", "))
depsgraph, err := deps.OutputDotGraph(depsfrom)
depsgraph, err := deps.OutputDotGraph(depsfrom, c.includeAppsWithoutDeployDir)
if err != nil {
return err
}

cc.Println(depsgraph)
case "json":
secondsorted, err := depsfrom.DeploymentOrder()
secondsorted, err := depsfrom.DeploymentOrder(c.includeAppsWithoutDeployDir)
if err != nil {
return fmt.Errorf("could not generate graph: %w", err)
}
Expand Down
7 changes: 5 additions & 2 deletions internal/cmd/deployorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ func TestExportImport(t *testing.T) {

stdoutBuf := bytes.Buffer{}
deployOrderCmd.Command.SetOut(&stdoutBuf)
deployOrderCmd.includeAppsWithoutDeployDir = true

err = deployOrderCmd.run(deployOrderCmd.Command, nil)
if err != nil {
t.Fatal(err)
}
const expectedOut = `stg-eu-service
const expectedOut = `b-service
stg-eu-service
postgres
consul
a-service
Expand All @@ -53,6 +55,7 @@ a-service
func TestDeployOrderInJson(t *testing.T) {
deployOrderCmd := newDeployOrder()
deployOrderCmd.format = "json"
deployOrderCmd.includeAppsWithoutDeployDir = true

err := deployOrderCmd.PreRunE(nil, []string{relTestDataDirPath, "stg", "eu"})
if err != nil {
Expand All @@ -72,7 +75,7 @@ func TestDeployOrderInJson(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expected := []string{"stg-eu-service", "postgres", "consul", "a-service"}
expected := []string{"b-service", "stg-eu-service", "postgres", "consul", "a-service"}
if !slices.Equal(res, expected) {
t.Errorf("expected unmarshaled json result: %v, got: %v", expected, res)
}
Expand Down
87 changes: 56 additions & 31 deletions internal/deps/composition.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"slices"
"strings"

Expand All @@ -14,7 +15,8 @@ import (
)

type Service struct {
DependsOn map[string]struct{} `json:"depends_on"`
DependsOn map[string]struct{} `json:"depends_on"`
Deployable bool `json:"deployable"`
}

// AddDependency declares name to be a dependency of s.
Expand All @@ -25,12 +27,12 @@ func (s *Service) AddDependency(name string) {
}

// NewService creates a Service
func NewService(deps ...string) Service {
func NewService(deployable bool, deps ...string) Service {
m := map[string]struct{}{}
for _, dep := range deps {
m[dep] = struct{}{}
}
return Service{DependsOn: m}
return Service{Deployable: deployable, DependsOn: m}
}

type Composition struct {
Expand All @@ -56,13 +58,24 @@ func CompositionFromSisuDir(directory, env, region string) (*Composition, error)
return nil, fmt.Errorf("could not toml decode %v, %w", tomlfile, err)
}

service := NewService(t.TalksTo...)
isDeployable := dirExists(filepath.Join(filepath.Dir(tomlfile), "deploy"))

service := NewService(isDeployable, t.TalksTo...)
comp.AddService(t.Name, service)
}

return comp, nil
}

func dirExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}

return info.IsDir()
}

func CompositionFromJSON(filePath string) (*Composition, error) {
var result Composition

Expand Down Expand Up @@ -109,17 +122,20 @@ func (comp *Composition) AddService(name string, service Service) {
}

// DeploymentOrder ... deploy from order[0] to order[len(order) -1] :)
func (comp Composition) DeploymentOrder() (order []string, err error) {
func (comp Composition) DeploymentOrder(includeUndeployable bool) (order []string, err error) {
var reverse []string
var nodeps []string

for serviceName := range comp.Services {
if len(comp.Services[serviceName].DependsOn) == 0 {
nodeps = append(nodeps, serviceName)
service := comp.Services[serviceName]
if len(service.DependsOn) == 0 {
if includeUndeployable || service.Deployable {
nodeps = append(nodeps, serviceName)
}
}
}

graph, err := sortableGraph(comp)
graph, err := sortableGraph(comp, includeUndeployable)
if err != nil {
return order, err
}
Expand Down Expand Up @@ -172,17 +188,14 @@ func (comp Composition) RecursiveDepsOf(s string) (newcomp *Composition, err err
todo[strings.TrimSpace(n)] = true
}

finished := false

newcomp = NewComposition()

for !finished {

for len(todo) > 0 {
for serviceName := range todo {
_, ok := comp.Services[serviceName]
service, ok := comp.Services[serviceName]

if !ok {
comp.AddService(serviceName, NewService())
comp.AddService(serviceName, NewService(service.Deployable))
}

newcomp.Services[serviceName] = comp.Services[serviceName]
Expand All @@ -198,14 +211,15 @@ func (comp Composition) RecursiveDepsOf(s string) (newcomp *Composition, err err
}
}

if len(todo) == 0 {
finished = true
}
}

return newcomp, nil
}

func (comp *Composition) isDeployable(serviceName string) bool {
return comp.Services[serviceName].Deployable
}

func (comp *Composition) ToJSONFile(path string) error {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
Expand All @@ -221,23 +235,27 @@ func (comp *Composition) ToJSONFile(path string) error {
return f.Close()
}

func sortableGraph(comp Composition) (graph *graphs.Graph, err error) {
func sortableGraph(comp Composition, includeUndeployable bool) (graph *graphs.Graph, err error) {
graph = graphs.NewDigraph()

for service, dependencies := range comp.Services {
s := service
graph.AddVertex(s)
for serviceName, service := range comp.Services {
if !includeUndeployable && !service.Deployable {
continue
}

for depservice := range dependencies.DependsOn {
d := depservice
graph.AddEdge(s, d)
graph.AddVertex(serviceName)

for depservice := range service.DependsOn {
if includeUndeployable || comp.isDeployable(serviceName) {
graph.AddEdge(serviceName, depservice)
}
}
}

return graph, nil
}

func OutputDotGraph(comp Composition) (s string, err error) {
func OutputDotGraph(comp Composition, includeUndeployable bool) (s string, err error) {
graph := gographviz.NewGraph()
graph.Name = "G"
graph.Directed = true
Expand All @@ -249,24 +267,31 @@ func OutputDotGraph(comp Composition) (s string, err error) {
return s, fmt.Errorf("could not add Attribute ranksep: %w", err)
}

for service, dependencies := range comp.Services {
s := service
for serviceName, service := range comp.Services {
s := serviceName

if !includeUndeployable && !service.Deployable {
continue
}

if err := graph.AddNode("G", s, nil); err != nil {
return s, fmt.Errorf("could not add service %v to graph: %w", service, err)
return s, fmt.Errorf("could not add service %v to graph: %w", serviceName, err)
}

for depservice := range dependencies.DependsOn {
for depservice := range service.DependsOn {
d := depservice
if !includeUndeployable && !service.Deployable {
continue
}

if !graph.IsNode(d) {
if err := graph.AddNode("G", d, nil); err != nil {
return s, fmt.Errorf("could not add service %v to graph: %w", service, err)
return s, fmt.Errorf("could not add service %v to graph: %w", serviceName, err)
}
}

if err := graph.AddEdge(s, d, true, nil); err != nil {
return s, fmt.Errorf("could not add edge from %v to %v: %w", service, depservice, err)
return s, fmt.Errorf("could not add edge from %v to %v: %w", serviceName, depservice, err)
}
}
}
Expand Down
Loading

0 comments on commit 67bb6a6

Please sign in to comment.