asteris-llc/converge

View on GitHub
render/factory.go

Summary

Maintainability
A
45 mins
Test Coverage
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package render

import (
    "errors"
    "fmt"
    "strings"

    "github.com/asteris-llc/converge/graph"
    "github.com/asteris-llc/converge/render/extensions"
    "github.com/asteris-llc/converge/resource"
    "github.com/asteris-llc/converge/resource/module"
    "golang.org/x/net/context"
)

// Factory generates Renderers
type Factory struct {
    Graph     *graph.Graph
    DotValues map[string]*LazyValue
    Language  *extensions.LanguageExtension
}

// ValueThunk lazily evaluates a param
type ValueThunk func() (resource.Value, bool, error)

// LazyValue wraps a ValueThunk in an interface and provides a way to cache the
// thunk evaluation.
type LazyValue struct {
    val interface{}
}

// Value returns the result of a ValueThunk, evaluating it if necessary
func (v *LazyValue) Value() (resource.Value, bool, error) {
    switch result := v.val.(type) {
    case [3]interface{}:
        var resultErr error
        if result[2] != nil {
            resultErr = result[2].(error)
        }
        return result[0].(string), result[1].(bool), resultErr
    case ValueThunk:
        val, found, err := result()
        v.val = [3]interface{}{val, found, err}
        return val, found, err
    default:
        return "", false, errors.New("value is not a thunk")
    }
}

// GetRenderer returns a Factory for the specific graph node
func (f *Factory) GetRenderer(id string) (*Renderer, error) {
    r := &Renderer{Language: f.Language, Graph: func() *graph.Graph { return f.Graph }, ID: id}
    if dotVal, found := f.DotValues[id]; found {
        if valResult, valFound, err := dotVal.Value(); err != nil {
            return nil, err
        } else if valFound {
            r.DotValue = valResult
            r.DotValuePresent = true
        }
    }
    return r, nil
}

// NewFactory generates a new Render factory
func NewFactory(ctx context.Context, g *graph.Graph) (*Factory, error) {
    f := &Factory{
        Graph:     g,
        Language:  extensions.DefaultLanguage(),
        DotValues: make(map[string]*LazyValue),
    }

    for _, vertex := range g.Vertices() {
        if dotVal, found := getParamOverrides(func() *graph.Graph { return f.Graph }, vertex); found {
            f.DotValues[vertex] = &LazyValue{dotVal}
        }
    }
    return f, nil
}

func getParamOverrides(gFunc func() *graph.Graph, id string) (ValueThunk, bool) {
    name := graph.BaseID(id)
    f := func() (resource.Value, bool, error) { return resource.Value(""), false, nil }
    if strings.HasPrefix(name, "param") {
        f = func() (resource.Value, bool, error) {
            parentMeta, ok := gFunc().GetParent(id)
            if !ok {
                return "", false, fmt.Errorf("%q was missing from the graph", id)
            }
            parentTask, ok := resource.ResolveTask(parentMeta.Value())
            if !ok {
                return "", false, fmt.Errorf("parent of node %s (%s) was not a valid task type", id, graph.ParentID(id))
            }
            parent, ok := parentTask.(*module.Module)
            if !ok {
                return "", false, fmt.Errorf("Parent of param %s was not a module, was %T", id, parentTask)
            }
            if val, ok := parent.Params[name[len("param."):]]; ok {
                return val, true, nil
            }
            return "", false, nil
        }
    }
    return f, true
}