Skip to content

Commit

Permalink
Merge pull request #113 from clarsonneur/flow-management
Browse files Browse the repository at this point in the history
Flow management
  • Loading branch information
clarsonneur authored Feb 2, 2018
2 parents bfb550b + b173b17 commit 5d65f27
Show file tree
Hide file tree
Showing 33 changed files with 1,139 additions and 185 deletions.
16 changes: 9 additions & 7 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"forjj/creds"
"strings"
"github.com/forj-oss/goforjj"
"forjj/flow"
)

// TODO: Support multiple contrib sources.
Expand Down Expand Up @@ -62,7 +63,6 @@ type Forj struct {
Branch string // Update feature branch name
ContribRepo_uri *url.URL // URL to github raw files for plugin files.
RepotemplateRepo_uri *url.URL // URL to github raw files for RepoTemplates.
FlowRepo_uri *url.URL // URL to github raw files for Flows.
appMapEntries map[string]AppMapEntry
no_maintain *bool // At create time. true to not start maintain task at the end of create.
debug_instances []string // List of instances in debug mode
Expand All @@ -77,6 +77,8 @@ type Forj struct {
s creds.YamlSecure // credential file support.
o ForjjOptions // Data structured stored in the root of the infra repo. See forjj-options.go

flows flow.Flows

i repository.GitRepoStruct // Infra Repository management.
}

Expand All @@ -102,7 +104,7 @@ const (
repo = "repo"
infra = "infra"
app = "app"
flow = "flow"
flow_obj = "flow"
)

const (
Expand Down Expand Up @@ -161,8 +163,8 @@ func (a *Forj) init() {
opts_required := cli.Opts().Required()
//opts_ssh_dir := cli.Opts().Default(fmt.Sprintf("%s/.ssh", os.Getenv("HOME")))
opts_contribs_repo := cli.Opts().Envar("CONTRIBS_REPO").Default("https://github.com/forj-oss/forjj-contribs/raw/master")
opts_flows_repo := cli.Opts().Envar("FLOWS_REPO").Default("https://github.com/forj-oss/forjj-repotemplates/raw/master")
opts_repotmpl := cli.Opts().Envar("REPOTEMPLATES_REPO").Default("https://github.com/forj-oss/forjj-flows/raw/master")
opts_flows_repo := cli.Opts().Envar("FLOWS_REPO").Default("https://github.com/forj-oss/forjj-flows/raw/master")
opts_repotmpl := cli.Opts().Envar("REPOTEMPLATES_REPO").Default("https://github.com/forj-oss/forjj-repotemplates/raw/master")
opts_infra_repo := cli.Opts().Short('I').Default("<organization>-infra")
opts_creds_file := cli.Opts().Short('C')
opts_orga_name := cli.Opts().Short('O')
Expand Down Expand Up @@ -343,9 +345,9 @@ func (a *Forj) init() {
}

// Flow - Not fully defined.
if a.cli.NewObject(flow, "flow over applications", "internal").NoFields().
if a.cli.NewObject(flow_obj, "flow over applications", "internal").NoFields().
DefineActions(add_act, rem_act, list_act) == nil {
log.Printf("infra: %s", a.cli.GetObject(flow).Error())
log.Printf("infra: %s", a.cli.GetObject(flow_obj).Error())
}

// Enhance create action. Plugins can add options to create with `only-for-actions`
Expand Down Expand Up @@ -394,7 +396,7 @@ func (a *Forj) init() {
// Add Forjfile/cli mapping for simple forj data getter
a.AddMap(orga_f, workspace, "", orga_f, "settings", "", orga_f)
a.AddMap(infra_name_f, infra, "", infra_name_f, infra, "", "name")
a.AddMap(infra_upstream_f, infra, "", infra_upstream_f, infra, "", "upstream")
a.AddMap(infra_upstream_f, infra, "", infra_upstream_f, infra, "", "apps:upstream")
a.AddMap(infra_path_f, workspace, "", infra_path_f, workspace, "", infra_path_f)
// TODO: Add git-remote cli mapping
}
Expand Down
6 changes: 3 additions & 3 deletions cli_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (a *Forj) ParseContext(c *cli.ForjCli, _ interface{}) (error, bool) {
a.w.Load()

// Read definition file from repo.
is_valid_action := (inStringList(action, val_act, cr_act, upd_act, maint_act, add_act, rem_act, ren_act, chg_act, list_act) != "")
is_valid_action := (utils.InStringList(action, val_act, cr_act, upd_act, maint_act, add_act, rem_act, ren_act, chg_act, list_act) != "")
need_to_create := (action == cr_act)
need_to_validate := (action == val_act)
if err := a.f.SetInfraPath(a.w.InfraPath(), is_valid_action && (need_to_create || need_to_validate)) ; err != nil {
Expand All @@ -63,7 +63,7 @@ func (a *Forj) ParseContext(c *cli.ForjCli, _ interface{}) (error, bool) {

// Load Forjfile from infra repo, if found.
if err := a.LoadForge() ; err != nil {
if inStringList(action, upd_act, maint_act, add_act, rem_act, ren_act, chg_act, list_act) != "" {
if utils.InStringList(action, upd_act, maint_act, add_act, rem_act, ren_act, chg_act, list_act) != "" {
a.w.SetError(fmt.Errorf("Forjfile not loaded. %s", err))
return nil, false
}
Expand Down Expand Up @@ -94,7 +94,7 @@ func (a *Forj) ParseContext(c *cli.ForjCli, _ interface{}) (error, bool) {
return fmt.Errorf("Contribs repository url issue: %s", err), false
}
if v, err := a.set_from_urlflag("flows-repo", &a.w.Flow_repo_path); err == nil {
a.FlowRepo_uri = v
a.flows.AddRepoPath(v)
gotrace.Trace("Using '%s' for '%s'", v, "flows-repo")
} else {
gotrace.Error("Flow repository url issue: %s", err)
Expand Down
20 changes: 14 additions & 6 deletions create.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,19 @@ func (a *Forj) Create() error {
return fmt.Errorf("Your Forjfile is having issues. %s Try to fix and retry.", err)
}

if ok, err := a.f.Forjfile().Repos.AllHasAppWith("appRelName:upstream") ; err != nil {
return err
} else if err = a.DefineDefaultUpstream() ; ok && err != nil {
gotrace.Warning("%s", err)
}


if err := a.define_infra_upstream(); err != nil {
return fmt.Errorf("Unable to identify a valid infra repository upstream. %s", err)
}

gotrace.Trace("Infra upstream selected: '%s'", a.w.Instance)

a.DefineDefaultUpstream()

// TODO: Set/clone infra git remote when git-remote is set.

// In create use case, a repository should not exist. If it exists one, we need an extra option to force using
Expand All @@ -124,11 +129,14 @@ func (a *Forj) Create() error {
return err
}

// Now, we are in the infra repo root directory and at least, the 1st commit exist
// Now, we are in the infra repo root directory and at least, the 1st commit exist with a Forjfile created/updated
// The Forjfile in memory has been saved and won't be saved later in the process.
// The flow will update it in memory to apply all integration and automation

// TODO: flow_start to execute instructions before creating source code for new apps in appropriate branch.
// Possible if a flow is already implemented otherwise git must stay in master branch
// flow_start()
// Enhance forjfile inMemory representation with Flow definition.
if err := a.FlowInit() ; err != nil {
return err
}

defer func() {
// save infra repository location in the workspace.
Expand Down
12 changes: 5 additions & 7 deletions drivers_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"text/template"
"forjj/drivers"
"forjj/forjfile"
"forjj/utils"
)

// Load driver options to a Command requested.
Expand Down Expand Up @@ -76,7 +77,7 @@ func (a *Forj) read_driver(instance_name string) (err error) {
ContribRepoUri := *a.ContribRepo_uri
ContribRepoUri.Path = path.Join(ContribRepoUri.Path, driver.DriverType, driver.Name, driver.Name+".yaml")

if yaml_data, err = read_document_from(&ContribRepoUri); err != nil {
if yaml_data, err = utils.ReadDocumentFrom(&ContribRepoUri); err != nil {
return
}

Expand Down Expand Up @@ -121,7 +122,7 @@ func (a *Forj) get_valid_driver_actions() (validObjectActions, validCommandActio
validObjectActions = make([]string, 0, len(actions))
validCommandActions = make([]string, 0, len(actions))
for action_name := range actions {
if inStringList(action_name, cr_act, upd_act, maint_act) == "" {
if utils.InStringList(action_name, cr_act, upd_act, maint_act) == "" {
validObjectActions = append(validObjectActions, action_name)
} else {
validCommandActions = append(validCommandActions, action_name)
Expand Down Expand Up @@ -494,7 +495,7 @@ func (a *Forj) DispatchObjectFlags(object_name, instance_name, flag_prefix strin
return
}

// IsRepoManaged check is the upstream driver is the repository owner.
// IsRepoManaged check if the upstream driver is the repository owner.
// It returns the repo owner declared and true if the upstream driver is that owner.
func (a *Forj) IsRepoManaged(d *drivers.Driver, object_name, instance_name string) (repo_upstream string, is_owner bool) {
// Determine if the upstream instance is set to this instance.
Expand All @@ -517,10 +518,7 @@ func (a *Forj) RepoManagedBy(object_name, instance_name string) (_ string) {
if v, found := a.f.GetString(object_name, instance_name, "git-remote"); found && v != "" {
return
}
if v, found := a.f.GetString(object_name, instance_name, "upstream"); found {
return v
}
if v, found := a.f.GetString("settings", "default", "upstream-instance"); found {
if v, found := a.f.GetString(object_name, instance_name, "apps:upstream"); found {
return v
}
return
Expand Down
44 changes: 44 additions & 0 deletions flow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"fmt"
"github.com/forj-oss/forjj-modules/trace"
)

// FlowStart load the flow in memory,
// configure it with Forjfile information
// and update Forjfile inMemory object data.
func (a *Forj)FlowInit() error {
bInError := false
if err := a.flows.Load(a.f.GetDeclaredFlows() ...) ; err != nil {
return err
}
default_flow_to_apply := "default"
if v, found := a.f.Get("settings", "default", "flow") ; found {
default_flow_to_apply = v.GetString()
}

if err := a.flows.Apply(default_flow_to_apply, nil, &a.f) ; err != nil {// Applying Flow to Forjfile
gotrace.Error("Forjfile: %s", err)
bInError = true
}

for _, repo := range a.f.Forjfile().Repos {
flow_to_apply := default_flow_to_apply
if repo.Flow.Name != "" {
flow_to_apply = repo.Flow.Name
}

if err := a.flows.Apply(flow_to_apply, repo, &a.f) ; err != nil {// Applying Flow to Forjfile repo
gotrace.Error("Repo '%s': %s", repo.GetString("name"), err)
bInError = true
}
}

if bInError {
return fmt.Errorf("Several errors has been detected when trying to apply flows on Repositories. %s", "Please review and fix them.")
}

return nil
}

108 changes: 108 additions & 0 deletions flow/flow_define.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package flow

import (
"forjj/forjfile"
"github.com/forj-oss/forjj-modules/trace"
"fmt"
"forjj/utils"
)

type FlowDefine struct { // Yaml structure
Name string
Title string // Flow title
Define map[string]FlowPluginTypeDef
OnRepo map[string]FlowTaskDef `yaml:"on-repo-do"`
OnForj map[string]FlowTaskDef `yaml:"on-forjfile-do"`
}

func (fd *FlowDefine)apply(repo *forjfile.RepoStruct, Forjfile *forjfile.Forge) error {
bInError := false

var tasks map[string]FlowTaskDef
if repo == nil {
tasks = fd.OnForj
} else {
tasks = fd.OnRepo
}

for _, flowTask := range tasks {
onWhat := "Forjfile"
if repo != nil {
onWhat = fmt.Sprintf("repository '%s'", repo.GetString("name"))
} else {

}
gotrace.Trace("flow '%s': %s on %s is being checked.\n---", fd.Name, flowTask.Description, onWhat)

task_to_set, err := flowTask.if_section(repo, Forjfile)
if err != nil {
gotrace.Error("Flow '%s' - if section: Unable to apply flow task '%s'.", fd.Name, err)
bInError = true
continue
}

if ! task_to_set {
gotrace.Trace("Flow task not applied to %s. if condition fails.", onWhat)
continue
}

gotrace.Trace("'%s' flow task \"%s\" applying to %s.", fd.Name, flowTask.Description, onWhat)

tmpl_data := New_FlowTaskModel(repo, Forjfile)

if flowTask.List == nil {
if err := flowTask.Set.apply(tmpl_data, Forjfile); err != nil {
gotrace.Error("Unable to apply '%s' flow task '%s' on %s. %s", fd.Name, flowTask.Description, onWhat, err)
continue
}
gotrace.Trace("'%s' flow task '%s' applied on %s.\n---", fd.Name, flowTask.Description, onWhat)
continue
}

// Load list
max := make([]int, len(flowTask.List))

for index, taskList := range flowTask.List {
taskList.list = taskList.Get(repo, Forjfile)
max[index] = len(taskList.list)
}

// Loop on list and set CurrentList
looplist := utils.NewMLoop(max...)
tmpl_data.List = make(map[string]interface{})
for !looplist.Eol() {
for index, pos := range looplist.Cur() {
flowTaskList := flowTask.List[index]
tmpl_data.List[flowTaskList.Name] = flowTaskList.list[pos]
}

if err := flowTask.Set.apply(tmpl_data, Forjfile); err != nil {
gotrace.Error("Unable to apply flow task '%s' on %s. %s", fd.Name, onWhat, err)
} else {
gotrace.Trace("'%s' flow task '%s' applied on %s.\n---", fd.Name, flowTask.Description, onWhat)
}

looplist.Increment()
}
}
if bInError {
return fmt.Errorf("Failed to apply '%s'. Errors detected.", fd.Name)
}

return nil
}

func (ftd *FlowTaskDef)if_section(repo *forjfile.RepoStruct, Forjfile *forjfile.Forge) (task_to_set bool, _ error) {
task_to_set = true
if ftd.If != nil {
for _, ftif := range ftd.If {
if v, err := ftif.IfEvaluate(repo, Forjfile); err != nil {
return false, err
} else if !v {
task_to_set = false
break
}
}
}
return
}
50 changes: 50 additions & 0 deletions flow/flow_task_if.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package flow

import (
"forjj/forjfile"
"text/template"
"fmt"
"bytes"
"strconv"
"strings"
"github.com/forj-oss/forjj-modules/trace"
)

type FlowTaskIf struct {
Rule string
List map[string]string `yaml:",inline"`
}

// IfEvaluate will interpret
func (fti *FlowTaskIf)IfEvaluate(repo *forjfile.RepoStruct, Forjfile *forjfile.Forge) (_ bool, _ error) {
if fti.Rule != "" {
var doc bytes.Buffer

if t, err:= template.New("flow-eval").Funcs(template.FuncMap{
}).Parse(fti.Rule); err != nil {
return false, fmt.Errorf("Error in template evaluation. %s", err)
} else {
t.Execute(&doc, New_FlowTaskModel(repo, Forjfile))
}

result := doc.String()
gotrace.Trace("'%s' evaluated to '%s'", fti.Rule, result)
switch strings.ToLower(result) {
case "", "not found" :
return
case "found" :
return true, nil
default:
return strconv.ParseBool(result)
}
}

if fti.List != nil {
rules := make([]string, 0, len(fti.List))
for key, value := range fti.List {
rules = append(rules, key + ":" + value)
}
return repo.HasValues(rules ...)
}
return true, nil
}
Loading

0 comments on commit 5d65f27

Please sign in to comment.