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

package main

import (
	"flag"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/coreos/coreos-cloudinit/config"
	"github.com/coreos/coreos-cloudinit/config/validate"
	"github.com/coreos/coreos-cloudinit/datasource"
	"github.com/coreos/coreos-cloudinit/datasource/configdrive"
	"github.com/coreos/coreos-cloudinit/datasource/file"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
	"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
	"github.com/coreos/coreos-cloudinit/datasource/url"
	"github.com/coreos/coreos-cloudinit/datasource/waagent"
	"github.com/coreos/coreos-cloudinit/initialize"
	"github.com/coreos/coreos-cloudinit/network"
	"github.com/coreos/coreos-cloudinit/pkg"
	"github.com/coreos/coreos-cloudinit/system"
)

const (
	version               = "1.3.4+git"
	datasourceInterval    = 100 * time.Millisecond
	datasourceMaxInterval = 30 * time.Second
	datasourceTimeout     = 5 * time.Minute
)

var (
	flags = struct {
		printVersion  bool
		ignoreFailure bool
		sources       struct {
			file                        string
			configDrive                 string
			waagent                     string
			metadataService             bool
			ec2MetadataService          string
			cloudSigmaMetadataService   bool
			digitalOceanMetadataService string
			url                         string
			procCmdLine                 bool
		}
		convertNetconf string
		workspace      string
		sshKeyName     string
		oem            string
		validate       bool
	}{}
)

func init() {
	flag.BoolVar(&flags.printVersion, "version", false, "Print the version and exit")
	flag.BoolVar(&flags.ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")
	flag.StringVar(&flags.sources.file, "from-file", "", "Read user-data from provided file")
	flag.StringVar(&flags.sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
	flag.StringVar(&flags.sources.waagent, "from-waagent", "", "Read data from provided waagent directory")
	flag.BoolVar(&flags.sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
	flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
	flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
	flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
	flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url")
	flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
	flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM")
	flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
	flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
	flag.StringVar(&flags.sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
	flag.BoolVar(&flags.validate, "validate", false, "[EXPERIMENTAL] Validate the user-data but do not apply it to the system")
}

type oemConfig map[string]string

var (
	oemConfigs = map[string]oemConfig{
		"digitalocean": oemConfig{
			"from-digitalocean-metadata": "http://169.254.169.254/",
			"convert-netconf":            "digitalocean",
		},
		"ec2-compat": oemConfig{
			"from-ec2-metadata": "http://169.254.169.254/",
			"from-configdrive":  "/media/configdrive",
		},
		"rackspace-onmetal": oemConfig{
			"from-configdrive": "/media/configdrive",
			"convert-netconf":  "debian",
		},
		"azure": oemConfig{
			"from-waagent": "/var/lib/waagent",
		},
		"cloudsigma": oemConfig{
			"from-cloudsigma-metadata": "true",
		},
	}
)

func main() {
	failure := false

	flag.Parse()

	if c, ok := oemConfigs[flags.oem]; ok {
		for k, v := range c {
			flag.Set(k, v)
		}
	} else if flags.oem != "" {
		oems := make([]string, 0, len(oemConfigs))
		for k := range oemConfigs {
			oems = append(oems, k)
		}
		fmt.Printf("Invalid option to --oem: %q. Supported options: %q\n", flags.oem, oems)
		os.Exit(2)
	}

	if flags.printVersion == true {
		fmt.Printf("coreos-cloudinit version %s\n", version)
		os.Exit(0)
	}

	switch flags.convertNetconf {
	case "":
	case "debian":
	case "digitalocean":
	default:
		fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", flags.convertNetconf)
		os.Exit(2)
	}

	dss := getDatasources()
	if len(dss) == 0 {
		fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline")
		os.Exit(2)
	}

	ds := selectDatasource(dss)
	if ds == nil {
		fmt.Println("No datasources available in time")
		os.Exit(1)
	}

	fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
	userdataBytes, err := ds.FetchUserdata()
	if err != nil {
		fmt.Printf("Failed fetching user-data from datasource: %v\nContinuing...\n", err)
		failure = true
	}

	if report, err := validate.Validate(userdataBytes); err == nil {
		ret := 0
		for _, e := range report.Entries() {
			fmt.Println(e)
			ret = 1
		}
		if flags.validate {
			os.Exit(ret)
		}
	} else {
		fmt.Printf("Failed while validating user_data (%q)\n", err)
		if flags.validate {
			os.Exit(1)
		}
	}

	fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
	metadata, err := ds.FetchMetadata()
	if err != nil {
		fmt.Printf("Failed fetching meta-data from datasource: %v\n", err)
		os.Exit(1)
	}

	// Apply environment to user-data
	env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
	userdata := env.Apply(string(userdataBytes))

	var ccu *config.CloudConfig
	var script *config.Script
	if ud, err := initialize.ParseUserData(userdata); err != nil {
		fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
		failure = true
	} else {
		switch t := ud.(type) {
		case *config.CloudConfig:
			ccu = t
		case *config.Script:
			script = t
		}
	}

	fmt.Println("Merging cloud-config from meta-data and user-data")
	cc := mergeConfigs(ccu, metadata)

	var ifaces []network.InterfaceGenerator
	if flags.convertNetconf != "" {
		var err error
		switch flags.convertNetconf {
		case "debian":
			ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig)
		case "digitalocean":
			ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig)
		default:
			err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
		}
		if err != nil {
			fmt.Printf("Failed to generate interfaces: %v\n", err)
			os.Exit(1)
		}
	}

	if err = initialize.Apply(cc, ifaces, env); err != nil {
		fmt.Printf("Failed to apply cloud-config: %v\n", err)
		os.Exit(1)
	}

	if script != nil {
		if err = runScript(*script, env); err != nil {
			fmt.Printf("Failed to run script: %v\n", err)
			os.Exit(1)
		}
	}

	if failure && !flags.ignoreFailure {
		os.Exit(1)
	}
}

// mergeConfigs merges certain options from md (meta-data from the datasource)
// onto cc (a CloudConfig derived from user-data), if they are not already set
// on cc (i.e. user-data always takes precedence)
func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.CloudConfig) {
	if cc != nil {
		out = *cc
	}

	if md.Hostname != "" {
		if out.Hostname != "" {
			fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
		} else {
			out.Hostname = md.Hostname
		}
	}
	for _, key := range md.SSHPublicKeys {
		out.SSHAuthorizedKeys = append(out.SSHAuthorizedKeys, key)
	}
	return
}

// getDatasources creates a slice of possible Datasources for cloudinit based
// on the different source command-line flags.
func getDatasources() []datasource.Datasource {
	dss := make([]datasource.Datasource, 0, 5)
	if flags.sources.file != "" {
		dss = append(dss, file.NewDatasource(flags.sources.file))
	}
	if flags.sources.url != "" {
		dss = append(dss, url.NewDatasource(flags.sources.url))
	}
	if flags.sources.configDrive != "" {
		dss = append(dss, configdrive.NewDatasource(flags.sources.configDrive))
	}
	if flags.sources.metadataService {
		dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
	}
	if flags.sources.ec2MetadataService != "" {
		dss = append(dss, ec2.NewDatasource(flags.sources.ec2MetadataService))
	}
	if flags.sources.cloudSigmaMetadataService {
		dss = append(dss, cloudsigma.NewServerContextService())
	}
	if flags.sources.digitalOceanMetadataService != "" {
		dss = append(dss, digitalocean.NewDatasource(flags.sources.digitalOceanMetadataService))
	}
	if flags.sources.waagent != "" {
		dss = append(dss, waagent.NewDatasource(flags.sources.waagent))
	}
	if flags.sources.procCmdLine {
		dss = append(dss, proc_cmdline.NewDatasource())
	}
	return dss
}

// selectDatasource attempts to choose a valid Datasource to use based on its
// current availability. The first Datasource to report to be available is
// returned. Datasources will be retried if possible if they are not
// immediately available. If all Datasources are permanently unavailable or
// datasourceTimeout is reached before one becomes available, nil is returned.
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
	ds := make(chan datasource.Datasource)
	stop := make(chan struct{})
	var wg sync.WaitGroup

	for _, s := range sources {
		wg.Add(1)
		go func(s datasource.Datasource) {
			defer wg.Done()

			duration := datasourceInterval
			for {
				fmt.Printf("Checking availability of %q\n", s.Type())
				if s.IsAvailable() {
					ds <- s
					return
				} else if !s.AvailabilityChanges() {
					return
				}
				select {
				case <-stop:
					return
				case <-time.After(duration):
					duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
				}
			}
		}(s)
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	var s datasource.Datasource
	select {
	case s = <-ds:
	case <-done:
	case <-time.After(datasourceTimeout):
	}

	close(stop)
	return s
}

// TODO(jonboulle): this should probably be refactored and moved into a different module
func runScript(script config.Script, env *initialize.Environment) error {
	err := initialize.PrepWorkspace(env.Workspace())
	if err != nil {
		fmt.Printf("Failed preparing workspace: %v\n", err)
		return err
	}
	path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
	if err == nil {
		var name string
		name, err = system.ExecuteScript(path)
		initialize.PersistUnitNameInWorkspace(name, env.Workspace())
	}
	return err
}
<commit_msg>coreos-cloudinit: bump to v1.4.0<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/coreos/coreos-cloudinit/config"
	"github.com/coreos/coreos-cloudinit/config/validate"
	"github.com/coreos/coreos-cloudinit/datasource"
	"github.com/coreos/coreos-cloudinit/datasource/configdrive"
	"github.com/coreos/coreos-cloudinit/datasource/file"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
	"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
	"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
	"github.com/coreos/coreos-cloudinit/datasource/url"
	"github.com/coreos/coreos-cloudinit/datasource/waagent"
	"github.com/coreos/coreos-cloudinit/initialize"
	"github.com/coreos/coreos-cloudinit/network"
	"github.com/coreos/coreos-cloudinit/pkg"
	"github.com/coreos/coreos-cloudinit/system"
)

const (
	version               = "1.4.0"
	datasourceInterval    = 100 * time.Millisecond
	datasourceMaxInterval = 30 * time.Second
	datasourceTimeout     = 5 * time.Minute
)

var (
	flags = struct {
		printVersion  bool
		ignoreFailure bool
		sources       struct {
			file                        string
			configDrive                 string
			waagent                     string
			metadataService             bool
			ec2MetadataService          string
			cloudSigmaMetadataService   bool
			digitalOceanMetadataService string
			url                         string
			procCmdLine                 bool
		}
		convertNetconf string
		workspace      string
		sshKeyName     string
		oem            string
		validate       bool
	}{}
)

func init() {
	flag.BoolVar(&flags.printVersion, "version", false, "Print the version and exit")
	flag.BoolVar(&flags.ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")
	flag.StringVar(&flags.sources.file, "from-file", "", "Read user-data from provided file")
	flag.StringVar(&flags.sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
	flag.StringVar(&flags.sources.waagent, "from-waagent", "", "Read data from provided waagent directory")
	flag.BoolVar(&flags.sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
	flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
	flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
	flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
	flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url")
	flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
	flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM")
	flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
	flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
	flag.StringVar(&flags.sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
	flag.BoolVar(&flags.validate, "validate", false, "[EXPERIMENTAL] Validate the user-data but do not apply it to the system")
}

type oemConfig map[string]string

var (
	oemConfigs = map[string]oemConfig{
		"digitalocean": oemConfig{
			"from-digitalocean-metadata": "http://169.254.169.254/",
			"convert-netconf":            "digitalocean",
		},
		"ec2-compat": oemConfig{
			"from-ec2-metadata": "http://169.254.169.254/",
			"from-configdrive":  "/media/configdrive",
		},
		"rackspace-onmetal": oemConfig{
			"from-configdrive": "/media/configdrive",
			"convert-netconf":  "debian",
		},
		"azure": oemConfig{
			"from-waagent": "/var/lib/waagent",
		},
		"cloudsigma": oemConfig{
			"from-cloudsigma-metadata": "true",
		},
	}
)

func main() {
	failure := false

	flag.Parse()

	if c, ok := oemConfigs[flags.oem]; ok {
		for k, v := range c {
			flag.Set(k, v)
		}
	} else if flags.oem != "" {
		oems := make([]string, 0, len(oemConfigs))
		for k := range oemConfigs {
			oems = append(oems, k)
		}
		fmt.Printf("Invalid option to --oem: %q. Supported options: %q\n", flags.oem, oems)
		os.Exit(2)
	}

	if flags.printVersion == true {
		fmt.Printf("coreos-cloudinit version %s\n", version)
		os.Exit(0)
	}

	switch flags.convertNetconf {
	case "":
	case "debian":
	case "digitalocean":
	default:
		fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", flags.convertNetconf)
		os.Exit(2)
	}

	dss := getDatasources()
	if len(dss) == 0 {
		fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline")
		os.Exit(2)
	}

	ds := selectDatasource(dss)
	if ds == nil {
		fmt.Println("No datasources available in time")
		os.Exit(1)
	}

	fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
	userdataBytes, err := ds.FetchUserdata()
	if err != nil {
		fmt.Printf("Failed fetching user-data from datasource: %v\nContinuing...\n", err)
		failure = true
	}

	if report, err := validate.Validate(userdataBytes); err == nil {
		ret := 0
		for _, e := range report.Entries() {
			fmt.Println(e)
			ret = 1
		}
		if flags.validate {
			os.Exit(ret)
		}
	} else {
		fmt.Printf("Failed while validating user_data (%q)\n", err)
		if flags.validate {
			os.Exit(1)
		}
	}

	fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
	metadata, err := ds.FetchMetadata()
	if err != nil {
		fmt.Printf("Failed fetching meta-data from datasource: %v\n", err)
		os.Exit(1)
	}

	// Apply environment to user-data
	env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
	userdata := env.Apply(string(userdataBytes))

	var ccu *config.CloudConfig
	var script *config.Script
	if ud, err := initialize.ParseUserData(userdata); err != nil {
		fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
		failure = true
	} else {
		switch t := ud.(type) {
		case *config.CloudConfig:
			ccu = t
		case *config.Script:
			script = t
		}
	}

	fmt.Println("Merging cloud-config from meta-data and user-data")
	cc := mergeConfigs(ccu, metadata)

	var ifaces []network.InterfaceGenerator
	if flags.convertNetconf != "" {
		var err error
		switch flags.convertNetconf {
		case "debian":
			ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig)
		case "digitalocean":
			ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig)
		default:
			err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
		}
		if err != nil {
			fmt.Printf("Failed to generate interfaces: %v\n", err)
			os.Exit(1)
		}
	}

	if err = initialize.Apply(cc, ifaces, env); err != nil {
		fmt.Printf("Failed to apply cloud-config: %v\n", err)
		os.Exit(1)
	}

	if script != nil {
		if err = runScript(*script, env); err != nil {
			fmt.Printf("Failed to run script: %v\n", err)
			os.Exit(1)
		}
	}

	if failure && !flags.ignoreFailure {
		os.Exit(1)
	}
}

// mergeConfigs merges certain options from md (meta-data from the datasource)
// onto cc (a CloudConfig derived from user-data), if they are not already set
// on cc (i.e. user-data always takes precedence)
func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.CloudConfig) {
	if cc != nil {
		out = *cc
	}

	if md.Hostname != "" {
		if out.Hostname != "" {
			fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
		} else {
			out.Hostname = md.Hostname
		}
	}
	for _, key := range md.SSHPublicKeys {
		out.SSHAuthorizedKeys = append(out.SSHAuthorizedKeys, key)
	}
	return
}

// getDatasources creates a slice of possible Datasources for cloudinit based
// on the different source command-line flags.
func getDatasources() []datasource.Datasource {
	dss := make([]datasource.Datasource, 0, 5)
	if flags.sources.file != "" {
		dss = append(dss, file.NewDatasource(flags.sources.file))
	}
	if flags.sources.url != "" {
		dss = append(dss, url.NewDatasource(flags.sources.url))
	}
	if flags.sources.configDrive != "" {
		dss = append(dss, configdrive.NewDatasource(flags.sources.configDrive))
	}
	if flags.sources.metadataService {
		dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
	}
	if flags.sources.ec2MetadataService != "" {
		dss = append(dss, ec2.NewDatasource(flags.sources.ec2MetadataService))
	}
	if flags.sources.cloudSigmaMetadataService {
		dss = append(dss, cloudsigma.NewServerContextService())
	}
	if flags.sources.digitalOceanMetadataService != "" {
		dss = append(dss, digitalocean.NewDatasource(flags.sources.digitalOceanMetadataService))
	}
	if flags.sources.waagent != "" {
		dss = append(dss, waagent.NewDatasource(flags.sources.waagent))
	}
	if flags.sources.procCmdLine {
		dss = append(dss, proc_cmdline.NewDatasource())
	}
	return dss
}

// selectDatasource attempts to choose a valid Datasource to use based on its
// current availability. The first Datasource to report to be available is
// returned. Datasources will be retried if possible if they are not
// immediately available. If all Datasources are permanently unavailable or
// datasourceTimeout is reached before one becomes available, nil is returned.
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
	ds := make(chan datasource.Datasource)
	stop := make(chan struct{})
	var wg sync.WaitGroup

	for _, s := range sources {
		wg.Add(1)
		go func(s datasource.Datasource) {
			defer wg.Done()

			duration := datasourceInterval
			for {
				fmt.Printf("Checking availability of %q\n", s.Type())
				if s.IsAvailable() {
					ds <- s
					return
				} else if !s.AvailabilityChanges() {
					return
				}
				select {
				case <-stop:
					return
				case <-time.After(duration):
					duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
				}
			}
		}(s)
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	var s datasource.Datasource
	select {
	case s = <-ds:
	case <-done:
	case <-time.After(datasourceTimeout):
	}

	close(stop)
	return s
}

// TODO(jonboulle): this should probably be refactored and moved into a different module
func runScript(script config.Script, env *initialize.Environment) error {
	err := initialize.PrepWorkspace(env.Workspace())
	if err != nil {
		fmt.Printf("Failed preparing workspace: %v\n", err)
		return err
	}
	path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
	if err == nil {
		var name string
		name, err = system.ExecuteScript(path)
		initialize.PersistUnitNameInWorkspace(name, env.Workspace())
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/coreos/coreos-cloudinit/datasource"
	"github.com/coreos/coreos-cloudinit/initialize"
	"github.com/coreos/coreos-cloudinit/system"
)

const version = "0.5.0"

func main() {
	var printVersion bool
	flag.BoolVar(&printVersion, "version", false, "Print the version and exit")

	var ignoreFailure bool
	flag.BoolVar(&ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")

	var file string
	flag.StringVar(&file, "from-file", "", "Read user-data from provided file")

	var url string
	flag.StringVar(&url, "from-url", "", "Download user-data from provided url")

	var useProcCmdline bool
	flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))

	var workspace string
	flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")

	var sshKeyName string
	flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")

	flag.Parse()

	if printVersion == true {
		fmt.Printf("coreos-cloudinit version %s\n", version)
		os.Exit(0)
	}

	if file != "" && url != "" && !useProcCmdline {
		fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
		os.Exit(1)
	}

	var ds datasource.Datasource
	if file != "" {
		ds = datasource.NewLocalFile(file)
	} else if url != "" {
		ds = datasource.NewMetadataService(url)
	} else if useProcCmdline {
		ds = datasource.NewProcCmdline()
	} else {
		fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
		os.Exit(1)
	}

	log.Printf("Fetching user-data from datasource of type %q", ds.Type())
	userdataBytes, err := ds.Fetch()
	if err != nil {
		log.Printf("Failed fetching user-data from datasource: %v", err)
		if ignoreFailure {
			os.Exit(0)
		} else {
			os.Exit(1)
		}
	}

	if len(userdataBytes) == 0 {
		log.Printf("No user data to handle, exiting.")
		os.Exit(0)
	}

	env := initialize.NewEnvironment("/", workspace)

	userdata := string(userdataBytes)
	userdata = env.Apply(userdata)

	parsed, err := initialize.ParseUserData(userdata)
	if err != nil {
		log.Printf("Failed parsing user-data: %v", err)
		if ignoreFailure {
			os.Exit(0)
		} else {
			os.Exit(1)
		}
	}

	err = initialize.PrepWorkspace(env.Workspace())
	if err != nil {
		log.Fatalf("Failed preparing workspace: %v", err)
	}

	switch t := parsed.(type) {
	case initialize.CloudConfig:
		err = initialize.Apply(t, env)
	case system.Script:
		var path string
		path, err = initialize.PersistScriptInWorkspace(t, env.Workspace())
		if err == nil {
			var name string
			name, err = system.ExecuteScript(path)
			initialize.PersistUnitNameInWorkspace(name, workspace)
		}
	}

	if err != nil {
		log.Fatalf("Failed resolving user-data: %v", err)
	}
}

<commit_msg>chore(release): Bump version to v0.5.0+git<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/coreos/coreos-cloudinit/datasource"
	"github.com/coreos/coreos-cloudinit/initialize"
	"github.com/coreos/coreos-cloudinit/system"
)

const version = "0.5.0+git"

func main() {
	var printVersion bool
	flag.BoolVar(&printVersion, "version", false, "Print the version and exit")

	var ignoreFailure bool
	flag.BoolVar(&ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")

	var file string
	flag.StringVar(&file, "from-file", "", "Read user-data from provided file")

	var url string
	flag.StringVar(&url, "from-url", "", "Download user-data from provided url")

	var useProcCmdline bool
	flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))

	var workspace string
	flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")

	var sshKeyName string
	flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")

	flag.Parse()

	if printVersion == true {
		fmt.Printf("coreos-cloudinit version %s\n", version)
		os.Exit(0)
	}

	if file != "" && url != "" && !useProcCmdline {
		fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
		os.Exit(1)
	}

	var ds datasource.Datasource
	if file != "" {
		ds = datasource.NewLocalFile(file)
	} else if url != "" {
		ds = datasource.NewMetadataService(url)
	} else if useProcCmdline {
		ds = datasource.NewProcCmdline()
	} else {
		fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
		os.Exit(1)
	}

	log.Printf("Fetching user-data from datasource of type %q", ds.Type())
	userdataBytes, err := ds.Fetch()
	if err != nil {
		log.Printf("Failed fetching user-data from datasource: %v", err)
		if ignoreFailure {
			os.Exit(0)
		} else {
			os.Exit(1)
		}
	}

	if len(userdataBytes) == 0 {
		log.Printf("No user data to handle, exiting.")
		os.Exit(0)
	}

	env := initialize.NewEnvironment("/", workspace)

	userdata := string(userdataBytes)
	userdata = env.Apply(userdata)

	parsed, err := initialize.ParseUserData(userdata)
	if err != nil {
		log.Printf("Failed parsing user-data: %v", err)
		if ignoreFailure {
			os.Exit(0)
		} else {
			os.Exit(1)
		}
	}

	err = initialize.PrepWorkspace(env.Workspace())
	if err != nil {
		log.Fatalf("Failed preparing workspace: %v", err)
	}

	switch t := parsed.(type) {
	case initialize.CloudConfig:
		err = initialize.Apply(t, env)
	case system.Script:
		var path string
		path, err = initialize.PersistScriptInWorkspace(t, env.Workspace())
		if err == nil {
			var name string
			name, err = system.ExecuteScript(path)
			initialize.PersistUnitNameInWorkspace(name, workspace)
		}
	}

	if err != nil {
		log.Fatalf("Failed resolving user-data: %v", err)
	}
}

<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
	"fmt"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/graph"
	"github.com/jacobsa/comeback/repr"
	"golang.org/x/net/context"
)

// Create a visitor for the DAG of blobs in the supplied bucket. Node names are
// expected to be generated by Node.String.
//
// The visitor reads directory blobs, parses them, and emits their children as
// adjacent nodes. For file nodes, the visitor verifies that their score exists
// (according to allScores), and verifies that the blob can be loaded if
// readFiles is true.
//
// If work is to be preserved across runs, knownStructure should be filled in
// with parenthood information from previously-generated records (for both file
// and directories). Nodes that exist as keys will not be re-verified, except
// to confirm that they still exist in allScores.
//
// A record is written to the supplied channel for every piece of information
// that is verified.
//
// It is expected that the blob store's Load method does score verification for
// us.
func NewVisitor(
	readFiles bool,
	allScores []blob.Score,
	knownStructure map[Node][]Node,
	records chan<- Record,
	clock timeutil.Clock,
	bs blob.Store) (v graph.Visitor) {
	typed := &visitor{
		readFiles:      readFiles,
		records:        records,
		clock:          clock,
		blobStore:      bs,
		knownScores:    make(map[blob.Score]struct{}),
		knownStructure: knownStructure,
	}

	for _, score := range allScores {
		typed.knownScores[score] = struct{}{}
	}

	v = typed
	return
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type visitor struct {
	readFiles      bool
	records        chan<- Record
	clock          timeutil.Clock
	blobStore      blob.Store
	knownScores    map[blob.Score]struct{}
	knownStructure map[Node][]Node
}

func (v *visitor) visitFile(
	ctx context.Context,
	n Node) (err error) {
	// Make sure the score actually exists.
	if _, ok := v.knownScores[n.Score]; !ok {
		err = fmt.Errorf("Unknown file score: %s", n.Score.Hex())
		return
	}

	// If reading files is disabled, there is nothing further to do.
	if !v.readFiles {
		return
	}

	// If we have already verified this node, there is nothing further to do.
	if _, ok := v.knownStructure[n]; ok {
		return
	}

	// Make sure we can load the blob contents. Presumably the blob store
	// verifies the score (of the ciphertext) on the way through.
	_, err = v.blobStore.Load(ctx, n.Score)
	if err != nil {
		err = fmt.Errorf("Load(%s): %v", n.Score.Hex(), err)
		return
	}

	// Certify that we verified the file piece.
	r := Record{
		Time: v.clock.Now(),
		Node: n,
	}

	select {
	case <-ctx.Done():
		err = ctx.Err()
		return

	case v.records <- r:
	}

	return
}

func (v *visitor) visitDir(
	ctx context.Context,
	score blob.Score) (adjacent []string, err error) {
	// Load the blob contents.
	contents, err := v.blobStore.Load(ctx, score)
	if err != nil {
		err = fmt.Errorf("Load(%s): %v", score.Hex(), err)
		return
	}

	// Parse the listing.
	listing, err := repr.UnmarshalDir(contents)
	if err != nil {
		err = fmt.Errorf("UnmarshalDir(%s): %v", score.Hex(), err)
		return
	}

	// Build a record containing a child node for each score in each entry.
	r := Record{
		Time: v.clock.Now(),
		Node: Node{
			Score: score,
			Dir:   true,
		},
	}

	for _, entry := range listing {
		var n Node

		// Is this a directory?
		switch entry.Type {
		case fs.TypeFile:
			n.Dir = false

		case fs.TypeDirectory:
			n.Dir = true

		case fs.TypeSymlink:
			if len(entry.Scores) != 0 {
				err = fmt.Errorf(
					"Dir %s: symlink unexpectedly contains scores",
					score.Hex())

				return
			}

		default:
			err = fmt.Errorf("Dir %s: unknown entry type %v", score.Hex(), entry.Type)
			return
		}

		// Add a node for each score.
		for _, score := range entry.Scores {
			n.Score = score
			r.Children = append(r.Children, n)
		}
	}

	// Certify that we verified the directory.
	select {
	case <-ctx.Done():
		err = ctx.Err()
		return

	case v.records <- r:
	}

	// Return child node names.
	for _, child := range r.Children {
		adjacent = append(adjacent, child.String())
	}

	return
}

func (v *visitor) Visit(
	ctx context.Context,
	nodeName string) (adjacent []string, err error) {
	// Parse the node name.
	n, err := ParseNode(nodeName)
	if err != nil {
		err = fmt.Errorf("ParseNode(%q): %v", nodeName, err)
		return
	}

	if n.Dir {
		adjacent, err = v.visitDir(ctx, n.Score)
		return
	} else {
		err = v.visitFile(ctx, n)
		return
	}
}
<commit_msg>Refactored.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
	"fmt"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/graph"
	"github.com/jacobsa/comeback/repr"
	"golang.org/x/net/context"
)

// Create a visitor for the DAG of blobs in the supplied bucket. Node names are
// expected to be generated by Node.String.
//
// The visitor reads directory blobs, parses them, and emits their children as
// adjacent nodes. For file nodes, the visitor verifies that their score exists
// (according to allScores), and verifies that the blob can be loaded if
// readFiles is true.
//
// If work is to be preserved across runs, knownStructure should be filled in
// with parenthood information from previously-generated records (for both file
// and directories). Nodes that exist as keys will not be re-verified, except
// to confirm that they still exist in allScores.
//
// A record is written to the supplied channel for every piece of information
// that is verified.
//
// It is expected that the blob store's Load method does score verification for
// us.
func NewVisitor(
	readFiles bool,
	allScores []blob.Score,
	knownStructure map[Node][]Node,
	records chan<- Record,
	clock timeutil.Clock,
	bs blob.Store) (v graph.Visitor) {
	typed := &visitor{
		readFiles:      readFiles,
		records:        records,
		clock:          clock,
		blobStore:      bs,
		knownScores:    make(map[blob.Score]struct{}),
		knownStructure: knownStructure,
	}

	for _, score := range allScores {
		typed.knownScores[score] = struct{}{}
	}

	v = typed
	return
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type visitor struct {
	readFiles      bool
	records        chan<- Record
	clock          timeutil.Clock
	blobStore      blob.Store
	knownScores    map[blob.Score]struct{}
	knownStructure map[Node][]Node
}

func (v *visitor) visitFile(
	ctx context.Context,
	n Node) (err error) {
	// If reading files is disabled, there is nothing further to do.
	if !v.readFiles {
		return
	}

	// Make sure we can load the blob contents. Presumably the blob store
	// verifies the score (of the ciphertext) on the way through.
	_, err = v.blobStore.Load(ctx, n.Score)
	if err != nil {
		err = fmt.Errorf("Load(%s): %v", n.Score.Hex(), err)
		return
	}

	// Certify that we verified the file piece.
	r := Record{
		Time: v.clock.Now(),
		Node: n,
	}

	select {
	case <-ctx.Done():
		err = ctx.Err()
		return

	case v.records <- r:
	}

	return
}

func (v *visitor) visitDir(
	ctx context.Context,
	n Node) (adjacent []string, err error) {
	// Load the blob contents.
	contents, err := v.blobStore.Load(ctx, n.Score)
	if err != nil {
		err = fmt.Errorf("Load(%s): %v", n.Score.Hex(), err)
		return
	}

	// Parse the listing.
	listing, err := repr.UnmarshalDir(contents)
	if err != nil {
		err = fmt.Errorf("UnmarshalDir(%s): %v", n.Score.Hex(), err)
		return
	}

	// Build a record containing a child node for each score in each entry.
	r := Record{
		Time: v.clock.Now(),
		Node: n,
	}

	for _, entry := range listing {
		var n Node

		// Is this a directory?
		switch entry.Type {
		case fs.TypeFile:
			n.Dir = false

		case fs.TypeDirectory:
			n.Dir = true

		case fs.TypeSymlink:
			if len(entry.Scores) != 0 {
				err = fmt.Errorf(
					"Dir %s: symlink unexpectedly contains scores",
					n.Score.Hex())

				return
			}

		default:
			err = fmt.Errorf(
				"Dir %s: unknown entry type %v",
				n.Score.Hex(),
				entry.Type)

			return
		}

		// Add a node for each score.
		for _, score := range entry.Scores {
			n.Score = score
			r.Children = append(r.Children, n)
		}
	}

	// Certify that we verified the directory.
	select {
	case <-ctx.Done():
		err = ctx.Err()
		return

	case v.records <- r:
	}

	// Return child node names.
	for _, child := range r.Children {
		adjacent = append(adjacent, child.String())
	}

	return
}

func (v *visitor) Visit(
	ctx context.Context,
	nodeName string) (adjacent []string, err error) {
	// Parse the node name.
	n, err := ParseNode(nodeName)
	if err != nil {
		err = fmt.Errorf("ParseNode(%q): %v", nodeName, err)
		return
	}

	// Make sure the score actually exists.
	if _, ok := v.knownScores[n.Score]; !ok {
		err = fmt.Errorf("Unknown score for node: %s", n.String())
		return
	}

	// If we have already verified this node, there is nothing further to do.
	if _, ok := v.knownStructure[n]; ok {
		return
	}

	// Perform file or directory-specific logic.
	if n.Dir {
		adjacent, err = v.visitDir(ctx, n)
		return
	} else {
		err = v.visitFile(ctx, n)
		return
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package authentication

import (
	//"github.com/ctdk/goiardi/chef_crypto"
	"github.com/ctdk/goiardi/actor"
	"github.com/ctdk/goiardi/util"
	"github.com/ctdk/goiardi/config"
	"net/http"
	"io"
	"io/ioutil"
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"log"
	"strings"
	"regexp"
	"strconv"
	"time"
)

func CheckHeader(user_id string, r *http.Request) (bool, util.Gerror) {
	user, err := actor.Get(user_id)
	_ = user
/*	if err != nil {
		gerr := util.Errorf(err.Error())
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	} */
	contentHash := r.Header.Get("X-OPS-CONTENT-HASH")
	if contentHash == "" {
		gerr := util.Errorf("no content hash provided")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	}
	authTimestamp := r.Header.Get("x-ops-timestamp")
	if authTimestamp == "" {
		gerr := util.Errorf("no timestamp header provided")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	} else {
		// check the time stamp w/ allowed slew
		tok, terr := checkTimeStamp(authTimestamp, config.Config.TimeSlew)
		if !tok {
			return false, terr
		}
	}
	var bodyStr string
	if r.Body == nil {
		bodyStr = ""
	} else {
		save := r.Body
		save, r.Body, err = drainBody(r.Body)
		if err != nil {
			gerr := util.Errorf("could not copy body")
			gerr.SetStatus(http.StatusInternalServerError)
			return false, gerr
		}
		buf := new(bytes.Buffer)
		buf.ReadFrom(r.Body)
		bodyStr = buf.String()
		r.Body = save
	}
	h := sha1.New()
	io.WriteString(h, bodyStr)
	chkHash := base64.StdEncoding.EncodeToString(h.Sum(nil))
	log.Printf("content hash is: %s\ncalcnew hash is: %s\n", contentHash, chkHash)
	if chkHash != contentHash {
		gerr := util.Errorf("Content hash did not match hash of request body")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	}

	signedHeaders, sherr  := assembleSignedHeader(r)
	if sherr != nil {
		return false, sherr
	}
	log.Printf("The full signed encoded header is: %s\n", signedHeaders)

	return true, nil
}

// liberated from net/httputil
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, nil, err
	}
	if err = b.Close(); err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
}

func assembleSignedHeader(r *http.Request) (string, util.Gerror) {
	sHeadStore := make(map[int]string)
	authHeader := regexp.MustCompile(`(?i)^X-Ops-Authorization-(\d+)`)
	for k := range r.Header {
		if c := authHeader.FindStringSubmatch(k); c != nil {
			/* Have to put it into a map first, then sort, in case
			 * the headers don't come out in the right order */
			// skipping this error because we shouldn't even be
			// able to get here with something that won't be an
			// integer. Famous last words, I'm sure.
			i, _ := strconv.Atoi(c[1])
			sHeadStore[i] = r.Header.Get(k)
		}
	}
	if len(sHeadStore) == 0 {
		gerr := util.Errorf("No authentication headers found!")
		gerr.SetStatus(http.StatusUnauthorized)
		return "", gerr
	}

	sH := make([]string, len(sHeadStore))
	for k, v := range sHeadStore {
		sH[k - 1] = v
	}
	signedHeaders := strings.Join(sH, "")

	return signedHeaders, nil
}

func checkTimeStamp(timestamp string, slew time.Duration) (bool, util.Gerror) {
	timeNow := time.Now().UTC()
	timeHeader, terr := time.Parse(time.RFC3339, timestamp)
	if terr != nil {
		err := util.Errorf(terr.Error())
		err.SetStatus(http.StatusUnauthorized)
		return false, err
	}
	tdiff := timeNow.Sub(timeHeader)
	// no easy integer based abs function
	if tdiff < 0 {
		tdiff = -tdiff
	}
	if tdiff > slew {
		err := util.Errorf("Authentication failed. Please check your system's clock.")
		err.SetStatus(http.StatusUnauthorized)
		return false, err
	}
	return true, nil
}
<commit_msg>Assemble signed header thing<commit_after>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package authentication

/* Geez, import all the things why don't you. */

import (
	//"github.com/ctdk/goiardi/chef_crypto"
	"github.com/ctdk/goiardi/actor"
	"github.com/ctdk/goiardi/util"
	"github.com/ctdk/goiardi/config"
	"net/http"
	"io"
	"io/ioutil"
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"log"
	"strings"
	"regexp"
	"strconv"
	"time"
	"path"
	"fmt"
)

func CheckHeader(user_id string, r *http.Request) (bool, util.Gerror) {
	user, err := actor.Get(user_id)
	_ = user
	_ = err
/*	if err != nil {
		gerr := util.Errorf(err.Error())
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	} */
	contentHash := r.Header.Get("X-OPS-CONTENT-HASH")
	if contentHash == "" {
		gerr := util.Errorf("no content hash provided")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	}
	authTimestamp := r.Header.Get("x-ops-timestamp")
	if authTimestamp == "" {
		gerr := util.Errorf("no timestamp header provided")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	} else {
		// check the time stamp w/ allowed slew
		tok, terr := checkTimeStamp(authTimestamp, config.Config.TimeSlew)
		if !tok {
			return false, terr
		}
	}
	chkHash, chkerr := calcBodyHash(r)
	if chkerr != nil {
		return false, chkerr
	}
	log.Printf("content hash is: %s\ncalcnew hash is: %s\n", contentHash, chkHash)
	if chkHash != contentHash {
		gerr := util.Errorf("Content hash did not match hash of request body")
		gerr.SetStatus(http.StatusUnauthorized)
		return false, gerr
	}

	signedHeaders, sherr  := assembleSignedHeader(r)
	if sherr != nil {
		return false, sherr
	}
	log.Printf("The full signed encoded header is: %s\n", signedHeaders)
	headToCheck := assembleHeaderToCheck(r, chkHash)
	log.Printf("The candidate header:\n%s\n", headToCheck)

	return true, nil
}

// liberated from net/http/httputil
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, nil, err
	}
	if err = b.Close(); err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
}

func assembleSignedHeader(r *http.Request) (string, util.Gerror) {
	sHeadStore := make(map[int]string)
	authHeader := regexp.MustCompile(`(?i)^X-Ops-Authorization-(\d+)`)
	for k := range r.Header {
		if c := authHeader.FindStringSubmatch(k); c != nil {
			/* Have to put it into a map first, then sort, in case
			 * the headers don't come out in the right order */
			// skipping this error because we shouldn't even be
			// able to get here with something that won't be an
			// integer. Famous last words, I'm sure.
			i, _ := strconv.Atoi(c[1])
			sHeadStore[i] = r.Header.Get(k)
		}
	}
	if len(sHeadStore) == 0 {
		gerr := util.Errorf("No authentication headers found!")
		gerr.SetStatus(http.StatusUnauthorized)
		return "", gerr
	}

	sH := make([]string, len(sHeadStore))
	for k, v := range sHeadStore {
		sH[k - 1] = v
	}
	signedHeaders := strings.Join(sH, "")

	return signedHeaders, nil
}

func checkTimeStamp(timestamp string, slew time.Duration) (bool, util.Gerror) {
	timeNow := time.Now().UTC()
	timeHeader, terr := time.Parse(time.RFC3339, timestamp)
	if terr != nil {
		err := util.Errorf(terr.Error())
		err.SetStatus(http.StatusUnauthorized)
		return false, err
	}
	tdiff := timeNow.Sub(timeHeader)
	// no easy integer based abs function
	if tdiff < 0 {
		tdiff = -tdiff
	}
	if tdiff > slew {
		err := util.Errorf("Authentication failed. Please check your system's clock.")
		err.SetStatus(http.StatusUnauthorized)
		return false, err
	}
	return true, nil
}

func assembleHeaderToCheck(r *http.Request, cHash string) string {
	method := r.Method
	hashPath := hashStr(path.Clean(r.URL.Path))
	timestamp := r.Header.Get("x-ops-timestamp")
	user_id := r.Header.Get("x-ops-userid")

	headStr := fmt.Sprintf("Method:%s\nHashed Path:%s\nX-Ops-Content-Hash:%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s", method, hashPath, cHash, timestamp, user_id)
	return headStr
}

func hashStr(toHash string) string {
	h := sha1.New()
	io.WriteString(h, toHash)
	hashed := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return hashed
}

func calcBodyHash(r *http.Request) (string, util.Gerror) {
	var bodyStr string
	if r.Body == nil {
		bodyStr = ""
	} else {
		var err error
		save := r.Body
		save, r.Body, err = drainBody(r.Body)
		if err != nil {
			gerr := util.Errorf("could not copy body")
			gerr.SetStatus(http.StatusInternalServerError)
			return "", gerr
		}
		buf := new(bytes.Buffer)
		buf.ReadFrom(r.Body)
		bodyStr = buf.String()
		r.Body = save
	}
	chkHash := hashStr(bodyStr)
	return chkHash, nil
}
<|endoftext|>


<commit_before>package crunchyroll /* import "s32x.com/anirip/crunchyroll" */

import (
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"s32x.com/anirip/common"
)

// Show contins show metadata and child seasons
type Show struct {
	Title   string
	AdID    string
	Path    string
	URL     string
	Seasons []Season
}

// Scrape appends all the seasons/episodes found for the show
func (s *Show) Scrape(client *common.HTTPClient, showURL string) error {
	res, err := client.Get(showURL, nil)
	if err != nil {
		return common.NewError("There was an error retrieving show page", err)
	}

	// Creates the goquery document for scraping
	showDoc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return common.NewError("There was an error while accessing the show page", err)
	}

	// Sets Title, Path and URL on our show object
	s.Title = showDoc.Find("#container > h1 > span").First().Text()
	s.URL = showURL
	s.Path = strings.Replace(s.URL, "http://www.crunchyroll.com", "", 1)

	// Searches first for the search div
	showDoc.Find("ul.list-of-seasons.cf").Each(func(i int, seasonList *goquery.Selection) {
		seasonList.Find("li.season").Each(func(i2 int, episodeList *goquery.Selection) {
			// Adds a new season to the show containing all information
			seasonTitle, _ := episodeList.Find("a").First().Attr("title")

			// Adds the title minus any "Episode XX" for shows that only have one season
			s.Seasons = append(s.Seasons, Season{
				Title: strings.SplitN(seasonTitle, " Episode ", 2)[0],
			})

			// Within that season finds all episodes
			episodeList.Find("div.wrapper.container-shadow.hover-classes").Each(func(i3 int, episode *goquery.Selection) {
				// Appends all new episode information to newly appended season
				episodeTitle := strings.TrimSpace(strings.Replace(episode.Find("span.series-title.block.ellipsis").First().Text(), "\n", "", 1))
				episodeNumber, _ := strconv.ParseFloat(strings.Replace(episodeTitle, "Episode ", "", 1), 64)
				episodePath, _ := episode.Find("a").First().Attr("href")
				episodeID, _ := strconv.Atoi(episodePath[len(episodePath)-6:])
				s.Seasons[i2].Episodes = append(s.Seasons[i2].Episodes, Episode{
					ID:     episodeID,
					Title:  episodeTitle,
					Number: episodeNumber,
					Path:   episodePath,
					URL:    "http://www.crunchyroll.com" + episodePath,
				})
			})
		})
	})

	// Re-arranges seasons and episodes in the shows object so we have first to last
	tempSeasonArray := []Season{}
	for i := len(s.Seasons) - 1; i >= 0; i-- {
		// First sort episodes from first to last
		tempEpisodesArray := []Episode{}
		for n := len(s.Seasons[i].Episodes) - 1; n >= 0; n-- {
			tempEpisodesArray = append(tempEpisodesArray, s.Seasons[i].Episodes[n])
		}
		// Lets not bother appending anything if there are no episodes in the season
		if len(tempEpisodesArray) > 0 {
			tempSeasonArray = append(tempSeasonArray, Season{
				Title:    s.Seasons[i].Title,
				Length:   len(tempEpisodesArray),
				Episodes: tempEpisodesArray,
			})
		}
	}
	s.Seasons = tempSeasonArray

	// Assigns each season a number and episode a filename
	for k1, season := range s.Seasons {
		s.Seasons[k1].Number = k1 + 1
		for k2, episode := range season.Episodes {
			s.Seasons[k1].Episodes[k2].Filename = common.GenerateEpisodeFilename(s.Title, s.Seasons[k1].Number, episode.Number, "")
		}
	}

	// TODO Filter out episodes that aren't yet released (ex One Piece)
	return nil
}

// Gets the title of the show for referencing outside of this lib
func (show *Show) GetTitle() string {
	//CleanFileName to remove illegal chars from show name, if any.
	return common.CleanFilename(show.Title)
}

// Re-stores seasons belonging to the show and returns them for iteration
func (show *Show) GetSeasons() common.Seasons {
	seasons := []common.Season{}
	for i := 0; i < len(show.Seasons); i++ {
		seasons = append(seasons, &show.Seasons[i])
	}
	return seasons
}
<commit_msg>Fixed show title<commit_after>package crunchyroll /* import "s32x.com/anirip/crunchyroll" */

import (
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"s32x.com/anirip/common"
)

// Show contins show metadata and child seasons
type Show struct {
	Title   string
	AdID    string
	Path    string
	URL     string
	Seasons []Season
}

// Scrape appends all the seasons/episodes found for the show
func (s *Show) Scrape(client *common.HTTPClient, showURL string) error {
	res, err := client.Get(showURL, nil)
	if err != nil {
		return common.NewError("There was an error retrieving show page", err)
	}

	// Creates the goquery document for scraping
	showDoc, err := goquery.NewDocumentFromResponse(res)
	if err != nil {
		return common.NewError("There was an error while accessing the show page", err)
	}

	// Sets Title, Path and URL on our show object
	s.Title = showDoc.Find("#showview-content-header > div > h1 > span").First().Text()
	s.URL = showURL
	s.Path = strings.Replace(s.URL, "http://www.crunchyroll.com", "", 1)

	// Searches first for the search div
	showDoc.Find("ul.list-of-seasons.cf").Each(func(i int, seasonList *goquery.Selection) {
		seasonList.Find("li.season").Each(func(i2 int, episodeList *goquery.Selection) {
			// Adds a new season to the show containing all information
			seasonTitle, _ := episodeList.Find("a").First().Attr("title")

			// Adds the title minus any "Episode XX" for shows that only have one season
			s.Seasons = append(s.Seasons, Season{
				Title: strings.SplitN(seasonTitle, " Episode ", 2)[0],
			})

			// Within that season finds all episodes
			episodeList.Find("div.wrapper.container-shadow.hover-classes").Each(func(i3 int, episode *goquery.Selection) {
				// Appends all new episode information to newly appended season
				episodeTitle := strings.TrimSpace(strings.Replace(episode.Find("span.series-title.block.ellipsis").First().Text(), "\n", "", 1))
				episodeNumber, _ := strconv.ParseFloat(strings.Replace(episodeTitle, "Episode ", "", 1), 64)
				episodePath, _ := episode.Find("a").First().Attr("href")
				episodeID, _ := strconv.Atoi(episodePath[len(episodePath)-6:])
				s.Seasons[i2].Episodes = append(s.Seasons[i2].Episodes, Episode{
					ID:     episodeID,
					Title:  episodeTitle,
					Number: episodeNumber,
					Path:   episodePath,
					URL:    "http://www.crunchyroll.com" + episodePath,
				})
			})
		})
	})

	// Re-arranges seasons and episodes in the shows object so we have first to last
	tempSeasonArray := []Season{}
	for i := len(s.Seasons) - 1; i >= 0; i-- {
		// First sort episodes from first to last
		tempEpisodesArray := []Episode{}
		for n := len(s.Seasons[i].Episodes) - 1; n >= 0; n-- {
			tempEpisodesArray = append(tempEpisodesArray, s.Seasons[i].Episodes[n])
		}
		// Lets not bother appending anything if there are no episodes in the season
		if len(tempEpisodesArray) > 0 {
			tempSeasonArray = append(tempSeasonArray, Season{
				Title:    s.Seasons[i].Title,
				Length:   len(tempEpisodesArray),
				Episodes: tempEpisodesArray,
			})
		}
	}
	s.Seasons = tempSeasonArray

	// Assigns each season a number and episode a filename
	for k1, season := range s.Seasons {
		s.Seasons[k1].Number = k1 + 1
		for k2, episode := range season.Episodes {
			s.Seasons[k1].Episodes[k2].Filename = common.GenerateEpisodeFilename(s.Title, s.Seasons[k1].Number, episode.Number, "")
		}
	}

	// TODO Filter out episodes that aren't yet released (ex One Piece)
	return nil
}

// Gets the title of the show for referencing outside of this lib
func (show *Show) GetTitle() string {
	//CleanFileName to remove illegal chars from show name, if any.
	return common.CleanFilename(show.Title)
}

// Re-stores seasons belonging to the show and returns them for iteration
func (show *Show) GetSeasons() common.Seasons {
	seasons := []common.Season{}
	for i := 0; i < len(show.Seasons); i++ {
		seasons = append(seasons, &show.Seasons[i])
	}
	return seasons
}
<|endoftext|>


<commit_before>// Package locationiq is a geo-golang based LocationIQ geocode/reverse geocode client
package locationiq

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/codingsince1985/geo-golang"
)

type baseURL string

type geocodeResponse struct {
	DisplayName     string `json:"display_name"`
	Lat, Lon, Error string
	Addr            locationiqAddress `json:"address"`
}

type locationiqAddress struct {
	HouseNumber   string `json:"house_number"`
	Suburb        string `json:"suburb"`
	City          string `json:"city"`
	County        string `json:"county"`
	Village       string `json:"village"`
	Country       string `json:"country"`
	CountryCode   string `json:"country_code"`
	Road          string `json:"road"`
	State         string `json:"state"`
	StateDistrict string `json:"state_district"`
	Postcode      string `json:"postcode"`
}

const (
	defaultURL  = "http://locationiq.org/v1/"
	minZoom     = 0  // Min zoom level for locationiq - country level
	maxZoom     = 18 // Max zoom level for locationiq - house level
	defaultZoom = 18
)

var (
	key  string
	zoom int
)

// Geocoder constructs LocationIQ geocoder
func Geocoder(k string, z int, baseURLs ...string) geo.Geocoder {
	key = k

	var url string
	if len(baseURLs) > 0 {
		url = baseURLs[0]
	} else {
		url = defaultURL
	}

	if z > minZoom && z <= maxZoom {
		zoom = z
	} else {
		zoom = defaultZoom
	}

	return geo.HTTPGeocoder{
		EndpointBuilder:       baseURL(url),
		ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
	}
}

func (b baseURL) GeocodeURL(address string) string {
	return string(b) + "search.php?key=" + key + "&format=json&limit=1&q=" + address
}

func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
	return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f&zoom=%d", l.Lat, l.Lng, zoom)
}

func (r *geocodeResponse) Location() (*geo.Location, error) {
	if r.Lat == "" || r.Lon == "" {
		return nil, fmt.Errorf("empty lat/lon value: %s", r.Error)
	}

	return &geo.Location{
		Lat: geo.ParseFloat(r.Lat),
		Lng: geo.ParseFloat(r.Lon),
	}, nil
}

func (r *geocodeResponse) Address() (*geo.Address, error) {
	if r.Error != "" {
		return nil, fmt.Errorf("error reverse geocoding: %s", r.Error)
	}
	var locality string
	if r.Addr.City != "" {
		locality = r.Addr.City
	} else {
		locality = r.Addr.Village
	}
	return &geo.Address{
		FormattedAddress: r.DisplayName,
		Street:           r.Addr.Road,
		HouseNumber:      r.Addr.HouseNumber,
		City:             locality,
		Postcode:         r.Addr.Postcode,
		Suburb:           r.Addr.Suburb,
		State:            r.Addr.State,
		Country:          r.Addr.Country,
		CountryCode:      strings.ToUpper(r.Addr.CountryCode),
	}, nil
}

func (r *geocodeResponse) FormattedAddress() string {
	if r.Error != "" {
		return ""
	}
	return r.DisplayName
}
<commit_msg>Remove getter func, since the field is exported; improve error handling<commit_after>// Package locationiq is a geo-golang based LocationIQ geocode/reverse geocode client
package locationiq

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/codingsince1985/geo-golang"
)

type baseURL string

type geocodeResponse struct {
	DisplayName     string `json:"display_name"`
	Lat, Lon, Error string
	Addr            locationiqAddress `json:"address"`
}

type locationiqAddress struct {
	HouseNumber   string `json:"house_number"`
	Suburb        string `json:"suburb"`
	City          string `json:"city"`
	County        string `json:"county"`
	Village       string `json:"village"`
	Country       string `json:"country"`
	CountryCode   string `json:"country_code"`
	Road          string `json:"road"`
	State         string `json:"state"`
	StateDistrict string `json:"state_district"`
	Postcode      string `json:"postcode"`
}

const (
	defaultURL  = "http://locationiq.org/v1/"
	minZoom     = 0  // Min zoom level for locationiq - country level
	maxZoom     = 18 // Max zoom level for locationiq - house level
	defaultZoom = 18
)

var (
	key  string
	zoom int
)

// Geocoder constructs LocationIQ geocoder
func Geocoder(k string, z int, baseURLs ...string) geo.Geocoder {
	key = k

	var url string
	if len(baseURLs) > 0 {
		url = baseURLs[0]
	} else {
		url = defaultURL
	}

	if z > minZoom && z <= maxZoom {
		zoom = z
	} else {
		zoom = defaultZoom
	}

	return geo.HTTPGeocoder{
		EndpointBuilder:       baseURL(url),
		ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
	}
}

func (b baseURL) GeocodeURL(address string) string {
	return string(b) + "search.php?key=" + key + "&format=json&limit=1&q=" + address
}

func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
	return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f&zoom=%d", l.Lat, l.Lng, zoom)
}

func (r *geocodeResponse) Location() (*geo.Location, error) {
	if r.Error != "" {
		return nil, fmt.Errorf("geocoding error: %s", r.Error)
	}
	if r.Lat == "" || r.Lon == "" {
		return nil, fmt.Errorf("empty lat/lon value: %s", r.Error)
	}

	return &geo.Location{
		Lat: geo.ParseFloat(r.Lat),
		Lng: geo.ParseFloat(r.Lon),
	}, nil
}

func (r *geocodeResponse) Address() (*geo.Address, error) {
	if r.Error != "" {
		return nil, fmt.Errorf("reverse geocoding error: %s", r.Error)
	}
	var locality string
	if r.Addr.City != "" {
		locality = r.Addr.City
	} else {
		locality = r.Addr.Village
	}
	return &geo.Address{
		FormattedAddress: r.DisplayName,
		Street:           r.Addr.Road,
		HouseNumber:      r.Addr.HouseNumber,
		City:             locality,
		Postcode:         r.Addr.Postcode,
		Suburb:           r.Addr.Suburb,
		State:            r.Addr.State,
		Country:          r.Addr.Country,
		CountryCode:      strings.ToUpper(r.Addr.CountryCode),
	}, nil
}

func (r *geocodeResponse) FormattedAddress() string {
	if r.Error != "" {
		return ""
	}
	return r.DisplayName
}
<|endoftext|>


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

package fields

import (
	"encoding/json"
	"reflect"
	"testing"
)

type embed1 struct {
	Em1    int
	Dup    int // annihilates with embed2.Dup
	Shadow int
	embed3
}

type embed2 struct {
	Dup int
	embed3
	embed4
}

type embed3 struct {
	Em3 int // annihilated because embed3 is in both embed1 and embed2
	embed5
}

type embed4 struct {
	Em4     int
	Dup     int // annihilation of Dup in embed1, embed2 hides this Dup
	*embed1     // ignored because it occurs at a higher level
}

type embed5 struct {
	x int
}

type Anonymous int

type S1 struct {
	Exported   int
	unexported int
	Shadow     int // shadows S1.Shadow
	embed1
	*embed2
	Anonymous
}

var intType = reflect.TypeOf(int(0))

func TestFieldsNoTags(t *testing.T) {
	got := Fields(reflect.TypeOf(S1{}), nil)
	want := []Field{
		{Name: "Anonymous", Index: []int{5}, Type: reflect.TypeOf(Anonymous(0))},
		{Name: "Em1", Index: []int{3, 0}, Type: intType},
		{Name: "Em4", Index: []int{4, 2, 0}, Type: intType},
		{Name: "Exported", Index: []int{0}, Type: intType},
		{Name: "Shadow", Index: []int{2}, Type: intType},
	}
	if len(got) != len(want) {
		t.Fatalf("got %d fields, want %d", len(got), len(want))
	}
	for i, g := range got {
		w := want[i]
		if !reflect.DeepEqual(got, want) {
			t.Errorf("got %+v, want %+v", g, w)
		}
	}
}

func TestAgainstJSONEncodingNoTags(t *testing.T) {
	// Demonstrates that this package produces the same set of fields as encoding/json.
	s1 := S1{
		Exported:   1,
		unexported: 2,
		Shadow:     3,
		embed1: embed1{
			Em1:    4,
			Dup:    5,
			Shadow: 6,
			embed3: embed3{
				Em3:    7,
				embed5: embed5{x: 8},
			},
		},
		embed2: &embed2{
			Dup: 9,
			embed3: embed3{
				Em3:    10,
				embed5: embed5{x: 11},
			},
			embed4: embed4{
				Em4:    12,
				Dup:    13,
				embed1: &embed1{Em1: 14},
			},
		},
		Anonymous: Anonymous(15),
	}
	bytes, err := json.Marshal(s1)
	if err != nil {
		t.Fatal(err)
	}
	var want S1
	if err := json.Unmarshal(bytes, &want); err != nil {
		t.Fatal(err)
	}

	var got S1
	got.embed2 = &embed2{} // need this because reflection won't create it
	fields := Fields(reflect.TypeOf(got), nil)
	setFields(fields, &got, s1)
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got\n%+v\nwant\n%+v", got, want)
	}
}

type S2 struct {
	NoTag     int
	XXX       int           `json:"tag"` // tag name takes precedence
	Anonymous `json:"anon"` // anonymous non-structs also get their name from the tag
	Embed     `json:"em"`   // embedded structs with tags become fields
	tEmbed1
	tEmbed2
}

type Embed struct {
	Em int
}

type tEmbed1 struct {
	Dup int
	X   int `json:"Dup2"`
}

type tEmbed2 struct {
	Y int `json:"Dup"`  // takes precedence over tEmbed1.Dup because it is tagged
	Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored
}

func jsonTagParser(t reflect.StructTag) string { return t.Get("json") }

func TestFieldsWithTags(t *testing.T) {
	got := Fields(reflect.TypeOf(S2{}), jsonTagParser)
	want := []Field{
		{Name: "Dup", NameFromTag: true, Index: []int{5, 0}, Type: intType},
		{Name: "NoTag", Index: []int{0}, Type: intType},
		{Name: "anon", NameFromTag: true, Index: []int{2}, Type: reflect.TypeOf(Anonymous(0))},
		{Name: "em", NameFromTag: true, Index: []int{3}, Type: reflect.TypeOf(Embed{})},
		{Name: "tag", NameFromTag: true, Index: []int{1}, Type: intType},
	}
	if len(got) != len(want) {
		t.Fatalf("got %d fields, want %d", len(got), len(want))
	}
	for i, g := range got {
		w := want[i]
		if !reflect.DeepEqual(g, w) {
			t.Errorf("got %+v, want %+v", g, w)
		}
	}
}

func TestAgainstJSONEncodingWithTags(t *testing.T) {
	// Demonstrates that this package produces the same set of fields as encoding/json.
	s2 := S2{
		NoTag:     1,
		XXX:       2,
		Anonymous: 3,
		Embed: Embed{
			Em: 4,
		},
		tEmbed1: tEmbed1{
			Dup: 5,
			X:   6,
		},
		tEmbed2: tEmbed2{
			Y: 7,
			Z: 8,
		},
	}
	bytes, err := json.Marshal(s2)
	if err != nil {
		t.Fatal(err)
	}
	var want S2
	if err := json.Unmarshal(bytes, &want); err != nil {
		t.Fatal(err)
	}

	var got S2
	fields := Fields(reflect.TypeOf(got), jsonTagParser)
	setFields(fields, &got, s2)
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got\n%+v\nwant\n%+v", got, want)
	}
}

// Set the fields of dst from those of src.
// dst must be a pointer to a struct value.
// src must be a struct value.
func setFields(fields []Field, dst, src interface{}) {
	vsrc := reflect.ValueOf(src)
	vdst := reflect.ValueOf(dst).Elem()
	for _, f := range fields {
		fdst := vdst.FieldByIndex(f.Index)
		fsrc := vsrc.FieldByIndex(f.Index)
		fdst.Set(fsrc)
	}
}
<commit_msg>fields: test that unexported, tagged field is ignored<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fields

import (
	"encoding/json"
	"reflect"
	"testing"
)

type embed1 struct {
	Em1    int
	Dup    int // annihilates with embed2.Dup
	Shadow int
	embed3
}

type embed2 struct {
	Dup int
	embed3
	embed4
}

type embed3 struct {
	Em3 int // annihilated because embed3 is in both embed1 and embed2
	embed5
}

type embed4 struct {
	Em4     int
	Dup     int // annihilation of Dup in embed1, embed2 hides this Dup
	*embed1     // ignored because it occurs at a higher level
}

type embed5 struct {
	x int
}

type Anonymous int

type S1 struct {
	Exported   int
	unexported int
	Shadow     int // shadows S1.Shadow
	embed1
	*embed2
	Anonymous
}

var intType = reflect.TypeOf(int(0))

func TestFieldsNoTags(t *testing.T) {
	got := Fields(reflect.TypeOf(S1{}), nil)
	want := []Field{
		{Name: "Anonymous", Index: []int{5}, Type: reflect.TypeOf(Anonymous(0))},
		{Name: "Em1", Index: []int{3, 0}, Type: intType},
		{Name: "Em4", Index: []int{4, 2, 0}, Type: intType},
		{Name: "Exported", Index: []int{0}, Type: intType},
		{Name: "Shadow", Index: []int{2}, Type: intType},
	}
	if len(got) != len(want) {
		t.Fatalf("got %d fields, want %d", len(got), len(want))
	}
	for i, g := range got {
		w := want[i]
		if !reflect.DeepEqual(got, want) {
			t.Errorf("got %+v, want %+v", g, w)
		}
	}
}

func TestAgainstJSONEncodingNoTags(t *testing.T) {
	// Demonstrates that this package produces the same set of fields as encoding/json.
	s1 := S1{
		Exported:   1,
		unexported: 2,
		Shadow:     3,
		embed1: embed1{
			Em1:    4,
			Dup:    5,
			Shadow: 6,
			embed3: embed3{
				Em3:    7,
				embed5: embed5{x: 8},
			},
		},
		embed2: &embed2{
			Dup: 9,
			embed3: embed3{
				Em3:    10,
				embed5: embed5{x: 11},
			},
			embed4: embed4{
				Em4:    12,
				Dup:    13,
				embed1: &embed1{Em1: 14},
			},
		},
		Anonymous: Anonymous(15),
	}
	bytes, err := json.Marshal(s1)
	if err != nil {
		t.Fatal(err)
	}
	var want S1
	if err := json.Unmarshal(bytes, &want); err != nil {
		t.Fatal(err)
	}

	var got S1
	got.embed2 = &embed2{} // need this because reflection won't create it
	fields := Fields(reflect.TypeOf(got), nil)
	setFields(fields, &got, s1)
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got\n%+v\nwant\n%+v", got, want)
	}
}

type S2 struct {
	NoTag      int
	XXX        int           `json:"tag"` // tag name takes precedence
	Anonymous  `json:"anon"` // anonymous non-structs also get their name from the tag
	unexported int           `json:"tag"`
	Embed      `json:"em"`   // embedded structs with tags become fields
	tEmbed1
	tEmbed2
}

type Embed struct {
	Em int
}

type tEmbed1 struct {
	Dup int
	X   int `json:"Dup2"`
}

type tEmbed2 struct {
	Y int `json:"Dup"`  // takes precedence over tEmbed1.Dup because it is tagged
	Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored
}

func jsonTagParser(t reflect.StructTag) string { return t.Get("json") }

func TestFieldsWithTags(t *testing.T) {
	got := Fields(reflect.TypeOf(S2{}), jsonTagParser)
	want := []Field{
		{Name: "Dup", NameFromTag: true, Index: []int{6, 0}, Type: intType},
		{Name: "NoTag", Index: []int{0}, Type: intType},
		{Name: "anon", NameFromTag: true, Index: []int{2}, Type: reflect.TypeOf(Anonymous(0))},
		{Name: "em", NameFromTag: true, Index: []int{4}, Type: reflect.TypeOf(Embed{})},
		{Name: "tag", NameFromTag: true, Index: []int{1}, Type: intType},
	}
	if len(got) != len(want) {
		t.Fatalf("got %d fields, want %d", len(got), len(want))
	}
	for i, g := range got {
		w := want[i]
		if !reflect.DeepEqual(g, w) {
			t.Errorf("got %+v, want %+v", g, w)
		}
	}
}

func TestAgainstJSONEncodingWithTags(t *testing.T) {
	// Demonstrates that this package produces the same set of fields as encoding/json.
	s2 := S2{
		NoTag:     1,
		XXX:       2,
		Anonymous: 3,
		Embed: Embed{
			Em: 4,
		},
		tEmbed1: tEmbed1{
			Dup: 5,
			X:   6,
		},
		tEmbed2: tEmbed2{
			Y: 7,
			Z: 8,
		},
	}
	bytes, err := json.Marshal(s2)
	if err != nil {
		t.Fatal(err)
	}
	var want S2
	if err := json.Unmarshal(bytes, &want); err != nil {
		t.Fatal(err)
	}

	var got S2
	fields := Fields(reflect.TypeOf(got), jsonTagParser)
	setFields(fields, &got, s2)
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got\n%+v\nwant\n%+v", got, want)
	}
}

// Set the fields of dst from those of src.
// dst must be a pointer to a struct value.
// src must be a struct value.
func setFields(fields []Field, dst, src interface{}) {
	vsrc := reflect.ValueOf(src)
	vdst := reflect.ValueOf(dst).Elem()
	for _, f := range fields {
		fdst := vdst.FieldByIndex(f.Index)
		fsrc := vsrc.FieldByIndex(f.Index)
		fdst.Set(fsrc)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package integration

import (
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"time"

	"gopkg.in/check.v1"
)

var (
	T            = NewCommand("tsuru").WithArgs
	allPlatforms = []string{
		"tsuru/python",
		"tsuru/go",
		"tsuru/buildpack",
		"tsuru/cordova",
		"tsuru/elixir",
		"tsuru/java",
		"tsuru/nodejs",
		"tsuru/php",
		"tsuru/play",
		"tsuru/pypy",
		"tsuru/python3",
		"tsuru/ruby",
		"tsuru/static",
	}
	allProvisioners = []string{
		"docker",
		"swarm",
	}
	flows = []ExecFlow{
		platformsToInstall(),
		installerConfigTest(),
		installerTest(),
		targetTest(),
		loginTest(),
		removeInstallNodes(),
		quotaTest(),
		teamTest(),
		poolAdd(),
		nodeRemove(),
		platformAdd(),
		exampleApps(),
	}
)

var installerConfig = `driver:
  name: virtualbox
  options:
    virtualbox-cpu-count: 2
    virtualbox-memory: 2048
hosts:
  apps:
    size: 2
components:
  tsuru:
    version: latest
    install-dashboard: false
`

func platformsToInstall() ExecFlow {
	flow := ExecFlow{
		provides: []string{"platformimages"},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		for _, platImg := range allPlatforms {
			res.Env.Add("platformimages", platImg)
		}
	})
	return flow
}

func installerConfigTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"installerconfig"},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		f, err := ioutil.TempFile("", "installer-config")
		c.Assert(err, check.IsNil)
		defer f.Close()
		f.Write([]byte(installerConfig))
		res.Env.Set("installerconfig", f.Name())
	})
	flow.AddRollback(NewCommand("rm", "{{.installerconfig}}"))
	return flow
}

func installerTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"targetaddr"},
	}
	flow.Add(T("install", "--config", "{{.installerconfig}}").WithTimeout(15 * time.Minute))
	flow.AddRollback(T("uninstall", "-y"))
	flow.AddHook(func(c *check.C, res *Result) {
		regex := regexp.MustCompile(`(?si).*Core Hosts:.*?([\d.]+)\s.*`)
		parts := regex.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		targetHost := parts[1]
		regex = regexp.MustCompile(`(?si).*Tsuru API.*?\|\s(\d+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		targetPort := parts[1]
		res.Env.Set("targetaddr", fmt.Sprintf("http://%s:%s", targetHost, targetPort))
		regex = regexp.MustCompile(`\| (https?[^\s]+?) \|`)
		allParts := regex.FindAllStringSubmatch(res.Stdout.String(), -1)
		for _, parts = range allParts {
			c.Assert(parts, check.HasLen, 2)
			res.Env.Add("nodeopts", fmt.Sprintf("--register address=%s --cacert ~/.tsuru/installs/tsuru/certs/ca.pem --clientcert ~/.tsuru/installs/tsuru/certs/cert.pem --clientkey ~/.tsuru/installs/tsuru/certs/key.pem", parts[1]))
			res.Env.Add("nodestoremove", parts[1])
		}
		regex = regexp.MustCompile(`Username: ([[:print:]]+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		res.Env.Set("adminuser", parts[1])
		regex = regexp.MustCompile(`Password: ([[:print:]]+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		res.Env.Set("adminpassword", parts[1])
	})
	return flow
}

func targetTest() ExecFlow {
	flow := ExecFlow{}
	targetName := "integration-target"
	flow.Add(T("target-add", targetName, "{{.targetaddr}}"))
	flow.Add(T("target-list"), Expected{Stdout: `\s+` + targetName + ` .*`})
	flow.Add(T("target-set", targetName))
	return flow
}

func loginTest() ExecFlow {
	flow := ExecFlow{}
	flow.Add(T("login", "{{.adminuser}}").WithInput("{{.adminpassword}}"))
	return flow
}

func removeInstallNodes() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"node": "nodestoremove",
		},
	}
	flow.Add(T("node-remove", "-y", "--no-rebalance", "{{.node}}"))
	return flow
}

func quotaTest() ExecFlow {
	flow := ExecFlow{}
	flow.Add(T("user-quota-change", "{{.adminuser}}", "100"))
	flow.Add(T("user-quota-view", "{{.adminuser}}"), Expected{Stdout: `(?s)Apps usage.*/100`})
	return flow
}

func teamTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"team"},
	}
	teamName := "integration-team"
	flow.Add(T("team-create", teamName))
	flow.AddHook(func(c *check.C, res *Result) {
		res.Env.Set("team", teamName)
	})
	flow.AddRollback(T("team-remove", "-y", teamName))
	return flow
}

func poolAdd() ExecFlow {
	flow := ExecFlow{
		provides: []string{"poolnames"},
	}
	for _, prov := range allProvisioners {
		poolName := "ipool-" + prov
		flow.Add(T("pool-add", "--provisioner", prov, poolName))
		flow.AddHook(func(c *check.C, res *Result) {
			res.Env.Add("poolnames", poolName)
		})
		flow.AddRollback(T("pool-remove", "-y", poolName))
		flow.Add(T("pool-teams-add", poolName, "{{.team}}"))
		flow.AddRollback(T("pool-teams-remove", poolName, "{{.team}}"))
		flow.Add(T("node-add", "{{.nodeopts}}", "pool="+poolName))
		flow.Add(T("event-list"))
		flow.AddHook(func(c *check.C, res *Result) {
			nodeopts := res.Env.All("nodeopts")
			res.Env.Set("nodeopts", append(nodeopts[1:], nodeopts[0])...)
			regex := regexp.MustCompile(`node.create.*?node:\s+(.*?)\s+`)
			parts := regex.FindStringSubmatch(res.Stdout.String())
			c.Assert(parts, check.HasLen, 2)
			res.Env.Add("nodeaddrs", parts[1])
			regex = regexp.MustCompile(parts[1] + `.*?ready`)
			ok := retry(time.Minute, func() bool {
				res = T("node-list").Run(res.Env)
				return regex.MatchString(res.Stdout.String())
			})
			c.Assert(ok, check.Equals, true, check.Commentf("node not ready after 1 minute: %v", res))
		})
	}
	return flow
}

func nodeRemove() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"node": "nodeaddrs",
		},
	}
	flow.AddRollback(T("node-remove", "-y", "--no-rebalance", "{{.node}}"))
	return flow
}

func platformAdd() ExecFlow {
	flow := ExecFlow{
		provides: []string{"platforms"},
		matrix: map[string]string{
			"platimg": "platformimages",
		},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		img := res.Env.Get("platimg")
		res.Env.Set("platimgsuffix", img[strings.LastIndex(img, "/")+1:])
	})
	flow.Add(T("platform-add", "iplat-{{.platimgsuffix}}", "-i", "{{.platimg}}"))
	flow.AddHook(func(c *check.C, res *Result) {
		res.Env.Add("platforms", "iplat-"+res.Env.Get("platimgsuffix"))
	})
	flow.AddRollback(T("platform-remove", "-y", "iplat-{{.platimgsuffix}}"))
	flow.Add(T("platform-list"), Expected{Stdout: "(?s).*- iplat-{{.platimgsuffix}}.*"})
	return flow
}

func exampleApps() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"pool": "poolnames",
			"plat": "platforms",
		},
	}
	appName := "iapp-{{.plat}}-{{.pool}}"
	flow.Add(T("app-create", appName, "{{.plat}}", "-t", "{{.team}}", "-o", "{{.pool}}"))
	flow.AddRollback(T("app-remove", "-y", "-a", appName))
	flow.Add(T("app-info", "-a", appName))
	flow.AddHook(func(c *check.C, res *Result) {
		platRE := regexp.MustCompile(`(?s)Platform: (.*?)\n`)
		parts := platRE.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		res.Env.Set("language", strings.Replace(parts[1], "iplat-", "", -1))
	})
	flow.Add(T("app-deploy", "-a", appName, "{{.examplesdir}}/{{.language}}"))
	flow.Add(T("app-info", "-a", appName))
	flow.AddHook(func(c *check.C, res *Result) {
		addrRE := regexp.MustCompile(`(?s)Address: (.*?)\n`)
		parts := addrRE.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		res.Env.Set("appaddr", parts[1])
	})
	flow.AddHook(func(c *check.C, res *Result) {
		cmd := NewCommand("curl", "-sSf", "http://{{.appaddr}}")
		ok := retry(time.Minute, func() bool {
			res = cmd.Run(res.Env)
			return res.ExitCode == 0
		})
		c.Assert(ok, check.Equals, true, check.Commentf("invalid result: %v", res))
	})
	return flow
}

func (s *S) TestBase(c *check.C) {
	env := NewEnvironment()
	if !env.Has("enabled") {
		return
	}
	var executedFlows []*ExecFlow
	defer func() {
		for i := len(executedFlows) - 1; i >= 0; i-- {
			executedFlows[i].Rollback(c, env)
		}
	}()
	for i := range flows {
		f := &flows[i]
		if len(f.provides) > 0 {
			providesAll := true
			for _, envVar := range f.provides {
				if env.Get(envVar) == "" {
					providesAll = false
					break
				}
			}
			if providesAll {
				continue
			}
		}
		executedFlows = append(executedFlows, f)
		f.Run(c, env)
	}
}
<commit_msg>integration: even longer timeout for initial install<commit_after>// Copyright 2017 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package integration

import (
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"time"

	"gopkg.in/check.v1"
)

var (
	T            = NewCommand("tsuru").WithArgs
	allPlatforms = []string{
		"tsuru/python",
		"tsuru/go",
		"tsuru/buildpack",
		"tsuru/cordova",
		"tsuru/elixir",
		"tsuru/java",
		"tsuru/nodejs",
		"tsuru/php",
		"tsuru/play",
		"tsuru/pypy",
		"tsuru/python3",
		"tsuru/ruby",
		"tsuru/static",
	}
	allProvisioners = []string{
		"docker",
		"swarm",
	}
	flows = []ExecFlow{
		platformsToInstall(),
		installerConfigTest(),
		installerTest(),
		targetTest(),
		loginTest(),
		removeInstallNodes(),
		quotaTest(),
		teamTest(),
		poolAdd(),
		nodeRemove(),
		platformAdd(),
		exampleApps(),
	}
)

var installerConfig = `driver:
  name: virtualbox
  options:
    virtualbox-cpu-count: 2
    virtualbox-memory: 2048
hosts:
  apps:
    size: 2
components:
  tsuru:
    version: latest
    install-dashboard: false
`

func platformsToInstall() ExecFlow {
	flow := ExecFlow{
		provides: []string{"platformimages"},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		for _, platImg := range allPlatforms {
			res.Env.Add("platformimages", platImg)
		}
	})
	return flow
}

func installerConfigTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"installerconfig"},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		f, err := ioutil.TempFile("", "installer-config")
		c.Assert(err, check.IsNil)
		defer f.Close()
		f.Write([]byte(installerConfig))
		res.Env.Set("installerconfig", f.Name())
	})
	flow.AddRollback(NewCommand("rm", "{{.installerconfig}}"))
	return flow
}

func installerTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"targetaddr"},
	}
	flow.Add(T("install", "--config", "{{.installerconfig}}").WithTimeout(30 * time.Minute))
	flow.AddRollback(T("uninstall", "-y"))
	flow.AddHook(func(c *check.C, res *Result) {
		regex := regexp.MustCompile(`(?si).*Core Hosts:.*?([\d.]+)\s.*`)
		parts := regex.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		targetHost := parts[1]
		regex = regexp.MustCompile(`(?si).*Tsuru API.*?\|\s(\d+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		targetPort := parts[1]
		res.Env.Set("targetaddr", fmt.Sprintf("http://%s:%s", targetHost, targetPort))
		regex = regexp.MustCompile(`\| (https?[^\s]+?) \|`)
		allParts := regex.FindAllStringSubmatch(res.Stdout.String(), -1)
		for _, parts = range allParts {
			c.Assert(parts, check.HasLen, 2)
			res.Env.Add("nodeopts", fmt.Sprintf("--register address=%s --cacert ~/.tsuru/installs/tsuru/certs/ca.pem --clientcert ~/.tsuru/installs/tsuru/certs/cert.pem --clientkey ~/.tsuru/installs/tsuru/certs/key.pem", parts[1]))
			res.Env.Add("nodestoremove", parts[1])
		}
		regex = regexp.MustCompile(`Username: ([[:print:]]+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		res.Env.Set("adminuser", parts[1])
		regex = regexp.MustCompile(`Password: ([[:print:]]+)`)
		parts = regex.FindStringSubmatch(res.Stdout.String())
		res.Env.Set("adminpassword", parts[1])
	})
	return flow
}

func targetTest() ExecFlow {
	flow := ExecFlow{}
	targetName := "integration-target"
	flow.Add(T("target-add", targetName, "{{.targetaddr}}"))
	flow.Add(T("target-list"), Expected{Stdout: `\s+` + targetName + ` .*`})
	flow.Add(T("target-set", targetName))
	return flow
}

func loginTest() ExecFlow {
	flow := ExecFlow{}
	flow.Add(T("login", "{{.adminuser}}").WithInput("{{.adminpassword}}"))
	return flow
}

func removeInstallNodes() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"node": "nodestoremove",
		},
	}
	flow.Add(T("node-remove", "-y", "--no-rebalance", "{{.node}}"))
	return flow
}

func quotaTest() ExecFlow {
	flow := ExecFlow{}
	flow.Add(T("user-quota-change", "{{.adminuser}}", "100"))
	flow.Add(T("user-quota-view", "{{.adminuser}}"), Expected{Stdout: `(?s)Apps usage.*/100`})
	return flow
}

func teamTest() ExecFlow {
	flow := ExecFlow{
		provides: []string{"team"},
	}
	teamName := "integration-team"
	flow.Add(T("team-create", teamName))
	flow.AddHook(func(c *check.C, res *Result) {
		res.Env.Set("team", teamName)
	})
	flow.AddRollback(T("team-remove", "-y", teamName))
	return flow
}

func poolAdd() ExecFlow {
	flow := ExecFlow{
		provides: []string{"poolnames"},
	}
	for _, prov := range allProvisioners {
		poolName := "ipool-" + prov
		flow.Add(T("pool-add", "--provisioner", prov, poolName))
		flow.AddHook(func(c *check.C, res *Result) {
			res.Env.Add("poolnames", poolName)
		})
		flow.AddRollback(T("pool-remove", "-y", poolName))
		flow.Add(T("pool-teams-add", poolName, "{{.team}}"))
		flow.AddRollback(T("pool-teams-remove", poolName, "{{.team}}"))
		flow.Add(T("node-add", "{{.nodeopts}}", "pool="+poolName))
		flow.Add(T("event-list"))
		flow.AddHook(func(c *check.C, res *Result) {
			nodeopts := res.Env.All("nodeopts")
			res.Env.Set("nodeopts", append(nodeopts[1:], nodeopts[0])...)
			regex := regexp.MustCompile(`node.create.*?node:\s+(.*?)\s+`)
			parts := regex.FindStringSubmatch(res.Stdout.String())
			c.Assert(parts, check.HasLen, 2)
			res.Env.Add("nodeaddrs", parts[1])
			regex = regexp.MustCompile(parts[1] + `.*?ready`)
			ok := retry(time.Minute, func() bool {
				res = T("node-list").Run(res.Env)
				return regex.MatchString(res.Stdout.String())
			})
			c.Assert(ok, check.Equals, true, check.Commentf("node not ready after 1 minute: %v", res))
		})
	}
	return flow
}

func nodeRemove() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"node": "nodeaddrs",
		},
	}
	flow.AddRollback(T("node-remove", "-y", "--no-rebalance", "{{.node}}"))
	return flow
}

func platformAdd() ExecFlow {
	flow := ExecFlow{
		provides: []string{"platforms"},
		matrix: map[string]string{
			"platimg": "platformimages",
		},
	}
	flow.AddHook(func(c *check.C, res *Result) {
		img := res.Env.Get("platimg")
		res.Env.Set("platimgsuffix", img[strings.LastIndex(img, "/")+1:])
	})
	flow.Add(T("platform-add", "iplat-{{.platimgsuffix}}", "-i", "{{.platimg}}"))
	flow.AddHook(func(c *check.C, res *Result) {
		res.Env.Add("platforms", "iplat-"+res.Env.Get("platimgsuffix"))
	})
	flow.AddRollback(T("platform-remove", "-y", "iplat-{{.platimgsuffix}}"))
	flow.Add(T("platform-list"), Expected{Stdout: "(?s).*- iplat-{{.platimgsuffix}}.*"})
	return flow
}

func exampleApps() ExecFlow {
	flow := ExecFlow{
		matrix: map[string]string{
			"pool": "poolnames",
			"plat": "platforms",
		},
	}
	appName := "iapp-{{.plat}}-{{.pool}}"
	flow.Add(T("app-create", appName, "{{.plat}}", "-t", "{{.team}}", "-o", "{{.pool}}"))
	flow.AddRollback(T("app-remove", "-y", "-a", appName))
	flow.Add(T("app-info", "-a", appName))
	flow.AddHook(func(c *check.C, res *Result) {
		platRE := regexp.MustCompile(`(?s)Platform: (.*?)\n`)
		parts := platRE.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		res.Env.Set("language", strings.Replace(parts[1], "iplat-", "", -1))
	})
	flow.Add(T("app-deploy", "-a", appName, "{{.examplesdir}}/{{.language}}"))
	flow.Add(T("app-info", "-a", appName))
	flow.AddHook(func(c *check.C, res *Result) {
		addrRE := regexp.MustCompile(`(?s)Address: (.*?)\n`)
		parts := addrRE.FindStringSubmatch(res.Stdout.String())
		c.Assert(parts, check.HasLen, 2)
		res.Env.Set("appaddr", parts[1])
	})
	flow.AddHook(func(c *check.C, res *Result) {
		cmd := NewCommand("curl", "-sSf", "http://{{.appaddr}}")
		ok := retry(time.Minute, func() bool {
			res = cmd.Run(res.Env)
			return res.ExitCode == 0
		})
		c.Assert(ok, check.Equals, true, check.Commentf("invalid result: %v", res))
	})
	return flow
}

func (s *S) TestBase(c *check.C) {
	env := NewEnvironment()
	if !env.Has("enabled") {
		return
	}
	var executedFlows []*ExecFlow
	defer func() {
		for i := len(executedFlows) - 1; i >= 0; i-- {
			executedFlows[i].Rollback(c, env)
		}
	}()
	for i := range flows {
		f := &flows[i]
		if len(f.provides) > 0 {
			providesAll := true
			for _, envVar := range f.provides {
				if env.Get(envVar) == "" {
					providesAll = false
					break
				}
			}
			if providesAll {
				continue
			}
		}
		executedFlows = append(executedFlows, f)
		f.Run(c, env)
	}
}
<|endoftext|>