grim/convey
Clone
Summary
Browse
Changes
Graph
Merged in efritz/convey/env-loop (pull request #21)
2017-09-26, Gary Kramlich
47c2851788ff
Merged in efritz/convey/env-loop (pull request #21)
Fix infinite env mapping loop
Fixes #124
/*
* Convey
* Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package
runners
import
(
"bytes"
"fmt"
"regexp"
"text/template"
"bitbucket.org/rw_grim/convey/color"
"bitbucket.org/rw_grim/convey/config"
"bitbucket.org/rw_grim/convey/logging"
"bitbucket.org/rw_grim/convey/plans"
"bitbucket.org/rw_grim/convey/state"
)
var
graphvizTemplate
=
`
digraph {
graph[rankdir="LR"]
node[style="filled,rounded" shape="rect"]
# add the meta plans
node[shape="rect" fillcolor="plum"]
{{range .MetaPlans}} {{.Normalized}}_start[label="{{.Name}} start"]
{{.Normalized}}_finish[label="{{.Name}} finish"]
{{end}}
# add a start and finish node for each plan
node[shape="component" fillcolor="palegreen"]
{{range .Plans}} {{.Normalized}}_start[label="{{.Name}} start"]
{{.Normalized}}_finish[label="{{.Name}} finish"]
{{end}}
# add the tasks
node[style="filled,rounded" shape="rect" fillcolor="powderblue"]
{{range .Tasks}} {{.Normalized}}[label="{{.Name}}"]
{{end}}
{{range .Plans}}
# add the stages for {{.Name}}
edge[color="{{.Color}}"]{{$planNormalized := .Normalized }}
{{- range .Stages}}
{{$planNormalized}}_{{.Normalized}} [label="{{.Name}}" shape="octagon" fillcolor="{{if .Enabled}}lightpink1{{else}}gray{{end}}"{{if .Always}} style="bold,filled,rounded"{{end}}]{{end}}
# {{.Name}} happy path
{{.Normalized}}_start{{range .Stages}} -> {{$planNormalized}}_{{.Normalized}}{{if .Concurrent}} -> { {{range .Tasks}} {{.}} {{end}} }{{else}}{{range .Tasks}} -> {{.}}{{end}}{{end}}{{end}} -> {{.Normalized}}_finish
# {{.Name}} unhappy path
{{ range .Stages}}{{$stage := .}}{{range .FallThroughs}}{{$planNormalized}}_{{$stage.Normalized}} -> {{$planNormalized}}_{{.Normalized}} [style="dotted"]
{{end}}{{end -}}
{{- end}}
{{range .MetaPlans}}
# metaplan {{.Name}}
edge[color="{{.Color}}" style="dashed,bold"]
{{.Normalized}}_start -> {{range .Plans}}{{.Normalized}}_start
{{.Normalized}}_finish -> {{end}} {{.Normalized}}_finish
{{end}}
}
`
var
normalizeRegex
*
regexp
.
Regexp
type
(
Graphviz
struct
{}
GraphvizTask
struct
{
Normalized
string
Name
string
}
GraphvizStage
struct
{
Normalized
string
Name
string
Enabled
bool
Concurrent
bool
Always
bool
Tasks
[]
string
FallThroughs
[]
GraphvizStage
}
GraphvizPlan
struct
{
Normalized
string
Name
string
Color
string
Stages
[]
GraphvizStage
}
GraphvizMetaPlan
struct
{
Normalized
string
Name
string
Color
string
Plans
[]
GraphvizPlan
}
)
func
normalize
(
prefix
,
str
string
)
string
{
if
normalizeRegex
==
nil
{
normalizeRegex
=
regexp
.
MustCompile
(
"[^a-zA-Z0-9_]"
)
}
return
prefix
+
"_"
+
normalizeRegex
.
ReplaceAllString
(
str
,
"_"
)
}
func
(
g
*
Graphviz
)
getTasks
(
cfg
*
config
.
Config
)
[]
GraphvizTask
{
tasks
:=
[]
GraphvizTask
{}
for
name
,
_
:=
range
cfg
.
Tasks
{
task
:=
GraphvizTask
{
Normalized
:
normalize
(
"task"
,
name
),
Name
:
name
,
}
tasks
=
append
(
tasks
,
task
)
}
return
tasks
}
func
(
g
*
Graphviz
)
getMetaPlans
(
cfg
*
config
.
Config
)
[]
GraphvizMetaPlan
{
metaPlans
:=
[]
GraphvizMetaPlan
{}
for
name
,
cMetaPlan
:=
range
cfg
.
MetaPlans
{
metaPlan
:=
GraphvizMetaPlan
{
Normalized
:
normalize
(
"metaplan"
,
name
),
Name
:
name
,
Color
:
color
.
X11
(
name
),
}
for
_
,
name
:=
range
cMetaPlan
.
Plans
{
if
plan
,
found
:=
cfg
.
Plans
[
name
];
found
{
metaPlan
.
Plans
=
append
(
metaPlan
.
Plans
,
g
.
plan
(
name
,
plan
))
}
}
metaPlans
=
append
(
metaPlans
,
metaPlan
)
}
return
metaPlans
}
func
(
g
Graphviz
)
plan
(
name
string
,
plan
plans
.
Plan
)
GraphvizPlan
{
graphvizPlan
:=
GraphvizPlan
{
Normalized
:
normalize
(
"plan"
,
name
),
Name
:
name
,
Color
:
color
.
X11
(
name
),
Stages
:
[]
GraphvizStage
{},
}
for
idx
,
stage
:=
range
plan
.
Stages
{
graphvizStage
:=
GraphvizStage
{
Normalized
:
normalize
(
"stage"
,
stage
.
Name
),
Name
:
stage
.
Name
,
Enabled
:
stage
.
Enabled
,
Concurrent
:
stage
.
Concurrent
,
Always
:
stage
.
Always
,
Tasks
:
[]
string
{},
}
for
_
,
task
:=
range
stage
.
Tasks
{
graphvizStage
.
Tasks
=
append
(
graphvizStage
.
Tasks
,
normalize
(
"task"
,
task
))
}
graphvizPlan
.
Stages
=
append
(
graphvizPlan
.
Stages
,
graphvizStage
)
// if this always runs, if it's not the first add it to the previous stages' fallthroughs
if
stage
.
Always
&&
idx
>
0
{
for
i
:=
0
;
i
<
idx
;
i
++
{
graphvizPlan
.
Stages
[
i
].
FallThroughs
=
append
(
graphvizPlan
.
Stages
[
idx
-
1
].
FallThroughs
,
graphvizStage
)
}
}
}
return
graphvizPlan
}
func
(
g
*
Graphviz
)
getPlans
(
cfg
*
config
.
Config
)
[]
GraphvizPlan
{
plans
:=
[]
GraphvizPlan
{}
for
name
,
plan
:=
range
cfg
.
Plans
{
plans
=
append
(
plans
,
g
.
plan
(
name
,
plan
))
}
return
plans
}
func
(
g
*
Graphviz
)
Run
(
cfg
*
config
.
Config
,
plans
[]
string
,
env
[]
string
,
st
*
state
.
State
)
int
{
logger
:=
logging
.
NewAdapter
(
"graphviz"
)
params
:=
map
[
string
]
interface
{}{
"Tasks"
:
g
.
getTasks
(
cfg
),
"Plans"
:
g
.
getPlans
(
cfg
),
"MetaPlans"
:
g
.
getMetaPlans
(
cfg
),
}
// Load the template
tmpl
,
err
:=
template
.
New
(
"graphviz"
).
Parse
(
graphvizTemplate
)
if
err
!=
nil
{
logger
.
Fatalf
(
"error: %s"
,
err
)
return
1
}
// Run the template
output
:=
new
(
bytes
.
Buffer
)
err
=
tmpl
.
Execute
(
output
,
params
)
if
err
!=
nil
{
logger
.
Fatalf
(
"error: %s"
,
err
)
return
1
}
// Output the template
fmt
.
Print
(
output
)
return
0
}