<commit_before>package app

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"sync"

	"koding/kites/kloud/stack"
	"koding/kites/kloud/stack/provider"
	"koding/kites/kloud/utils/object"
	"koding/klientctl/app/mixin"
	"koding/klientctl/endpoint/credential"
	"koding/klientctl/endpoint/kloud"
	"koding/klientctl/endpoint/remoteapi"
	"koding/klientctl/helper"

	"github.com/koding/logging"
)

// Builder allows for manipulating contents of stack
// templates in a provider-agnostic manner.
type Builder struct {
	Stack object.Object // the resulting stack object

	Desc stack.Descriptions // provider description; retrieved from kloud, if nil
	Log  logging.Logger     // logger; kloud.DefaultLog if nil

	Koding     *remoteapi.Client  // remote.api client; remoteapi.DefaultClient if nil
	Credential *credential.Client // credential client; credential.DefaultClient if nil

	once sync.Once // for b.init()
}

// TemplateOptions are used when building a template.
type TemplateOptions struct {
	UseDefaults bool         // forces default value when true; disables interactive mode
	Provider    string       // provider name; inferred from Template if empty
	Template    string       // base template to use; retrieved from remote.api's samples, if empty
	Mixin       *mixin.Mixin // optionally a mixin to replace default's user_data
}

// BuildTemplate builds a template with the given options.
//
// If method finishes successfully, returning nil error, b.Stack field
// will hold the built template.
func (b *Builder) BuildTemplate(opts *TemplateOptions) error {
	b.init()

	var (
		defaults map[string]interface{}
		tmpl     = opts.Template
		prov     = opts.Provider
		err      error
	)

	if tmpl == "" {
		if prov == "" {
			return errors.New("either provider or template is required to be non-empty")
		}

		tmpl, defaults, err = b.koding().SampleTemplate(prov)
		if err != nil {
			return err
		}
	}

	if prov == "" {
		prov, err = stack.ReadProvider([]byte(tmpl))
		if err != nil {
			return err
		}
	}

	desc, ok := b.Desc[prov]
	if !ok {
		return fmt.Errorf("provider %q does not exist", prov)
	}

	if opts.Mixin != nil {
		if !desc.CloudInit {
			return fmt.Errorf("provider %q does not support cloud-init files", prov)
		}

		t, err := replaceUserData(tmpl, opts.Mixin, desc)
		if err != nil {
			return err
		}

		p, err := json.Marshal(t)
		if err != nil {
			return err
		}

		tmpl = string(p)
	}

	vars := provider.ReadVariables(tmpl)
	input := make(map[string]string, len(vars))

	for _, v := range vars {
		if !strings.HasPrefix(v.Name, "userInput_") {
			continue
		}

		name := v.Name[len("userInput_"):]
		defValue := ""
		if v, ok := defaults[name]; ok && v != nil {
			defValue = fmt.Sprintf("%v", v)
		}

		var value string

		if !opts.UseDefaults {
			if value, err = helper.Ask("Set %q to [%s]: ", name, defValue); err != nil {
				return err
			}
		}

		if value == "" {
			value = defValue
		}

		input[v.Name] = value
	}

	tmpl = provider.ReplaceVariablesFunc(tmpl, vars, func(v *provider.Variable) string {
		if s, ok := input[v.Name]; ok {
			return s
		}

		return v.String()
	})

	return json.Unmarshal([]byte(tmpl), &b.Stack)
}

func (b *Builder) init() {
	b.once.Do(b.initBuilder)
}

func (b *Builder) initBuilder() {
	if b.Desc == nil {
		var err error
		if b.Desc, err = b.credential().Describe(); err != nil {
			b.log().Warning("unable to retrieve credential description: %s", err)
		}
	}

	if b.Stack == nil {
		b.Stack = make(object.Object)
	}
}

func (b *Builder) log() logging.Logger {
	if b.Log != nil {
		return b.Log
	}
	return kloud.DefaultLog
}

func (b *Builder) koding() *remoteapi.Client {
	if b.Koding != nil {
		return b.Koding
	}
	return remoteapi.DefaultClient
}

func (b *Builder) credential() *credential.Client {
	if b.Credential != nil {
		return b.Credential
	}
	return credential.DefaultClient
}

func replaceUserData(tmpl string, m *mixin.Mixin, desc *stack.Description) (map[string]interface{}, error) {
	var (
		root      map[string]interface{}
		key, keys = "", append([]string{"resource"}, desc.UserData...)
		ok        bool
	)

	if err := json.Unmarshal([]byte(tmpl), &root); err != nil {
		return nil, err
	}

	machines := root
<commit_msg>app/builder: workaround for ineffassign's false positive<commit_after>package app

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"sync"

	"koding/kites/kloud/stack"
	"koding/kites/kloud/stack/provider"
	"koding/kites/kloud/utils/object"
	"koding/klientctl/app/mixin"
	"koding/klientctl/endpoint/credential"
	"koding/klientctl/endpoint/kloud"
	"koding/klientctl/endpoint/remoteapi"
	"koding/klientctl/helper"

	"github.com/koding/logging"
)

// Builder allows for manipulating contents of stack
// templates in a provider-agnostic manner.
type Builder struct {
	Stack object.Object // the resulting stack object

	Desc stack.Descriptions // provider description; retrieved from kloud, if nil
	Log  logging.Logger     // logger; kloud.DefaultLog if nil

	Koding     *remoteapi.Client  // remote.api client; remoteapi.DefaultClient if nil
	Credential *credential.Client // credential client; credential.DefaultClient if nil

	once sync.Once // for b.init()
}

// TemplateOptions are used when building a template.
type TemplateOptions struct {
	UseDefaults bool         // forces default value when true; disables interactive mode
	Provider    string       // provider name; inferred from Template if empty
	Template    string       // base template to use; retrieved from remote.api's samples, if empty
	Mixin       *mixin.Mixin // optionally a mixin to replace default's user_data
}

// BuildTemplate builds a template with the given options.
//
// If method finishes successfully, returning nil error, b.Stack field
// will hold the built template.
func (b *Builder) BuildTemplate(opts *TemplateOptions) error {
	b.init()

	var (
		defaults map[string]interface{}
		tmpl     = opts.Template
		prov     = opts.Provider
		err      error
	)

	if tmpl == "" {
		if prov == "" {
			return errors.New("either provider or template is required to be non-empty")
		}

		tmpl, defaults, err = b.koding().SampleTemplate(prov)
		if err != nil {
			return err
		}
	}

	if prov == "" {
		prov, err = stack.ReadProvider([]byte(tmpl))
		if err != nil {
			return err
		}
	}

	desc, ok := b.Desc[prov]
	if !ok {
		return fmt.Errorf("provider %q does not exist", prov)
	}

	if opts.Mixin != nil {
		if !desc.CloudInit {
			return fmt.Errorf("provider %q does not support cloud-init files", prov)
		}

		t, err := replaceUserData(tmpl, opts.Mixin, desc)
		if err != nil {
			return err
		}

		p, err := json.Marshal(t)
		if err != nil {
			return err
		}

		tmpl = string(p)
	}

	vars := provider.ReadVariables(tmpl)
	input := make(map[string]string, len(vars))

	for _, v := range vars {
		if !strings.HasPrefix(v.Name, "userInput_") {
			continue
		}

		name := v.Name[len("userInput_"):]
		defValue := ""
		if v, ok := defaults[name]; ok && v != nil {
			defValue = fmt.Sprintf("%v", v)
		}

		var value string

		if !opts.UseDefaults {
			if value, err = helper.Ask("Set %q to [%s]: ", name, defValue); err != nil {
				return err
			}
		}

		if value == "" {
			value = defValue
		}

		input[v.Name] = value
	}

	tmpl = provider.ReplaceVariablesFunc(tmpl, vars, func(v *provider.Variable) string {
		if s, ok := input[v.Name]; ok {
			return s
		}

		return v.String()
	})

	return json.Unmarshal([]byte(tmpl), &b.Stack)
}

func (b *Builder) init() {
	b.once.Do(b.initBuilder)
}

func (b *Builder) initBuilder() {
	if b.Desc == nil {
		var err error
		if b.Desc, err = b.credential().Describe(); err != nil {
			b.log().Warning("unable to retrieve credential description: %s", err)
		}
	}

	if b.Stack == nil {
		b.Stack = make(object.Object)
	}
}

func (b *Builder) log() logging.Logger {
	if b.Log != nil {
		return b.Log
	}
	return kloud.DefaultLog
}

func (b *Builder) koding() *remoteapi.Client {
	if b.Koding != nil {
		return b.Koding
	}
	return remoteapi.DefaultClient
}

func (b *Builder) credential() *credential.Client {
	if b.Credential != nil {
		return b.Credential
	}
	return credential.DefaultClient
}

func replaceUserData(tmpl string, m *mixin.Mixin, desc *stack.Description) (map[string]interface{}, error) {
	var (
		root map[string]interface{}
		key  string
		keys = append([]string{"resource"}, desc.UserData...)
		ok   bool
	)

	if err := json.Unmarshal([]byte(tmpl), &root); err != nil {
		return nil, err
	}

	machines := root

{'fraction_non_alphanumeric': 0.13280510879848628, 'fraction_numerical': 0.0, 'mean_word_length': 3.254024144869215, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '916277', 'n_tokens_mistral': 3078, 'n_tokens_neox': 2840, 'n_words': 814}