<commit_before>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 exec

import (
	"context"
	"fmt"
	"reflect"

	"github.com/apache/beam/sdks/go/pkg/beam/core/funcx"
	"github.com/apache/beam/sdks/go/pkg/beam/core/graph"
	"github.com/apache/beam/sdks/go/pkg/beam/core/typex"
)

//go:generate specialize --input=callers.tmpl --x=data,universals

// NOTE(herohde) 12/11/2017: the below helpers are ripe for type-specialization,
// if the reflection overhead here turns out to be significant. It would
// be nice to be able to quantify any potential improvements first, however.

// MainInput is the main input and is unfolded in the invocation, if present.
type MainInput struct {
	Key    FullValue
	Values []ReStream
}

// Invoke invokes the fn with the given values. The extra values must match the non-main
// side input and emitters. It returns the direct output, if any.
func Invoke(ctx context.Context, fn *funcx.Fn, opt *MainInput, extra ...reflect.Value) (*FullValue, error) {
	if fn == nil {
		return nil, nil // ok: nothing to Invoke
	}

	// (1) Populate contexts

	args := make([]reflect.Value, len(fn.Param))

	if index, ok := fn.Context(); ok {
		args[index] = reflect.ValueOf(ctx)
	}

	// (2) Main input from value, if any.

	in := fn.Params(funcx.FnValue | funcx.FnIter | funcx.FnReIter | funcx.FnEmit)
	i := 0

	if opt != nil {
		if index, ok := fn.EventTime(); ok {
			args[index] = reflect.ValueOf(opt.Key.Timestamp)
		}

		args[in[i]] = Convert(opt.Key.Elm, fn.Param[in[i]].T)
		i++
		if opt.Key.Elm2.Kind() != reflect.Invalid {
			args[in[i]] = Convert(opt.Key.Elm2, fn.Param[in[i]].T)
			i++
		}

		for _, iter := range opt.Values {
			param := fn.Param[in[i]]

			if param.Kind != funcx.FnIter {
				return nil, fmt.Errorf("GBK result values must be iterable: %v", param)
			}

			// TODO(herohde) 12/12/2017: allow form conversion on GBK results?

			args[in[i]] = makeIter(param.T, iter).Value()
			i++
		}
	}

	// (3) Precomputed side input and emitters (or other output).

	for _, arg := range extra {
		args[in[i]] = arg
		i++
	}

	// (4) Invoke

	ret, err := reflectCallNoPanic(fn.Fn, args)
	if err != nil {
		return nil, err
	}
	if index, ok := fn.Error(); ok && ret[index].Interface() != nil {
		return nil, ret[index].Interface().(error)
	}

	// (5) Return direct output, if any.

	out := fn.Returns(funcx.RetValue)
	if len(out) > 0 {
		value := &FullValue{}
		if index, ok := fn.OutEventTime(); ok {
			value.Timestamp = ret[index].Interface().(typex.EventTime)
		}

		value.Elm = ret[out[0]]
		if len(out) > 1 {
			value.Elm2 = ret[out[1]]
		}
		return value, nil
	}

	return nil, nil
}

func makeSideInputs(fn *funcx.Fn, in []*graph.Inbound, side []ReStream) ([]ReusableInput, error) {
	if len(side) == 0 {
		return nil, nil // ok: no side input
	}

	if len(in) != len(side)+1 {
		return nil, fmt.Errorf("found %v inbound, want %v", len(in), len(side)+1)
	}
	param := fn.Params(funcx.FnValue | funcx.FnIter | funcx.FnReIter)
	if len(param) <= len(side) {
		return nil, fmt.Errorf("found %v params, want >%v", len(param), len(side))
	}

	// The side input are last of the above params, so we can compute the offset easily.
	offset := len(param) - len(side)

	var ret []ReusableInput
	for i := 0; i < len(side); i++ {
		s, err := makeSideInput(in[i+1].Kind, fn.Param[i+offset].T, side[i])
		if err != nil {
			return nil, fmt.Errorf("failed to make side input %v: %v", i, err)
		}
		ret = append(ret, s)
	}
	return ret, nil
}

func makeEmitters(fn *funcx.Fn, nodes []Node) ([]ReusableEmitter, error) {
	if len(nodes) == 0 {
		return nil, nil // ok: no output nodes
	}

	offset := 0
	if len(fn.Returns(funcx.RetValue)) > 0 {
		offset = 1
	}
	out := fn.Params(funcx.FnEmit)
	if len(out) != len(nodes)-offset {
		return nil, fmt.Errorf("found %v emitters, want %v", len(out), len(nodes)-offset)
	}

	var ret []ReusableEmitter
	for i := 0; i < len(out); i++ {
		param := fn.Param[out[i]]
		ret = append(ret, makeEmit(param.T, nodes[i+offset]))
	}
	return ret, nil
}

// makeSideInput returns a reusable side input of the given kind and type.
func makeSideInput(kind graph.InputKind, t reflect.Type, values ReStream) (ReusableInput, error) {
	switch kind {
	case graph.Singleton:
		elms, err := ReadAll(values.Open())
		if err != nil {
			return nil, err
		}
		if len(elms) != 1 {
			return nil, fmt.Errorf("singleton side input %v for %v ill-defined", kind, t)
		}
		return &fixedValue{val: Convert(elms[0].Elm, t)}, nil

	case graph.Slice:
		elms, err := ReadAll(values.Open())
		if err != nil {
			return nil, err
		}
		slice := reflect.MakeSlice(t, len(elms), len(elms))
		for i := 0; i < len(elms); i++ {
			slice.Index(i).Set(Convert(elms[i].Elm, t.Elem()))
		}
		return &fixedValue{val: slice}, nil

	case graph.Iter:
		return makeIter(t, values), nil

	case graph.ReIter:
		return makeReIter(t, values), nil

	default:
		panic(fmt.Sprintf("Unexpected side input kind: %v", kind))
	}
}
<commit_msg>BEAM-3473: Fix GroupByKey iterators to be initialized.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 exec

import (
	"context"
	"fmt"
	"reflect"

	"github.com/apache/beam/sdks/go/pkg/beam/core/funcx"
	"github.com/apache/beam/sdks/go/pkg/beam/core/graph"
	"github.com/apache/beam/sdks/go/pkg/beam/core/typex"
)

//go:generate specialize --input=callers.tmpl --x=data,universals

// NOTE(herohde) 12/11/2017: the below helpers are ripe for type-specialization,
// if the reflection overhead here turns out to be significant. It would
// be nice to be able to quantify any potential improvements first, however.

// MainInput is the main input and is unfolded in the invocation, if present.
type MainInput struct {
	Key    FullValue
	Values []ReStream
}

// Invoke invokes the fn with the given values. The extra values must match the non-main
// side input and emitters. It returns the direct output, if any.
func Invoke(ctx context.Context, fn *funcx.Fn, opt *MainInput, extra ...reflect.Value) (*FullValue, error) {
	if fn == nil {
		return nil, nil // ok: nothing to Invoke
	}

	// (1) Populate contexts

	args := make([]reflect.Value, len(fn.Param))

	if index, ok := fn.Context(); ok {
		args[index] = reflect.ValueOf(ctx)
	}

	// (2) Main input from value, if any.

	in := fn.Params(funcx.FnValue | funcx.FnIter | funcx.FnReIter | funcx.FnEmit)
	i := 0

	if opt != nil {
		if index, ok := fn.EventTime(); ok {
			args[index] = reflect.ValueOf(opt.Key.Timestamp)
		}

		args[in[i]] = Convert(opt.Key.Elm, fn.Param[in[i]].T)
		i++
		if opt.Key.Elm2.Kind() != reflect.Invalid {
			args[in[i]] = Convert(opt.Key.Elm2, fn.Param[in[i]].T)
			i++
		}

		for _, iter := range opt.Values {
			param := fn.Param[in[i]]

			if param.Kind != funcx.FnIter {
				return nil, fmt.Errorf("GBK result values must be iterable: %v", param)
			}

			// TODO(herohde) 12/12/2017: allow form conversion on GBK results?

			it := makeIter(param.T, iter)
			it.Init()
			args[in[i]] = it.Value()
			i++
		}
	}

	// (3) Precomputed side input and emitters (or other output).

	for _, arg := range extra {
		args[in[i]] = arg
		i++
	}

	// (4) Invoke

	ret, err := reflectCallNoPanic(fn.Fn, args)
	if err != nil {
		return nil, err
	}
	if index, ok := fn.Error(); ok && ret[index].Interface() != nil {
		return nil, ret[index].Interface().(error)
	}

	// (5) Return direct output, if any.

	out := fn.Returns(funcx.RetValue)
	if len(out) > 0 {
		value := &FullValue{}
		if index, ok := fn.OutEventTime(); ok {
			value.Timestamp = ret[index].Interface().(typex.EventTime)
		}

		value.Elm = ret[out[0]]
		if len(out) > 1 {
			value.Elm2 = ret[out[1]]
		}
		return value, nil
	}

	return nil, nil
}

func makeSideInputs(fn *funcx.Fn, in []*graph.Inbound, side []ReStream) ([]ReusableInput, error) {
	if len(side) == 0 {
		return nil, nil // ok: no side input
	}

	if len(in) != len(side)+1 {
		return nil, fmt.Errorf("found %v inbound, want %v", len(in), len(side)+1)
	}
	param := fn.Params(funcx.FnValue | funcx.FnIter | funcx.FnReIter)
	if len(param) <= len(side) {
		return nil, fmt.Errorf("found %v params, want >%v", len(param), len(side))
	}

	// The side input are last of the above params, so we can compute the offset easily.
	offset := len(param) - len(side)

	var ret []ReusableInput
	for i := 0; i < len(side); i++ {
		s, err := makeSideInput(in[i+1].Kind, fn.Param[i+offset].T, side[i])
		if err != nil {
			return nil, fmt.Errorf("failed to make side input %v: %v", i, err)
		}
		ret = append(ret, s)
	}
	return ret, nil
}

func makeEmitters(fn *funcx.Fn, nodes []Node) ([]ReusableEmitter, error) {
	if len(nodes) == 0 {
		return nil, nil // ok: no output nodes
	}

	offset := 0
	if len(fn.Returns(funcx.RetValue)) > 0 {
		offset = 1
	}
	out := fn.Params(funcx.FnEmit)
	if len(out) != len(nodes)-offset {
		return nil, fmt.Errorf("found %v emitters, want %v", len(out), len(nodes)-offset)
	}

	var ret []ReusableEmitter
	for i := 0; i < len(out); i++ {
		param := fn.Param[out[i]]
		ret = append(ret, makeEmit(param.T, nodes[i+offset]))
	}
	return ret, nil
}

// makeSideInput returns a reusable side input of the given kind and type.
func makeSideInput(kind graph.InputKind, t reflect.Type, values ReStream) (ReusableInput, error) {
	switch kind {
	case graph.Singleton:
		elms, err := ReadAll(values.Open())
		if err != nil {
			return nil, err
		}
		if len(elms) != 1 {
			return nil, fmt.Errorf("singleton side input %v for %v ill-defined", kind, t)
		}
		return &fixedValue{val: Convert(elms[0].Elm, t)}, nil

	case graph.Slice:
		elms, err := ReadAll(values.Open())
		if err != nil {
			return nil, err
		}
		slice := reflect.MakeSlice(t, len(elms), len(elms))
		for i := 0; i < len(elms); i++ {
			slice.Index(i).Set(Convert(elms[i].Elm, t.Elem()))
		}
		return &fixedValue{val: slice}, nil

	case graph.Iter:
		return makeIter(t, values), nil

	case graph.ReIter:
		return makeReIter(t, values), nil

	default:
		panic(fmt.Sprintf("Unexpected side input kind: %v", kind))
	}
}
<|endoftext|>


<commit_before>package sitrep

import (
	"log"
	"fmt"
	"strings"
	"encoding/json"
	"net/http"
	"github.com/icambridge/genkins"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/gorilla/mux"
	"sitrep/model"
)

func getJenkinsJobs() *memcache.Item {
	keyStr := "jenkins.jobs.all"

	item, err := memClient.Get(keyStr)

	if err != nil {
		log.Println(err)
	}

	if item == nil {
		jobs, err := jenkins.Jobs.GetAll()

		if err != nil {
			log.Printf("Tried to get jobs for jenkins but got %v", err)
		}

		json, err := json.Marshal(jobs.Jobs)

		if err != nil {
			log.Printf("Tried to turn jobs into json but got %v", err)
		}

		item = &memcache.Item{Key: keyStr, Value: json, Expiration: 300}
		memClient.Set(item)
	}

	return item
}

func JenkinsBuild(w http.ResponseWriter, r *http.Request) {

	params := mux.Vars(r)
	repo := strings.ToLower(params["repo"])
	branch := strings.ToLower(params["branch"])

	p := map[string]string{
		"branchName": branch,
	}

	jenkins.Builds.TriggerWithParameters(repo, p)

	fmt.Fprint(w, "{\"status\":\"Success\"}")
}

func JenkinsHook(w http.ResponseWriter, r *http.Request) {

	job, err := genkins.GetHook(r)

	if err != nil {
		log.Println(err)
	}

	info, err := jenkins.Builds.GetInfo(&job.Build)
	if err != nil {
		log.Println(err)
	}

	branchName := info.GetBranchName()

	b := &model.Build{
		BuildId: job.Build.Number,
		ApplicationName: job.Name,
		Status: job.Build.Status,
		Phase: job.Build.Phase,
		Branch: branchName,
	}

	if b.Phase != "FINISHED"  {
		return
	}

	// TODO seperate out logic
	err = buildModel.Save(b)

	if err != nil {
		log.Println(err)
	}
	owner, _ := cfg.String("bitbucket", "owner")

	pr, err := bitbucket.PullRequests.GetBranch(owner, b.ApplicationName, b.Branch)

	if err != nil {
		log.Println(err)
	}

	if pr == nil {
		return
	}

	if b.Status == "SUCCESS" {
		err = bitbucket.PullRequests.Approve(owner, b.ApplicationName, pr.Id)
		if err != nil {
			log.Println(err)
		}
	} else if b.Status == "FAILURE" {
		err = bitbucket.PullRequests.Unapprove(owner, b.ApplicationName, pr.Id)
		if err != nil {
			log.Println(err)
		}
	}
}
<commit_msg>Closes #28 - Removed strings.ToLower on branch name<commit_after>package sitrep

import (
	"log"
	"fmt"
	"strings"
	"encoding/json"
	"net/http"
	"github.com/icambridge/genkins"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/gorilla/mux"
	"sitrep/model"
)

func getJenkinsJobs() *memcache.Item {
	keyStr := "jenkins.jobs.all"

	item, err := memClient.Get(keyStr)

	if err != nil {
		log.Println(err)
	}

	if item == nil {
		jobs, err := jenkins.Jobs.GetAll()

		if err != nil {
			log.Printf("Tried to get jobs for jenkins but got %v", err)
		}

		json, err := json.Marshal(jobs.Jobs)

		if err != nil {
			log.Printf("Tried to turn jobs into json but got %v", err)
		}

		item = &memcache.Item{Key: keyStr, Value: json, Expiration: 300}
		memClient.Set(item)
	}

	return item
}

func JenkinsBuild(w http.ResponseWriter, r *http.Request) {

	params := mux.Vars(r)
	repo := strings.ToLower(params["repo"])
	branch := params["branch"]

	p := map[string]string{
		"branchName": branch,
	}

	jenkins.Builds.TriggerWithParameters(repo, p)

	fmt.Fprint(w, "{\"status\":\"Success\"}")
}

func JenkinsHook(w http.ResponseWriter, r *http.Request) {

	job, err := genkins.GetHook(r)

	if err != nil {
		log.Println(err)
	}

	info, err := jenkins.Builds.GetInfo(&job.Build)
	if err != nil {
		log.Println(err)
	}

	branchName := info.GetBranchName()

	b := &model.Build{
		BuildId: job.Build.Number,
		ApplicationName: job.Name,
		Status: job.Build.Status,
		Phase: job.Build.Phase,
		Branch: branchName,
	}

	if b.Phase != "FINISHED"  {
		return
	}

	// TODO seperate out logic
	err = buildModel.Save(b)

	if err != nil {
		log.Println(err)
	}
	owner, _ := cfg.String("bitbucket", "owner")

	pr, err := bitbucket.PullRequests.GetBranch(owner, b.ApplicationName, b.Branch)

	if err != nil {
		log.Println(err)
	}

	if pr == nil {
		return
	}

	if b.Status == "SUCCESS" {
		err = bitbucket.PullRequests.Approve(owner, b.ApplicationName, pr.Id)
		if err != nil {
			log.Println(err)
		}
	} else if b.Status == "FAILURE" {
		err = bitbucket.PullRequests.Unapprove(owner, b.ApplicationName, pr.Id)
		if err != nil {
			log.Println(err)
		}
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/gruntwork-io/terragrunt/config"
	"os"
	"github.com/gruntwork-io/terragrunt/errors"
	"path/filepath"
	"github.com/hashicorp/go-getter"
	urlhelper "github.com/hashicorp/go-getter/helper/url"
	"io/ioutil"
	"net/url"
	"fmt"
)

type TerraformSource struct {
	// A canonical version of RawSource, in URL format
	CanonicalSourceURL *url.URL

	// The folder where we should download the source to
	DownloadDir         string

	// The path to a file in DownloadDir that stores the version number of the code
	VersionFile         string
}

func (src *TerraformSource) String() string {
	return fmt.Sprintf("TerraformSource{CanonicalSourceURL = %v, DownloadDir = %v, VersionFile = %v}", src.CanonicalSourceURL, src.DownloadDir, src.VersionFile)
}

// 1. Download the given source URL, which should use Terraform's module source syntax, into a temporary folder
// 2. Copy the contents of terragruntOptions.WorkingDir into the temporary folder.
// 3. Set terragruntOptions.WorkingDir to the temporary folder.
//
// See the processTerraformSource method for how we determine the temporary folder so we can reuse it across multiple
// runs of Terragrunt to avoid downloading everything from scratch every time.
func downloadTerraformSource(source string, terragruntOptions *options.TerragruntOptions) error {
	terraformSource, err := processTerraformSource(source, terragruntOptions)
	if err != nil {
		return err
	}

	if err := downloadTerraformSourceIfNecessary(terraformSource, terragruntOptions); err != nil {
		return err
	}

	terragruntOptions.Logger.Printf("Copying files from %s into %s", terragruntOptions.WorkingDir, terraformSource.DownloadDir)
	if err := util.CopyFolderContents(terragruntOptions.WorkingDir, terraformSource.DownloadDir); err != nil {
		return err
	}

	terragruntOptions.Logger.Printf("Setting working directory to %s", terraformSource.DownloadDir)
	terragruntOptions.WorkingDir = terraformSource.DownloadDir

	return nil
}

// Download the specified TerraformSource if the latest code hasn't already been downloaded.
func downloadTerraformSourceIfNecessary(terraformSource *TerraformSource, terragruntOptions *options.TerragruntOptions) error {
	alreadyLatest, err := alreadyHaveLatestCode(terraformSource)
	if err != nil {
		return err
	}

	if alreadyLatest {
		terragruntOptions.Logger.Printf("Terraform files in %s are up to date. Will not download again.", terraformSource.DownloadDir)
		return nil
	}

	if err := cleanupTerraformFiles(terraformSource.DownloadDir, terragruntOptions); err != nil {
		return err
	}

	if err := terraformInit(terraformSource, terragruntOptions); err != nil {
		return err
	}

	if err := writeVersionFile(terraformSource); err != nil {
		return err
	}

	return nil
}

// Returns true if the specified TerraformSource, of the exact same version, has already been downloaded into the
// DownloadFolder. This helps avoid downloading the same code multiple times. Note that if the TerraformSource points
// to a local file path, we assume the user is doing local development and always return false to ensure the latest
// code is downloaded (or rather, copied) every single time. See the processTerraformSource method for more info.
func alreadyHaveLatestCode(terraformSource *TerraformSource) (bool, error) {
	if 	isLocalSource(terraformSource.CanonicalSourceURL) ||
		!util.FileExists(terraformSource.DownloadDir) ||
		!util.FileExists(terraformSource.VersionFile) {

		return false, nil
	}

	currentVersion := encodeSourceVersion(terraformSource.CanonicalSourceURL)
	previousVersion, err := readVersionFile(terraformSource)

	if err != nil {
		return false, err
	}

	return previousVersion == currentVersion, nil
}

// Return the version number stored in the DownloadDir. This version number can be used to check if the Terraform code
// that has already been downloaded is the same as the version the user is currently requesting. The version number is
// calculated using the encodeSourceVersion method.
func readVersionFile(terraformSource *TerraformSource) (string, error) {
	return util.ReadFileAsString(terraformSource.VersionFile)
}

// Write a file into the DownloadDir that contains the version number of this source code. The version number is
// calculated using the encodeSourceVersion method.
func writeVersionFile(terraformSource *TerraformSource) error {
	version := encodeSourceVersion(terraformSource.CanonicalSourceURL)
	return errors.WithStackTrace(ioutil.WriteFile(terraformSource.VersionFile, []byte(version), 0640))
}

// Take the given source path and create a TerraformSource struct from it, including the folder where the source should
// be downloaded to. Our goal is to reuse the download folder for the same source URL between Terragrunt runs.
// Otherwise, for every Terragrunt command, you'd have to wait for Terragrunt to download your Terraform code, download
// that code's dependencies (terraform get), and configure remote state (terraform remote config), which is very slow.
//
// To maximize reuse, given a working directory w and a source URL s, we download the code into the folder /T/W/S where:
//
// 1. T is the OS temp dir (e.g. /tmp).
// 2. W is the base 64 encoded sha1 hash of w. This ensures that if you are running Terragrunt concurrently in
//    multiple folders (e.g. during automated tests), then even if those folders are using the same source URL s, they
//    do not overwrite each other.
// 3. S is the base 64 encoded sha1 has of s without its query string. For remote source URLs (e.g. Git
//    URLs), this is based on the assumption that the scheme/host/path of the URL
//    (e.g. git::github.com/foo/bar//some-module) identifies the module name, and we always want to download the same
//    module name into the same folder (see the encodeSourceName method). We also assume the version of the module is
//    stored in the query string (e.g. ref=v0.0.3), so we store the base 64 encoded sha1 of the query string in a
//    file called .terragrunt-source-version within S.
//
// The downloadTerraformSourceIfNecessary decides when we should download the Terraform code and when not to. It uses
// the following rules:
//
// 1. Always download source URLs pointing to local file paths.
// 2. Only download source URLs pointing to remote paths if /T/W/S doesn't already exist or, if it does exist, if the
//    version number in /T/W/S/.terragrunt-source-version doesn't match the current version.
func processTerraformSource(source string, terragruntOptions *options.TerragruntOptions) (*TerraformSource, error) {
	canonicalWorkingDir, err := util.CanonicalPath(terragruntOptions.WorkingDir, "")
	if err != nil {
		return nil, err
	}

	rawSourceUrl, err := getter.Detect(source, canonicalWorkingDir, getter.Detectors)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	canonicalSourceUrl, err := urlhelper.Parse(rawSourceUrl)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	if isLocalSource(canonicalSourceUrl) {
		// Always use canonical file paths for local source folders, rather than relative paths, to ensure
		// that the same local folder always maps to the same download folder, no matter how the local folder
		// path is specified
		canonicalFilePath, err := util.CanonicalPath(canonicalSourceUrl.Path, "")
		if err != nil {
			return nil, err
		}
		canonicalSourceUrl.Path = canonicalFilePath
	}

	moduleName, err := encodeSourceName(canonicalSourceUrl)
	if err != nil {
		return nil, err
	}

	encodedWorkingDir := util.EncodeBase64Sha1(canonicalWorkingDir)
	downloadDir := filepath.Join(os.TempDir(), "terragrunt-download", encodedWorkingDir, moduleName)
	versionFile := filepath.Join(downloadDir, ".terragrunt-source-version")

	return &TerraformSource{
		CanonicalSourceURL: canonicalSourceUrl,
		DownloadDir: downloadDir,
		VersionFile: versionFile,
	}, nil
}

// Encode a version number for the given source URL. When calculating a version number, we simply take the query
// string of the source URL, calculate its sha1, and base 64 encode it. For remote URLs (e.g. Git URLs), this is
// based on the assumption that the scheme/host/path of the URL (e.g. git::github.com/foo/bar//some-module) identifies
// the module name and the query string (e.g. ?ref=v0.0.3) identifies the version. For local file paths, there is no
// query string, so the same file path (/foo/bar) is always considered the same version. See also the encodeSourceName
// and processTerraformSource methods.
func encodeSourceVersion(sourceUrl *url.URL) string {
	return util.EncodeBase64Sha1(sourceUrl.Query().Encode())
}

// Encode a the module name for the given source URL. When calculating a module name, we calculate the base 64 encoded
// sha1 of the entire source URL without the query string. For remote URLs (e.g. Git URLs), this is based on the
// assumption that the scheme/host/path of the URL (e.g. git::github.com/foo/bar//some-module) identifies
// the module name and the query string (e.g. ?ref=v0.0.3) identifies the version. For local file paths, there is no
// query string, so the same file path (/foo/bar) is always considered the same version. See also the encodeSourceVersion
// and processTerraformSource methods.
func encodeSourceName(sourceUrl *url.URL) (string, error) {
	sourceUrlNoQuery, err := urlhelper.Parse(sourceUrl.String())
	if err != nil {
		return "", errors.WithStackTrace(err)
	}

	sourceUrlNoQuery.RawQuery = ""

	return util.EncodeBase64Sha1(sourceUrlNoQuery.String()), nil
}

// Returns true if the given URL refers to a path on the local file system
func isLocalSource(sourceUrl *url.URL) bool {
	return sourceUrl.Scheme == "file"
}

// If this temp folder already exists, simply delete all the Terraform configurations (*.tf) within it
// (the terraform init command will redownload the latest ones), but leave all the other files, such
// as the .terraform folder with the downloaded modules and remote state settings.
func cleanupTerraformFiles(path string, terragruntOptions *options.TerragruntOptions) error {
	if !util.FileExists(path) {
		return nil
	}

	terragruntOptions.Logger.Printf("Cleaning up existing *.tf files in %s", path)

	files, err := filepath.Glob(filepath.Join(path, "*.tf"))
	if err != nil {
		return errors.WithStackTrace(err)
	}
	return util.DeleteFiles(files)
}

// There are two ways a user can tell Terragrunt that it needs to download Terraform configurations from a specific
// URL: via a command-line option or via an entry in the .terragrunt config file. If the user used one of these, this
// method returns the source URL and the boolean true; if not, this method returns an empty string and false.
func getTerraformSourceUrl(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) (string, bool) {
	if terragruntOptions.Source != "" {
		return terragruntOptions.Source, true
	} else if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.Source != "" {
		return terragruntConfig.Terraform.Source, true
	} else {
		return "", false
	}
}

// Download the code from the Canonical Source URL into the Download Folder using the terraform init command
func terraformInit(terraformSource *TerraformSource, terragruntOptions *options.TerragruntOptions) error {
	terragruntOptions.Logger.Printf("Downloading Terraform configurations from %s into %s", terraformSource.CanonicalSourceURL, terraformSource.DownloadDir)

	terragruntInitOptions := terragruntOptions.Clone(terragruntOptions.TerragruntConfigPath)
	terragruntInitOptions.TerraformCliArgs = []string{"init", terraformSource.CanonicalSourceURL.String(), terraformSource.DownloadDir}

	return runTerraformCommand(terragruntInitOptions)
}
<commit_msg>Add comments on TerraformSource struct<commit_after>package cli

import (
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/gruntwork-io/terragrunt/config"
	"os"
	"github.com/gruntwork-io/terragrunt/errors"
	"path/filepath"
	"github.com/hashicorp/go-getter"
	urlhelper "github.com/hashicorp/go-getter/helper/url"
	"io/ioutil"
	"net/url"
	"fmt"
)

// This struct represents information about Terraform source code that needs to be downloaded
type TerraformSource struct {
	// A canonical version of RawSource, in URL format
	CanonicalSourceURL *url.URL

	// The folder where we should download the source to
	DownloadDir         string

	// The path to a file in DownloadDir that stores the version number of the code
	VersionFile         string
}

func (src *TerraformSource) String() string {
	return fmt.Sprintf("TerraformSource{CanonicalSourceURL = %v, DownloadDir = %v, VersionFile = %v}", src.CanonicalSourceURL, src.DownloadDir, src.VersionFile)
}

// 1. Download the given source URL, which should use Terraform's module source syntax, into a temporary folder
// 2. Copy the contents of terragruntOptions.WorkingDir into the temporary folder.
// 3. Set terragruntOptions.WorkingDir to the temporary folder.
//
// See the processTerraformSource method for how we determine the temporary folder so we can reuse it across multiple
// runs of Terragrunt to avoid downloading everything from scratch every time.
func downloadTerraformSource(source string, terragruntOptions *options.TerragruntOptions) error {
	terraformSource, err := processTerraformSource(source, terragruntOptions)
	if err != nil {
		return err
	}

	if err := downloadTerraformSourceIfNecessary(terraformSource, terragruntOptions); err != nil {
		return err
	}

	terragruntOptions.Logger.Printf("Copying files from %s into %s", terragruntOptions.WorkingDir, terraformSource.DownloadDir)
	if err := util.CopyFolderContents(terragruntOptions.WorkingDir, terraformSource.DownloadDir); err != nil {
		return err
	}

	terragruntOptions.Logger.Printf("Setting working directory to %s", terraformSource.DownloadDir)
	terragruntOptions.WorkingDir = terraformSource.DownloadDir

	return nil
}

// Download the specified TerraformSource if the latest code hasn't already been downloaded.
func downloadTerraformSourceIfNecessary(terraformSource *TerraformSource, terragruntOptions *options.TerragruntOptions) error {
	alreadyLatest, err := alreadyHaveLatestCode(terraformSource)
	if err != nil {
		return err
	}

	if alreadyLatest {
		terragruntOptions.Logger.Printf("Terraform files in %s are up to date. Will not download again.", terraformSource.DownloadDir)
		return nil
	}

	if err := cleanupTerraformFiles(terraformSource.DownloadDir, terragruntOptions); err != nil {
		return err
	}

	if err := terraformInit(terraformSource, terragruntOptions); err != nil {
		return err
	}

	if err := writeVersionFile(terraformSource); err != nil {
		return err
	}

	return nil
}

// Returns true if the specified TerraformSource, of the exact same version, has already been downloaded into the
// DownloadFolder. This helps avoid downloading the same code multiple times. Note that if the TerraformSource points
// to a local file path, we assume the user is doing local development and always return false to ensure the latest
// code is downloaded (or rather, copied) every single time. See the processTerraformSource method for more info.
func alreadyHaveLatestCode(terraformSource *TerraformSource) (bool, error) {
	if 	isLocalSource(terraformSource.CanonicalSourceURL) ||
		!util.FileExists(terraformSource.DownloadDir) ||
		!util.FileExists(terraformSource.VersionFile) {

		return false, nil
	}

	currentVersion := encodeSourceVersion(terraformSource.CanonicalSourceURL)
	previousVersion, err := readVersionFile(terraformSource)

	if err != nil {
		return false, err
	}

	return previousVersion == currentVersion, nil
}

// Return the version number stored in the DownloadDir. This version number can be used to check if the Terraform code
// that has already been downloaded is the same as the version the user is currently requesting. The version number is
// calculated using the encodeSourceVersion method.
func readVersionFile(terraformSource *TerraformSource) (string, error) {
	return util.ReadFileAsString(terraformSource.VersionFile)
}

// Write a file into the DownloadDir that contains the version number of this source code. The version number is
// calculated using the encodeSourceVersion method.
func writeVersionFile(terraformSource *TerraformSource) error {
	version := encodeSourceVersion(terraformSource.CanonicalSourceURL)
	return errors.WithStackTrace(ioutil.WriteFile(terraformSource.VersionFile, []byte(version), 0640))
}

// Take the given source path and create a TerraformSource struct from it, including the folder where the source should
// be downloaded to. Our goal is to reuse the download folder for the same source URL between Terragrunt runs.
// Otherwise, for every Terragrunt command, you'd have to wait for Terragrunt to download your Terraform code, download
// that code's dependencies (terraform get), and configure remote state (terraform remote config), which is very slow.
//
// To maximize reuse, given a working directory w and a source URL s, we download the code into the folder /T/W/S where:
//
// 1. T is the OS temp dir (e.g. /tmp).
// 2. W is the base 64 encoded sha1 hash of w. This ensures that if you are running Terragrunt concurrently in
//    multiple folders (e.g. during automated tests), then even if those folders are using the same source URL s, they
//    do not overwrite each other.
// 3. S is the base 64 encoded sha1 has of s without its query string. For remote source URLs (e.g. Git
//    URLs), this is based on the assumption that the scheme/host/path of the URL
//    (e.g. git::github.com/foo/bar//some-module) identifies the module name, and we always want to download the same
//    module name into the same folder (see the encodeSourceName method). We also assume the version of the module is
//    stored in the query string (e.g. ref=v0.0.3), so we store the base 64 encoded sha1 of the query string in a
//    file called .terragrunt-source-version within S.
//
// The downloadTerraformSourceIfNecessary decides when we should download the Terraform code and when not to. It uses
// the following rules:
//
// 1. Always download source URLs pointing to local file paths.
// 2. Only download source URLs pointing to remote paths if /T/W/S doesn't already exist or, if it does exist, if the
//    version number in /T/W/S/.terragrunt-source-version doesn't match the current version.
func processTerraformSource(source string, terragruntOptions *options.TerragruntOptions) (*TerraformSource, error) {
	canonicalWorkingDir, err := util.CanonicalPath(terragruntOptions.WorkingDir, "")
	if err != nil {
		return nil, err
	}

	rawSourceUrl, err := getter.Detect(source, canonicalWorkingDir, getter.Detectors)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	canonicalSourceUrl, err := urlhelper.Parse(rawSourceUrl)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	if isLocalSource(canonicalSourceUrl) {
		// Always use canonical file paths for local source folders, rather than relative paths, to ensure
		// that the same local folder always maps to the same download folder, no matter how the local folder
		// path is specified
		canonicalFilePath, err := util.CanonicalPath(canonicalSourceUrl.Path, "")
		if err != nil {
			return nil, err
		}
		canonicalSourceUrl.Path = canonicalFilePath
	}

	moduleName, err := encodeSourceName(canonicalSourceUrl)
	if err != nil {
		return nil, err
	}

	encodedWorkingDir := util.EncodeBase64Sha1(canonicalWorkingDir)
	downloadDir := filepath.Join(os.TempDir(), "terragrunt-download", encodedWorkingDir, moduleName)
	versionFile := filepath.Join(downloadDir, ".terragrunt-source-version")

	return &TerraformSource{
		CanonicalSourceURL: canonicalSourceUrl,
		DownloadDir: downloadDir,
		VersionFile: versionFile,
	}, nil
}

// Encode a version number for the given source URL. When calculating a version number, we simply take the query
// string of the source URL, calculate its sha1, and base 64 encode it. For remote URLs (e.g. Git URLs), this is
// based on the assumption that the scheme/host/path of the URL (e.g. git::github.com/foo/bar//some-module) identifies
// the module name and the query string (e.g. ?ref=v0.0.3) identifies the version. For local file paths, there is no
// query string, so the same file path (/foo/bar) is always considered the same version. See also the encodeSourceName
// and processTerraformSource methods.
func encodeSourceVersion(sourceUrl *url.URL) string {
	return util.EncodeBase64Sha1(sourceUrl.Query().Encode())
}

// Encode a the module name for the given source URL. When calculating a module name, we calculate the base 64 encoded
// sha1 of the entire source URL without the query string. For remote URLs (e.g. Git URLs), this is based on the
// assumption that the scheme/host/path of the URL (e.g. git::github.com/foo/bar//some-module) identifies
// the module name and the query string (e.g. ?ref=v0.0.3) identifies the version. For local file paths, there is no
// query string, so the same file path (/foo/bar) is always considered the same version. See also the encodeSourceVersion
// and processTerraformSource methods.
func encodeSourceName(sourceUrl *url.URL) (string, error) {
	sourceUrlNoQuery, err := urlhelper.Parse(sourceUrl.String())
	if err != nil {
		return "", errors.WithStackTrace(err)
	}

	sourceUrlNoQuery.RawQuery = ""

	return util.EncodeBase64Sha1(sourceUrlNoQuery.String()), nil
}

// Returns true if the given URL refers to a path on the local file system
func isLocalSource(sourceUrl *url.URL) bool {
	return sourceUrl.Scheme == "file"
}

// If this temp folder already exists, simply delete all the Terraform configurations (*.tf) within it
// (the terraform init command will redownload the latest ones), but leave all the other files, such
// as the .terraform folder with the downloaded modules and remote state settings.
func cleanupTerraformFiles(path string, terragruntOptions *options.TerragruntOptions) error {
	if !util.FileExists(path) {
		return nil
	}

	terragruntOptions.Logger.Printf("Cleaning up existing *.tf files in %s", path)

	files, err := filepath.Glob(filepath.Join(path, "*.tf"))
	if err != nil {
		return errors.WithStackTrace(err)
	}
	return util.DeleteFiles(files)
}

// There are two ways a user can tell Terragrunt that it needs to download Terraform configurations from a specific
// URL: via a command-line option or via an entry in the .terragrunt config file. If the user used one of these, this
// method returns the source URL and the boolean true; if not, this method returns an empty string and false.
func getTerraformSourceUrl(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) (string, bool) {
	if terragruntOptions.Source != "" {
		return terragruntOptions.Source, true
	} else if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.Source != "" {
		return terragruntConfig.Terraform.Source, true
	} else {
		return "", false
	}
}

// Download the code from the Canonical Source URL into the Download Folder using the terraform init command
func terraformInit(terraformSource *TerraformSource, terragruntOptions *options.TerragruntOptions) error {
	terragruntOptions.Logger.Printf("Downloading Terraform configurations from %s into %s", terraformSource.CanonicalSourceURL, terraformSource.DownloadDir)

	terragruntInitOptions := terragruntOptions.Clone(terragruntOptions.TerragruntConfigPath)
	terragruntInitOptions.TerraformCliArgs = []string{"init", terraformSource.CanonicalSourceURL.String(), terraformSource.DownloadDir}

	return runTerraformCommand(terragruntInitOptions)
}
<|endoftext|>


<commit_before>package gircclient

import (
	"bufio"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"net"
	"runtime"
	"testing"
	"time"

	"github.com/DanielOaks/girc-go/ircmap"
	"github.com/DanielOaks/girc-go/ircmsg"
)

func TestPlainConnection(t *testing.T) {
	reactor := NewReactor()
	client := reactor.CreateServer("local")

	initialiseServerConnection(client)

	// we mock up a server connection to test the client
	listener, _ := net.Listen("tcp", ":0")

	client.Connect(listener.Addr().String(), false, nil)
	go client.ReceiveLoop()

	testServerConnection(t, reactor, client, listener)
}

func TestTLSConnection(t *testing.T) {
	reactor := NewReactor()
	client := reactor.CreateServer("local")

	initialiseServerConnection(client)

	// generate a test certificate to use
	priv, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)

	duration30Days, _ := time.ParseDuration("-30h")
	notBefore := time.Now().Add(duration30Days) // valid 30 hours ago
	duration1Year, _ := time.ParseDuration("90h")
	notAfter := notBefore.Add(duration1Year) // for 90 hours

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"gIRC-Go Co"},
		},
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IsCA: true,
	}

	template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1"))
	template.IPAddresses = append(template.IPAddresses, net.ParseIP("::"))
	template.DNSNames = append(template.DNSNames, "localhost")

	derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)

	c := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	b, _ := x509.MarshalECPrivateKey(priv)
	k := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})

	// we mock up a server connection to test the client
	listenerKeyPair, _ := tls.X509KeyPair(c, k)

	var listenerTLSConfig tls.Config
	listenerTLSConfig.Certificates = make([]tls.Certificate, 0)
	listenerTLSConfig.Certificates = append(listenerTLSConfig.Certificates, listenerKeyPair)
	listener, _ := tls.Listen("tcp", ":0", &listenerTLSConfig)

	// mock up the client side too
	clientTLSCertPool := x509.NewCertPool()
	clientTLSCertPool.AppendCertsFromPEM(c)

	var clientTLSConfig tls.Config
	clientTLSConfig.RootCAs = clientTLSCertPool
	clientTLSConfig.ServerName = "localhost"
	go client.Connect(listener.Addr().String(), true, &clientTLSConfig)
	go client.ReceiveLoop()

	testServerConnection(t, reactor, client, listener)
}

func sendMessage(conn net.Conn, tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
	ircmsg := ircmsg.MakeMessage(tags, prefix, command, params...)
	line, err := ircmsg.Line()
	if err != nil {
		return
	}
	fmt.Fprintf(conn, line)

	// need to wait for a quick moment here for TLS to process any changes this
	// message has caused
	runtime.Gosched()
	waitTime, _ := time.ParseDuration("10ms")
	time.Sleep(waitTime)
}

func initialiseServerConnection(client *ServerConnection) {
	client.InitialNick = "coolguy"
	client.InitialUser = "c"
	client.InitialRealName = "girc-go Test Client  "
}

func testServerConnection(t *testing.T, reactor Reactor, client *ServerConnection, listener net.Listener) {
	// start our reader
	conn, _ := listener.Accept()
	reader := bufio.NewReader(conn)

	var message string

	// CAP
	message, _ = reader.ReadString('\n')
	if message != "CAP LS 302\r\n" {
		t.Error(
			"Did not receive CAP LS message, received: [",
			message,
			"]",
		)
		return
	}

	sendMessage(conn, nil, "example.com", "CAP", "*", "LS", "*", "multi-prefix userhost-in-names sasl=PLAIN")
	sendMessage(conn, nil, "example.com", "CAP", "*", "LS", "chghost")

	message, _ = reader.ReadString('\n')
	if message != "CAP REQ :chghost multi-prefix sasl userhost-in-names\r\n" {
		t.Error(
			"Did not receive CAP REQ message, received: [",
			message,
			"]",
		)
		return
	}

	// these should be silently ignored
	fmt.Fprintf(conn, "\r\n\r\n\r\n")

	sendMessage(conn, nil, "example.com", "CAP", "*", "ACK", "chghost multi-prefix userhost-in-names sasl")

	message, _ = reader.ReadString('\n')
	if message != "CAP END\r\n" {
		t.Error(
			"Did not receive CAP END message, received: [",
			message,
			"]",
		)
		return
	}

	// NICK/USER
	message, _ = reader.ReadString('\n')
	if message != "NICK coolguy\r\n" {
		t.Error(
			"Did not receive NICK message, received: [",
			message,
			"]",
		)
		return
	}

	message, _ = reader.ReadString('\n')
	if message != "USER c 0 * :girc-go Test Client  \r\n" {
		t.Error(
			"Did not receive USER message, received: [",
			message,
			"]",
		)
		return
	}

	// make sure nick changes properly
	sendMessage(conn, nil, "example.com", "001", "dan", "Welcome to the gIRC-Go Test Network!")

	if client.Nick != "dan" {
		t.Error(
			"Nick was not set with 001, expected",
			"dan",
			"got",
			client.Nick,
		)
		return
	}

	// make sure casemapping is set properly
	sendMessage(conn, nil, "example.com", "005", "dan", "CASEMAPPING=ascii", "are available on this server")

	if client.Casemapping != ircmap.ASCII {
		t.Error(
			"Casemapping was not set with 005, expected",
			ircmap.ASCII,
			"got",
			client.Casemapping,
		)
		return
	}

	// shutdown client
	reactor.Shutdown(" Get mad!  ")

	message, _ = reader.ReadString('\n')
	if message != "QUIT : Get mad!  \r\n" {
		t.Error(
			"Did not receive QUIT message, received: [",
			message,
			"]",
		)
		return
	}

	// close connection and listener
	conn.Close()
	listener.Close()
}
<commit_msg>client: Improve features.go test coverage<commit_after>package gircclient

import (
	"bufio"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"net"
	"runtime"
	"testing"
	"time"

	"github.com/DanielOaks/girc-go/ircmap"
	"github.com/DanielOaks/girc-go/ircmsg"
)

func TestPlainConnection(t *testing.T) {
	reactor := NewReactor()
	client := reactor.CreateServer("local")

	initialiseServerConnection(client)

	// we mock up a server connection to test the client
	listener, _ := net.Listen("tcp", ":0")

	client.Connect(listener.Addr().String(), false, nil)
	go client.ReceiveLoop()

	testServerConnection(t, reactor, client, listener)
}

func TestTLSConnection(t *testing.T) {
	reactor := NewReactor()
	client := reactor.CreateServer("local")

	initialiseServerConnection(client)

	// generate a test certificate to use
	priv, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)

	duration30Days, _ := time.ParseDuration("-30h")
	notBefore := time.Now().Add(duration30Days) // valid 30 hours ago
	duration1Year, _ := time.ParseDuration("90h")
	notAfter := notBefore.Add(duration1Year) // for 90 hours

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"gIRC-Go Co"},
		},
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IsCA: true,
	}

	template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1"))
	template.IPAddresses = append(template.IPAddresses, net.ParseIP("::"))
	template.DNSNames = append(template.DNSNames, "localhost")

	derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)

	c := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	b, _ := x509.MarshalECPrivateKey(priv)
	k := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})

	// we mock up a server connection to test the client
	listenerKeyPair, _ := tls.X509KeyPair(c, k)

	var listenerTLSConfig tls.Config
	listenerTLSConfig.Certificates = make([]tls.Certificate, 0)
	listenerTLSConfig.Certificates = append(listenerTLSConfig.Certificates, listenerKeyPair)
	listener, _ := tls.Listen("tcp", ":0", &listenerTLSConfig)

	// mock up the client side too
	clientTLSCertPool := x509.NewCertPool()
	clientTLSCertPool.AppendCertsFromPEM(c)

	var clientTLSConfig tls.Config
	clientTLSConfig.RootCAs = clientTLSCertPool
	clientTLSConfig.ServerName = "localhost"
	go client.Connect(listener.Addr().String(), true, &clientTLSConfig)
	go client.ReceiveLoop()

	testServerConnection(t, reactor, client, listener)
}

func sendMessage(conn net.Conn, tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
	ircmsg := ircmsg.MakeMessage(tags, prefix, command, params...)
	line, err := ircmsg.Line()
	if err != nil {
		return
	}
	fmt.Fprintf(conn, line)

	// need to wait for a quick moment here for TLS to process any changes this
	// message has caused
	runtime.Gosched()
	waitTime, _ := time.ParseDuration("10ms")
	time.Sleep(waitTime)
}

func initialiseServerConnection(client *ServerConnection) {
	client.InitialNick = "coolguy"
	client.InitialUser = "c"
	client.InitialRealName = "girc-go Test Client  "
}

func testServerConnection(t *testing.T, reactor Reactor, client *ServerConnection, listener net.Listener) {
	// start our reader
	conn, _ := listener.Accept()
	reader := bufio.NewReader(conn)

	var message string

	// CAP
	message, _ = reader.ReadString('\n')
	if message != "CAP LS 302\r\n" {
		t.Error(
			"Did not receive CAP LS message, received: [",
			message,
			"]",
		)
		return
	}

	sendMessage(conn, nil, "example.com", "CAP", "*", "LS", "*", "multi-prefix userhost-in-names sasl=PLAIN")
	sendMessage(conn, nil, "example.com", "CAP", "*", "LS", "chghost")

	message, _ = reader.ReadString('\n')
	if message != "CAP REQ :chghost multi-prefix sasl userhost-in-names\r\n" {
		t.Error(
			"Did not receive CAP REQ message, received: [",
			message,
			"]",
		)
		return
	}

	// these should be silently ignored
	fmt.Fprintf(conn, "\r\n\r\n\r\n")

	sendMessage(conn, nil, "example.com", "CAP", "*", "ACK", "chghost multi-prefix userhost-in-names sasl")

	message, _ = reader.ReadString('\n')
	if message != "CAP END\r\n" {
		t.Error(
			"Did not receive CAP END message, received: [",
			message,
			"]",
		)
		return
	}

	// NICK/USER
	message, _ = reader.ReadString('\n')
	if message != "NICK coolguy\r\n" {
		t.Error(
			"Did not receive NICK message, received: [",
			message,
			"]",
		)
		return
	}

	message, _ = reader.ReadString('\n')
	if message != "USER c 0 * :girc-go Test Client  \r\n" {
		t.Error(
			"Did not receive USER message, received: [",
			message,
			"]",
		)
		return
	}

	// make sure nick changes properly
	sendMessage(conn, nil, "example.com", "001", "dan", "Welcome to the gIRC-Go Test Network!")

	if client.Nick != "dan" {
		t.Error(
			"Nick was not set with 001, expected",
			"dan",
			"got",
			client.Nick,
		)
		return
	}

	// make sure LINELEN gets set correctly
	sendMessage(conn, nil, "example.com", "005", "dan", "LINELEN=", "are available on this server")

	if client.Features["LINELEN"].(int) != 512 {
		t.Error(
			"LINELEN default was not set with 005, expected",
			512,
			"got",
			client.Features["LINELEN"],
		)
		return
	}

	// make sure casemapping and other ISUPPORT values are set properly
	sendMessage(conn, nil, "example.com", "005", "dan", "CASEMAPPING=ascii", "NICKLEN=27", "USERLEN=", "SAFELIST", "are available on this server")

	if client.Casemapping != ircmap.ASCII {
		t.Error(
			"Casemapping was not set with 005, expected",
			ircmap.ASCII,
			"got",
			client.Casemapping,
		)
		return
	}

	if client.Features["NICKLEN"].(int) != 27 {
		t.Error(
			"NICKLEN was not set with 005, expected",
			27,
			"got",
			client.Features["NICKLEN"],
		)
		return
	}

	if client.Features["USERLEN"] != nil {
		t.Error(
			"USERLEN was not set with 005, expected",
			nil,
			"got",
			client.Features["USERLEN"],
		)
		return
	}

	if client.Features["SAFELIST"].(bool) != true {
		t.Error(
			"SAFELIST was not set with 005, expected",
			true,
			"got",
			client.Features["SAFELIST"],
		)
		return
	}

	// shutdown client
	reactor.Shutdown(" Get mad!  ")

	message, _ = reader.ReadString('\n')
	if message != "QUIT : Get mad!  \r\n" {
		t.Error(
			"Did not receive QUIT message, received: [",
			message,
			"]",
		)
		return
	}

	// close connection and listener
	conn.Close()
	listener.Close()
}
<|endoftext|>


<commit_before>package listener

import (
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/jeffpierce/cassabon/config"
	"github.com/jeffpierce/cassabon/logging"
)

func TestCarbonSocket(t *testing.T) {

	config.G.Log.System = logging.NewLogger("system", "", logging.Fatal)
	config.G.Log.Carbon = logging.NewLogger("carbon", "", logging.Fatal)
	logging.Statsd.Open("", "", "cassabon")

	fmt.Println("Testing TCP socket connection...")
	go CarbonTCP("127.0.0.1", "2003")

	//	fmt.Println("Testing UDP socket connection...")
	//	go CarbonUDP("127.0.0.1", 2003)

	time.Sleep(10)

	fmt.Println("Sleeping while the connections are opened.")

	for i := 0; i < 10; i++ {
		fmt.Println("Sending good metric to TCP...")
		tcpconn, _ := net.Dial("tcp", "127.0.0.1:2003")
		GoodMetric(tcpconn)
		tcpconn.Close()
	}

	fmt.Println("Sending bad metric to TCP...")
	tcpconnbad, _ := net.Dial("tcp", "127.0.0.1:2003")
	BadMetric(tcpconnbad)

	/*	fmt.Println("Sending good metric to UDP...")
		udpconn, err := net.Dial("udp", "127.0.0.1:2003")
		GoodMetric(udpconn)

		fmt.Println("Sending bad metric to UDP...")
		udpconnbad, err := net.Dial("udp", "127.0.0.1:2003")
		BadMetric(udpconnbad) */

	time.Sleep(100 * time.Millisecond)
}

func GoodMetric(conn net.Conn) {
	testMetric := fmt.Sprintf("carbon.test 1 %d", time.Now().Unix())
	fmt.Println("Sending metric:", testMetric)
	fmt.Fprintf(conn, testMetric+"\n")
}

func BadMetric(conn net.Conn) {
	testMetric := "carbon.terrible 9 Qsplork"
	fmt.Println("Sending bad metric:", testMetric)
	fmt.Fprintf(conn, testMetric+"\n")
	conn.Close()
}
<commit_msg>Fix compile errors in listener tests.<commit_after>package listener

import (
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/jeffpierce/cassabon/config"
	"github.com/jeffpierce/cassabon/logging"
)

func TestCarbonSocket(t *testing.T) {

	config.G.Log.System = logging.NewLogger("system", "", logging.Fatal)
	config.G.Log.Carbon = logging.NewLogger("carbon", "", logging.Fatal)
	logging.Statsd.Open("", "", "cassabon")

	fmt.Println("Testing TCP socket connection...")
	cpl := new(CarbonPlaintextListener)
	go cpl.carbonTCP("127.0.0.1", "2003")

	fmt.Println("Testing UDP socket connection...")
	go cpl.carbonUDP("127.0.0.1", "2003")

	time.Sleep(10)

	fmt.Println("Sleeping while the connections are opened.")

	for i := 0; i < 10; i++ {
		fmt.Println("Sending good metric to TCP...")
		tcpconn, _ := net.Dial("tcp", "127.0.0.1:2003")
		GoodMetric(tcpconn)
		tcpconn.Close()
	}

	fmt.Println("Sending bad metric to TCP...")
	tcpconnbad, _ := net.Dial("tcp", "127.0.0.1:2003")
	BadMetric(tcpconnbad)

	fmt.Println("Sending good metric to UDP...")
	udpconn, _ := net.Dial("udp", "127.0.0.1:2003")
	GoodMetric(udpconn)

	fmt.Println("Sending bad metric to UDP...")
	udpconnbad, _ := net.Dial("udp", "127.0.0.1:2003")
	BadMetric(udpconnbad)

	time.Sleep(100 * time.Millisecond)
}

func GoodMetric(conn net.Conn) {
	testMetric := fmt.Sprintf("carbon.test 1 %d", time.Now().Unix())
	fmt.Println("Sending metric:", testMetric)
	fmt.Fprintf(conn, testMetric+"\n")
}

func BadMetric(conn net.Conn) {
	testMetric := "carbon.terrible 9 Qsplork"
	fmt.Println("Sending bad metric:", testMetric)
	fmt.Fprintf(conn, testMetric+"\n")
	conn.Close()
}
<|endoftext|>


<commit_before>// Package mcstored implements the server for storage requests.
package mcstored

import (
	"fmt"
	"net/http"
	"os"

	"github.com/jessevdk/go-flags"
	"github.com/materials-commons/config"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/db"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/domain"
	"github.com/materials-commons/mcstore/server/mcstored/service"
)

// Options for server startup
type serverOptions struct {
	MCDir    string `long:"mcdir" description:"Directory path to materials commons file storage"`
	PrintPid bool   `long:"print-pid" description:"Prints the server pid to stdout"`
	HTTPPort uint   `long:"http-port" description:"Port webserver listens on" default:"5010"`
}

// Options for the database
type databaseOptions struct {
	Connection string `long:"db-connect" description:"The database connection string"`
	Name       string `long:"db" description:"Database to use"`
	Type       string `long:"db-type" description:"The type of database to connect to"`
}

// Break the options into option groups.
type options struct {
	Server   serverOptions   `group:"Server Options"`
	Database databaseOptions `group:"Database Options"`
}

// configErrorHandler gives us a chance to handle configuration look up errors.
func configErrorHandler(key string, err error, args ...interface{}) {

}

// init initializes config for the server.
func init() {
	config.Init(config.TwelveFactorWithOverride)
	config.SetErrorHandler(configErrorHandler)
}

func main() {
	var opts options
	_, err := flags.Parse(&opts)

	if err != nil {
		os.Exit(1)
	}

	if opts.Server.PrintPid {
		fmt.Println(os.Getpid())
	}

	setupConfig(opts)
	server(opts.Server.HTTPPort)
}

// setupConfig sets up configuration overrides that were passed in on the command line.
func setupConfig(opts options) {
	if opts.Database.Connection != "" {
		config.Set("MCDB_CONNECTION", opts.Database.Connection)
	}

	if opts.Database.Name != "" {
		config.Set("MCDB_NAME", opts.Database.Name)
	}

	if opts.Database.Type != "" {
		config.Set("MCDB_TYPE", opts.Database.Type)
	}

	if opts.Server.MCDir != "" {
		config.Set("MCDIR", opts.Server.MCDir)
	}
}

// server implements the actual serve for mcstored.
func server(port uint) {
	session := db.RSessionMust()
	access := domain.NewAccess(dai.NewRGroups(session), dai.NewRFiles(session), dai.NewRUsers(session))
	dataHandler := service.NewDataHandler(access)
	http.Handle("/data/", dataHandler)
	app.Log.Crit("http Server failed", "error", http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
<commit_msg>Change package to main<commit_after>// Package mcstored implements the server for storage requests.
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/jessevdk/go-flags"
	"github.com/materials-commons/config"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/db"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/domain"
	"github.com/materials-commons/mcstore/server/mcstored/service"
	"github.com/materials-commons/mcstore/server/mcstored/service/rest"
)

// Options for server startup
type serverOptions struct {
	MCDir    string `long:"mcdir" description:"Directory path to materials commons file storage"`
	PrintPid bool   `long:"print-pid" description:"Prints the server pid to stdout"`
	HTTPPort uint   `long:"http-port" description:"Port webserver listens on" default:"5010"`
}

// Options for the database
type databaseOptions struct {
	Connection string `long:"db-connect" description:"The database connection string"`
	Name       string `long:"db" description:"Database to use"`
	Type       string `long:"db-type" description:"The type of database to connect to"`
}

// Break the options into option groups.
type options struct {
	Server   serverOptions   `group:"Server Options"`
	Database databaseOptions `group:"Database Options"`
}

// configErrorHandler gives us a chance to handle configuration look up errors.
func configErrorHandler(key string, err error, args ...interface{}) {

}

// init initializes config for the server.
func init() {
	config.Init(config.TwelveFactorWithOverride)
	config.SetErrorHandler(configErrorHandler)
}

func main() {
	var opts options
	_, err := flags.Parse(&opts)

	if err != nil {
		os.Exit(1)
	}

	if opts.Server.PrintPid {
		fmt.Println(os.Getpid())
	}

	setupConfig(opts)
	server(opts.Server.HTTPPort)
}

// setupConfig sets up configuration overrides that were passed in on the command line.
func setupConfig(opts options) {
	if opts.Database.Connection != "" {
		config.Set("MCDB_CONNECTION", opts.Database.Connection)
	}

	if opts.Database.Name != "" {
		config.Set("MCDB_NAME", opts.Database.Name)
	}

	if opts.Database.Type != "" {
		config.Set("MCDB_TYPE", opts.Database.Type)
	}

	if opts.Server.MCDir != "" {
		config.Set("MCDIR", opts.Server.MCDir)
	}
}

// server implements the actual serve for mcstored.
func server(port uint) {
	session := db.RSessionMust()
	access := domain.NewAccess(dai.NewRGroups(session), dai.NewRFiles(session), dai.NewRUsers(session))
	container := rest.NewServicesContainer()
	http.Handle("/", container)
	dataHandler := service.NewDataHandler(access)
	http.Handle("/datafiles/static/", dataHandler)
	app.Log.Crit("http Server failed", "error", http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
<|endoftext|>


<commit_before>package helm

import (
	"fmt"
	"strings"
	"time"

	"github.com/keel-hq/keel/approvals"
	"github.com/keel-hq/keel/types"
	"github.com/keel-hq/keel/util/image"
	"github.com/keel-hq/keel/util/version"

	hapi_chart "k8s.io/helm/pkg/proto/hapi/chart"

	"github.com/keel-hq/keel/extension/notification"

	log "github.com/Sirupsen/logrus"
	"github.com/ghodss/yaml"
	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/helm"
	"k8s.io/helm/pkg/strvals"
)

// Manager - high level interface into helm provider related data used by
// triggers
type Manager interface {
	Images() ([]*image.Reference, error)
}

// ProviderName - helm provider name
const ProviderName = "helm"

// DefaultUpdateTimeout - update timeout in seconds
const DefaultUpdateTimeout = 300

// UpdatePlan - release update plan
type UpdatePlan struct {
	Namespace string
	Name      string

	Config *KeelChartConfig

	// chart
	Chart *hapi_chart.Chart

	// values to update path=value
	Values map[string]string

	// Current (last seen cluster version)
	CurrentVersion string
	// New version that's already in the deployment
	NewVersion string
}

// keel:
//   # keel policy (all/major/minor/patch/force)
//   policy: all
//   # trigger type, defaults to events such as pubsub, webhooks
//   trigger: poll
//   pollSchedule: "@every 2m"
//   # images to track and update
//   images:
//     - repository: image.repository
//       tag: image.tag

// Root - root element of the values yaml
type Root struct {
	Keel KeelChartConfig `json:"keel"`
}

// KeelChartConfig - keel related configuration taken from values.yaml
type KeelChartConfig struct {
	Policy           types.PolicyType  `json:"policy"`
	Trigger          types.TriggerType `json:"trigger"`
	PollSchedule     string            `json:"pollSchedule"`
	Approvals        int               `json:"approvals"`        // Minimum required approvals
	ApprovalDeadline int               `json:"approvalDeadline"` // Deadline in hours
	Images           []ImageDetails    `json:"images"`
}

// ImageDetails - image details
type ImageDetails struct {
	RepositoryPath string `json:"repository"`
	TagPath        string `json:"tag"`
}

// Provider - helm provider, responsible for managing release updates
type Provider struct {
	implementer Implementer

	sender notification.Sender

	approvalManager approvals.Manager

	events chan *types.Event
	stop   chan struct{}
}

// NewProvider - create new Helm provider
func NewProvider(implementer Implementer, sender notification.Sender, approvalManager approvals.Manager) *Provider {
	return &Provider{
		implementer:     implementer,
		approvalManager: approvalManager,
		sender:          sender,
		events:          make(chan *types.Event, 100),
		stop:            make(chan struct{}),
	}
}

// GetName - get provider name
func (p *Provider) GetName() string {
	return ProviderName
}

// Submit - submit event to provider
func (p *Provider) Submit(event types.Event) error {
	p.events <- &event
	return nil
}

// Start - starts kubernetes provider, waits for events
func (p *Provider) Start() error {
	return p.startInternal()
}

// Stop - stops kubernetes provider
func (p *Provider) Stop() {
	close(p.stop)
}

// TrackedImages - returns tracked images from all releases that have keel configuration
func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) {
	var trackedImages []*types.TrackedImage

	releaseList, err := p.implementer.ListReleases()
	if err != nil {
		return nil, err
	}

	for _, release := range releaseList.Releases {
		// getting configuration
		vals, err := values(release.Chart, release.Config)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get values.yaml for release")
			continue
		}

		cfg, err := getKeelConfig(vals)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get config for release")
			continue
		}

		if cfg.PollSchedule == "" {
			cfg.PollSchedule = types.KeelPollDefaultSchedule
		}
		// used to check pod secrets
		selector := fmt.Sprintf("app=%s,release=%s", release.Chart.Metadata.Name, release.Name)

		releaseImages, err := getImages(vals)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get images for release")
			continue
		}

		for _, img := range releaseImages {
			img.Meta = map[string]string{
				"selector": selector,
			}
			img.Namespace = release.Namespace
			img.Provider = ProviderName
			trackedImages = append(trackedImages, img)
		}

	}

	return trackedImages, nil
}

func (p *Provider) startInternal() error {
	for {
		select {
		case event := <-p.events:
			log.WithFields(log.Fields{
				"repository": event.Repository.Name,
				"tag":        event.Repository.Tag,
				"registry":   event.Repository.Host,
			}).Info("provider.helm: processing event")
			err := p.processEvent(event)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"image": event.Repository.Name,
					"tag":   event.Repository.Tag,
				}).Error("provider.helm: failed to process event")
			}
		case <-p.stop:
			log.Info("provider.helm: got shutdown signal, stopping...")
			return nil
		}
	}
}

func (p *Provider) processEvent(event *types.Event) (err error) {
	plans, err := p.createUpdatePlans(event)
	if err != nil {
		return err
	}

	approved := p.checkForApprovals(event, plans)

	return p.applyPlans(approved)
}

func (p *Provider) createUpdatePlans(event *types.Event) ([]*UpdatePlan, error) {
	var plans []*UpdatePlan

	releaseList, err := p.implementer.ListReleases()
	if err != nil {
		return nil, err
	}

	for _, release := range releaseList.Releases {

		newVersion, err := version.GetVersion(event.Repository.Tag)
		if err != nil {

			plan, update, errCheck := checkUnversionedRelease(&event.Repository, release.Namespace, release.Name, release.Chart, release.Config)
			if errCheck != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"deployment": release.Name,
					"namespace":  release.Namespace,
				}).Error("provider.helm: got error while checking unversioned release")
				continue
			}

			if update {
				plans = append(plans, plan)
				continue
			}

			log.WithFields(log.Fields{
				"error": err,
				"tag":   event.Repository.Tag,
			}).Error("provider.helm: failed to parse version")
			continue
		}

		plan, update, err := checkVersionedRelease(newVersion, &event.Repository, release.Namespace, release.Name, release.Chart, release.Config)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"name":      release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to process versioned release")
			continue
		}
		if update {
			plans = append(plans, plan)
		}
	}

	return plans, nil
}

func (p *Provider) applyPlans(plans []*UpdatePlan) error {
	for _, plan := range plans {

		p.sender.Send(types.EventNotification{
			Name:      "update release",
			Message:   fmt.Sprintf("Preparing to update release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
			CreatedAt: time.Now(),
			Type:      types.NotificationPreReleaseUpdate,
			Level:     types.LevelDebug,
		})

		err := updateHelmRelease(p.implementer, plan.Name, plan.Chart, plan.Values)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"name":      plan.Name,
				"namespace": plan.Namespace,
			}).Error("provider.helm: failed to apply plan")

			p.sender.Send(types.EventNotification{
				Name:      "update release",
				Message:   fmt.Sprintf("Release update feailed %s/%s %s->%s (%s), error: %s", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "), err),
				CreatedAt: time.Now(),
				Type:      types.NotificationReleaseUpdate,
				Level:     types.LevelError,
			})
			continue
		}

		p.sender.Send(types.EventNotification{
			Name:      "update release",
			Message:   fmt.Sprintf("Successfully updated release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
			CreatedAt: time.Now(),
			Type:      types.NotificationReleaseUpdate,
			Level:     types.LevelSuccess,
		})

	}

	return nil
}

// resp, err := u.client.UpdateRelease(
// 		u.release,
// 		chartPath,
// 		helm.UpdateValueOverrides(rawVals),
// 		helm.UpgradeDryRun(u.dryRun),
// 		helm.UpgradeRecreate(u.recreate),
// 		helm.UpgradeForce(u.force),
// 		helm.UpgradeDisableHooks(u.disableHooks),
// 		helm.UpgradeTimeout(u.timeout),
// 		helm.ResetValues(u.resetValues),
// 		helm.ReuseValues(u.reuseValues),
// 		helm.UpgradeWait(u.wait))
// 	if err != nil {
// 		return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
// 	}

func updateHelmRelease(implementer Implementer, releaseName string, chart *hapi_chart.Chart, overrideValues map[string]string) error {

	overrideBts, err := convertToYaml(mapToSlice(overrideValues))
	if err != nil {
		return err
	}

	resp, err := implementer.UpdateReleaseFromChart(releaseName, chart,
		helm.UpdateValueOverrides(overrideBts),
		helm.UpgradeDryRun(false),
		helm.UpgradeRecreate(false),
		helm.UpgradeForce(true),
		helm.UpgradeDisableHooks(false),
		helm.UpgradeTimeout(DefaultUpdateTimeout),
		helm.ResetValues(false),
		helm.ReuseValues(true),
		helm.UpgradeWait(true))

	if err != nil {
		return err
	}

	log.WithFields(log.Fields{
		"version": resp.Release.Version,
		"release": releaseName,
	}).Info("provider.helm: release updated")
	return nil
}

func mapToSlice(values map[string]string) []string {
	converted := []string{}
	for k, v := range values {
		concat := k + "=" + v
		converted = append(converted, concat)
	}
	return converted
}

// parse
func convertToYaml(values []string) ([]byte, error) {
	base := map[string]interface{}{}
	for _, value := range values {
		if err := strvals.ParseInto(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
		}
	}

	return yaml.Marshal(base)
}

func getValueAsString(vals chartutil.Values, path string) (string, error) {
	valinterface, err := vals.PathValue(path)
	if err != nil {
		return "", err
	}
	valString, ok := valinterface.(string)
	if !ok {
		return "", fmt.Errorf("failed to convert value  to string")
	}

	return valString, nil
}

func values(chart *hapi_chart.Chart, config *hapi_chart.Config) (chartutil.Values, error) {
	return chartutil.CoalesceValues(chart, config)
}

func getKeelConfig(vals chartutil.Values) (*KeelChartConfig, error) {
	yamlFull, err := vals.YAML()
	if err != nil {
		return nil, fmt.Errorf("failed to get vals config, error: %s", err)
	}

	var r Root
	err = yaml.Unmarshal([]byte(yamlFull), &r)
	if err != nil {
		return nil, fmt.Errorf("failed to parse keel config: %s", err)
	}
	return &r.Keel, nil
}
<commit_msg>Fix typo in helm release error message<commit_after>package helm

import (
	"fmt"
	"strings"
	"time"

	"github.com/keel-hq/keel/approvals"
	"github.com/keel-hq/keel/types"
	"github.com/keel-hq/keel/util/image"
	"github.com/keel-hq/keel/util/version"

	hapi_chart "k8s.io/helm/pkg/proto/hapi/chart"

	"github.com/keel-hq/keel/extension/notification"

	log "github.com/Sirupsen/logrus"
	"github.com/ghodss/yaml"
	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/helm"
	"k8s.io/helm/pkg/strvals"
)

// Manager - high level interface into helm provider related data used by
// triggers
type Manager interface {
	Images() ([]*image.Reference, error)
}

// ProviderName - helm provider name
const ProviderName = "helm"

// DefaultUpdateTimeout - update timeout in seconds
const DefaultUpdateTimeout = 300

// UpdatePlan - release update plan
type UpdatePlan struct {
	Namespace string
	Name      string

	Config *KeelChartConfig

	// chart
	Chart *hapi_chart.Chart

	// values to update path=value
	Values map[string]string

	// Current (last seen cluster version)
	CurrentVersion string
	// New version that's already in the deployment
	NewVersion string
}

// keel:
//   # keel policy (all/major/minor/patch/force)
//   policy: all
//   # trigger type, defaults to events such as pubsub, webhooks
//   trigger: poll
//   pollSchedule: "@every 2m"
//   # images to track and update
//   images:
//     - repository: image.repository
//       tag: image.tag

// Root - root element of the values yaml
type Root struct {
	Keel KeelChartConfig `json:"keel"`
}

// KeelChartConfig - keel related configuration taken from values.yaml
type KeelChartConfig struct {
	Policy           types.PolicyType  `json:"policy"`
	Trigger          types.TriggerType `json:"trigger"`
	PollSchedule     string            `json:"pollSchedule"`
	Approvals        int               `json:"approvals"`        // Minimum required approvals
	ApprovalDeadline int               `json:"approvalDeadline"` // Deadline in hours
	Images           []ImageDetails    `json:"images"`
}

// ImageDetails - image details
type ImageDetails struct {
	RepositoryPath string `json:"repository"`
	TagPath        string `json:"tag"`
}

// Provider - helm provider, responsible for managing release updates
type Provider struct {
	implementer Implementer

	sender notification.Sender

	approvalManager approvals.Manager

	events chan *types.Event
	stop   chan struct{}
}

// NewProvider - create new Helm provider
func NewProvider(implementer Implementer, sender notification.Sender, approvalManager approvals.Manager) *Provider {
	return &Provider{
		implementer:     implementer,
		approvalManager: approvalManager,
		sender:          sender,
		events:          make(chan *types.Event, 100),
		stop:            make(chan struct{}),
	}
}

// GetName - get provider name
func (p *Provider) GetName() string {
	return ProviderName
}

// Submit - submit event to provider
func (p *Provider) Submit(event types.Event) error {
	p.events <- &event
	return nil
}

// Start - starts kubernetes provider, waits for events
func (p *Provider) Start() error {
	return p.startInternal()
}

// Stop - stops kubernetes provider
func (p *Provider) Stop() {
	close(p.stop)
}

// TrackedImages - returns tracked images from all releases that have keel configuration
func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) {
	var trackedImages []*types.TrackedImage

	releaseList, err := p.implementer.ListReleases()
	if err != nil {
		return nil, err
	}

	for _, release := range releaseList.Releases {
		// getting configuration
		vals, err := values(release.Chart, release.Config)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get values.yaml for release")
			continue
		}

		cfg, err := getKeelConfig(vals)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get config for release")
			continue
		}

		if cfg.PollSchedule == "" {
			cfg.PollSchedule = types.KeelPollDefaultSchedule
		}
		// used to check pod secrets
		selector := fmt.Sprintf("app=%s,release=%s", release.Chart.Metadata.Name, release.Name)

		releaseImages, err := getImages(vals)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"release":   release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to get images for release")
			continue
		}

		for _, img := range releaseImages {
			img.Meta = map[string]string{
				"selector": selector,
			}
			img.Namespace = release.Namespace
			img.Provider = ProviderName
			trackedImages = append(trackedImages, img)
		}

	}

	return trackedImages, nil
}

func (p *Provider) startInternal() error {
	for {
		select {
		case event := <-p.events:
			log.WithFields(log.Fields{
				"repository": event.Repository.Name,
				"tag":        event.Repository.Tag,
				"registry":   event.Repository.Host,
			}).Info("provider.helm: processing event")
			err := p.processEvent(event)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"image": event.Repository.Name,
					"tag":   event.Repository.Tag,
				}).Error("provider.helm: failed to process event")
			}
		case <-p.stop:
			log.Info("provider.helm: got shutdown signal, stopping...")
			return nil
		}
	}
}

func (p *Provider) processEvent(event *types.Event) (err error) {
	plans, err := p.createUpdatePlans(event)
	if err != nil {
		return err
	}

	approved := p.checkForApprovals(event, plans)

	return p.applyPlans(approved)
}

func (p *Provider) createUpdatePlans(event *types.Event) ([]*UpdatePlan, error) {
	var plans []*UpdatePlan

	releaseList, err := p.implementer.ListReleases()
	if err != nil {
		return nil, err
	}

	for _, release := range releaseList.Releases {

		newVersion, err := version.GetVersion(event.Repository.Tag)
		if err != nil {

			plan, update, errCheck := checkUnversionedRelease(&event.Repository, release.Namespace, release.Name, release.Chart, release.Config)
			if errCheck != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"deployment": release.Name,
					"namespace":  release.Namespace,
				}).Error("provider.helm: got error while checking unversioned release")
				continue
			}

			if update {
				plans = append(plans, plan)
				continue
			}

			log.WithFields(log.Fields{
				"error": err,
				"tag":   event.Repository.Tag,
			}).Error("provider.helm: failed to parse version")
			continue
		}

		plan, update, err := checkVersionedRelease(newVersion, &event.Repository, release.Namespace, release.Name, release.Chart, release.Config)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"name":      release.Name,
				"namespace": release.Namespace,
			}).Error("provider.helm: failed to process versioned release")
			continue
		}
		if update {
			plans = append(plans, plan)
		}
	}

	return plans, nil
}

func (p *Provider) applyPlans(plans []*UpdatePlan) error {
	for _, plan := range plans {

		p.sender.Send(types.EventNotification{
			Name:      "update release",
			Message:   fmt.Sprintf("Preparing to update release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
			CreatedAt: time.Now(),
			Type:      types.NotificationPreReleaseUpdate,
			Level:     types.LevelDebug,
		})

		err := updateHelmRelease(p.implementer, plan.Name, plan.Chart, plan.Values)
		if err != nil {
			log.WithFields(log.Fields{
				"error":     err,
				"name":      plan.Name,
				"namespace": plan.Namespace,
			}).Error("provider.helm: failed to apply plan")

			p.sender.Send(types.EventNotification{
				Name:      "update release",
				Message:   fmt.Sprintf("Release update failed %s/%s %s->%s (%s), error: %s", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "), err),
				CreatedAt: time.Now(),
				Type:      types.NotificationReleaseUpdate,
				Level:     types.LevelError,
			})
			continue
		}

		p.sender.Send(types.EventNotification{
			Name:      "update release",
			Message:   fmt.Sprintf("Successfully updated release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
			CreatedAt: time.Now(),
			Type:      types.NotificationReleaseUpdate,
			Level:     types.LevelSuccess,
		})

	}

	return nil
}

// resp, err := u.client.UpdateRelease(
// 		u.release,
// 		chartPath,
// 		helm.UpdateValueOverrides(rawVals),
// 		helm.UpgradeDryRun(u.dryRun),
// 		helm.UpgradeRecreate(u.recreate),
// 		helm.UpgradeForce(u.force),
// 		helm.UpgradeDisableHooks(u.disableHooks),
// 		helm.UpgradeTimeout(u.timeout),
// 		helm.ResetValues(u.resetValues),
// 		helm.ReuseValues(u.reuseValues),
// 		helm.UpgradeWait(u.wait))
// 	if err != nil {
// 		return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
// 	}

func updateHelmRelease(implementer Implementer, releaseName string, chart *hapi_chart.Chart, overrideValues map[string]string) error {

	overrideBts, err := convertToYaml(mapToSlice(overrideValues))
	if err != nil {
		return err
	}

	resp, err := implementer.UpdateReleaseFromChart(releaseName, chart,
		helm.UpdateValueOverrides(overrideBts),
		helm.UpgradeDryRun(false),
		helm.UpgradeRecreate(false),
		helm.UpgradeForce(true),
		helm.UpgradeDisableHooks(false),
		helm.UpgradeTimeout(DefaultUpdateTimeout),
		helm.ResetValues(false),
		helm.ReuseValues(true),
		helm.UpgradeWait(true))

	if err != nil {
		return err
	}

	log.WithFields(log.Fields{
		"version": resp.Release.Version,
		"release": releaseName,
	}).Info("provider.helm: release updated")
	return nil
}

func mapToSlice(values map[string]string) []string {
	converted := []string{}
	for k, v := range values {
		concat := k + "=" + v
		converted = append(converted, concat)
	}
	return converted
}

// parse
func convertToYaml(values []string) ([]byte, error) {
	base := map[string]interface{}{}
	for _, value := range values {
		if err := strvals.ParseInto(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
		}
	}

	return yaml.Marshal(base)
}

func getValueAsString(vals chartutil.Values, path string) (string, error) {
	valinterface, err := vals.PathValue(path)
	if err != nil {
		return "", err
	}
	valString, ok := valinterface.(string)
	if !ok {
		return "", fmt.Errorf("failed to convert value  to string")
	}

	return valString, nil
}

func values(chart *hapi_chart.Chart, config *hapi_chart.Config) (chartutil.Values, error) {
	return chartutil.CoalesceValues(chart, config)
}

func getKeelConfig(vals chartutil.Values) (*KeelChartConfig, error) {
	yamlFull, err := vals.YAML()
	if err != nil {
		return nil, fmt.Errorf("failed to get vals config, error: %s", err)
	}

	var r Root
	err = yaml.Unmarshal([]byte(yamlFull), &r)
	if err != nil {
		return nil, fmt.Errorf("failed to parse keel config: %s", err)
	}
	return &r.Keel, nil
}
<|endoftext|>


<commit_before>package cloudstack

import "testing"

type Object struct {
	Name string
	Id   string
}

func TestEqualName(t *testing.T) {
	name := "objectname"
	obj := Object{Name: name}
	if !equalName(obj, name) {
		t.Errorf("equalName failed. return false, expected true.")
	}
	if equalName(obj, "abracadabra") {
		t.Errorf("equalName failed. return trule, expected false.")
	}
}

func TestNameToId(t *testing.T) {
	name := "objectname"
	obj := Object{Name: name, Id: "1"}
	nameToID()

	if !equalName(obj, name) {
		t.Errorf("equalName failed. return false, expected true.")
	}
	if equalName(obj, "abracadabra") {
		t.Errorf("equalName failed. return trule, expected false.")
	}
}
<commit_msg>fix util_test.go<commit_after>package cloudstack

import (
	"testing"

	"github.com/atsaki/golang-cloudstack-library"
)

type Object struct {
	Name cloudstack.NullString
	Id   cloudstack.ID
}

func TestEqualName(t *testing.T) {
	name := "objectname"
	obj := Object{}
	obj.Name.Set(name)
	if !equalName(obj, name) {
		t.Errorf("equalName failed. return false, expected true.")
	}
	if equalName(obj, "abracadabra") {
		t.Errorf("equalName failed. return trule, expected false.")
	}
}
<|endoftext|>


<commit_before>/*

	Autoreader is a simple program, designed to be run from go:generate, that
	helps generate the annoying boilerplate to implement
	boardgame.PropertyReader and boardgame.PropertyReadSetter.

	Autoreader processes a package of go files, searching for structs that
	have a comment immediately above their declaration that begins with
	"+autoreader". For each such struct, it creates a Reader() and
	PropertyReader() method that just use boardgame.DefaultReader and
	boardgame.DefaultReadSetter.

	You can configure which package to process and where to write output via
	command-line flags. By default it processes the current package and writes
	its output to auto_reader.go, overwriting whatever file was there before.
	See command-line options by passing -h.

	The defaults are set reasonably so that you can use go:generate very
	easily. See examplepkg/ for a very simple example.

*/
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/MarcGrol/golangAnnotations/parser"
	"go/format"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
)

var headerTemplate *template.Template
var readerTemplate *template.Template
var readSetterTemplate *template.Template

const magicDocLinePrefix = "+autoreader"

type appOptions struct {
	OutputFile       string
	PackageDirectory string
	PrintToConsole   bool
	Help             bool
	flagSet          *flag.FlagSet
}

type templateConfig struct {
	FirstLetter string
	StructName  string
}

func init() {
	headerTemplate = template.Must(template.New("header").Parse(headerTemplateText))
	readerTemplate = template.Must(template.New("reader").Parse(readerTemplateText))
	readSetterTemplate = template.Must(template.New("readsetter").Parse(readSetterTemplateText))
}

func defineFlags(options *appOptions) {
	options.flagSet.StringVar(&options.OutputFile, "out", "auto_reader.go", "Defines which file to render output to. WARNING: it will be overwritten!")
	options.flagSet.StringVar(&options.PackageDirectory, "pkg", ".", "Which package to process")
	options.flagSet.BoolVar(&options.Help, "h", false, "If set, print help message and quit.")
	options.flagSet.BoolVar(&options.PrintToConsole, "print", false, "If true, will print result to console instead of writing to out.")
}

func getOptions(flagSet *flag.FlagSet, flagArguments []string) *appOptions {
	options := &appOptions{flagSet: flagSet}
	defineFlags(options)
	flagSet.Parse(flagArguments)
	return options
}

func main() {
	flagSet := flag.CommandLine
	process(getOptions(flagSet, os.Args[1:]), os.Stdout, os.Stderr)
}

func process(options *appOptions, out io.ReadWriter, errOut io.ReadWriter) {

	if options.Help {
		options.flagSet.SetOutput(out)
		options.flagSet.PrintDefaults()
		return
	}

	output, err := processPackage(options.PackageDirectory)

	if err != nil {
		fmt.Fprintln(errOut, "ERROR", err)
		return
	}

	if options.PrintToConsole {
		fmt.Fprintln(out, output)
	} else {
		ioutil.WriteFile(options.OutputFile, []byte(output), 0644)
	}

}

func processPackage(location string) (output string, err error) {
	sources, err := parser.ParseSourceDir(location, ".*")

	if err != nil {
		return "", errors.New("Couldn't parse sources: " + err.Error())
	}

	haveOutputHeader := false

	for _, theStruct := range sources.Structs {

		if !haveOutputHeader {
			output += headerForPackage(theStruct.PackageName)
			haveOutputHeader = true
		}

		enableAutoReader := false

		for _, docLine := range theStruct.DocLines {
			docLine = strings.TrimPrefix(docLine, "//")
			docLine = strings.TrimSpace(docLine)
			if strings.HasPrefix(docLine, magicDocLinePrefix) {
				enableAutoReader = true
				break
			}
		}

		if !enableAutoReader {
			continue
		}

		output += readerForStruct(theStruct.Name)
		output += readSetterForStruct(theStruct.Name)
	}

	formattedBytes, err := format.Source([]byte(output))

	if err != nil {
		return "", errors.New("Couldn't go fmt code: " + err.Error())
	}

	return string(formattedBytes), nil
}

func templateOutput(template *template.Template, values interface{}) string {
	buf := new(bytes.Buffer)

	err := template.Execute(buf, values)

	if err != nil {
		log.Println(err)
	}

	return buf.String()
}

func headerForPackage(packageName string) string {
	return templateOutput(headerTemplate, map[string]string{
		"packageName": packageName,
	})
}

func readerForStruct(structName string) string {

	return templateOutput(readerTemplate, templateConfig{
		FirstLetter: structName[:1],
		StructName:  structName,
	})

}

func readSetterForStruct(structName string) string {
	return templateOutput(readSetterTemplate, templateConfig{
		FirstLetter: structName[:1],
		StructName:  structName,
	})
}

const headerTemplateText = `/************************************
 *
 * This file contains auto-generated methods to help certain structs
 * implement boardgame.SubState and boardgame.MutableSubState. It was
 * generated by autoreader.
 *
 * DO NOT EDIT by hand.
 *
 ************************************/
package {{.packageName}}

import (
	"github.com/jkomoros/boardgame"
)

`

const readerTemplateText = `func ({{.FirstLetter}} *{{.StructName}}) Reader() boardgame.PropertyReader {
	return boardgame.DefaultReader({{.FirstLetter}})
}

`

const readSetterTemplateText = `func ({{.FirstLetter}} *{{.StructName}}) ReadSetter() boardgame.PropertyReadSetter {
	return boardgame.DefaultReadSetter({{.FirstLetter}})
}

`
<commit_msg>A bit of refactoring of how we tell whether to create a reader or not. Part of #301.<commit_after>/*

	Autoreader is a simple program, designed to be run from go:generate, that
	helps generate the annoying boilerplate to implement
	boardgame.PropertyReader and boardgame.PropertyReadSetter.

	Autoreader processes a package of go files, searching for structs that
	have a comment immediately above their declaration that begins with
	"+autoreader". For each such struct, it creates a Reader() and
	PropertyReader() method that just use boardgame.DefaultReader and
	boardgame.DefaultReadSetter.

	You can configure which package to process and where to write output via
	command-line flags. By default it processes the current package and writes
	its output to auto_reader.go, overwriting whatever file was there before.
	See command-line options by passing -h.

	The defaults are set reasonably so that you can use go:generate very
	easily. See examplepkg/ for a very simple example.

*/
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/MarcGrol/golangAnnotations/parser"
	"go/format"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
)

var headerTemplate *template.Template
var readerTemplate *template.Template
var readSetterTemplate *template.Template

const magicDocLinePrefix = "+autoreader"

type appOptions struct {
	OutputFile       string
	PackageDirectory string
	PrintToConsole   bool
	Help             bool
	flagSet          *flag.FlagSet
}

type templateConfig struct {
	FirstLetter string
	StructName  string
}

func init() {
	headerTemplate = template.Must(template.New("header").Parse(headerTemplateText))
	readerTemplate = template.Must(template.New("reader").Parse(readerTemplateText))
	readSetterTemplate = template.Must(template.New("readsetter").Parse(readSetterTemplateText))
}

func defineFlags(options *appOptions) {
	options.flagSet.StringVar(&options.OutputFile, "out", "auto_reader.go", "Defines which file to render output to. WARNING: it will be overwritten!")
	options.flagSet.StringVar(&options.PackageDirectory, "pkg", ".", "Which package to process")
	options.flagSet.BoolVar(&options.Help, "h", false, "If set, print help message and quit.")
	options.flagSet.BoolVar(&options.PrintToConsole, "print", false, "If true, will print result to console instead of writing to out.")
}

func getOptions(flagSet *flag.FlagSet, flagArguments []string) *appOptions {
	options := &appOptions{flagSet: flagSet}
	defineFlags(options)
	flagSet.Parse(flagArguments)
	return options
}

func main() {
	flagSet := flag.CommandLine
	process(getOptions(flagSet, os.Args[1:]), os.Stdout, os.Stderr)
}

func process(options *appOptions, out io.ReadWriter, errOut io.ReadWriter) {

	if options.Help {
		options.flagSet.SetOutput(out)
		options.flagSet.PrintDefaults()
		return
	}

	output, err := processPackage(options.PackageDirectory)

	if err != nil {
		fmt.Fprintln(errOut, "ERROR", err)
		return
	}

	if options.PrintToConsole {
		fmt.Fprintln(out, output)
	} else {
		ioutil.WriteFile(options.OutputFile, []byte(output), 0644)
	}

}

func processPackage(location string) (output string, err error) {
	sources, err := parser.ParseSourceDir(location, ".*")

	if err != nil {
		return "", errors.New("Couldn't parse sources: " + err.Error())
	}

	haveOutputHeader := false

	for _, theStruct := range sources.Structs {

		if !haveOutputHeader {
			output += headerForPackage(theStruct.PackageName)
			haveOutputHeader = true
		}

		outputReader, outputReadSetter := structConfig(theStruct.DocLines)

		if outputReader {
			output += readerForStruct(theStruct.Name)
		}
		if outputReadSetter {
			output += readSetterForStruct(theStruct.Name)
		}
	}

	formattedBytes, err := format.Source([]byte(output))

	if err != nil {
		return "", errors.New("Couldn't go fmt code: " + err.Error())
	}

	return string(formattedBytes), nil
}

func structConfig(docLines []string) (outputReader bool, outputReadSetter bool) {

	for _, docLine := range docLines {
		docLine = strings.TrimPrefix(docLine, "//")
		docLine = strings.TrimSpace(docLine)
		if strings.HasPrefix(docLine, magicDocLinePrefix) {
			return true, true
		}
	}
	return false, false
}

func templateOutput(template *template.Template, values interface{}) string {
	buf := new(bytes.Buffer)

	err := template.Execute(buf, values)

	if err != nil {
		log.Println(err)
	}

	return buf.String()
}

func headerForPackage(packageName string) string {
	return templateOutput(headerTemplate, map[string]string{
		"packageName": packageName,
	})
}

func readerForStruct(structName string) string {

	return templateOutput(readerTemplate, templateConfig{
		FirstLetter: structName[:1],
		StructName:  structName,
	})

}

func readSetterForStruct(structName string) string {
	return templateOutput(readSetterTemplate, templateConfig{
		FirstLetter: structName[:1],
		StructName:  structName,
	})
}

const headerTemplateText = `/************************************
 *
 * This file contains auto-generated methods to help certain structs
 * implement boardgame.SubState and boardgame.MutableSubState. It was
 * generated by autoreader.
 *
 * DO NOT EDIT by hand.
 *
 ************************************/
package {{.packageName}}

import (
	"github.com/jkomoros/boardgame"
)

`

const readerTemplateText = `func ({{.FirstLetter}} *{{.StructName}}) Reader() boardgame.PropertyReader {
	return boardgame.DefaultReader({{.FirstLetter}})
}

`

const readSetterTemplateText = `func ({{.FirstLetter}} *{{.StructName}}) ReadSetter() boardgame.PropertyReadSetter {
	return boardgame.DefaultReadSetter({{.FirstLetter}})
}

`
<|endoftext|>