<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package main

import (
	"fmt"
	"os"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"gopkg.in/juju/charm.v3"
	"launchpad.net/gnuflag"

	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/constraints"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/bootstrap"
	"github.com/juju/juju/environs/imagemetadata"
	"github.com/juju/juju/environs/tools"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/provider"
)

const bootstrapDoc = `
bootstrap starts a new environment of the current type (it will return an error
if the environment has already been bootstrapped).  Bootstrapping an environment
will provision a new machine in the environment and run the juju state server on
that machine.

If constraints are specified in the bootstrap command, they will apply to the
machine provisioned for the juju state server.  They will also be set as default
constraints on the environment for all future machines, exactly as if the
constraints were set with juju set-constraints.

Bootstrap initializes the cloud environment synchronously and displays information
about the current installation steps.  The time for bootstrap to complete varies
across cloud providers from a few seconds to several minutes.  Once bootstrap has
completed, you can run other juju commands against your environment. You can change
the default timeout and retry delays used during the bootstrap by changing the
following settings in your environments.yaml (all values represent number of seconds):

    # How long to wait for a connection to the state server.
    bootstrap-timeout: 600 # default: 10 minutes
    # How long to wait between connection attempts to a state server address.
    bootstrap-retry-delay: 5 # default: 5 seconds
    # How often to refresh state server addresses from the API server.
    bootstrap-addresses-delay: 10 # default: 10 seconds

Private clouds may need to specify their own custom image metadata, and possibly upload
Juju tools to cloud storage if no outgoing Internet access is available. In this case,
use the --metadata-source paramater to tell bootstrap a local directory from which to
upload tools and/or image metadata.

See Also:
   juju help switch
   juju help constraints
   juju help set-constraints
`

// BootstrapCommand is responsible for launching the first machine in a juju
// environment, and setting up everything necessary to continue working.
type BootstrapCommand struct {
	envcmd.EnvCommandBase
	Constraints           constraints.Value
	UploadTools           bool
	Series                []string
	seriesOld             []string
	MetadataSource        string
	Placement             string
	KeepBrokenEnvironment bool
}

func (c *BootstrapCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "bootstrap",
		Purpose: "start up an environment from scratch",
		Doc:     bootstrapDoc,
	}
}

func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
	f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set environment constraints")
	f.BoolVar(&c.UploadTools, "upload-tools", false, "upload local version of tools before bootstrapping")
	f.Var(newSeriesValue(nil, &c.Series), "upload-series", "upload tools for supplied comma-separated series list (OBSOLETE)")
	f.Var(newSeriesValue(nil, &c.seriesOld), "series", "see --upload-series (OBSOLETE)")
	f.StringVar(&c.MetadataSource, "metadata-source", "", "local path to use as tools and/or metadata source")
	f.StringVar(&c.Placement, "to", "", "a placement directive indicating an instance to bootstrap")
	f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "do not destory the environment if bootstrap fails")
}

func (c *BootstrapCommand) Init(args []string) (err error) {
	if len(c.Series) > 0 && !c.UploadTools {
		return fmt.Errorf("--upload-series requires --upload-tools")
	}
	if len(c.seriesOld) > 0 && !c.UploadTools {
		return fmt.Errorf("--series requires --upload-tools")
	}
	if len(c.Series) > 0 && len(c.seriesOld) > 0 {
		return fmt.Errorf("--upload-series and --series can't be used together")
	}

	// Parse the placement directive. Bootstrap currently only
	// supports provider-specific placement directives.
	if c.Placement != "" {
		_, err = instance.ParsePlacement(c.Placement)
		if err != instance.ErrPlacementScopeMissing {
			// We only support unscoped placement directives for bootstrap.
			return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement)
		}
	}
	return cmd.CheckEmpty(args)
}

type seriesValue struct {
	*cmd.StringsValue
}

// newSeriesValue is used to create the type passed into the gnuflag.FlagSet Var function.
func newSeriesValue(defaultValue []string, target *[]string) *seriesValue {
	v := seriesValue{(*cmd.StringsValue)(target)}
	*(v.StringsValue) = defaultValue
	return &v
}

// Implements gnuflag.Value Set.
func (v *seriesValue) Set(s string) error {
	if err := v.StringsValue.Set(s); err != nil {
		return err
	}
	for _, name := range *(v.StringsValue) {
		if !charm.IsValidSeries(name) {
			v.StringsValue = nil
			return fmt.Errorf("invalid series name %q", name)
		}
	}
	return nil
}

// bootstrap functionality that Run calls to support cleaner testing
type BootstrapInterface interface {
	EnsureNotBootstrapped(env environs.Environ) error
	Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error
}

type bootstrapFuncs struct{}

func (b bootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error {
	return bootstrap.EnsureNotBootstrapped(env)
}

func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
	return bootstrap.Bootstrap(ctx, env, args)
}

var getBootstrapFuncs = func() BootstrapInterface {
	return &bootstrapFuncs{}
}

// Run connects to the environment specified on the command line and bootstraps
// a juju in that environment if none already exists. If there is as yet no environments.yaml file,
// the user is informed how to create one.
func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
	bootstrapFuncs := getBootstrapFuncs()

	if len(c.seriesOld) > 0 {
		fmt.Fprintln(ctx.Stderr, "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.")
	}
	if len(c.Series) > 0 {
		fmt.Fprintln(ctx.Stderr, "Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.")
	}

	if c.ConnectionName() == "" {
		return fmt.Errorf("the name of the environment must be specified")
	}

	environ, cleanup, err := environFromName(ctx, c.ConnectionName(), "Bootstrap")
	if err != nil {
		return errors.Annotatef(err, "there was an issue examining the environment")
	}

	// If we error out for any reason, clean up the environment.
	defer func() {
		if resultErr != nil {
			if c.KeepBrokenEnvironment {
				logger.Warningf("bootstrap failed but --keep-broken was specified so environment is not being destroyed.\n"+
					"When you are finished diagnosing the problem, remember to run juju destroy-environment --force\n") +
					"to clean up the environment."
			} else {
				cleanup()
			}
		}
	}()

	// We want to validate constraints early. However, if a custom image metadata
	// source is specified, we can't validate the arch because that depends on what
	// images metadata is to be uploaded. So we validate here if no custom metadata
	// source is specified, and defer till later if not.
	if c.MetadataSource == "" {
		if err := validateConstraints(c.Constraints, environ); err != nil {
			return err
		}
	}

	// Check to see if this environment is already bootstrapped. If it
	// is, we inform the user and exit early. If an error is returned
	// but it is not that the environment is already bootstrapped,
	// then we're in an unknown state.
	if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err {
		if environs.ErrAlreadyBootstrapped == err {
			logger.Warningf("This juju environment is already bootstrapped. If you want to start a new Juju\nenvironment, first run juju destroy-environment to clean up, or switch to an\nalternative environment.")
			return err
		}
		return errors.Annotatef(err, "cannot determine if environment is already bootstrapped.")
	}

	// Block interruption during bootstrap. Providers may also
	// register for interrupt notification so they can exit early.
	interrupted := make(chan os.Signal, 1)
	defer close(interrupted)
	ctx.InterruptNotify(interrupted)
	defer ctx.StopInterruptNotify(interrupted)
	go func() {
		for _ = range interrupted {
			ctx.Infof("Interrupt signalled: waiting for bootstrap to exit")
		}
	}()

	// If --metadata-source is specified, override the default tools metadata source so
	// SyncTools can use it, and also upload any image metadata.
	if c.MetadataSource != "" {
		metadataDir := ctx.AbsPath(c.MetadataSource)
		if err := uploadCustomMetadata(metadataDir, environ); err != nil {
			return err
		}
		if err := validateConstraints(c.Constraints, environ); err != nil {
			return err
		}
	}
	// TODO (wallyworld): 2013-09-20 bug 1227931
	// We can set a custom tools data source instead of doing an
	// unnecessary upload.
	if environ.Config().Type() == provider.Local {
		c.UploadTools = true
	}

	err = bootstrapFuncs.Bootstrap(ctx, environ, bootstrap.BootstrapParams{
		Constraints: c.Constraints,
		Placement:   c.Placement,
		UploadTools: c.UploadTools,
	})
	if err != nil {
		return errors.Annotate(err, "failed to bootstrap environment")
	}
	return c.SetBootstrapEndpointAddress(environ)
}

var allInstances = func(environ environs.Environ) ([]instance.Instance, error) {
	return environ.AllInstances()
}

// SetBootstrapEndpointAddress writes the API endpoint address of the
// bootstrap server into the connection information. This should only be run
// once directly after Bootstrap. It assumes that there is just one instance
// in the environment - the bootstrap instance.
func (c *BootstrapCommand) SetBootstrapEndpointAddress(environ environs.Environ) error {
	instances, err := allInstances(environ)
	if err != nil {
		return errors.Trace(err)
	}
	length := len(instances)
	if length == 0 {
		return errors.Errorf("found no instances, expected at least one")
	}
	if length > 1 {
		logger.Warningf("expected one instance, got %d", length)
	}
	bootstrapInstance := instances[0]
	cfg := environ.Config()
	info, err := envcmd.ConnectionInfoForName(c.ConnectionName())
	if err != nil {
		return errors.Annotate(err, "failed to get connection info")
	}

	// Don't use c.ConnectionEndpoint as it attempts to contact the state
	// server if no addresses are found in connection info.
	endpoint := info.APIEndpoint()
	netAddrs, err := bootstrapInstance.Addresses()
	apiPort := cfg.APIPort()
	apiAddrs := make([]string, len(netAddrs))
	for i, hp := range network.AddressesWithPort(netAddrs, apiPort) {
		apiAddrs[i] = hp.NetAddr()
	}
	endpoint.Addresses = apiAddrs
	writer, err := c.ConnectionWriter()
	if err != nil {
		return errors.Annotate(err, "failed to get connection writer")
	}
	writer.SetAPIEndpoint(endpoint)
	err = writer.Write()
	if err != nil {
		return errors.Annotate(err, "failed to write API endpoint to connection info")
	}
	return nil
}

var uploadCustomMetadata = func(metadataDir string, env environs.Environ) error {
	logger.Infof("Setting default tools and image metadata sources: %s", metadataDir)
	tools.DefaultBaseURL = metadataDir
	if err := imagemetadata.UploadImageMetadata(env.Storage(), metadataDir); err != nil {
		// Do not error if image metadata directory doesn't exist.
		if !os.IsNotExist(err) {
			return fmt.Errorf("uploading image metadata: %v", err)
		}
	} else {
		logger.Infof("custom image metadata uploaded")
	}
	return nil
}

var validateConstraints = func(cons constraints.Value, env environs.Environ) error {
	validator, err := env.ConstraintsValidator()
	if err != nil {
		return err
	}
	unsupported, err := validator.Validate(cons)
	if len(unsupported) > 0 {
		logger.Warningf("unsupported constraints: %v", err)
	} else if err != nil {
		return err
	}
	return nil
}
<commit_msg>Better warning message<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package main

import (
	"fmt"
	"os"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"gopkg.in/juju/charm.v3"
	"launchpad.net/gnuflag"

	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/constraints"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/bootstrap"
	"github.com/juju/juju/environs/imagemetadata"
	"github.com/juju/juju/environs/tools"
	"github.com/juju/juju/instance"
	"github.com/juju/juju/network"
	"github.com/juju/juju/provider"
)

const bootstrapDoc = `
bootstrap starts a new environment of the current type (it will return an error
if the environment has already been bootstrapped).  Bootstrapping an environment
will provision a new machine in the environment and run the juju state server on
that machine.

If constraints are specified in the bootstrap command, they will apply to the
machine provisioned for the juju state server.  They will also be set as default
constraints on the environment for all future machines, exactly as if the
constraints were set with juju set-constraints.

Bootstrap initializes the cloud environment synchronously and displays information
about the current installation steps.  The time for bootstrap to complete varies
across cloud providers from a few seconds to several minutes.  Once bootstrap has
completed, you can run other juju commands against your environment. You can change
the default timeout and retry delays used during the bootstrap by changing the
following settings in your environments.yaml (all values represent number of seconds):

    # How long to wait for a connection to the state server.
    bootstrap-timeout: 600 # default: 10 minutes
    # How long to wait between connection attempts to a state server address.
    bootstrap-retry-delay: 5 # default: 5 seconds
    # How often to refresh state server addresses from the API server.
    bootstrap-addresses-delay: 10 # default: 10 seconds

Private clouds may need to specify their own custom image metadata, and possibly upload
Juju tools to cloud storage if no outgoing Internet access is available. In this case,
use the --metadata-source paramater to tell bootstrap a local directory from which to
upload tools and/or image metadata.

See Also:
   juju help switch
   juju help constraints
   juju help set-constraints
`

// BootstrapCommand is responsible for launching the first machine in a juju
// environment, and setting up everything necessary to continue working.
type BootstrapCommand struct {
	envcmd.EnvCommandBase
	Constraints           constraints.Value
	UploadTools           bool
	Series                []string
	seriesOld             []string
	MetadataSource        string
	Placement             string
	KeepBrokenEnvironment bool
}

func (c *BootstrapCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "bootstrap",
		Purpose: "start up an environment from scratch",
		Doc:     bootstrapDoc,
	}
}

func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
	f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "set environment constraints")
	f.BoolVar(&c.UploadTools, "upload-tools", false, "upload local version of tools before bootstrapping")
	f.Var(newSeriesValue(nil, &c.Series), "upload-series", "upload tools for supplied comma-separated series list (OBSOLETE)")
	f.Var(newSeriesValue(nil, &c.seriesOld), "series", "see --upload-series (OBSOLETE)")
	f.StringVar(&c.MetadataSource, "metadata-source", "", "local path to use as tools and/or metadata source")
	f.StringVar(&c.Placement, "to", "", "a placement directive indicating an instance to bootstrap")
	f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "do not destory the environment if bootstrap fails")
}

func (c *BootstrapCommand) Init(args []string) (err error) {
	if len(c.Series) > 0 && !c.UploadTools {
		return fmt.Errorf("--upload-series requires --upload-tools")
	}
	if len(c.seriesOld) > 0 && !c.UploadTools {
		return fmt.Errorf("--series requires --upload-tools")
	}
	if len(c.Series) > 0 && len(c.seriesOld) > 0 {
		return fmt.Errorf("--upload-series and --series can't be used together")
	}

	// Parse the placement directive. Bootstrap currently only
	// supports provider-specific placement directives.
	if c.Placement != "" {
		_, err = instance.ParsePlacement(c.Placement)
		if err != instance.ErrPlacementScopeMissing {
			// We only support unscoped placement directives for bootstrap.
			return fmt.Errorf("unsupported bootstrap placement directive %q", c.Placement)
		}
	}
	return cmd.CheckEmpty(args)
}

type seriesValue struct {
	*cmd.StringsValue
}

// newSeriesValue is used to create the type passed into the gnuflag.FlagSet Var function.
func newSeriesValue(defaultValue []string, target *[]string) *seriesValue {
	v := seriesValue{(*cmd.StringsValue)(target)}
	*(v.StringsValue) = defaultValue
	return &v
}

// Implements gnuflag.Value Set.
func (v *seriesValue) Set(s string) error {
	if err := v.StringsValue.Set(s); err != nil {
		return err
	}
	for _, name := range *(v.StringsValue) {
		if !charm.IsValidSeries(name) {
			v.StringsValue = nil
			return fmt.Errorf("invalid series name %q", name)
		}
	}
	return nil
}

// bootstrap functionality that Run calls to support cleaner testing
type BootstrapInterface interface {
	EnsureNotBootstrapped(env environs.Environ) error
	Bootstrap(ctx environs.BootstrapContext, environ environs.Environ, args bootstrap.BootstrapParams) error
}

type bootstrapFuncs struct{}

func (b bootstrapFuncs) EnsureNotBootstrapped(env environs.Environ) error {
	return bootstrap.EnsureNotBootstrapped(env)
}

func (b bootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
	return bootstrap.Bootstrap(ctx, env, args)
}

var getBootstrapFuncs = func() BootstrapInterface {
	return &bootstrapFuncs{}
}

// Run connects to the environment specified on the command line and bootstraps
// a juju in that environment if none already exists. If there is as yet no environments.yaml file,
// the user is informed how to create one.
func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
	bootstrapFuncs := getBootstrapFuncs()

	if len(c.seriesOld) > 0 {
		fmt.Fprintln(ctx.Stderr, "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.")
	}
	if len(c.Series) > 0 {
		fmt.Fprintln(ctx.Stderr, "Use of --upload-series is obsolete. --upload-tools now expands to all supported series of the same operating system.")
	}

	if c.ConnectionName() == "" {
		return fmt.Errorf("the name of the environment must be specified")
	}

	environ, cleanup, err := environFromName(ctx, c.ConnectionName(), "Bootstrap")
	if err != nil {
		return errors.Annotatef(err, "there was an issue examining the environment")
	}

	// If we error out for any reason, clean up the environment.
	defer func() {
		if resultErr != nil {
			if c.KeepBrokenEnvironment {
				logger.Warningf("bootstrap failed but --keep-broken was specified so environment is not being destroyed.\n" +
					"When you are finished diagnosing the problem, remember to run juju destroy-environment --force\n" +
					"to clean up the environment.")
			} else {
				cleanup()
			}
		}
	}()

	// We want to validate constraints early. However, if a custom image metadata
	// source is specified, we can't validate the arch because that depends on what
	// images metadata is to be uploaded. So we validate here if no custom metadata
	// source is specified, and defer till later if not.
	if c.MetadataSource == "" {
		if err := validateConstraints(c.Constraints, environ); err != nil {
			return err
		}
	}

	// Check to see if this environment is already bootstrapped. If it
	// is, we inform the user and exit early. If an error is returned
	// but it is not that the environment is already bootstrapped,
	// then we're in an unknown state.
	if err := bootstrapFuncs.EnsureNotBootstrapped(environ); nil != err {
		if environs.ErrAlreadyBootstrapped == err {
			logger.Warningf("This juju environment is already bootstrapped. If you want to start a new Juju\nenvironment, first run juju destroy-environment to clean up, or switch to an\nalternative environment.")
			return err
		}
		return errors.Annotatef(err, "cannot determine if environment is already bootstrapped.")
	}

	// Block interruption during bootstrap. Providers may also
	// register for interrupt notification so they can exit early.
	interrupted := make(chan os.Signal, 1)
	defer close(interrupted)
	ctx.InterruptNotify(interrupted)
	defer ctx.StopInterruptNotify(interrupted)
	go func() {
		for _ = range interrupted {
			ctx.Infof("Interrupt signalled: waiting for bootstrap to exit")
		}
	}()

	// If --metadata-source is specified, override the default tools metadata source so
	// SyncTools can use it, and also upload any image metadata.
	if c.MetadataSource != "" {
		metadataDir := ctx.AbsPath(c.MetadataSource)
		if err := uploadCustomMetadata(metadataDir, environ); err != nil {
			return err
		}
		if err := validateConstraints(c.Constraints, environ); err != nil {
			return err
		}
	}
	// TODO (wallyworld): 2013-09-20 bug 1227931
	// We can set a custom tools data source instead of doing an
	// unnecessary upload.
	if environ.Config().Type() == provider.Local {
		c.UploadTools = true
	}

	err = bootstrapFuncs.Bootstrap(ctx, environ, bootstrap.BootstrapParams{
		Constraints: c.Constraints,
		Placement:   c.Placement,
		UploadTools: c.UploadTools,
	})
	if err != nil {
		return errors.Annotate(err, "failed to bootstrap environment")
	}
	return c.SetBootstrapEndpointAddress(environ)
}

var allInstances = func(environ environs.Environ) ([]instance.Instance, error) {
	return environ.AllInstances()
}

// SetBootstrapEndpointAddress writes the API endpoint address of the
// bootstrap server into the connection information. This should only be run
// once directly after Bootstrap. It assumes that there is just one instance
// in the environment - the bootstrap instance.
func (c *BootstrapCommand) SetBootstrapEndpointAddress(environ environs.Environ) error {
	instances, err := allInstances(environ)
	if err != nil {
		return errors.Trace(err)
	}
	length := len(instances)
	if length == 0 {
		return errors.Errorf("found no instances, expected at least one")
	}
	if length > 1 {
		logger.Warningf("expected one instance, got %d", length)
	}
	bootstrapInstance := instances[0]
	cfg := environ.Config()
	info, err := envcmd.ConnectionInfoForName(c.ConnectionName())
	if err != nil {
		return errors.Annotate(err, "failed to get connection info")
	}

	// Don't use c.ConnectionEndpoint as it attempts to contact the state
	// server if no addresses are found in connection info.
	endpoint := info.APIEndpoint()
	netAddrs, err := bootstrapInstance.Addresses()
	apiPort := cfg.APIPort()
	apiAddrs := make([]string, len(netAddrs))
	for i, hp := range network.AddressesWithPort(netAddrs, apiPort) {
		apiAddrs[i] = hp.NetAddr()
	}
	endpoint.Addresses = apiAddrs
	writer, err := c.ConnectionWriter()
	if err != nil {
		return errors.Annotate(err, "failed to get connection writer")
	}
	writer.SetAPIEndpoint(endpoint)
	err = writer.Write()
	if err != nil {
		return errors.Annotate(err, "failed to write API endpoint to connection info")
	}
	return nil
}

var uploadCustomMetadata = func(metadataDir string, env environs.Environ) error {
	logger.Infof("Setting default tools and image metadata sources: %s", metadataDir)
	tools.DefaultBaseURL = metadataDir
	if err := imagemetadata.UploadImageMetadata(env.Storage(), metadataDir); err != nil {
		// Do not error if image metadata directory doesn't exist.
		if !os.IsNotExist(err) {
			return fmt.Errorf("uploading image metadata: %v", err)
		}
	} else {
		logger.Infof("custom image metadata uploaded")
	}
	return nil
}

var validateConstraints = func(cons constraints.Value, env environs.Environ) error {
	validator, err := env.ConstraintsValidator()
	if err != nil {
		return err
	}
	unsupported, err := validator.Validate(cons)
	if len(unsupported) > 0 {
		logger.Warningf("unsupported constraints: %v", err)
	} else if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"gnd.la/admin"
	"gnd.la/gen/json"
	"gnd.la/mux"
)

func GenJson(ctx *mux.Context) {
	if err := json.Gen(".", nil); err != nil {
		panic(err)
	}
}

func init() {
	admin.Register(GenJson, &admin.Options{
		Help: "Generate JSONWriter methods for the exported types in the current directory",
	})
}
<commit_msg>Replace gen-json command with gen<commit_after>package main

import (
	"gnd.la/admin"
	"gnd.la/gen"
	"gnd.la/mux"
)

func Gen(ctx *mux.Context) {
	var genfile string
	ctx.ParseParamValue("genfile", &genfile)
	if err := gen.Gen(".", genfile); err != nil {
		panic(err)
	}
}

func init() {
	admin.Register(Gen, &admin.Options{
		Help: "Perform code generation in the current directory according the rules in the config file",
		Flags: admin.Flags(
			admin.StringFlag("genfile", "genfile.yaml", "Code generation configuration file"),
		),
	})
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/valyala/fasthttp"
	"github.com/valyala/httpteleport"
	"github.com/valyala/tcplisten"
	"log"
	"net"
	"os"
	"strings"
	"time"
)

var (
	reusePort = flag.Bool("reusePort", false, "Whether to enable SO_REUSEPORT on -in if -inType is http or httptp")

	in     = flag.String("in", ":8080", "-inType addresses to listen to for incoming requests")
	inType = flag.String("inType", "http", "Type of -in address. Possible values:\n"+
		"\thttp - listen for HTTP requests over TCP, e.g. -in=127.0.0.1:8080\n"+
		"\tunix - listen for HTTP requests over unix socket, e.g. -in=/var/httptp/sock.unix\n"+
		"\thttptp - listen for httptp connections over TCP, e.g. -in=127.0.0.1:8043")
	inDelay = flag.Duration("inDelay", 0, "How long to wait before sending batched responses back if -inType=httptp")

	out = flag.String("out", "127.0.0.1:8043", "Comma-separated list of -outType addresses to forward requests to.\n"+
		"Each request is forwarded to the least loaded address")
	outType = flag.String("outType", "httptp", "Type of -out address. Possible values:\n"+
		"\thttp - forward requests to HTTP servers on TCP, e.g. -out=127.0.0.1:80\n"+
		"\tunix - forward requests to HTTP servers on unix socket, e.g. -out=/var/nginx/sock.unix\n"+
		"\thttptp - forward requests to httptp servers over TCP, e.g. -out=127.0.0.1:8043")
	outDelay = flag.Duration("outDelay", 0, "How long to wait before forwarding incoming requests to -out if -outType=httptp")

	concurrency = flag.Int("concurrency", 100000, "The maximum number of concurrent requests httptp may process")
	timeout     = flag.Duration("timeout", 3*time.Second, "The maximum duration for waiting response from -out server")
)

func main() {
	flag.Parse()

	outs := strings.Split(*out, ",")

	switch *outType {
	case "http":
		initHTTPClients(outs)
	case "unix":
		initUnixClients(outs)
	case "httptp":
		initHTTPTPClients(outs)
	default:
		log.Fatalf("unknown -outType=%q. Supported values are: http, unix, httptp", *outType)
	}

	switch *inType {
	case "http":
		serveHTTP()
	case "unix":
		serveUnix()
	case "httptp":
		serveHTTPTP()
	default:
		log.Fatalf("unknown -inType=%q. Supported values are: http, unix and httptp", *inType)
	}
}

func initHTTPClients(outs []string) {
	connsPerAddr := *concurrency / len(outs)
	for _, addr := range outs {
		c := newHTTPClient(fasthttp.Dial, addr, connsPerAddr)
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to HTTP servers at %q", outs)
}

func initUnixClients(outs []string) {
	connsPerAddr := *concurrency / len(outs)
	for _, addr := range outs {
		verifyUnixAddr(addr)
		c := newHTTPClient(dialUnix, addr, connsPerAddr)
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to HTTP servers at unix:%q", outs)
}

func verifyUnixAddr(addr string) {
	fi, err := os.Stat(addr)
	if err != nil {
		log.Fatalf("error when accessing unix:%q: %s", addr, err)
	}
	mode := fi.Mode()
	if (mode & os.ModeSocket) == 0 {
		log.Fatalf("the %q must be unix socket", addr)
	}
}

func initHTTPTPClients(outs []string) {
	for _, addr := range outs {
		c := &httpteleport.Client{
			Addr:               addr,
			MaxBatchDelay:      *outDelay,
			MaxPendingRequests: *concurrency,
			ReadTimeout:        120 * time.Second,
			WriteTimeout:       5 * time.Second,
		}
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to httptp servers at %q", outs)
}

func newHTTPClient(dial fasthttp.DialFunc, addr string, connsPerAddr int) client {
	return &fasthttp.HostClient{
		Addr:         addr,
		Dial:         dial,
		MaxConns:     connsPerAddr,
		ReadTimeout:  *timeout * 5,
		WriteTimeout: *timeout,
	}
}

func dialUnix(addr string) (net.Conn, error) {
	return net.Dial("unix", addr)
}

func serveHTTP() {
	ln := newTCPListener()
	s := newHTTPServer()

	log.Printf("listening for HTTP requests on %q", *in)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func serveUnix() {
	addr := *in
	if _, err := os.Stat(addr); err == nil {
		verifyUnixAddr(addr)
		if err := os.Remove(addr); err != nil {
			log.Fatalf("cannot remove %q: %s", addr, err)
		}
	}

	ln, err := net.Listen("unix", addr)
	if err != nil {
		log.Fatalf("cannot listen to -in=%q: %s", addr, err)
	}
	s := newHTTPServer()

	log.Printf("listening for HTTP requests on unix:%q", addr)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func serveHTTPTP() {
	ln := newTCPListener()
	s := httpteleport.Server{
		Handler:           httptpRequestHandler,
		Concurrency:       *concurrency,
		MaxBatchDelay:     *inDelay,
		ReduceMemoryUsage: true,
		ReadTimeout:       120 * time.Second,
		WriteTimeout:      5 * time.Second,
	}

	log.Printf("listening for httptp connections on %q", *in)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func newTCPListener() net.Listener {
	cfg := tcplisten.Config{
		ReusePort: *reusePort,
	}
	ln, err := cfg.NewListener("tcp4", *in)
	if err != nil {
		log.Fatalf("cannot listen to -in=%q: %s", *in, err)
	}
	return ln
}

func newHTTPServer() *fasthttp.Server {
	return &fasthttp.Server{
		Handler:           httpRequestHandler,
		Concurrency:       *concurrency,
		ReduceMemoryUsage: true,
		ReadTimeout:       120 * time.Second,
		WriteTimeout:      5 * time.Second,
	}
}

type client interface {
	DoTimeout(req *fasthttp.Request, resp *fasthttp.Response, timeout time.Duration) error
	PendingRequests() int
}

var upstreamClients []client

func httpRequestHandler(ctx *fasthttp.RequestCtx) {
	var buf [16]byte
	ip := fasthttp.AppendIPv4(buf[:0], ctx.RemoteIP())
	ctx.Request.Header.SetBytesV("X-Forwarded-For", ip)

	c := leastLoadedClient()
	err := c.DoTimeout(&ctx.Request, &ctx.Response, *timeout)
	if err == nil {
		return
	}

	ctx.ResetBody()
	fmt.Fprintf(ctx, "HTTP proxying error: %s", err)
	if err == fasthttp.ErrTimeout {
		ctx.SetStatusCode(fasthttp.StatusGatewayTimeout)
	} else {
		ctx.SetStatusCode(fasthttp.StatusBadGateway)
	}
}

func httptpRequestHandler(ctx *fasthttp.RequestCtx) {
	c := leastLoadedClient()
	err := c.DoTimeout(&ctx.Request, &ctx.Response, *timeout)
	if err == nil {
		return
	}

	ctx.ResetBody()
	fmt.Fprintf(ctx, "httptp proxying error: %s", err)
	if err == httpteleport.ErrTimeout {
		ctx.SetStatusCode(fasthttp.StatusGatewayTimeout)
	} else {
		ctx.SetStatusCode(fasthttp.StatusBadGateway)
	}
}

func leastLoadedClient() client {
	minC := upstreamClients[0]
	minN := minC.PendingRequests()
	if minN == 0 {
		return minC
	}
	for _, c := range upstreamClients[1:] {
		n := c.PendingRequests()
		if n == 0 {
			return c
		}
		if n < minN {
			minC = c
			minN = n
		}
	}
	return minC
}
<commit_msg>httptp: reset 'Connection: close' request header in order to keep open keep-alive connections to HTTP servers<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/valyala/fasthttp"
	"github.com/valyala/httpteleport"
	"github.com/valyala/tcplisten"
	"log"
	"net"
	"os"
	"strings"
	"time"
)

var (
	reusePort = flag.Bool("reusePort", false, "Whether to enable SO_REUSEPORT on -in if -inType is http or httptp")

	in     = flag.String("in", ":8080", "-inType addresses to listen to for incoming requests")
	inType = flag.String("inType", "http", "Type of -in address. Possible values:\n"+
		"\thttp - listen for HTTP requests over TCP, e.g. -in=127.0.0.1:8080\n"+
		"\tunix - listen for HTTP requests over unix socket, e.g. -in=/var/httptp/sock.unix\n"+
		"\thttptp - listen for httptp connections over TCP, e.g. -in=127.0.0.1:8043")
	inDelay = flag.Duration("inDelay", 0, "How long to wait before sending batched responses back if -inType=httptp")

	out = flag.String("out", "127.0.0.1:8043", "Comma-separated list of -outType addresses to forward requests to.\n"+
		"Each request is forwarded to the least loaded address")
	outType = flag.String("outType", "httptp", "Type of -out address. Possible values:\n"+
		"\thttp - forward requests to HTTP servers on TCP, e.g. -out=127.0.0.1:80\n"+
		"\tunix - forward requests to HTTP servers on unix socket, e.g. -out=/var/nginx/sock.unix\n"+
		"\thttptp - forward requests to httptp servers over TCP, e.g. -out=127.0.0.1:8043")
	outDelay = flag.Duration("outDelay", 0, "How long to wait before forwarding incoming requests to -out if -outType=httptp")

	concurrency = flag.Int("concurrency", 100000, "The maximum number of concurrent requests httptp may process")
	timeout     = flag.Duration("timeout", 3*time.Second, "The maximum duration for waiting response from -out server")
)

func main() {
	flag.Parse()

	outs := strings.Split(*out, ",")

	switch *outType {
	case "http":
		initHTTPClients(outs)
	case "unix":
		initUnixClients(outs)
	case "httptp":
		initHTTPTPClients(outs)
	default:
		log.Fatalf("unknown -outType=%q. Supported values are: http, unix, httptp", *outType)
	}

	switch *inType {
	case "http":
		serveHTTP()
	case "unix":
		serveUnix()
	case "httptp":
		serveHTTPTP()
	default:
		log.Fatalf("unknown -inType=%q. Supported values are: http, unix and httptp", *inType)
	}
}

func initHTTPClients(outs []string) {
	connsPerAddr := *concurrency / len(outs)
	for _, addr := range outs {
		c := newHTTPClient(fasthttp.Dial, addr, connsPerAddr)
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to HTTP servers at %q", outs)
}

func initUnixClients(outs []string) {
	connsPerAddr := *concurrency / len(outs)
	for _, addr := range outs {
		verifyUnixAddr(addr)
		c := newHTTPClient(dialUnix, addr, connsPerAddr)
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to HTTP servers at unix:%q", outs)
}

func verifyUnixAddr(addr string) {
	fi, err := os.Stat(addr)
	if err != nil {
		log.Fatalf("error when accessing unix:%q: %s", addr, err)
	}
	mode := fi.Mode()
	if (mode & os.ModeSocket) == 0 {
		log.Fatalf("the %q must be unix socket", addr)
	}
}

func initHTTPTPClients(outs []string) {
	for _, addr := range outs {
		c := &httpteleport.Client{
			Addr:               addr,
			MaxBatchDelay:      *outDelay,
			MaxPendingRequests: *concurrency,
			ReadTimeout:        120 * time.Second,
			WriteTimeout:       5 * time.Second,
		}
		upstreamClients = append(upstreamClients, c)
	}
	log.Printf("Forwarding requests to httptp servers at %q", outs)
}

func newHTTPClient(dial fasthttp.DialFunc, addr string, connsPerAddr int) client {
	return &fasthttp.HostClient{
		Addr:         addr,
		Dial:         dial,
		MaxConns:     connsPerAddr,
		ReadTimeout:  *timeout * 5,
		WriteTimeout: *timeout,
	}
}

func dialUnix(addr string) (net.Conn, error) {
	return net.Dial("unix", addr)
}

func serveHTTP() {
	ln := newTCPListener()
	s := newHTTPServer()

	log.Printf("listening for HTTP requests on %q", *in)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func serveUnix() {
	addr := *in
	if _, err := os.Stat(addr); err == nil {
		verifyUnixAddr(addr)
		if err := os.Remove(addr); err != nil {
			log.Fatalf("cannot remove %q: %s", addr, err)
		}
	}

	ln, err := net.Listen("unix", addr)
	if err != nil {
		log.Fatalf("cannot listen to -in=%q: %s", addr, err)
	}
	s := newHTTPServer()

	log.Printf("listening for HTTP requests on unix:%q", addr)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func serveHTTPTP() {
	ln := newTCPListener()
	s := httpteleport.Server{
		Handler:           httptpRequestHandler,
		Concurrency:       *concurrency,
		MaxBatchDelay:     *inDelay,
		ReduceMemoryUsage: true,
		ReadTimeout:       120 * time.Second,
		WriteTimeout:      5 * time.Second,
	}

	log.Printf("listening for httptp connections on %q", *in)
	if err := s.Serve(ln); err != nil {
		log.Fatalf("error in fasthttp server: %s", err)
	}
}

func newTCPListener() net.Listener {
	cfg := tcplisten.Config{
		ReusePort: *reusePort,
	}
	ln, err := cfg.NewListener("tcp4", *in)
	if err != nil {
		log.Fatalf("cannot listen to -in=%q: %s", *in, err)
	}
	return ln
}

func newHTTPServer() *fasthttp.Server {
	return &fasthttp.Server{
		Handler:           httpRequestHandler,
		Concurrency:       *concurrency,
		ReduceMemoryUsage: true,
		ReadTimeout:       120 * time.Second,
		WriteTimeout:      5 * time.Second,
	}
}

type client interface {
	DoTimeout(req *fasthttp.Request, resp *fasthttp.Response, timeout time.Duration) error
	PendingRequests() int
}

var upstreamClients []client

func httpRequestHandler(ctx *fasthttp.RequestCtx) {
	var buf [16]byte
	ip := fasthttp.AppendIPv4(buf[:0], ctx.RemoteIP())
	ctx.Request.Header.SetBytesV("X-Forwarded-For", ip)

	c := leastLoadedClient()
	err := c.DoTimeout(&ctx.Request, &ctx.Response, *timeout)
	if err == nil {
		return
	}

	ctx.ResetBody()
	fmt.Fprintf(ctx, "HTTP proxying error: %s", err)
	if err == fasthttp.ErrTimeout {
		ctx.SetStatusCode(fasthttp.StatusGatewayTimeout)
	} else {
		ctx.SetStatusCode(fasthttp.StatusBadGateway)
	}
}

func httptpRequestHandler(ctx *fasthttp.RequestCtx) {
	// Reset 'Connection: close' request header in order to prevent
	// from closing keep-alive connections to -out servers.
	ctx.Request.Header.ResetConnectionClose()

	c := leastLoadedClient()
	err := c.DoTimeout(&ctx.Request, &ctx.Response, *timeout)
	if err == nil {
		return
	}

	ctx.ResetBody()
	fmt.Fprintf(ctx, "httptp proxying error: %s", err)
	if err == httpteleport.ErrTimeout {
		ctx.SetStatusCode(fasthttp.StatusGatewayTimeout)
	} else {
		ctx.SetStatusCode(fasthttp.StatusBadGateway)
	}
}

func leastLoadedClient() client {
	minC := upstreamClients[0]
	minN := minC.PendingRequests()
	if minN == 0 {
		return minC
	}
	for _, c := range upstreamClients[1:] {
		n := c.PendingRequests()
		if n == 0 {
			return c
		}
		if n < minN {
			minC = c
			minN = n
		}
	}
	return minC
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/x509"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/log/cmdlogger"
	"github.com/Symantec/keymaster/lib/client/config"
	"github.com/Symantec/keymaster/lib/client/twofa"
	"github.com/Symantec/keymaster/lib/client/twofa/u2f"
	"github.com/Symantec/keymaster/lib/client/util"
)

const DefaultSSHKeysLocation = "/.ssh/"
const DefaultTLSKeysLocation = "/.ssl/"

var (
	// Must be a global variable in the data segment so that the build
	// process can inject the version number on the fly when building the
	// binary. Use only from the Usage() function.
	Version = "No version provided"
)

var (
	configFilename = flag.String("config", filepath.Join(os.Getenv("HOME"), ".keymaster", "client_config.yml"), "The filename of the configuration")
	rootCAFilename = flag.String("rootCAFilename", "", "(optional) name for using non OS root CA to verify TLS connections")
	configHost     = flag.String("configHost", "", "Get a bootstrap config from this host")
	cliUsername    = flag.String("username", "", "username for keymaster")
	checkDevices   = flag.Bool("checkDevices", false, "CheckU2F devices in your system")
	cliFilePrefix  = flag.String("fileprefix", "", "Prefix for the output files")
	FilePrefix     = "keymaster"
)

func maybeGetRootCas(logger log.Logger) *x509.CertPool {
	var rootCAs *x509.CertPool
	if len(*rootCAFilename) > 1 {
		caData, err := ioutil.ReadFile(*rootCAFilename)
		if err != nil {
			logger.Printf("Failed to read caFilename")
			logger.Fatal(err)
		}
		rootCAs = x509.NewCertPool()
		if !rootCAs.AppendCertsFromPEM(caData) {
			logger.Fatal("cannot append file data")
		}

	}
	return rootCAs
}

func getUserNameAndHomeDir(logger log.Logger) (userName, homeDir string) {
	usr, err := user.Current()
	if err != nil {
		logger.Printf("cannot get current user info")
		logger.Fatal(err)
	}
	userName = usr.Username

	if runtime.GOOS == "windows" {
		splitName := strings.Split(userName, "\\")
		if len(splitName) == 2 {
			userName = strings.ToLower(splitName[1])
		}
	}

	homeDir, err = util.GetUserHomeDir(usr)
	if err != nil {
		logger.Fatal(err)
	}
	return
}

func loadConfigFile(rootCAs *x509.CertPool, logger log.Logger) (
	configContents config.AppConfigFile) {
	configPath, _ := filepath.Split(*configFilename)

	err := os.MkdirAll(configPath, 0755)
	if err != nil {
		logger.Fatal(err)
	}

	if len(*configHost) > 1 {
		err = config.GetConfigFromHost(*configFilename, *configHost, rootCAs, logger)
		if err != nil {
			logger.Fatal(err)
		}
	} else if len(defaultConfigHost) > 1 { // if there is a configHost AND there is NO config file, create one
		if _, err := os.Stat(*configFilename); os.IsNotExist(err) {
			err = config.GetConfigFromHost(
				*configFilename, defaultConfigHost, rootCAs, logger)
			if err != nil {
				logger.Fatal(err)
			}
		}
	}

	configContents, err = config.LoadVerifyConfigFile(*configFilename)
	if err != nil {
		logger.Fatal(err)
	}
	return
}

func setupCerts(
	rootCAs *x509.CertPool,
	userName,
	homeDir string,
	configContents config.AppConfigFile,
	logger log.DebugLogger) {
	// create dirs
	sshKeyPath := filepath.Join(homeDir, DefaultSSHKeysLocation, FilePrefix)
	sshConfigPath, _ := filepath.Split(sshKeyPath)
	err := os.MkdirAll(sshConfigPath, 0700)
	if err != nil {
		logger.Fatal(err)
	}
	tlsKeyPath := filepath.Join(homeDir, DefaultTLSKeysLocation, FilePrefix)
	tlsConfigPath, _ := filepath.Split(tlsKeyPath)
	err = os.MkdirAll(tlsConfigPath, 0700)
	if err != nil {
		logger.Fatal(err)
	}

	// get signer
	tempPrivateKeyPath := filepath.Join(homeDir, DefaultSSHKeysLocation, "keymaster-temp")
	signer, tempPublicKeyPath, err := util.GenKeyPair(
		tempPrivateKeyPath, userName+"@keymaster", logger)
	if err != nil {
		logger.Fatal(err)
	}
	defer os.Remove(tempPrivateKeyPath)
	defer os.Remove(tempPublicKeyPath)
	// Get user creds
	password, err := util.GetUserCreds(userName)
	if err != nil {
		logger.Fatal(err)
	}

	// Get the certs
	sshCert, x509Cert, kubernetesCert, err := twofa.GetCertFromTargetUrls(
		signer,
		userName,
		password,
		strings.Split(configContents.Base.Gen_Cert_URLS, ","),
		rootCAs,
		false,
		logger)
	if err != nil {
		logger.Fatal(err)
	}
	if sshCert == nil || x509Cert == nil {
		err := errors.New("Could not get cert from any url")
		logger.Fatal(err)
	}
	logger.Debugf(0, "Got Certs from server")
	//..
	if _, ok := os.LookupEnv("SSH_AUTH_SOCK"); ok {
		// TODO(rgooch): Parse certificate to get actual lifetime.
		cmd := exec.Command("ssh-add", "-d", sshKeyPath)
		cmd.Run()
	}

	//rename files to expected paths
	err = os.Rename(tempPrivateKeyPath, sshKeyPath)
	if err != nil {
		err := errors.New("Could not rename private Key")
		logger.Fatal(err)
	}

	err = os.Rename(tempPublicKeyPath, sshKeyPath+".pub")
	if err != nil {
		err := errors.New("Could not rename public Key")
		logger.Fatal(err)
	}
	// Now handle the key in the tls directory
	tlsPrivateKeyName := filepath.Join(homeDir, DefaultTLSKeysLocation, FilePrefix+".key")
	os.Remove(tlsPrivateKeyName)
	err = os.Symlink(sshKeyPath, tlsPrivateKeyName)
	if err != nil {
		// Try to copy instead (windows symlink does not work)
		from, err := os.Open(sshKeyPath)
		if err != nil {
			logger.Fatal(err)
		}
		defer from.Close()
		to, err := os.OpenFile(tlsPrivateKeyName, os.O_RDWR|os.O_CREATE, 0660)
		if err != nil {
			logger.Fatal(err)
		}
		defer to.Close()

		_, err = io.Copy(to, from)
		if err != nil {
			logger.Fatal(err)
		}
	}

	// now we write the cert file...
	sshCertPath := sshKeyPath + "-cert.pub"
	err = ioutil.WriteFile(sshCertPath, sshCert, 0644)
	if err != nil {
		err := errors.New("Could not write ssh cert")
		logger.Fatal(err)
	}
	x509CertPath := tlsKeyPath + ".cert"
	err = ioutil.WriteFile(x509CertPath, x509Cert, 0644)
	if err != nil {
		err := errors.New("Could not write ssh cert")
		logger.Fatal(err)
	}
	if kubernetesCert != nil {
		kubernetesCertPath := tlsKeyPath + "-kubernetes.cert"
		err = ioutil.WriteFile(kubernetesCertPath, kubernetesCert, 0644)
		if err != nil {
			err := errors.New("Could not write ssh cert")
			logger.Fatal(err)
		}
	}

	logger.Printf("Success")
	if _, ok := os.LookupEnv("SSH_AUTH_SOCK"); ok {
		// TODO(rgooch): Parse certificate to get actual lifetime.
		lifetime := fmt.Sprintf("%ds", uint64((*twofa.Duration).Seconds()))
		cmd := exec.Command("ssh-add", "-t", lifetime, sshKeyPath)
		cmd.Run()
	}
}

func Usage() {
	fmt.Fprintf(
		os.Stderr, "Usage of %s (version %s):\n", os.Args[0], Version)
	flag.PrintDefaults()
}

func main() {
	flag.Usage = Usage
	flag.Parse()
	logger := cmdlogger.New()

	if *checkDevices {
		u2f.CheckU2FDevices(logger)
		return
	}

	rootCAs := maybeGetRootCas(logger)
	userName, homeDir := getUserNameAndHomeDir(logger)
	config := loadConfigFile(rootCAs, logger)

	// Adjust user name
	if len(config.Base.Username) > 0 {
		userName = config.Base.Username
	}
	// command line always wins over pref or config
	if *cliUsername != "" {
		userName = *cliUsername
	}

	if len(config.Base.FilePrefix) > 0 {
		FilePrefix = config.Base.FilePrefix
	}
	if *cliFilePrefix != "" {
		FilePrefix = *cliFilePrefix
	}

	setupCerts(rootCAs, userName, homeDir, config, logger)
}
<commit_msg>fixin homedir on windows<commit_after>package main

import (
	"crypto/x509"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/log/cmdlogger"
	"github.com/Symantec/keymaster/lib/client/config"
	"github.com/Symantec/keymaster/lib/client/twofa"
	"github.com/Symantec/keymaster/lib/client/twofa/u2f"
	"github.com/Symantec/keymaster/lib/client/util"
)

const DefaultSSHKeysLocation = "/.ssh/"
const DefaultTLSKeysLocation = "/.ssl/"

var (
	// Must be a global variable in the data segment so that the build
	// process can inject the version number on the fly when building the
	// binary. Use only from the Usage() function.
	Version = "No version provided"
)

var (
	configFilename = flag.String("config", filepath.Join(getUserHomeDir(), ".keymaster", "client_config.yml"), "The filename of the configuration")
	rootCAFilename = flag.String("rootCAFilename", "", "(optional) name for using non OS root CA to verify TLS connections")
	configHost     = flag.String("configHost", "", "Get a bootstrap config from this host")
	cliUsername    = flag.String("username", "", "username for keymaster")
	checkDevices   = flag.Bool("checkDevices", false, "CheckU2F devices in your system")
	cliFilePrefix  = flag.String("fileprefix", "", "Prefix for the output files")
	FilePrefix     = "keymaster"
)

func getUserHomeDir() (homeDir string) {
	homeDir = os.Getenv("HOME")
	if homeDir != "" {
		return homeDir
	}
	usr, err := user.Current()
	if err != nil {
		return homeDir
	}
	// TODO: verify on Windows... see: http://stackoverflow.com/questions/7922270/obtain-users-home-directory
	homeDir = usr.HomeDir
	return
}

func maybeGetRootCas(logger log.Logger) *x509.CertPool {
	var rootCAs *x509.CertPool
	if len(*rootCAFilename) > 1 {
		caData, err := ioutil.ReadFile(*rootCAFilename)
		if err != nil {
			logger.Printf("Failed to read caFilename")
			logger.Fatal(err)
		}
		rootCAs = x509.NewCertPool()
		if !rootCAs.AppendCertsFromPEM(caData) {
			logger.Fatal("cannot append file data")
		}

	}
	return rootCAs
}

func getUserNameAndHomeDir(logger log.Logger) (userName, homeDir string) {
	usr, err := user.Current()
	if err != nil {
		logger.Printf("cannot get current user info")
		logger.Fatal(err)
	}
	userName = usr.Username

	if runtime.GOOS == "windows" {
		splitName := strings.Split(userName, "\\")
		if len(splitName) == 2 {
			userName = strings.ToLower(splitName[1])
		}
	}

	homeDir, err = util.GetUserHomeDir(usr)
	if err != nil {
		logger.Fatal(err)
	}
	return
}

func loadConfigFile(rootCAs *x509.CertPool, logger log.Logger) (
	configContents config.AppConfigFile) {
	configPath, _ := filepath.Split(*configFilename)

	err := os.MkdirAll(configPath, 0755)
	if err != nil {
		logger.Fatal(err)
	}

	if len(*configHost) > 1 {
		err = config.GetConfigFromHost(*configFilename, *configHost, rootCAs, logger)
		if err != nil {
			logger.Fatal(err)
		}
	} else if len(defaultConfigHost) > 1 { // if there is a configHost AND there is NO config file, create one
		if _, err := os.Stat(*configFilename); os.IsNotExist(err) {
			err = config.GetConfigFromHost(
				*configFilename, defaultConfigHost, rootCAs, logger)
			if err != nil {
				logger.Fatal(err)
			}
		}
	}

	configContents, err = config.LoadVerifyConfigFile(*configFilename)
	if err != nil {
		logger.Fatal(err)
	}
	return
}

func setupCerts(
	rootCAs *x509.CertPool,
	userName,
	homeDir string,
	configContents config.AppConfigFile,
	logger log.DebugLogger) {
	// create dirs
	sshKeyPath := filepath.Join(homeDir, DefaultSSHKeysLocation, FilePrefix)
	sshConfigPath, _ := filepath.Split(sshKeyPath)
	err := os.MkdirAll(sshConfigPath, 0700)
	if err != nil {
		logger.Fatal(err)
	}
	tlsKeyPath := filepath.Join(homeDir, DefaultTLSKeysLocation, FilePrefix)
	tlsConfigPath, _ := filepath.Split(tlsKeyPath)
	err = os.MkdirAll(tlsConfigPath, 0700)
	if err != nil {
		logger.Fatal(err)
	}

	// get signer
	tempPrivateKeyPath := filepath.Join(homeDir, DefaultSSHKeysLocation, "keymaster-temp")
	signer, tempPublicKeyPath, err := util.GenKeyPair(
		tempPrivateKeyPath, userName+"@keymaster", logger)
	if err != nil {
		logger.Fatal(err)
	}
	defer os.Remove(tempPrivateKeyPath)
	defer os.Remove(tempPublicKeyPath)
	// Get user creds
	password, err := util.GetUserCreds(userName)
	if err != nil {
		logger.Fatal(err)
	}

	// Get the certs
	sshCert, x509Cert, kubernetesCert, err := twofa.GetCertFromTargetUrls(
		signer,
		userName,
		password,
		strings.Split(configContents.Base.Gen_Cert_URLS, ","),
		rootCAs,
		false,
		logger)
	if err != nil {
		logger.Fatal(err)
	}
	if sshCert == nil || x509Cert == nil {
		err := errors.New("Could not get cert from any url")
		logger.Fatal(err)
	}
	logger.Debugf(0, "Got Certs from server")
	//..
	if _, ok := os.LookupEnv("SSH_AUTH_SOCK"); ok {
		// TODO(rgooch): Parse certificate to get actual lifetime.
		cmd := exec.Command("ssh-add", "-d", sshKeyPath)
		cmd.Run()
	}

	//rename files to expected paths
	err = os.Rename(tempPrivateKeyPath, sshKeyPath)
	if err != nil {
		err := errors.New("Could not rename private Key")
		logger.Fatal(err)
	}

	err = os.Rename(tempPublicKeyPath, sshKeyPath+".pub")
	if err != nil {
		err := errors.New("Could not rename public Key")
		logger.Fatal(err)
	}
	// Now handle the key in the tls directory
	tlsPrivateKeyName := filepath.Join(homeDir, DefaultTLSKeysLocation, FilePrefix+".key")
	os.Remove(tlsPrivateKeyName)
	err = os.Symlink(sshKeyPath, tlsPrivateKeyName)
	if err != nil {
		// Try to copy instead (windows symlink does not work)
		from, err := os.Open(sshKeyPath)
		if err != nil {
			logger.Fatal(err)
		}
		defer from.Close()
		to, err := os.OpenFile(tlsPrivateKeyName, os.O_RDWR|os.O_CREATE, 0660)
		if err != nil {
			logger.Fatal(err)
		}
		defer to.Close()

		_, err = io.Copy(to, from)
		if err != nil {
			logger.Fatal(err)
		}
	}

	// now we write the cert file...
	sshCertPath := sshKeyPath + "-cert.pub"
	err = ioutil.WriteFile(sshCertPath, sshCert, 0644)
	if err != nil {
		err := errors.New("Could not write ssh cert")
		logger.Fatal(err)
	}
	x509CertPath := tlsKeyPath + ".cert"
	err = ioutil.WriteFile(x509CertPath, x509Cert, 0644)
	if err != nil {
		err := errors.New("Could not write ssh cert")
		logger.Fatal(err)
	}
	if kubernetesCert != nil {
		kubernetesCertPath := tlsKeyPath + "-kubernetes.cert"
		err = ioutil.WriteFile(kubernetesCertPath, kubernetesCert, 0644)
		if err != nil {
			err := errors.New("Could not write ssh cert")
			logger.Fatal(err)
		}
	}

	logger.Printf("Success")
	if _, ok := os.LookupEnv("SSH_AUTH_SOCK"); ok {
		// TODO(rgooch): Parse certificate to get actual lifetime.
		lifetime := fmt.Sprintf("%ds", uint64((*twofa.Duration).Seconds()))
		cmd := exec.Command("ssh-add", "-t", lifetime, sshKeyPath)
		cmd.Run()
	}
}

func Usage() {
	fmt.Fprintf(
		os.Stderr, "Usage of %s (version %s):\n", os.Args[0], Version)
	flag.PrintDefaults()
}

func main() {
	flag.Usage = Usage
	flag.Parse()
	logger := cmdlogger.New()

	if *checkDevices {
		u2f.CheckU2FDevices(logger)
		return
	}

	rootCAs := maybeGetRootCas(logger)
	userName, homeDir := getUserNameAndHomeDir(logger)
	config := loadConfigFile(rootCAs, logger)

	// Adjust user name
	if len(config.Base.Username) > 0 {
		userName = config.Base.Username
	}
	// command line always wins over pref or config
	if *cliUsername != "" {
		userName = *cliUsername
	}

	if len(config.Base.FilePrefix) > 0 {
		FilePrefix = config.Base.FilePrefix
	}
	if *cliFilePrefix != "" {
		FilePrefix = *cliFilePrefix
	}

	setupCerts(rootCAs, userName, homeDir, config, logger)
}
<|endoftext|>


<commit_before>package appfile

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/hcl"
	hclobj "github.com/hashicorp/hcl/hcl"
	"github.com/mitchellh/mapstructure"
)

// Parse parses the Appfile from the given io.Reader.
//
// Due to current internal limitations, the entire contents of the
// io.Reader will be copied into memory first before parsing.
func Parse(r io.Reader) (*File, error) {
	// Copy the reader into an in-memory buffer first since HCL requires it.
	var buf bytes.Buffer
	if _, err := io.Copy(&buf, r); err != nil {
		return nil, err
	}

	// Parse the buffer
	obj, err := hcl.Parse(buf.String())
	if err != nil {
		return nil, fmt.Errorf("error parsing: %s", err)
	}
	buf.Reset()

	// Check for invalid keys
	valid := []string{
		"application",
		"customization",
		"import",
		"infrastructure",
		"project",
	}
	if err := checkHCLKeys(obj, valid); err != nil {
		return nil, err
	}

	var result File

	// Parse the imports
	if o := obj.Get("import", false); o != nil {
		if err := parseImport(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'import': %s", err)
		}
	}

	// Parse the application
	if o := obj.Get("application", false); o != nil {
		if err := parseApplication(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'application': %s", err)
		}
	}

	// Parse the project
	if o := obj.Get("project", false); o != nil {
		if err := parseProject(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'project': %s", err)
		}
	}

	// Parse the infrastructure
	if o := obj.Get("infrastructure", false); o != nil {
		if err := parseInfra(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'infrastructure': %s", err)
		}
	}

	// Parse the customizations
	if o := obj.Get("customization", false); o != nil {
		if err := parseCustomizations(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'customization': %s", err)
		}
	}

	return &result, nil
}

// ParseFile parses the given path as an Appfile.
func ParseFile(path string) (*File, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	result, err := Parse(f)
	if result != nil {
		result.Path = path
		if err := result.loadID(); err != nil {
			return nil, err
		}
	}

	return result, err
}

func parseApplication(result *File, obj *hclobj.Object) error {
	if obj.Len() > 1 {
		return fmt.Errorf("only one 'application' block allowed")
	}

	// Check for invalid keys
	valid := []string{"name", "type", "dependency"}
	if err := checkHCLKeys(obj, valid); err != nil {
		return multierror.Prefix(err, "application:")
	}

	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, obj); err != nil {
		return err
	}

	var app Application
	result.Application = &app
	return mapstructure.WeakDecode(m, &app)
}

func parseCustomizations(result *File, obj *hclobj.Object) error {
	// Get all the maps of keys to the actual object
	objects := make(map[string]*hclobj.Object)
	for _, o1 := range obj.Elem(false) {
		for _, o2 := range o1.Elem(true) {
			if _, ok := objects[o2.Key]; ok {
				return fmt.Errorf(
					"customization '%s' defined more than once",
					o2.Key)
			}

			objects[o2.Key] = o2
		}
	}

	if len(objects) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Customization, 0, len(objects))
	for n, o := range objects {
		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, o); err != nil {
			return err
		}

		var c Customization
		c.Type = strings.ToLower(n)
		c.Config = m

		collection = append(collection, &c)
	}

	result.Customization = &CustomizationSet{Raw: collection}
	return nil
}

func parseImport(result *File, obj *hclobj.Object) error {
	// Get all the maps of keys to the actual object
	objects := make([]*hclobj.Object, 0, 3)
	set := make(map[string]struct{})
	for _, o1 := range obj.Elem(false) {
		for _, o2 := range o1.Elem(true) {
			if _, ok := set[o2.Key]; ok {
				return fmt.Errorf(
					"imported '%s' more than once",
					o2.Key)
			}

			objects = append(objects, o2)
			set[o2.Key] = struct{}{}
		}
	}

	if len(objects) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Import, 0, len(objects))
	for _, o := range objects {
		// Check for invalid keys
		if err := checkHCLKeys(o, nil); err != nil {
			return multierror.Prefix(err, fmt.Sprintf(
				"import '%s':", o.Key))
		}

		collection = append(collection, &Import{
			Source: o.Key,
		})
	}

	result.Imports = collection
	return nil
}

func parseInfra(result *File, obj *hclobj.Object) error {
	// Get all the maps of keys to the actual object
	objects := make(map[string]*hclobj.Object)
	for _, o1 := range obj.Elem(false) {
		for _, o2 := range o1.Elem(true) {
			if _, ok := objects[o2.Key]; ok {
				return fmt.Errorf(
					"infrastructure '%s' defined more than once",
					o2.Key)
			}

			objects[o2.Key] = o2
		}
	}

	if len(objects) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Infrastructure, 0, len(objects))
	for n, o := range objects {
		// Check for invalid keys
		valid := []string{"name", "type", "flavor", "foundation"}
		if err := checkHCLKeys(o, valid); err != nil {
			return multierror.Prefix(err, fmt.Sprintf(
				"infrastructure '%s':", n))
		}

		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, o); err != nil {
			return err
		}

		var infra Infrastructure
		if err := mapstructure.WeakDecode(m, &infra); err != nil {
			return fmt.Errorf(
				"error parsing infrastructure '%s': %s", n, err)
		}

		infra.Name = n
		if infra.Type == "" {
			infra.Type = infra.Name
		}

		// Parse the foundations if we have any
		if o2 := o.Get("foundation", false); o != nil {
			if err := parseFoundations(&infra, o2); err != nil {
				return fmt.Errorf("error parsing 'foundation': %s", err)
			}
		}

		collection = append(collection, &infra)
	}

	result.Infrastructure = collection
	return nil
}

func parseFoundations(result *Infrastructure, obj *hclobj.Object) error {
	// Get all the maps of keys to the actual object
	objects := make(map[string]*hclobj.Object)
	for _, o1 := range obj.Elem(false) {
		for _, o2 := range o1.Elem(true) {
			if _, ok := objects[o2.Key]; ok {
				return fmt.Errorf(
					"foundation '%s' defined more than once",
					o2.Key)
			}

			objects[o2.Key] = o2
		}
	}

	if len(objects) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Foundation, 0, len(objects))
	for n, o := range objects {
		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, o); err != nil {
			return err
		}

		var f Foundation
		f.Name = n
		f.Config = m

		collection = append(collection, &f)
	}

	// Set the results
	result.Foundations = collection
	return nil
}

func parseProject(result *File, obj *hclobj.Object) error {
	if obj.Len() > 1 {
		return fmt.Errorf("only one 'project' block allowed")
	}

	// Check for invalid keys
	valid := []string{"name", "infrastructure"}
	if err := checkHCLKeys(obj, valid); err != nil {
		return multierror.Prefix(err, "project:")
	}

	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, obj); err != nil {
		return err
	}

	// Parse the project
	var proj Project
	result.Project = &proj
	if err := mapstructure.WeakDecode(m, &proj); err != nil {
		return err
	}

	return nil
}

func checkHCLKeys(obj *hclobj.Object, valid []string) error {
	validMap := make(map[string]struct{}, len(valid))
	for _, v := range valid {
		validMap[v] = struct{}{}
	}

	var result error
	for _, o := range obj.Elem(true) {
		if _, ok := validMap[o.Key]; !ok {
			result = multierror.Append(result, fmt.Errorf(
				"invald key: %s", o.Key))
		}
	}

	return result
}
<commit_msg>appfile: parse with new API<commit_after>package appfile

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/hcl"
	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/mitchellh/mapstructure"
)

// Parse parses the Appfile from the given io.Reader.
//
// Due to current internal limitations, the entire contents of the
// io.Reader will be copied into memory first before parsing.
func Parse(r io.Reader) (*File, error) {
	// Copy the reader into an in-memory buffer first since HCL requires it.
	var buf bytes.Buffer
	if _, err := io.Copy(&buf, r); err != nil {
		return nil, err
	}

	// Parse the buffer
	root, err := hcl.Parse(buf.String())
	if err != nil {
		return nil, fmt.Errorf("error parsing: %s", err)
	}
	buf.Reset()

	// Top-level item should be the object list
	list, ok := root.Node.(*ast.ObjectList)
	if !ok {
		return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
	}

	// Check for invalid keys
	valid := []string{
		"application",
		"customization",
		"import",
		"infrastructure",
		"project",
	}
	if err := checkHCLKeys(list, valid); err != nil {
		return nil, err
	}

	var result File

	// Parse the imports
	if o := list.Filter("import"); len(o.Items) > 0 {
		if err := parseImport(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'import': %s", err)
		}
	}

	// Parse the application
	if o := list.Filter("application"); len(o.Items) > 0 {
		if err := parseApplication(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'application': %s", err)
		}
	}

	// Parse the project
	if o := list.Filter("project"); len(o.Items) > 0 {
		if err := parseProject(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'project': %s", err)
		}
	}

	// Parse the infrastructure
	if o := list.Filter("infrastructure"); len(o.Items) > 0 {
		if err := parseInfra(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'infrastructure': %s", err)
		}
	}

	// Parse the customizations
	if o := list.Filter("customization"); len(o.Items) > 0 {
		if err := parseCustomizations(&result, o); err != nil {
			return nil, fmt.Errorf("error parsing 'customization': %s", err)
		}
	}

	return &result, nil
}

// ParseFile parses the given path as an Appfile.
func ParseFile(path string) (*File, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	result, err := Parse(f)
	if result != nil {
		result.Path = path
		if err := result.loadID(); err != nil {
			return nil, err
		}
	}

	return result, err
}

func parseApplication(result *File, list *ast.ObjectList) error {
	if len(list.Items) > 1 {
		return fmt.Errorf("only one 'application' block allowed")
	}

	// Get our one item
	item := list.Items[0]

	// Check for invalid keys
	valid := []string{"name", "type", "dependency"}
	if err := checkHCLKeys(item.Val, valid); err != nil {
		return multierror.Prefix(err, "application:")
	}

	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, item.Val); err != nil {
		return err
	}

	var app Application
	result.Application = &app
	return mapstructure.WeakDecode(m, &app)
}

func parseCustomizations(result *File, list *ast.ObjectList) error {
	list = list.Prefix("customization")
	if len(list.Items) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Customization, 0, len(list.Items))
	for _, item := range list.Items {
		key := item.Keys[0].Token.Value().(string)

		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, item.Val); err != nil {
			return err
		}

		var c Customization
		c.Type = strings.ToLower(key)
		c.Config = m

		collection = append(collection, &c)
	}

	result.Customization = &CustomizationSet{Raw: collection}
	return nil
}

func parseImport(result *File, list *ast.ObjectList) error {
	list = list.Prefix("import")
	if len(list.Items) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Import, 0, len(list.Items))
	seen := make(map[string]struct{})
	for _, item := range list.Items {
		key := item.Keys[0].Token.Value().(string)

		// Make sure we haven't already found this import
		if _, ok := seen[key]; ok {
			return fmt.Errorf("import '%s' defined more than once", key)
		}
		seen[key] = struct{}{}

		// Check for invalid keys
		if err := checkHCLKeys(item.Val, nil); err != nil {
			return multierror.Prefix(err, fmt.Sprintf(
				"import '%s':", key))
		}

		collection = append(collection, &Import{
			Source: key,
		})
	}

	result.Imports = collection
	return nil
}

func parseInfra(result *File, list *ast.ObjectList) error {
	list = list.Prefix("infrastructure")
	if len(list.Items) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Infrastructure, 0, len(list.Items))
	seen := make(map[string]struct{})
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		// Make sure we haven't already found this
		if _, ok := seen[n]; ok {
			return fmt.Errorf("infrastructure '%s' defined more than once", n)
		}
		seen[n] = struct{}{}

		// Check for invalid keys
		valid := []string{"name", "type", "flavor", "foundation"}
		if err := checkHCLKeys(item.Val, valid); err != nil {
			return multierror.Prefix(err, fmt.Sprintf(
				"infrastructure '%s':", n))
		}

		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return fmt.Errorf("infrastructure '%s': should be an object", n)
		}

		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, item.Val); err != nil {
			return err
		}

		var infra Infrastructure
		if err := mapstructure.WeakDecode(m, &infra); err != nil {
			return fmt.Errorf(
				"error parsing infrastructure '%s': %s", n, err)
		}

		infra.Name = n
		if infra.Type == "" {
			infra.Type = infra.Name
		}

		// Parse the foundations if we have any
		if o2 := listVal.Filter("foundation"); len(o2.Items) > 0 {
			if err := parseFoundations(&infra, o2); err != nil {
				return fmt.Errorf("error parsing 'foundation': %s", err)
			}
		}

		collection = append(collection, &infra)
	}

	result.Infrastructure = collection
	return nil
}

func parseFoundations(result *Infrastructure, list *ast.ObjectList) error {
	list = list.Prefix("foundation")
	if len(list.Items) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*Foundation, 0, len(list.Items))
	seen := make(map[string]struct{})
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		// Make sure we haven't already found this
		if _, ok := seen[n]; ok {
			return fmt.Errorf("foundation '%s' defined more than once", n)
		}
		seen[n] = struct{}{}

		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, item.Val); err != nil {
			return err
		}

		var f Foundation
		f.Name = n
		f.Config = m

		collection = append(collection, &f)
	}

	// Set the results
	result.Foundations = collection
	return nil
}

func parseProject(result *File, list *ast.ObjectList) error {
	if len(list.Items) > 1 {
		return fmt.Errorf("only one 'project' block allowed")
	}

	// Get our one item
	item := list.Items[0]

	// Check for invalid keys
	valid := []string{"name", "infrastructure"}
	if err := checkHCLKeys(item.Val, valid); err != nil {
		return multierror.Prefix(err, "project:")
	}

	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, item.Val); err != nil {
		return err
	}

	// Parse the project
	var proj Project
	result.Project = &proj
	if err := mapstructure.WeakDecode(m, &proj); err != nil {
		return err
	}

	return nil
}

func checkHCLKeys(node ast.Node, valid []string) error {
	var list *ast.ObjectList
	switch n := node.(type) {
	case *ast.ObjectList:
		list = n
	case *ast.ObjectType:
		list = n.List
	default:
		return fmt.Errorf("cannot check HCL keys of type %T", n)
	}

	validMap := make(map[string]struct{}, len(valid))
	for _, v := range valid {
		validMap[v] = struct{}{}
	}

	var result error
	for _, item := range list.Items {
		key := item.Keys[0].Token.Value().(string)
		if _, ok := validMap[key]; !ok {
			result = multierror.Append(result, fmt.Errorf(
				"invald key: %s", key))
		}
	}

	return result
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/constabulary/kodos"
)

func check(err error) {
	if err != nil {
		fatal(err)
		os.Exit(1)
	}
}

func fatal(arg interface{}, args ...interface{}) {
	fmt.Fprint(os.Stderr, "fatal: ", arg)
	fmt.Fprintln(os.Stderr, args...)
	os.Exit(1)
}

func main() {
	flag.Parse()
	dir, err := findreporoot(cwd())
	check(err)

	fmt.Println("Using", dir)

	workdir, err := ioutil.TempDir("", "kodos")
	check(err)

	pkgdir := filepath.Join(dir, ".kodos", "pkg")

	ctx := &kodos.Context{
		GOOS:    runtime.GOOS,
		GOARCH:  runtime.GOARCH,
		Workdir: workdir,
		Pkgdir:  pkgdir,
		Bindir:  dir,
	}

	action := "build"
	prefix := "github.com/constabulary/kodos"

	switch action {
	case "build":
		srcs := loadSources(prefix, dir)
		for _, src := range srcs {
			fmt.Printf("loaded %s (%s)\n", src.ImportPath, src.Name)
		}

		srcs = loadDependencies(dir, srcs...)

		pkgs := transform(ctx, srcs...)
		computeStale(pkgs...)

		targets := make(map[string]func() error)
		fn, err := buildPackages(targets, pkgs...)
		check(err)
		check(fn())
	default:
		fatal("unknown action:", action)
	}
}

func cwd() string {
	wd, err := os.Getwd()
	check(err)
	return wd
}

// transform takes a slice of go/build.Package and returns the
// corresponding slice of kodos.Packages.
func transform(ctx *kodos.Context, v ...*build.Package) []*kodos.Package {
	srcs := make(map[string]*build.Package)
	for _, pkg := range v {
		srcs[pkg.ImportPath] = pkg
	}

	var pkgs []*kodos.Package
	seen := make(map[string]bool)

	var walk func(src *build.Package)
	walk = func(src *build.Package) {
		if seen[src.ImportPath] {
			return
		}
		seen[src.ImportPath] = true

		for _, i := range src.Imports {
			pkg, ok := srcs[i]
			if !ok {
				fatal("transform: pkg ", i, "is not loaded")
			}
			walk(pkg)
		}

		pkgs = append(pkgs, &kodos.Package{
			Context:    ctx,
			ImportPath: src.ImportPath,
			Dir:        src.Dir,
			GoFiles:    src.GoFiles,
			Main:       src.Name == "main",
		})
	}
	for _, p := range v {
		walk(p)
	}
	return pkgs
}

// computeStale sets the UpToDate flag on a set of package roots.
func computeStale(roots ...*kodos.Package) {
	seen := make(map[*kodos.Package]bool)

	var walk func(pkg *kodos.Package) bool
	walk = func(pkg *kodos.Package) bool {
		if seen[pkg] {
			return pkg.NotStale
		}
		seen[pkg] = true

		for _, i := range pkg.Imports {
			if !walk(i) {
				// a dep is stale so we are stale
				return false
			}
		}

		stale := pkg.IsStale()
		pkg.NotStale = !stale
		return !stale
	}

	for _, root := range roots {
		walk(root)
	}
}

// findreporoot returns the location of the closest .git directory
// relative to the dir provided.
func findreporoot(dir string) (string, error) {
	orig := dir
	for {
		path := filepath.Join(dir, ".git")
		fi, err := os.Stat(path)
		if err == nil && fi.IsDir() {
			return dir, nil
		}
		if err != nil && !os.IsNotExist(err) {
			check(err)
		}
		d := filepath.Dir(dir)
		if d == dir {
			// got to the root directory without
			return "", fmt.Errorf("could not locate .git in %s", orig)
		}
		dir = d
	}
}

func buildPackages(targets map[string]func() error, pkgs ...*kodos.Package) (func() error, error) {
	var deps []func() error
	for _, pkg := range pkgs {
		fn, err := buildPackage(targets, pkg)
		check(err)
		deps = append(deps, fn)
	}
	return func() error {
		for _, fn := range deps {
			if err := fn(); err != nil {
				return err
			}
		}
		return nil
	}, nil
}

func buildPackage(targets map[string]func() error, pkg *kodos.Package) (func() error, error) {

	// if this action is already present in the map, return it
	// rather than creating a new action.
	if fn, ok := targets[pkg.ImportPath]; ok {
		return fn, nil
	}

	// step 0. are we stale ?
	// if this package is not stale, then by definition none of its
	// dependencies are stale, so ignore this whole tree.
	if pkg.NotStale {
		return func() error {
			fmt.Println(pkg.ImportPath, "is up to date")
			return nil
		}, nil
	}

	// step 1. build dependencies
	var deps []func() error
	for _, pkg := range pkg.Imports {
		fn, err := buildPackage(targets, pkg)
		if err != nil {
			return nil, err
		}
		deps = append(deps, fn)
	}

	// step 2. build this package
	build := func() error {
		for _, dep := range deps {
			if err := dep(); err != nil {
				return err
			}
		}
		if err := pkg.Compile(); err != nil {
			return err
		}
		if !pkg.Main {
			return nil // we're done
		}
		return pkg.Link()
	}

	// record the final action as the action that represents
	// building this package.
	targets[pkg.ImportPath] = build

	return build, nil
}

func loadSources(prefix string, dir string) []*build.Package {
	f, err := os.Open(dir)
	check(err)
	files, err := f.Readdir(-1)
	check(err)
	f.Close()

	var srcs []*build.Package
	for _, fi := range files {
		name := fi.Name()
		if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") || name == "testdata" || name == "vendor" {
			// ignore it
			continue
		}
		if fi.IsDir() {
			srcs = append(srcs, loadSources(path.Join(prefix, name), filepath.Join(dir, name))...)
		}
	}

	pkg, err := build.ImportDir(dir, 0)
	switch err := err.(type) {
	case nil:
		// ImportDir does not know the import path for this package
		// but we know the prefix, so fix it.
		pkg.ImportPath = prefix
		srcs = append(srcs, pkg)
	case (*build.NoGoError):
		// do nothing
	default:
		check(err)
	}

	return srcs
}

func loadDependencies(rootdir string, srcs ...*build.Package) []*build.Package {
	load := func(path string) *build.Package {
		fmt.Println("searching", path, "in", filepath.Join(runtime.GOROOT(), "src"), "(GOROOT)")
		dir := filepath.Join(runtime.GOROOT(), "src", path)
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			fatal("cannot resolve path ", path)
		}
		return importPath(path, dir)
	}

	seen := make(map[string]bool)
	var walk func(string)
	walk = func(path string) {
		if seen[path] {
			return
		}
		seen[path] = true
		pkg := load(path)
		srcs = append(srcs, pkg)
		for _, i := range pkg.Imports {
			walk(i)
		}
	}
	for _, src := range srcs {
		seen[src.ImportPath] = true
	}
	for _, src := range srcs[:] {
		for _, i := range src.Imports {
			walk(i)
		}
	}
	return srcs
}

func register(rootdir, prefix, kind, arg string, next func(string) *build.Package) func(string) *build.Package {
	dir := cacheDir(rootdir, prefix+kind+"="+arg)
	fmt.Println("registered:", prefix, "@", arg)
	return func(path string) *build.Package {
		if !strings.HasPrefix(path, prefix) {
			return next(path)
		}
		fmt.Println("searching", path, "in", prefix, "@", arg)
		dir := filepath.Join(dir, path)
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			check(err)
		}
		return importPath(path, dir)
	}
}

func importPath(path, dir string) *build.Package {
	pkg, err := build.ImportDir(dir, 0)
	check(err)
	// ImportDir does not know the import path for this package
	// but we know the prefix, so fix it.
	pkg.ImportPath = path
	return pkg
}

func cacheDir(rootdir, key string) string {
	hash := sha1.Sum([]byte(key))
	return filepath.Join(rootdir, ".kang", "cache", fmt.Sprintf("%x", hash[0:1]), fmt.Sprintf("%x", hash[1:]))
}
<commit_msg>small commit to tickle travis<commit_after>package main

import (
	"crypto/sha1"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/constabulary/kodos"
)

func check(err error) {
	if err != nil {
		fatal(err)
		os.Exit(1)
	}
}

func fatal(arg interface{}, args ...interface{}) {
	fmt.Fprint(os.Stderr, "fatal: ", arg)
	fmt.Fprintln(os.Stderr, args...)
	os.Exit(1)
}

func main() {
	flag.Parse()
	dir, err := findreporoot(cwd())
	check(err)

	fmt.Println("Using", dir)

	workdir, err := ioutil.TempDir("", "kodos")
	check(err)

	pkgdir := filepath.Join(dir, ".kodos", "pkg")

	ctx := &kodos.Context{
		GOOS:    runtime.GOOS,
		GOARCH:  runtime.GOARCH,
		Workdir: workdir,
		Pkgdir:  pkgdir,
		Bindir:  dir,
	}

	action := "build"
	prefix := "github.com/constabulary/kodos"

	switch action {
	case "build":
		srcs := loadSources(prefix, dir)
		for _, src := range srcs {
			fmt.Printf("loaded %s (%s)\n", src.ImportPath, src.Name)
		}

		srcs = loadDependencies(dir, srcs...)

		pkgs := transform(ctx, srcs...)
		computeStale(pkgs...)

		targets := make(map[string]func() error)
		fn, err := buildPackages(targets, pkgs...)
		check(err)
		check(fn())
	default:
		fatal("unknown action:", action)
	}
}

func cwd() string {
	wd, err := os.Getwd()
	check(err)
	return wd
}

// transform takes a slice of go/build.Package and returns the
// corresponding slice of kodos.Packages.
func transform(ctx *kodos.Context, v ...*build.Package) []*kodos.Package {
	srcs := make(map[string]*build.Package)
	for _, pkg := range v {
		srcs[pkg.ImportPath] = pkg
	}

	var pkgs []*kodos.Package
	seen := make(map[string]bool)

	var walk func(src *build.Package)
	walk = func(src *build.Package) {
		if seen[src.ImportPath] {
			return
		}
		seen[src.ImportPath] = true

		for _, i := range src.Imports {
			pkg, ok := srcs[i]
			if !ok {
				fatal("transform: pkg ", i, "is not loaded")
			}
			walk(pkg)
		}

		pkgs = append(pkgs, &kodos.Package{
			Context:    ctx,
			ImportPath: src.ImportPath,
			Dir:        src.Dir,
			GoFiles:    src.GoFiles,
			Main:       src.Name == "main",
		})
	}
	for _, p := range v {
		walk(p)
	}
	return pkgs
}

// computeStale sets the UpToDate flag on a set of package roots.
func computeStale(roots ...*kodos.Package) {
	seen := make(map[*kodos.Package]bool)

	var walk func(pkg *kodos.Package) bool
	walk = func(pkg *kodos.Package) bool {
		if seen[pkg] {
			return pkg.NotStale
		}
		seen[pkg] = true

		for _, i := range pkg.Imports {
			if !walk(i) {
				// a dep is stale so we are stale
				return false
			}
		}

		stale := pkg.IsStale()
		pkg.NotStale = !stale
		return !stale
	}

	for _, root := range roots {
		walk(root)
	}
}

// findreporoot returns the location of the closest .git directory
// relative to the dir provided.
func findreporoot(dir string) (string, error) {
	orig := dir
	for {
		path := filepath.Join(dir, ".git")
		fi, err := os.Stat(path)
		if err == nil && fi.IsDir() {
			return dir, nil
		}
		if err != nil && !os.IsNotExist(err) {
			check(err)
		}
		d := filepath.Dir(dir)
		if d == dir {
			// got to the root directory without
			return "", fmt.Errorf("could not locate .git in %s", orig)
		}
		dir = d
	}
}

func buildPackages(targets map[string]func() error, pkgs ...*kodos.Package) (func() error, error) {
	var deps []func() error
	for _, pkg := range pkgs {
		fn, err := buildPackage(targets, pkg)
		check(err)
		deps = append(deps, fn)
	}
	return func() error {
		for _, fn := range deps {
			if err := fn(); err != nil {
				return err
			}
		}
		return nil
	}, nil
}

func buildPackage(targets map[string]func() error, pkg *kodos.Package) (func() error, error) {

	// if this action is already present in the map, return it
	// rather than creating a new action.
	if fn, ok := targets[pkg.ImportPath]; ok {
		return fn, nil
	}

	// step 0. are we stale ?
	// if this package is not stale, then by definition none of its
	// dependencies are stale, so ignore this whole tree.
	if pkg.NotStale {
		return func() error {
			fmt.Println(pkg.ImportPath, "is up to date")
			return nil
		}, nil
	}

	// step 1. build dependencies
	var deps []func() error
	for _, pkg := range pkg.Imports {
		fn, err := buildPackage(targets, pkg)
		if err != nil {
			return nil, err
		}
		deps = append(deps, fn)
	}

	// step 2. build this package
	build := func() error {
		for _, dep := range deps {
			if err := dep(); err != nil {
				return err
			}
		}
		if err := pkg.Compile(); err != nil {
			return err
		}
		if !pkg.Main {
			return nil // we're done
		}
		return pkg.Link()
	}

	// record the final action as the action that represents
	// building this package.
	targets[pkg.ImportPath] = build

	return build, nil
}

func loadSources(prefix string, dir string) []*build.Package {
	f, err := os.Open(dir)
	check(err)
	files, err := f.Readdir(-1)
	check(err)
	f.Close()

	var srcs []*build.Package
	for _, fi := range files {
		name := fi.Name()
		if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") || name == "testdata" || name == "vendor" {
			// ignore it
			continue
		}
		if fi.IsDir() {
			srcs = append(srcs, loadSources(path.Join(prefix, name), filepath.Join(dir, name))...)
		}
	}

	pkg, err := build.ImportDir(dir, 0)
	switch err := err.(type) {
	case nil:
		// ImportDir does not know the import path for this package
		// but we know the prefix, so fix it.
		pkg.ImportPath = prefix
		srcs = append(srcs, pkg)
	case (*build.NoGoError):
		// do nothing
	default:
		check(err)
	}

	return srcs
}

func loadDependencies(rootdir string, srcs ...*build.Package) []*build.Package {
	load := func(path string) *build.Package {
		fmt.Println("searching", path, "in", filepath.Join(runtime.GOROOT(), "src"), "(GOROOT)")
		dir := filepath.Join(runtime.GOROOT(), "src", path)
		if _, err := os.Stat(dir); err != nil {
			fatal("cannot resolve path ", path, err.Error())
		}
		return importPath(path, dir)
	}

	seen := make(map[string]bool)
	var walk func(string)
	walk = func(path string) {
		if seen[path] {
			return
		}
		seen[path] = true
		pkg := load(path)
		srcs = append(srcs, pkg)
		for _, i := range pkg.Imports {
			walk(i)
		}
	}
	for _, src := range srcs {
		seen[src.ImportPath] = true
	}
	for _, src := range srcs[:] {
		for _, i := range src.Imports {
			walk(i)
		}
	}
	return srcs
}

func register(rootdir, prefix, kind, arg string, next func(string) *build.Package) func(string) *build.Package {
	dir := cacheDir(rootdir, prefix+kind+"="+arg)
	fmt.Println("registered:", prefix, "@", arg)
	return func(path string) *build.Package {
		if !strings.HasPrefix(path, prefix) {
			return next(path)
		}
		fmt.Println("searching", path, "in", prefix, "@", arg)
		dir := filepath.Join(dir, path)
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			check(err)
		}
		return importPath(path, dir)
	}
}

func importPath(path, dir string) *build.Package {
	pkg, err := build.ImportDir(dir, 0)
	check(err)
	// ImportDir does not know the import path for this package
	// but we know the prefix, so fix it.
	pkg.ImportPath = path
	return pkg
}

func cacheDir(rootdir, key string) string {
	hash := sha1.Sum([]byte(key))
	return filepath.Join(rootdir, ".kang", "cache", fmt.Sprintf("%x", hash[0:1]), fmt.Sprintf("%x", hash[1:]))
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Intel Corporation
//
// 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 (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

// Github represents a control version repository to
// interact with github.com
type Github struct {
	client *github.Client
	owner  string
	repo   string
	url    string
}

const (
	timeoutShortRequest = 10 * time.Second
	timeoutLongRequest  = 20 * time.Second
)

// newGithub returns an object of type Github
func newGithub(url, token string) (CVR, error) {
	url = strings.TrimSpace(url)

	ownerRepo := strings.SplitAfter(url, "/"+githubDomain+"/")

	// at least we need two tokens
	if len(ownerRepo) < 2 {
		return nil, fmt.Errorf("missing owner and repo %s", url)
	}

	ownerRepo = strings.Split(ownerRepo[1], "/")

	// at least we need two tokens: owner and repo
	if len(ownerRepo) < 2 {
		return nil, fmt.Errorf("failed to get owner and repo %s", url)
	}

	if len(ownerRepo[0]) == 0 {
		return nil, fmt.Errorf("missing owner in url %s", url)
	}

	if len(ownerRepo[1]) == 0 {
		return nil, fmt.Errorf("missing repository in url %s", url)
	}

	// create a new http client using the token
	var client *http.Client
	if token != "" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		)
		client = oauth2.NewClient(context.Background(), ts)
	}

	return &Github{
		client: github.NewClient(client),
		owner:  ownerRepo[0],
		repo:   ownerRepo[1],
		url:    url,
	}, nil
}

// getDomain returns the domain name
func (g *Github) getDomain() string {
	return githubDomain
}

// getOwner returns the owner of the repo
func (g *Github) getOwner() string {
	return g.owner
}

// getRepo returns the repository name
func (g *Github) getRepo() string {
	return g.repo
}

// getOpenPullRequests returns the open pull requests
func (g *Github) getOpenPullRequests() (map[string]*PullRequest, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	pullRequests, _, err := g.client.PullRequests.List(ctx, g.owner, g.repo, nil)
	if err != nil {
		ciLog.Errorf("failed to list pull requests: %s", err)
		return nil, err
	}

	prs := make(map[string]*PullRequest)

	for _, pr := range pullRequests {
		pullRequest, err := g.getPullRequest(*pr.Number)
		if err != nil {
			ciLog.Errorf("failed to get pull request %d: %s", *pr.Number, err)
			continue
		}

		prs[strconv.Itoa(*pr.Number)] = pullRequest
	}

	return prs, nil
}

// getPullRequest returns a specific pull request
func (g *Github) getPullRequest(pr int) (*PullRequest, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	// get all commits of the pull request
	listCommits, _, err := g.client.PullRequests.ListCommits(ctx, g.owner, g.repo, pr, nil)
	if err != nil {
		return nil, err
	}

	var commits []PullRequestCommit
	for _, c := range listCommits {
		commits = append(commits,
			PullRequestCommit{
				Sha:  *c.SHA,
				Time: *c.Commit.Committer.Date,
			},
		)
	}

	pullRequest, _, err := g.client.PullRequests.Get(ctx, g.owner, g.repo, pr)
	if err != nil {
		return nil, err
	}

	return &PullRequest{
		Number:    pr,
		Commits:   commits,
		Author:    *pullRequest.User.Login,
		Mergeable: *pullRequest.Mergeable,
	}, nil
}

// getLatestPullRequestComment returns the latest comment of a specific
// user in the specific pr. If comment.User is an empty string then any user
// could be the author of the latest pull request. If comment.Comment is an empty
// string an error is returned.
func (g *Github) getLatestPullRequestComment(pr int, comment PullRequestComment) (*PullRequestComment, error) {
	if len(comment.Comment) == 0 {
		return nil, fmt.Errorf("comment cannot be an empty string")
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeoutLongRequest)
	defer cancel()

	comments, _, err := g.client.Issues.ListComments(ctx, g.owner, g.repo, pr, nil)
	if err != nil {
		return nil, err
	}

	for i := len(comments) - 1; i >= 0; i-- {
		c := comments[i]
		if len(comment.User) != 0 {
			if strings.Compare(*c.User.Login, comment.User) != 0 {
				continue
			}
		}

		if strings.Compare(*c.Body, comment.Comment) == 0 {
			return &PullRequestComment{
				User:    comment.User,
				Comment: comment.Comment,
				time:    *c.CreatedAt,
			}, nil
		}
	}

	return nil, fmt.Errorf("comment '%+v' not found", comment)
}

func (g *Github) downloadPullRequest(pr int, workingDirectory string) (string, error) {
	projectDirectory, err := filepath.Abs(workingDirectory)
	if err != nil {
		return "", err
	}

	projectDirectory = filepath.Join(projectDirectory, g.repo)
	if err := os.MkdirAll(projectDirectory, 0755); err != nil {
		return "", fmt.Errorf("failed to create project directory %s", err)
	}

	var stderr bytes.Buffer

	cmd := exec.Command("git", "clone", g.url, ".")
	cmd.Dir = projectDirectory
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("failed to run git clone %s %s", stderr.String(), err)
	}

	stderr.Reset()
	cmd = exec.Command("git", "-c", "user.name='Foo Bar'", "-c", "user.email='foo@bar.com'",
		"pull", "--no-edit", "origin", fmt.Sprintf("pull/%d/head", pr))
	cmd.Dir = projectDirectory
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("failed to run git pull %s %s", stderr.String(), err)
	}

	return projectDirectory, nil
}

// createComment creates a comment in the specific pr
func (g *Github) createComment(pr int, comment string) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutLongRequest)
	defer cancel()

	c := &github.IssueComment{Body: &comment}

	_, _, err := g.client.Issues.CreateComment(ctx, g.owner, g.repo, pr, c)

	return err
}

// isMember returns true if the user is member of the organization, else false
func (g *Github) isMember(user string) (bool, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	ret, _, err := g.client.Organizations.IsMember(ctx, g.owner, user)

	return ret, err
}
<commit_msg>localCI: fix crash getting pull requests<commit_after>// Copyright (c) 2017 Intel Corporation
//
// 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 (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

// Github represents a control version repository to
// interact with github.com
type Github struct {
	client *github.Client
	owner  string
	repo   string
	url    string
}

const (
	timeoutShortRequest = 10 * time.Second
	timeoutLongRequest  = 20 * time.Second
)

// newGithub returns an object of type Github
func newGithub(url, token string) (CVR, error) {
	url = strings.TrimSpace(url)

	ownerRepo := strings.SplitAfter(url, "/"+githubDomain+"/")

	// at least we need two tokens
	if len(ownerRepo) < 2 {
		return nil, fmt.Errorf("missing owner and repo %s", url)
	}

	ownerRepo = strings.Split(ownerRepo[1], "/")

	// at least we need two tokens: owner and repo
	if len(ownerRepo) < 2 {
		return nil, fmt.Errorf("failed to get owner and repo %s", url)
	}

	if len(ownerRepo[0]) == 0 {
		return nil, fmt.Errorf("missing owner in url %s", url)
	}

	if len(ownerRepo[1]) == 0 {
		return nil, fmt.Errorf("missing repository in url %s", url)
	}

	// create a new http client using the token
	var client *http.Client
	if token != "" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		)
		client = oauth2.NewClient(context.Background(), ts)
	}

	return &Github{
		client: github.NewClient(client),
		owner:  ownerRepo[0],
		repo:   ownerRepo[1],
		url:    url,
	}, nil
}

// getDomain returns the domain name
func (g *Github) getDomain() string {
	return githubDomain
}

// getOwner returns the owner of the repo
func (g *Github) getOwner() string {
	return g.owner
}

// getRepo returns the repository name
func (g *Github) getRepo() string {
	return g.repo
}

// getOpenPullRequests returns the open pull requests
func (g *Github) getOpenPullRequests() (map[string]*PullRequest, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	pullRequests, _, err := g.client.PullRequests.List(ctx, g.owner, g.repo, nil)
	if err != nil {
		ciLog.Errorf("failed to list pull requests: %s", err)
		return nil, err
	}

	prs := make(map[string]*PullRequest)

	for _, pr := range pullRequests {
		if pr == nil || pr.Number == nil {
			continue
		}
		number := *pr.Number

		pullRequest, err := g.getPullRequest(number)
		if err != nil {
			ciLog.Errorf("failed to get pull request %d: %s", number, err)
			continue
		}

		prs[strconv.Itoa(number)] = pullRequest
	}

	return prs, nil
}

// getPullRequest returns a specific pull request
func (g *Github) getPullRequest(pr int) (*PullRequest, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	// get all commits of the pull request
	listCommits, _, err := g.client.PullRequests.ListCommits(ctx, g.owner, g.repo, pr, nil)
	if err != nil {
		return nil, err
	}

	var commits []PullRequestCommit
	for _, c := range listCommits {
		if c == nil {
			return nil, fmt.Errorf("failed to get all commits of the pull request %d", pr)
		}

		if c.SHA == nil {
			return nil, fmt.Errorf("failed to get commit SHA of the pull request %d", pr)
		}
		sha := *c.SHA

		if c.Commit == nil || c.Commit.Committer == nil || c.Commit.Committer.Date == nil {
			return nil, fmt.Errorf("failed to get commit time of the pull request %d", pr)
		}
		time := *c.Commit.Committer.Date

		commits = append(commits,
			PullRequestCommit{
				Sha:  sha,
				Time: time,
			},
		)
	}

	pullRequest, _, err := g.client.PullRequests.Get(ctx, g.owner, g.repo, pr)
	if err != nil {
		return nil, err
	}

	// check the integrity of the pullRuest object before use it
	if pullRequest == nil {
		return nil, fmt.Errorf("failed to get pull request %d", pr)
	}

	if pullRequest.User == nil || pullRequest.User.Login == nil {
		return nil, fmt.Errorf("failed to get the author of the pull request %d", pr)
	}

	author := *pullRequest.User.Login

	if pullRequest.Mergeable == nil {
		return nil, fmt.Errorf("Unable to know if the pull request %d is mergeable", pr)
	}

	mergeable := *pullRequest.Mergeable

	return &PullRequest{
		Number:    pr,
		Commits:   commits,
		Author:    author,
		Mergeable: mergeable,
	}, nil
}

// getLatestPullRequestComment returns the latest comment of a specific
// user in the specific pr. If comment.User is an empty string then any user
// could be the author of the latest pull request. If comment.Comment is an empty
// string an error is returned.
func (g *Github) getLatestPullRequestComment(pr int, comment PullRequestComment) (*PullRequestComment, error) {
	if len(comment.Comment) == 0 {
		return nil, fmt.Errorf("comment cannot be an empty string")
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeoutLongRequest)
	defer cancel()

	comments, _, err := g.client.Issues.ListComments(ctx, g.owner, g.repo, pr, nil)
	if err != nil {
		return nil, err
	}

	for i := len(comments) - 1; i >= 0; i-- {
		c := comments[i]
		if len(comment.User) != 0 {
			if strings.Compare(*c.User.Login, comment.User) != 0 {
				continue
			}
		}

		if strings.Compare(*c.Body, comment.Comment) == 0 {
			return &PullRequestComment{
				User:    comment.User,
				Comment: comment.Comment,
				time:    *c.CreatedAt,
			}, nil
		}
	}

	return nil, fmt.Errorf("comment '%+v' not found", comment)
}

func (g *Github) downloadPullRequest(pr int, workingDirectory string) (string, error) {
	projectDirectory, err := filepath.Abs(workingDirectory)
	if err != nil {
		return "", err
	}

	projectDirectory = filepath.Join(projectDirectory, g.repo)
	if err := os.MkdirAll(projectDirectory, 0755); err != nil {
		return "", fmt.Errorf("failed to create project directory %s", err)
	}

	var stderr bytes.Buffer

	cmd := exec.Command("git", "clone", g.url, ".")
	cmd.Dir = projectDirectory
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("failed to run git clone %s %s", stderr.String(), err)
	}

	stderr.Reset()
	cmd = exec.Command("git", "-c", "user.name='Foo Bar'", "-c", "user.email='foo@bar.com'",
		"pull", "--no-edit", "origin", fmt.Sprintf("pull/%d/head", pr))
	cmd.Dir = projectDirectory
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("failed to run git pull %s %s", stderr.String(), err)
	}

	return projectDirectory, nil
}

// createComment creates a comment in the specific pr
func (g *Github) createComment(pr int, comment string) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutLongRequest)
	defer cancel()

	c := &github.IssueComment{Body: &comment}

	_, _, err := g.client.Issues.CreateComment(ctx, g.owner, g.repo, pr, c)

	return err
}

// isMember returns true if the user is member of the organization, else false
func (g *Github) isMember(user string) (bool, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeoutShortRequest)
	defer cancel()

	ret, _, err := g.client.Organizations.IsMember(ctx, g.owner, user)

	return ret, err
}
<|endoftext|>