<commit_before>package main

import (
	"fmt"
	"os"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/cfpb/rhobot/config"
	"github.com/cfpb/rhobot/database"
	"github.com/cfpb/rhobot/gocd"
	"github.com/cfpb/rhobot/healthcheck"
	"github.com/cfpb/rhobot/report"
	"github.com/codegangsta/cli"
)

func main() {

	app := cli.NewApp()
	app.Name = "Rhobot"
	app.Usage = "Rhobot is a database development tool that uses DevOps best practices."
	app.EnableBashCompletion = true

	config := config.NewConfig()

	logLevelFlag := cli.StringFlag{
		Name:  "loglevel, lvl",
		Value: "",
		Usage: "sets the log level for Rhobot",
	}
	gocdHostFlag := cli.StringFlag{
		Name:  "host",
		Value: "",
		Usage: "host of the GoCD server",
	}
	reportFileFlag := cli.StringFlag{
		Name:  "report",
		Value: "",
		Usage: "path to the healthcheck report",
	}
	dburiFlag := cli.StringFlag{
		Name:  "dburi",
		Value: "",
		Usage: "database uri postgres://user:password@host:port/database",
	}
	emailListFlag := cli.StringFlag{
		Name:  "email",
		Value: "",
		Usage: "yaml file containing email distribution list",
	}

	app.Flags = []cli.Flag{logLevelFlag}
	app.Commands = []cli.Command{
		{
			Name:  "healthchecks",
			Usage: "HEALTHCHECK_FILE [--dburi DATABASE_URI] [--report REPORT_FILE] [--email DISTRIBUTION_FILE]",
			Flags: []cli.Flag{
				reportFileFlag,
				dburiFlag,
				emailListFlag,
			},
			Action: func(c *cli.Context) {
				if c.String("loglevel") != "" {
					config.SetLogLevel(c.String("loglevel"))
				}

				// variables to be populated by cli args
				var healthcheckPath string
				var reportPath string
				var emailListPath string

				if c.Args().Get(0) != "" {
					healthcheckPath = c.Args().Get(0)
				} else {
					log.Error("You must provide the path to the healthcheck file.")
					return
				}
				log.Info("Running health checks from ", healthcheckPath)

				if c.String("dburi") != "" {
					config.SetDBURI(c.String("dburi"))
				}
				log.Debug("DB_URI: ", config.DBURI())

				if c.String("report") != "" {
					reportPath = c.String("report")
					log.Debugf("Generating report at %v", reportPath)
				}

				if c.String("email") != "" {
					emailListPath = c.String("email")
					log.Debugf("Emailing report to %v", emailListPath)
				}

				healthcheckRunner(config, healthcheckPath, reportPath, emailListPath)
				log.Info("Success!")
			},
		},
		{
			Name:    "pipeline",
			Aliases: []string{},
			Usage:   "Interact with GoCD pipeline",
			Subcommands: []cli.Command{
				{
					Name:  "push",
					Usage: "PATH [PIPELINE_GROUP]",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						if c.String("loglevel") != "" {
							config.SetLogLevel(c.String("loglevel"))
						}

						if c.String("host") != "" {
							log.Debug("Setting GoCD host: ", c.String("host"))
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 0 {
							path := c.Args()[0]
							group := c.Args().Get(1)
							log.Infof("Pushing config from %v to pipeline group %v...", path, group)
							if err := gocd.Push(config.GoCDURL(), path, group); err != nil {
								log.Fatal("Failed to push pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A path to the pipeline config to push is required.")
						}
					},
				},
				{
					Name:  "pull",
					Usage: "PATH",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						if c.String("loglevel") != "" {
							config.SetLogLevel(c.String("loglevel"))
						}

						if c.String("host") != "" {
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 0 {
							path := c.Args()[0]
							log.Infof("Pulling config from %v to %v...", config.GoCDURL(), path)
							if err := gocd.Pull(config.GoCDURL(), path); err != nil {
								log.Fatal("Failed to pull pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A path to pull the pipeline config to is required.")
						}
					},
				},
				{
					Name:  "clone",
					Usage: "PIPELINE_NAME PATH",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						if c.String("loglevel") != "" {
							config.SetLogLevel(c.String("loglevel"))
						}

						if c.String("host") != "" {
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 1 {
							name := c.Args()[0]
							path := c.Args()[1]
							log.Infof("Cloning pipeline %v to %v...", name, path)
							if err := gocd.Clone(config.GoCDURL(), path, name); err != nil {
								log.Fatal("Failed to clone pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A pipeline name and a path to clone to are required.")
						}
					},
				},
			},
		},
	}

	app.Run(os.Args)
}

func healthcheckRunner(config *config.Config, healthcheckPath string, reportPath string, emailListPath string) {
	healthChecks, err := healthcheck.ReadHealthCheckYAMLFromFile(healthcheckPath)
	if err != nil {
		log.Fatal("Failed to read healthchecks: ", err)
	}
	cxn := database.GetPGConnection(config.DBURI())

	// TODO the error returned from PreformHealthChecks determis a bad exit
	results, _ := healthcheck.PreformHealthChecks(healthChecks, cxn)
	var elements []report.Element
	for _, val := range results {
		elements = append(elements, val)
	}

	// Make Templated report
	metadata := map[string]interface{}{
		"name":      healthChecks.Name,
		"db_name":   config.PgDatabase,
		"footer":    healthcheck.FooterHealthcheck,
		"timestamp": time.Now().UTC().String(),
	}

	prr := report.NewPongo2ReportRunnerFromString(healthcheck.TemplateHealthcheck)
	rs := report.Set{Elements: elements, Metadata: metadata}
	reader, _ := prr.ReportReader(rs)

	// Write report to file
	if reportPath != "" {
		fhr := report.FileHandler{Filename: reportPath}
		_ = fhr.HandleReport(reader)
	}

	// Email report
	if emailListPath != "" {

		df, err := report.ReadDistributionFormatYAMLFromFile(emailListPath)
		if err != nil {
			log.Fatal("Failed to read distribution format: ", err)
		}

		for _, level := range report.LogLevelArray {

			// TODO calculate a subject line to include hostname, DB, # of failed HCs
			hcName := metadata["name"]
			if hcName == "" {
				hcName = "healthchecks"
			}
			subjectStr := fmt.Sprintf("%s for %s at %s level",
				hcName, metadata["db_name"], strings.ToUpper(level))

			logFilteredSet := report.FilterReportSet(rs, level)
			reader, _ := prr.ReportReader(logFilteredSet)
			recipients := df.GetEmails(level)

			if recipients != nil && len(recipients) != 0 && len(logFilteredSet.Elements) != 0 {
				log.Infof("Send %s to: %v", subjectStr, recipients)
				ehr := report.EmailHandler{
					SMTPHost:    config.SMTPHost,
					SMTPPort:    config.SMTPPort,
					SenderEmail: config.SMTPEmail,
					SenderName:  config.SMTPName,
					Subject:     subjectStr,
					Recipients:  recipients,
					HTML:        true,
				}
				err = ehr.HandleReport(reader)
				if err != nil {
					log.Warn("Failed to email report: ", err)
				}
			}
		}

	}
}
<commit_msg>updateLogLevel uses GlobalString<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/cfpb/rhobot/config"
	"github.com/cfpb/rhobot/database"
	"github.com/cfpb/rhobot/gocd"
	"github.com/cfpb/rhobot/healthcheck"
	"github.com/cfpb/rhobot/report"
	"github.com/codegangsta/cli"
)

func main() {

	app := cli.NewApp()
	app.Name = "Rhobot"
	app.Usage = "Rhobot is a database development tool that uses DevOps best practices."
	app.EnableBashCompletion = true

	config := config.NewConfig()

	logLevelFlag := cli.StringFlag{
		Name:  "loglevel, lvl",
		Value: "",
		Usage: "sets the log level for Rhobot",
	}
	gocdHostFlag := cli.StringFlag{
		Name:  "host",
		Value: "",
		Usage: "host of the GoCD server",
	}
	reportFileFlag := cli.StringFlag{
		Name:  "report",
		Value: "",
		Usage: "path to the healthcheck report",
	}
	dburiFlag := cli.StringFlag{
		Name:  "dburi",
		Value: "",
		Usage: "database uri postgres://user:password@host:port/database",
	}
	emailListFlag := cli.StringFlag{
		Name:  "email",
		Value: "",
		Usage: "yaml file containing email distribution list",
	}

	app.Flags = []cli.Flag{logLevelFlag}
	app.Commands = []cli.Command{
		{
			Name:  "healthchecks",
			Usage: "HEALTHCHECK_FILE [--dburi DATABASE_URI] [--report REPORT_FILE] [--email DISTRIBUTION_FILE]",
			Flags: []cli.Flag{
				reportFileFlag,
				dburiFlag,
				emailListFlag,
			},
			Action: func(c *cli.Context) {
				updateLogLevel(c, config)

				// variables to be populated by cli args
				var healthcheckPath string
				var reportPath string
				var emailListPath string

				if c.Args().Get(0) != "" {
					healthcheckPath = c.Args().Get(0)
				} else {
					log.Error("You must provide the path to the healthcheck file.")
					return
				}
				log.Info("Running health checks from ", healthcheckPath)

				if c.String("dburi") != "" {
					config.SetDBURI(c.String("dburi"))
				}
				log.Debug("DB_URI: ", config.DBURI())

				if c.String("report") != "" {
					reportPath = c.String("report")
					log.Debugf("Generating report at %v", reportPath)
				}

				if c.String("email") != "" {
					emailListPath = c.String("email")
					log.Debugf("Emailing report to %v", emailListPath)
				}

				healthcheckRunner(config, healthcheckPath, reportPath, emailListPath)
				log.Info("Success!")
			},
		},
		{
			Name:    "pipeline",
			Aliases: []string{},
			Usage:   "Interact with GoCD pipeline",
			Subcommands: []cli.Command{
				{
					Name:  "push",
					Usage: "PATH [PIPELINE_GROUP]",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						updateLogLevel(c, config)

						if c.String("host") != "" {
							log.Debug("Setting GoCD host: ", c.String("host"))
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 0 {
							path := c.Args()[0]
							group := c.Args().Get(1)
							log.Infof("Pushing config from %v to pipeline group %v...", path, group)
							if err := gocd.Push(config.GoCDURL(), path, group); err != nil {
								log.Fatal("Failed to push pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A path to the pipeline config to push is required.")
						}
					},
				},
				{
					Name:  "pull",
					Usage: "PATH",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						updateLogLevel(c, config)

						if c.String("host") != "" {
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 0 {
							path := c.Args()[0]
							log.Infof("Pulling config from %v to %v...", config.GoCDURL(), path)
							if err := gocd.Pull(config.GoCDURL(), path); err != nil {
								log.Fatal("Failed to pull pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A path to pull the pipeline config to is required.")
						}
					},
				},
				{
					Name:  "clone",
					Usage: "PIPELINE_NAME PATH",
					Flags: []cli.Flag{
						gocdHostFlag,
					},
					Action: func(c *cli.Context) {
						updateLogLevel(c, config)

						if c.String("host") != "" {
							config.SetGoCDHost(c.String("host"))
						}

						if len(c.Args()) > 1 {
							name := c.Args()[0]
							path := c.Args()[1]
							log.Infof("Cloning pipeline %v to %v...", name, path)
							if err := gocd.Clone(config.GoCDURL(), path, name); err != nil {
								log.Fatal("Failed to clone pipeline config: ", err)
							}
							log.Info("Success!")
						} else {
							log.Fatal("A pipeline name and a path to clone to are required.")
						}
					},
				},
			},
		},
	}

	app.Run(os.Args)
}

func updateLogLevel(c *cli.Context, config *config.Config) {
	if c.GlobalString("loglevel") != "" {
		config.SetLogLevel(c.GlobalString("loglevel"))
	}
}

func healthcheckRunner(config *config.Config, healthcheckPath string, reportPath string, emailListPath string) {
	healthChecks, err := healthcheck.ReadHealthCheckYAMLFromFile(healthcheckPath)
	if err != nil {
		log.Fatal("Failed to read healthchecks: ", err)
	}
	cxn := database.GetPGConnection(config.DBURI())

	// TODO the error returned from PreformHealthChecks determis a bad exit
	results, HCerr := healthcheck.PreformHealthChecks(healthChecks, cxn)
	if HCerr != nil {
		log.Fatal("Failed to read healthchecks: ", HCerr)
	}
	var elements []report.Element
	for _, val := range results {
		elements = append(elements, val)
	}

	// Make Templated report
	metadata := map[string]interface{}{
		"name":      healthChecks.Name,
		"db_name":   config.PgDatabase,
		"footer":    healthcheck.FooterHealthcheck,
		"timestamp": time.Now().UTC().String(),
	}

	prr := report.NewPongo2ReportRunnerFromString(healthcheck.TemplateHealthcheck)
	rs := report.Set{Elements: elements, Metadata: metadata}
	reader, _ := prr.ReportReader(rs)

	// Write report to file
	if reportPath != "" {
		fhr := report.FileHandler{Filename: reportPath}
		_ = fhr.HandleReport(reader)
	}

	// Email report
	if emailListPath != "" {

		df, err := report.ReadDistributionFormatYAMLFromFile(emailListPath)
		if err != nil {
			log.Fatal("Failed to read distribution format: ", err)
		}

		for _, level := range report.LogLevelArray {

			// TODO calculate a subject line to include hostname, DB, # of failed HCs
			hcName := metadata["name"]
			if hcName == "" {
				hcName = "healthchecks"
			}
			subjectStr := fmt.Sprintf("%s for %s at %s level",
				hcName, metadata["db_name"], strings.ToUpper(level))

			logFilteredSet := report.FilterReportSet(rs, level)
			reader, _ := prr.ReportReader(logFilteredSet)
			recipients := df.GetEmails(level)

			if recipients != nil && len(recipients) != 0 && len(logFilteredSet.Elements) != 0 {
				log.Infof("Send %s to: %v", subjectStr, recipients)
				ehr := report.EmailHandler{
					SMTPHost:    config.SMTPHost,
					SMTPPort:    config.SMTPPort,
					SenderEmail: config.SMTPEmail,
					SenderName:  config.SMTPName,
					Subject:     subjectStr,
					Recipients:  recipients,
					HTML:        true,
				}
				err = ehr.HandleReport(reader)
				if err != nil {
					log.Warn("Failed to email report: ", err)
				}
			}
		}

	}
}
<|endoftext|>


<commit_before>package v3

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Token struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Token           string            `json:"token" norman:"writeOnly,noupdate"`
	UserPrincipal   Principal         `json:"userPrincipal" norman:"type=reference[principal]"`
	GroupPrincipals []Principal       `json:"groupPrincipals" norman:"type=array[reference[principal]]"`
	ProviderInfo    map[string]string `json:"providerInfo,omitempty"`
	UserID          string            `json:"userId" norman:"type=reference[user]"`
	AuthProvider    string            `json:"authProvider"`
	TTLMillis       int64             `json:"ttl"`
	LastUpdateTime  string            `json:"lastUpdateTime"`
	IsDerived       bool              `json:"isDerived"`
	Description     string            `json:"description"`
	Expired         bool              `json:"expired"`
	ExpiresAt       string            `json:"expiresAt"`
}

type User struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName        string   `json:"displayName,omitempty"`
	Description        string   `json:"description"`
	Username           string   `json:"username,omitempty"`
	Password           string   `json:"password,omitempty" norman:"writeOnly,noupdate"`
	MustChangePassword bool     `json:"mustChangePassword,omitempty"`
	PrincipalIDs       []string `json:"principalIds,omitempty" norman:"type=array[reference[principal]]"`
	Me                 bool     `json:"me,omitempty"`
}

// UserAttribute will have a CRD (and controller) generated for it, but will not be exposed in the API.
type UserAttribute struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	UserName        string
	GroupPrincipals map[string]Principals // the value is a []Principal, but code generator cannot handle slice as a value
}

type Principals struct {
	Items []Principal
}

type Group struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName string `json:"displayName,omitempty"`
}

type GroupMember struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	GroupName   string `json:"groupName,omitempty" norman:"type=reference[group]"`
	PrincipalID string `json:"principalId,omitempty" norman:"type=reference[principal]"`
}

type Principal struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName    string            `json:"displayName,omitempty"`
	LoginName      string            `json:"loginName,omitempty"`
	ProfilePicture string            `json:"profilePicture,omitempty"`
	ProfileURL     string            `json:"profileURL,omitempty"`
	PrincipalType  string            `json:"principalType,omitempty"`
	Me             bool              `json:"me,omitempty"`
	MemberOf       bool              `json:"memberOf,omitempty"`
	Provider       string            `json:"provider,omitempty"`
	ExtraInfo      map[string]string `json:"extraInfo,omitempty"`
}

type SearchPrincipalsInput struct {
	Name          string `json:"name" norman:"type=string,required,notnullable"`
	PrincipalType string `json:"principalType,omitempty" norman:"type=enum,options=user|group"`
}

type ChangePasswordInput struct {
	CurrentPassword string `json:"currentPassword" norman:"type=string,required"`
	NewPassword     string `json:"newPassword" norman:"type=string,required"`
}

type SetPasswordInput struct {
	NewPassword string `json:"newPassword" norman:"type=string,required"`
}

type AuthConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Type                string   `json:"type" norman:"noupdate"`
	Enabled             bool     `json:"enabled,omitempty"`
	AccessMode          string   `json:"accessMode,omitempty" norman:"required,notnullable,type=enum,options=required|restricted|unrestricted"`
	AllowedPrincipalIDs []string `json:"allowedPrincipalIds,omitempty" norman:"type=array[reference[principal]]"`
}

type LocalConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`
}

type GithubConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Hostname     string `json:"hostname,omitempty" norman:"default=github.com" norman:"required"`
	TLS          bool   `json:"tls,omitempty" norman:"notnullable,default=true" norman:"required"`
	ClientID     string `json:"clientId,omitempty" norman:"required"`
	ClientSecret string `json:"clientSecret,omitempty" norman:"required,type=password"`
}

type GithubConfigTestOutput struct {
	RedirectURL string `json:"redirectUrl"`
}

type GithubConfigApplyInput struct {
	GithubConfig GithubConfig `json:"githubConfig,omitempty"`
	Code         string       `json:"code,omitempty"`
	Enabled      bool         `json:"enabled,omitempty"`
}

type AzureADConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Endpoint          string `json:"endpoint,omitempty" norman:"default=https://login.microsoftonline.com/,required,notnullable"`
	GraphEndpoint     string `json:"graphEndpoint,omitempty" norman:"required,notnullable"`
	TokenEndpoint     string `json:"tokenEndpoint,omitempty" norman:"required,notnullable"`
	AuthEndpoint      string `json:"authEndpoint,omitempty" norman:"required,notnullable"`
	TenantID          string `json:"tenantId,omitempty" norman:"required,notnullable"`
	ApplicationID     string `json:"applicationId,omitempty" norman:"required,notnullable"`
	ApplicationSecret string `json:"applicationSecret,omitempty" norman:"required,notnullable,type=password"`
	RancherURL        string `json:"rancherUrl,omitempty" norman:"required,notnullable"`
}

type AzureADConfigTestOutput struct {
	RedirectURL string `json:"redirectUrl"`
}

type AzureADConfigApplyInput struct {
	Config AzureADConfig `json:"config,omitempty"`
	Code   string        `json:"code,omitempty"`
}

type ActiveDirectoryConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Servers                      []string `json:"servers,omitempty"                     norman:"type=array[string],required"`
	Port                         int64    `json:"port,omitempty"                        norman:"default=389"`
	TLS                          bool     `json:"tls,omitempty"                         norman:"default=false"`
	Certificate                  string   `json:"certificate,omitempty"`
	DefaultLoginDomain           string   `json:"defaultLoginDomain,omitempty"`
	ServiceAccountUsername       string   `json:"serviceAccountUsername,omitempty"      norman:"required"`
	ServiceAccountPassword       string   `json:"serviceAccountPassword,omitempty"      norman:"type=password,required"`
	UserDisabledBitMask          int64    `json:"userDisabledBitMask,omitempty"         norman:"default=2"`
	UserSearchBase               string   `json:"userSearchBase,omitempty"              norman:"required"`
	UserSearchAttribute          string   `json:"userSearchAttribute,omitempty"         norman:"default=sAMAccountName|sn|givenName,required"`
	UserLoginAttribute           string   `json:"userLoginAttribute,omitempty"          norman:"default=sAMAccountName,required"`
	UserObjectClass              string   `json:"userObjectClass,omitempty"             norman:"default=person,required"`
	UserNameAttribute            string   `json:"userNameAttribute,omitempty"           norman:"default=name,required"`
	UserEnabledAttribute         string   `json:"userEnabledAttribute,omitempty"        norman:"default=userAccountControl,required"`
	GroupSearchBase              string   `json:"groupSearchBase,omitempty"`
	GroupSearchAttribute         string   `json:"groupSearchAttribute,omitempty"        norman:"default=sAMAccountName,required"`
	GroupObjectClass             string   `json:"groupObjectClass,omitempty"            norman:"default=group,required"`
	GroupNameAttribute           string   `json:"groupNameAttribute,omitempty"          norman:"default=name,required"`
	GroupDNAttribute             string   `json:"groupDNAttribute,omitempty"            norman:"default=distinguishedName,required"`
	GroupMemberUserAttribute     string   `json:"groupMemberUserAttribute,omitempty"    norman:"default=distinguishedName,required"`
	GroupMemberMappingAttribute  string   `json:"groupMemberMappingAttribute,omitempty" norman:"default=member,required"`
	ConnectionTimeout            int64    `json:"connectionTimeout,omitempty"           norman:"default=5000,notnullable,required"`
	NestedGroupMembershipEnabled *bool    `json:"nestedGroupMembershipEnabled,omitempty" norman:"default=true"`
}

type ActiveDirectoryTestAndApplyInput struct {
	ActiveDirectoryConfig ActiveDirectoryConfig `json:"activeDirectoryConfig,omitempty"`
	Username              string                `json:"username"`
	Password              string                `json:"password"`
	Enabled               bool                  `json:"enabled,omitempty"`
}

type LdapConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Servers                         []string `json:"servers,omitempty"                     norman:"type=array[string],notnullable,required"`
	Port                            int64    `json:"port,omitempty"                        norman:"default=389,notnullable,required"`
	TLS                             bool     `json:"tls,omitempty"                         norman:"default=false,notnullable,required"`
	Certificate                     string   `json:"certificate,omitempty"`
	ServiceAccountDistinguishedName string   `json:"serviceAccountDistinguishedName,omitempty"      norman:"required"`
	ServiceAccountPassword          string   `json:"serviceAccountPassword,omitempty"      norman:"type=password,required"`
	UserDisabledBitMask             int64    `json:"userDisabledBitMask,omitempty"`
	UserSearchBase                  string   `json:"userSearchBase,omitempty"              norman:"notnullable,required"`
	UserSearchAttribute             string   `json:"userSearchAttribute,omitempty"         norman:"default=uid|sn|givenName,notnullable,required"`
	UserLoginAttribute              string   `json:"userLoginAttribute,omitempty"          norman:"default=uid,notnullable,required"`
	UserObjectClass                 string   `json:"userObjectClass,omitempty"             norman:"default=inetOrgPerson,notnullable,required"`
	UserNameAttribute               string   `json:"userNameAttribute,omitempty"           norman:"default=cn,notnullable,required"`
	UserMemberAttribute             string   `json:"userMemberAttribute,omitempty"           norman:"default=memberOf,notnullable,required"`
	UserEnabledAttribute            string   `json:"userEnabledAttribute,omitempty"`
	GroupSearchBase                 string   `json:"groupSearchBase,omitempty"`
	GroupSearchAttribute            string   `json:"groupSearchAttribute,omitempty"        norman:"default=cn,notnullable,required"`
	GroupObjectClass                string   `json:"groupObjectClass,omitempty"            norman:"default=groupOfNames,notnullable,required"`
	GroupNameAttribute              string   `json:"groupNameAttribute,omitempty"          norman:"default=cn,notnullable,required"`
	GroupDNAttribute                string   `json:"groupDNAttribute,omitempty"            norman:"default=entryDN,notnullable"`
	GroupMemberUserAttribute        string   `json:"groupMemberUserAttribute,omitempty"    norman:"default=entryDN,notnullable"`
	GroupMemberMappingAttribute     string   `json:"groupMemberMappingAttribute,omitempty" norman:"default=member,notnullable,required"`
	ConnectionTimeout               int64    `json:"connectionTimeout,omitempty"           norman:"default=5000,notnullable,required"`
	NestedGroupMembershipEnabled    bool     `json:"nestedGroupMembershipEnabled"    norman:"default=true"`
}

type LdapTestAndApplyInput struct {
	LdapConfig `json:"ldapConfig,omitempty"`
	Username   string `json:"username"`
	Password   string `json:"password" norman:"type=password,required"`
}

type OpenLdapConfig struct {
	LdapConfig `json:",inline" mapstructure:",squash"`
}

type OpenLdapTestAndApplyInput struct {
	LdapTestAndApplyInput `json:",inline" mapstructure:",squash"`
}

type FreeIpaConfig struct {
	LdapConfig `json:",inline" mapstructure:",squash"`
}

type FreeIpaTestAndApplyInput struct {
	LdapTestAndApplyInput `json:",inline" mapstructure:",squash"`
}
<commit_msg>Vendor changes-nested membership<commit_after>package v3

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Token struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Token           string            `json:"token" norman:"writeOnly,noupdate"`
	UserPrincipal   Principal         `json:"userPrincipal" norman:"type=reference[principal]"`
	GroupPrincipals []Principal       `json:"groupPrincipals" norman:"type=array[reference[principal]]"`
	ProviderInfo    map[string]string `json:"providerInfo,omitempty"`
	UserID          string            `json:"userId" norman:"type=reference[user]"`
	AuthProvider    string            `json:"authProvider"`
	TTLMillis       int64             `json:"ttl"`
	LastUpdateTime  string            `json:"lastUpdateTime"`
	IsDerived       bool              `json:"isDerived"`
	Description     string            `json:"description"`
	Expired         bool              `json:"expired"`
	ExpiresAt       string            `json:"expiresAt"`
}

type User struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName        string   `json:"displayName,omitempty"`
	Description        string   `json:"description"`
	Username           string   `json:"username,omitempty"`
	Password           string   `json:"password,omitempty" norman:"writeOnly,noupdate"`
	MustChangePassword bool     `json:"mustChangePassword,omitempty"`
	PrincipalIDs       []string `json:"principalIds,omitempty" norman:"type=array[reference[principal]]"`
	Me                 bool     `json:"me,omitempty"`
}

// UserAttribute will have a CRD (and controller) generated for it, but will not be exposed in the API.
type UserAttribute struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	UserName        string
	GroupPrincipals map[string]Principals // the value is a []Principal, but code generator cannot handle slice as a value
}

type Principals struct {
	Items []Principal
}

type Group struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName string `json:"displayName,omitempty"`
}

type GroupMember struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	GroupName   string `json:"groupName,omitempty" norman:"type=reference[group]"`
	PrincipalID string `json:"principalId,omitempty" norman:"type=reference[principal]"`
}

type Principal struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	DisplayName    string            `json:"displayName,omitempty"`
	LoginName      string            `json:"loginName,omitempty"`
	ProfilePicture string            `json:"profilePicture,omitempty"`
	ProfileURL     string            `json:"profileURL,omitempty"`
	PrincipalType  string            `json:"principalType,omitempty"`
	Me             bool              `json:"me,omitempty"`
	MemberOf       bool              `json:"memberOf,omitempty"`
	Provider       string            `json:"provider,omitempty"`
	ExtraInfo      map[string]string `json:"extraInfo,omitempty"`
}

type SearchPrincipalsInput struct {
	Name          string `json:"name" norman:"type=string,required,notnullable"`
	PrincipalType string `json:"principalType,omitempty" norman:"type=enum,options=user|group"`
}

type ChangePasswordInput struct {
	CurrentPassword string `json:"currentPassword" norman:"type=string,required"`
	NewPassword     string `json:"newPassword" norman:"type=string,required"`
}

type SetPasswordInput struct {
	NewPassword string `json:"newPassword" norman:"type=string,required"`
}

type AuthConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Type                string   `json:"type" norman:"noupdate"`
	Enabled             bool     `json:"enabled,omitempty"`
	AccessMode          string   `json:"accessMode,omitempty" norman:"required,notnullable,type=enum,options=required|restricted|unrestricted"`
	AllowedPrincipalIDs []string `json:"allowedPrincipalIds,omitempty" norman:"type=array[reference[principal]]"`
}

type LocalConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`
}

type GithubConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Hostname     string `json:"hostname,omitempty" norman:"default=github.com" norman:"required"`
	TLS          bool   `json:"tls,omitempty" norman:"notnullable,default=true" norman:"required"`
	ClientID     string `json:"clientId,omitempty" norman:"required"`
	ClientSecret string `json:"clientSecret,omitempty" norman:"required,type=password"`
}

type GithubConfigTestOutput struct {
	RedirectURL string `json:"redirectUrl"`
}

type GithubConfigApplyInput struct {
	GithubConfig GithubConfig `json:"githubConfig,omitempty"`
	Code         string       `json:"code,omitempty"`
	Enabled      bool         `json:"enabled,omitempty"`
}

type AzureADConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Endpoint          string `json:"endpoint,omitempty" norman:"default=https://login.microsoftonline.com/,required,notnullable"`
	GraphEndpoint     string `json:"graphEndpoint,omitempty" norman:"required,notnullable"`
	TokenEndpoint     string `json:"tokenEndpoint,omitempty" norman:"required,notnullable"`
	AuthEndpoint      string `json:"authEndpoint,omitempty" norman:"required,notnullable"`
	TenantID          string `json:"tenantId,omitempty" norman:"required,notnullable"`
	ApplicationID     string `json:"applicationId,omitempty" norman:"required,notnullable"`
	ApplicationSecret string `json:"applicationSecret,omitempty" norman:"required,notnullable,type=password"`
	RancherURL        string `json:"rancherUrl,omitempty" norman:"required,notnullable"`
}

type AzureADConfigTestOutput struct {
	RedirectURL string `json:"redirectUrl"`
}

type AzureADConfigApplyInput struct {
	Config AzureADConfig `json:"config,omitempty"`
	Code   string        `json:"code,omitempty"`
}

type ActiveDirectoryConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Servers                      []string `json:"servers,omitempty"                     norman:"type=array[string],required"`
	Port                         int64    `json:"port,omitempty"                        norman:"default=389"`
	TLS                          bool     `json:"tls,omitempty"                         norman:"default=false"`
	Certificate                  string   `json:"certificate,omitempty"`
	DefaultLoginDomain           string   `json:"defaultLoginDomain,omitempty"`
	ServiceAccountUsername       string   `json:"serviceAccountUsername,omitempty"      norman:"required"`
	ServiceAccountPassword       string   `json:"serviceAccountPassword,omitempty"      norman:"type=password,required"`
	UserDisabledBitMask          int64    `json:"userDisabledBitMask,omitempty"         norman:"default=2"`
	UserSearchBase               string   `json:"userSearchBase,omitempty"              norman:"required"`
	UserSearchAttribute          string   `json:"userSearchAttribute,omitempty"         norman:"default=sAMAccountName|sn|givenName,required"`
	UserLoginAttribute           string   `json:"userLoginAttribute,omitempty"          norman:"default=sAMAccountName,required"`
	UserObjectClass              string   `json:"userObjectClass,omitempty"             norman:"default=person,required"`
	UserNameAttribute            string   `json:"userNameAttribute,omitempty"           norman:"default=name,required"`
	UserEnabledAttribute         string   `json:"userEnabledAttribute,omitempty"        norman:"default=userAccountControl,required"`
	GroupSearchBase              string   `json:"groupSearchBase,omitempty"`
	GroupSearchAttribute         string   `json:"groupSearchAttribute,omitempty"        norman:"default=sAMAccountName,required"`
	GroupObjectClass             string   `json:"groupObjectClass,omitempty"            norman:"default=group,required"`
	GroupNameAttribute           string   `json:"groupNameAttribute,omitempty"          norman:"default=name,required"`
	GroupDNAttribute             string   `json:"groupDNAttribute,omitempty"            norman:"default=distinguishedName,required"`
	GroupMemberUserAttribute     string   `json:"groupMemberUserAttribute,omitempty"    norman:"default=distinguishedName,required"`
	GroupMemberMappingAttribute  string   `json:"groupMemberMappingAttribute,omitempty" norman:"default=member,required"`
	ConnectionTimeout            int64    `json:"connectionTimeout,omitempty"           norman:"default=5000,notnullable,required"`
	NestedGroupMembershipEnabled *bool    `json:"nestedGroupMembershipEnabled,omitempty" norman:"default=false"`
}

type ActiveDirectoryTestAndApplyInput struct {
	ActiveDirectoryConfig ActiveDirectoryConfig `json:"activeDirectoryConfig,omitempty"`
	Username              string                `json:"username"`
	Password              string                `json:"password"`
	Enabled               bool                  `json:"enabled,omitempty"`
}

type LdapConfig struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	AuthConfig        `json:",inline" mapstructure:",squash"`

	Servers                         []string `json:"servers,omitempty"                     norman:"type=array[string],notnullable,required"`
	Port                            int64    `json:"port,omitempty"                        norman:"default=389,notnullable,required"`
	TLS                             bool     `json:"tls,omitempty"                         norman:"default=false,notnullable,required"`
	Certificate                     string   `json:"certificate,omitempty"`
	ServiceAccountDistinguishedName string   `json:"serviceAccountDistinguishedName,omitempty"      norman:"required"`
	ServiceAccountPassword          string   `json:"serviceAccountPassword,omitempty"      norman:"type=password,required"`
	UserDisabledBitMask             int64    `json:"userDisabledBitMask,omitempty"`
	UserSearchBase                  string   `json:"userSearchBase,omitempty"              norman:"notnullable,required"`
	UserSearchAttribute             string   `json:"userSearchAttribute,omitempty"         norman:"default=uid|sn|givenName,notnullable,required"`
	UserLoginAttribute              string   `json:"userLoginAttribute,omitempty"          norman:"default=uid,notnullable,required"`
	UserObjectClass                 string   `json:"userObjectClass,omitempty"             norman:"default=inetOrgPerson,notnullable,required"`
	UserNameAttribute               string   `json:"userNameAttribute,omitempty"           norman:"default=cn,notnullable,required"`
	UserMemberAttribute             string   `json:"userMemberAttribute,omitempty"           norman:"default=memberOf,notnullable,required"`
	UserEnabledAttribute            string   `json:"userEnabledAttribute,omitempty"`
	GroupSearchBase                 string   `json:"groupSearchBase,omitempty"`
	GroupSearchAttribute            string   `json:"groupSearchAttribute,omitempty"        norman:"default=cn,notnullable,required"`
	GroupObjectClass                string   `json:"groupObjectClass,omitempty"            norman:"default=groupOfNames,notnullable,required"`
	GroupNameAttribute              string   `json:"groupNameAttribute,omitempty"          norman:"default=cn,notnullable,required"`
	GroupDNAttribute                string   `json:"groupDNAttribute,omitempty"            norman:"default=entryDN,notnullable"`
	GroupMemberUserAttribute        string   `json:"groupMemberUserAttribute,omitempty"    norman:"default=entryDN,notnullable"`
	GroupMemberMappingAttribute     string   `json:"groupMemberMappingAttribute,omitempty" norman:"default=member,notnullable,required"`
	ConnectionTimeout               int64    `json:"connectionTimeout,omitempty"           norman:"default=5000,notnullable,required"`
	NestedGroupMembershipEnabled    bool     `json:"nestedGroupMembershipEnabled"    norman:"default=false"`
}

type LdapTestAndApplyInput struct {
	LdapConfig `json:"ldapConfig,omitempty"`
	Username   string `json:"username"`
	Password   string `json:"password" norman:"type=password,required"`
}

type OpenLdapConfig struct {
	LdapConfig `json:",inline" mapstructure:",squash"`
}

type OpenLdapTestAndApplyInput struct {
	LdapTestAndApplyInput `json:",inline" mapstructure:",squash"`
}

type FreeIpaConfig struct {
	LdapConfig `json:",inline" mapstructure:",squash"`
}

type FreeIpaTestAndApplyInput struct {
	LdapTestAndApplyInput `json:",inline" mapstructure:",squash"`
}
<|endoftext|>


<commit_before>package server

import (
	"errors"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
	"github.com/nats-io/nuid"
)

// registerClient registers a new session.
func registerClient(data *signedConn) error {
	if data.message.GetSignature().String() == "" {
		p := data.message.GetPacket()
		if p.From.String() != "" {
			n := nuid.New()

			td := trackerData{
				verificationKey: p.From.String(),
				sessionID:       []byte(n.Next()),
				conn:            data.conn,
				number:          0,
			}
			data.tracker.add(data.conn, &td)

			err := registrationSuccess(data, &td)
			return err
		}
	}

	err := registrationFailed(data)
	if err != nil {
		return err
	}

	return errors.New("registration failed")
}

// registrationSuccess sends a registration success reply.
func registrationSuccess(data *signedConn, td *trackerData) error {
	m := message.Signed{
		Packet: &message.Packet{
			Session: td.sessionID,
			Number:  td.number,
		},
	}

	reply, err := proto.Marshal(&m)
	if err != nil {
		return err
	}

	data.conn.Write(reply)
	data.conn.Write(breakBytes)

	return nil
}

// registrationFailed sends a registration failed reply.
func registrationFailed(data *signedConn) error {
	m := message.Signed{
		Packet: &message.Packet{
			Message: &message.Message{
				Blame: &message.Blame{
					Reason: message.Reason_INVALIDFORMAT,
				},
			},
		},
	}

	reply, err := proto.Marshal(&m)
	if err != nil {
		return err
	}

	data.conn.Write(reply)
	data.conn.Write(breakBytes)

	return nil
}
<commit_msg>Added writeMessage for convenience<commit_after>package server

import (
	"errors"
	"net"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/golang/protobuf/proto"
	"github.com/nats-io/nuid"
)

// registerClient registers a new session.
func registerClient(data *signedConn) error {
	if data.message.GetSignature().String() == "" {
		p := data.message.GetPacket()
		if p.From.String() != "" {
			n := nuid.New()

			td := trackerData{
				verificationKey: p.From.String(),
				sessionID:       []byte(n.Next()),
				conn:            data.conn,
				number:          0,
			}
			data.tracker.add(data.conn, &td)

			err := registrationSuccess(data, &td)
			return err
		}
	}

	err := registrationFailed(data)
	if err != nil {
		return err
	}

	return errors.New("registration failed")
}

// writeMessage writes a *message.Signed to the connection via protobuf.
func writeMessage(conn net.Conn, m *message.Signed) error {
	reply, err := proto.Marshal(m)
	if err != nil {
		return err
	}

	_, err = conn.Write(reply)
	if err != nil {
		return err
	}

	_, err = conn.Write(breakBytes)
	if err != nil {
		return err
	}

	return nil
}

// registrationSuccess sends a registration success reply.
func registrationSuccess(data *signedConn, td *trackerData) error {
	m := message.Signed{
		Packet: &message.Packet{
			Session: td.sessionID,
			Number:  td.number,
		},
	}

	err := writeMessage(data.conn, &m)
	return err
}

// registrationFailed sends a registration failed reply.
func registrationFailed(data *signedConn) error {
	m := message.Signed{
		Packet: &message.Packet{
			Message: &message.Message{
				Blame: &message.Blame{
					Reason: message.Reason_INVALIDFORMAT,
				},
			},
		},
	}

	err := writeMessage(data.conn, &m)
	return err
}
<|endoftext|>


<commit_before>// Quick and dirty uploader and serial terminal for LPC8xx chips.
// -jcw, 2015-02-02

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/chimera/rs232"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	termFlag   = flag.Bool("t", false, "launch as serial terminal after upload")
	waitFlag   = flag.Bool("w", false, "wait for connection to the boot loader")
	offsetFlag = flag.Int("o", 0, "upload offset (must be a multiple of 1024)")
	idleFlag   = flag.Int("i", 0, "exit terminal after N idle seconds (0: off)")
	netFlag    = flag.Bool("n", false, "use telnet protocol for RTS & DTR")
)

func main() {
	log.SetFlags(0) // no timestamps

	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: lpc8xx ?options? tty ?binfile?")
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(2)
	}

	ttyName := flag.Arg(0)
	binFile := ""
	if flag.NArg() > 1 {
		binFile = flag.Arg(1)
	}

	conn := connect(ttyName)

	id := conn.Identify()
	fmt.Printf("found: %X\n", id)

	conn.SendAndWait("N", "0")
	buf := bytes.NewBuffer([]byte{})

	for i := 0; i < 4; i++ {
		b, err := strconv.ParseUint(<-conn.lines, 10, 32)
		Check(err)
		binary.Write(buf, binary.LittleEndian, uint32(b))
	}
	fmt.Printf("hwuid: %X\n", buf.Bytes())

	if binFile != "" {
		data, err := ioutil.ReadFile(binFile)
		Check(err)

		fmt.Print("flash: 00000 ")
		for n := range conn.Program(*offsetFlag, data) {
			fmt.Printf("\b\b\b\b\b\b%05d ", n*64)
		}
		fmt.Println("done")
	}

	conn.serial.SetDTR(true) // pulse DTR to reset
	conn.serial.SetDTR(false)

	if *termFlag {
		fmt.Println("entering terminal mode, press <ESC> to quit:\n")
		conn.Terminal()
		fmt.Println()
	}
}

func connect(port string) *connection {
	var dev serialLink

	if _, err := os.Stat(port); os.IsNotExist(err) {
		// if the tty is an existing device, open at as rs232 port
		sock, err := net.Dial("tcp", port)
		Check(err)
		if *netFlag {
			dev = &telnet{sock, 0} // insert telnet escape sequences
		} else {
			dev = &rawnet{sock} // no escape sequences
		}
	} else {
		// else assume it's a n ip address + port and open as a network port
		opt := rs232.Options{BitRate: 115200, DataBits: 8, StopBits: 1}
		dev, err = rs232.Open(port, opt)
		Check(err)
	}

	// the rest of the code is identical for either case
	// everything is abstracted away behind the "serialLink" interface
	conn := &connection{dev, make(chan string)}
	go func() {
		scanner := bufio.NewScanner(dev)
		for scanner.Scan() {
			conn.lines <- scanner.Text()
		}
		close(conn.lines)
	}()
	return conn
}

// a serialLink can read and write bytes, and set the DTR and RTS levels
type serialLink interface {
	io.ReadWriter
	SetDTR(level bool) error
	SetRTS(level bool) error
}

// telnet objects use a network connection as is, no signalling
type rawnet struct {
	sock net.Conn
}

func (c *rawnet) SetDTR(level bool) error {
	return nil
}

func (c *rawnet) SetRTS(level bool) error {
	return nil
}

func (c *rawnet) Read(buf []byte) (n int, err error) {
	return c.sock.Read(buf)
}

func (c *rawnet) Write(buf []byte) (int, error) {
	return c.sock.Write(buf)
}

// telnet objects use a network connection with telnet in-band signalling
type telnet struct {
	sock    net.Conn
	inState int
}

const (
	Iac = 255
	Sb  = 250
	Se  = 240

	ComPortOpt = 44
	SetControl = 5
)

func (c *telnet) SetDTR(level bool) error {
	return c.sendEscape(level, 8, 9)
}

func (c *telnet) SetRTS(level bool) error {
	return c.sendEscape(level, 11, 12)
}

func (c *telnet) sendEscape(flag bool, yes, no uint8) error {
	b := no
	if flag {
		b = yes
	}

	_, err := c.sock.Write([]byte{Iac, Sb, ComPortOpt, SetControl, b, Iac, Se})
	return err
}

func (c *telnet) Read(buf []byte) (n int, err error) {
	j := 0
	for {
		n, err := c.sock.Read(buf)
		if err != nil {
			return n, err
		}
		for i := 0; i < n; i++ {
			b := buf[i]
			buf[j] = b
			switch c.inState {
			case 0: // normal, copying
				if b == Iac {
					c.inState = 1
				} else {
					j++
				}
			case 1: // seen Iac
				if b == Sb {
					c.inState = 2
				} else {
					j++
					c.inState = 0
				}
			case 2: // inside command
				if b == Iac {
					c.inState = 3
				}
			case 3: // inside command, see Iac
				if b == Se {
					c.inState = 0
				} else {
					c.inState = 2
				}
			}
		}
		if j > 0 {
			break
		}
	}
	return j, nil
}

func (c *telnet) Write(buf []byte) (int, error) {
	if *netFlag {
		buf = bytes.Replace(buf, []byte{0xFF}, []byte{0xFF, 0xFF}, -1)
	}
	// FIXME returned count is wrong when escaped
	return c.sock.Write(buf)
}

type connection struct {
	serial serialLink
	lines  chan string
}

func (c *connection) ReadReply() string {
	select {
	case reply := <-c.lines:
		return reply
	case <-time.After(250 * time.Millisecond):
		return ""
	}
}

func (c *connection) SendAndWait(cmd string, expect string) {
	c.serial.Write([]byte(cmd + "\r\n"))
	var reply string
	for i := 0; i < 4; i++ {
		reply = c.ReadReply()
		if reply == "" {
			log.Fatal("no response, timeout")
		}
		if reply == expect {
			return
		}
	}
	log.Fatal(reply)
}

func (c *connection) Identify() int {
	c.serial.SetRTS(true) // keep RTS on for ISP mode

	for {
		c.serial.SetDTR(true) // pulse DTR to reset
		for c.ReadReply() != "" {
			// flush
		}
		c.serial.SetDTR(false)

		c.serial.Write([]byte("?\r\n"))
		if c.ReadReply() == "Synchronized" || !*waitFlag {
			break
		}
	}

	c.serial.SetRTS(false)

	c.SendAndWait("Synchronized", "OK")
	c.SendAndWait("12000", "OK")
	c.SendAndWait("A 0", "0")

	c.SendAndWait("J", "0")
	id, err := strconv.Atoi(c.ReadReply())
	Check(err)

	return id
}

func (c *connection) Program(startAddress int, data []byte) chan int {
	const sectorSize = 1024
	const pageSize = 64

	for len(data)%pageSize != 0 {
		data = append(data, 0xFF)
	}
	firstPage := startAddress / pageSize
	lastPage := firstPage + len(data)/pageSize - 1

	for len(data)%sectorSize != 0 {
		data = append(data, 0xFF)
	}
	firstSector := startAddress / sectorSize
	lastSector := firstSector + len(data)/sectorSize - 1

	if startAddress == 0 {
		fixChecksum(data)
	}

	c.SendAndWait("U 23130", "0") // unlock for programming

	// erase entire range, as 1024-byte sectors
	c.SendAndWait(fmt.Sprint("P ", firstSector, lastSector), "0") // prepare
	c.SendAndWait(fmt.Sprint("E ", firstSector, lastSector), "0") // erase

	r := make(chan int)
	go func() {
		defer close(r)
		// program in 64-byte pages (sectors won't fit in the LPC810's RAM)
		for page := firstPage; page <= lastPage; page++ {
			const RAM_ADDR = 0x10000300
			// write one page of data to RAM
			offset := (page - firstPage) * pageSize
			c.SendAndWait(fmt.Sprint("W ", RAM_ADDR, pageSize), "0")
			c.serial.Write(data[offset : offset+pageSize])
			// prepare and copy the data to flash memory
			sector := (page * pageSize) / sectorSize
			c.SendAndWait(fmt.Sprint("P ", sector, sector), "0")
			destAddr := page * pageSize
			c.SendAndWait(fmt.Sprint("C ", destAddr, RAM_ADDR, pageSize), "0")
			r <- (page - firstPage) + 1
		}
	}()
	return r
}

// fix the checksum to mark code as valid (see UM10398, p.416)
func fixChecksum(data []byte) {
	buf := bytes.NewReader(data)
	values := [7]uint32{}
	err := binary.Read(buf, binary.LittleEndian, values[:])
	Check(err)
	var sum uint32
	for _, v := range values {
		sum -= v
	}
	data[28] = byte(sum)
	data[29] = byte(sum >> 8)
	data[30] = byte(sum >> 16)
	data[31] = byte(sum >> 24)
}

func (c *connection) Terminal() {
	timeout := time.Duration(*idleFlag) * time.Second
	idleTimer := time.NewTimer(timeout)

	// FIXME still in line mode, so only complete lines will be shown
	//  look into bufio.Reader for a possible solution
	go func() {
		for s := range c.lines {
			idleTimer.Reset(timeout)
			fmt.Println(s)
		}
	}()

	// put stdin in raw mode
	oldState, err := terminal.MakeRaw(0)
	Check(err)
	defer terminal.Restore(0, oldState)

	// cleanup when program is terminated via a signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
	go func() {
		sigMsg := <-sigChan
		terminal.Restore(0, oldState)
		log.Fatal(sigMsg)
	}()

	// cleanup when idle timer fires, and exit cleanly
	if *idleFlag > 0 {
		go func() {
			<-idleTimer.C
			terminal.Restore(0, oldState)
			fmt.Println("\nidle timeout")
			os.Exit(0)
		}()
	}

	// copy key presses to the serial port
	for {
		var b [1]byte
		n, _ := os.Stdin.Read(b[:])
		idleTimer.Reset(timeout)
		if n < 1 || b[0] == 0x1B { // ESC
			break
		}
		c.serial.Write(b[:n])
	}

}

func Check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}
<commit_msg>change -t flag to -s (!)<commit_after>// Quick and dirty uploader and serial terminal for LPC8xx chips.
// -jcw, 2015-02-02

package main

// TODO refactor telnet and hardware handling of RTS and DTR
//  current code is messy, some of the logic can be combined
//  this will also allow re-using the telnet in-band logic for rs232

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/chimera/rs232"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	termFlag   = flag.Bool("s", false, "launch as serial terminal after upload")
	waitFlag   = flag.Bool("w", false, "wait for connection to the boot loader")
	offsetFlag = flag.Int("o", 0, "upload offset (must be a multiple of 1024)")
	idleFlag   = flag.Int("i", 0, "exit terminal after N idle seconds (0: off)")
	netFlag    = flag.Bool("t", false, "use telnet protocol for RTS & DTR")
)

func main() {
	log.SetFlags(0) // no timestamps

	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: lpc8xx ?options? tty ?binfile?")
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(2)
	}

	ttyName := flag.Arg(0)
	binFile := ""
	if flag.NArg() > 1 {
		binFile = flag.Arg(1)
	}

	conn := connect(ttyName)

	id := conn.Identify()
	fmt.Printf("found: %X\n", id)

	conn.SendAndWait("N", "0")
	buf := bytes.NewBuffer([]byte{})

	for i := 0; i < 4; i++ {
		b, err := strconv.ParseUint(<-conn.lines, 10, 32)
		Check(err)
		binary.Write(buf, binary.LittleEndian, uint32(b))
	}
	fmt.Printf("hwuid: %X\n", buf.Bytes())

	if binFile != "" {
		data, err := ioutil.ReadFile(binFile)
		Check(err)

		fmt.Print("flash: 00000 ")
		for n := range conn.Program(*offsetFlag, data) {
			fmt.Printf("\b\b\b\b\b\b%05d ", n*64)
		}
		fmt.Println("done")
	}

	conn.serial.SetDTR(true) // pulse DTR to reset
	conn.serial.SetDTR(false)

	if *termFlag {
		fmt.Println("entering terminal mode, press <ESC> to quit:\n")
		conn.Terminal()
		fmt.Println()
	}
}

func connect(port string) *connection {
	var dev serialLink

	if _, err := os.Stat(port); os.IsNotExist(err) {
		// if the tty is an existing device, open at as rs232 port
		sock, err := net.Dial("tcp", port)
		Check(err)
		if *netFlag {
			dev = &telnet{sock, 0} // insert telnet escape sequences
		} else {
			dev = &rawnet{sock} // no escape sequences
		}
	} else {
		// else assume it's a n ip address + port and open as a network port
		opt := rs232.Options{BitRate: 115200, DataBits: 8, StopBits: 1}
		dev, err = rs232.Open(port, opt)
		Check(err)
	}

	// the rest of the code is identical for either case
	// everything is abstracted away behind the "serialLink" interface
	conn := &connection{dev, make(chan string)}
	go func() {
		scanner := bufio.NewScanner(dev)
		for scanner.Scan() {
			conn.lines <- scanner.Text()
		}
		close(conn.lines)
	}()
	return conn
}

// a serialLink can read and write bytes, and set the DTR and RTS levels
type serialLink interface {
	io.ReadWriter
	SetDTR(level bool) error
	SetRTS(level bool) error
}

// telnet objects use a network connection as is, no signalling
type rawnet struct {
	sock net.Conn
}

func (c *rawnet) SetDTR(level bool) error {
	return nil
}

func (c *rawnet) SetRTS(level bool) error {
	return nil
}

func (c *rawnet) Read(buf []byte) (n int, err error) {
	return c.sock.Read(buf)
}

func (c *rawnet) Write(buf []byte) (int, error) {
	return c.sock.Write(buf)
}

// telnet objects use a network connection with telnet in-band signalling
type telnet struct {
	sock    net.Conn
	inState int
}

const (
	Iac = 255
	Sb  = 250
	Se  = 240

	ComPortOpt = 44
	SetControl = 5
)

func (c *telnet) SetDTR(level bool) error {
	return c.sendEscape(level, 8, 9)
}

func (c *telnet) SetRTS(level bool) error {
	return c.sendEscape(level, 11, 12)
}

func (c *telnet) sendEscape(flag bool, yes, no uint8) error {
	b := no
	if flag {
		b = yes
	}

	_, err := c.sock.Write([]byte{Iac, Sb, ComPortOpt, SetControl, b, Iac, Se})
	return err
}

func (c *telnet) Read(buf []byte) (n int, err error) {
	j := 0
	for {
		n, err := c.sock.Read(buf)
		if err != nil {
			return n, err
		}
		for i := 0; i < n; i++ {
			b := buf[i]
			buf[j] = b
			switch c.inState {
			case 0: // normal, copying
				if b == Iac {
					c.inState = 1
				} else {
					j++
				}
			case 1: // seen Iac
				if b == Sb {
					c.inState = 2
				} else {
					j++
					c.inState = 0
				}
			case 2: // inside command
				if b == Iac {
					c.inState = 3
				}
			case 3: // inside command, see Iac
				if b == Se {
					c.inState = 0
				} else {
					c.inState = 2
				}
			}
		}
		if j > 0 {
			break
		}
	}
	return j, nil
}

func (c *telnet) Write(buf []byte) (int, error) {
	if *netFlag {
		buf = bytes.Replace(buf, []byte{0xFF}, []byte{0xFF, 0xFF}, -1)
	}
	// FIXME returned count is wrong when escaped
	return c.sock.Write(buf)
}

type connection struct {
	serial serialLink
	lines  chan string
}

func (c *connection) ReadReply() string {
	select {
	case reply := <-c.lines:
		return reply
	case <-time.After(250 * time.Millisecond):
		return ""
	}
}

func (c *connection) SendAndWait(cmd string, expect string) {
	c.serial.Write([]byte(cmd + "\r\n"))
	var reply string
	for i := 0; i < 4; i++ {
		reply = c.ReadReply()
		if reply == "" {
			log.Fatal("no response, timeout")
		}
		if reply == expect {
			return
		}
	}
	log.Fatal(reply)
}

func (c *connection) Identify() int {
	c.serial.SetRTS(true) // keep RTS on for ISP mode

	for {
		c.serial.SetDTR(true) // pulse DTR to reset
		for c.ReadReply() != "" {
			// flush
		}
		c.serial.SetDTR(false)

		c.serial.Write([]byte("?\r\n"))
		if c.ReadReply() == "Synchronized" || !*waitFlag {
			break
		}
	}

	c.serial.SetRTS(false)

	c.SendAndWait("Synchronized", "OK")
	c.SendAndWait("12000", "OK")
	c.SendAndWait("A 0", "0")

	c.SendAndWait("J", "0")
	id, err := strconv.Atoi(c.ReadReply())
	Check(err)

	return id
}

func (c *connection) Program(startAddress int, data []byte) chan int {
	const sectorSize = 1024
	const pageSize = 64

	for len(data)%pageSize != 0 {
		data = append(data, 0xFF)
	}
	firstPage := startAddress / pageSize
	lastPage := firstPage + len(data)/pageSize - 1

	for len(data)%sectorSize != 0 {
		data = append(data, 0xFF)
	}
	firstSector := startAddress / sectorSize
	lastSector := firstSector + len(data)/sectorSize - 1

	if startAddress == 0 {
		fixChecksum(data)
	}

	c.SendAndWait("U 23130", "0") // unlock for programming

	// erase entire range, as 1024-byte sectors
	c.SendAndWait(fmt.Sprint("P ", firstSector, lastSector), "0") // prepare
	c.SendAndWait(fmt.Sprint("E ", firstSector, lastSector), "0") // erase

	r := make(chan int)
	go func() {
		defer close(r)
		// program in 64-byte pages (sectors won't fit in the LPC810's RAM)
		for page := firstPage; page <= lastPage; page++ {
			const RAM_ADDR = 0x10000300
			// write one page of data to RAM
			offset := (page - firstPage) * pageSize
			c.SendAndWait(fmt.Sprint("W ", RAM_ADDR, pageSize), "0")
			c.serial.Write(data[offset : offset+pageSize])
			// prepare and copy the data to flash memory
			sector := (page * pageSize) / sectorSize
			c.SendAndWait(fmt.Sprint("P ", sector, sector), "0")
			destAddr := page * pageSize
			c.SendAndWait(fmt.Sprint("C ", destAddr, RAM_ADDR, pageSize), "0")
			r <- (page - firstPage) + 1
		}
	}()
	return r
}

// fix the checksum to mark code as valid (see UM10398, p.416)
func fixChecksum(data []byte) {
	buf := bytes.NewReader(data)
	values := [7]uint32{}
	err := binary.Read(buf, binary.LittleEndian, values[:])
	Check(err)
	var sum uint32
	for _, v := range values {
		sum -= v
	}
	data[28] = byte(sum)
	data[29] = byte(sum >> 8)
	data[30] = byte(sum >> 16)
	data[31] = byte(sum >> 24)
}

func (c *connection) Terminal() {
	timeout := time.Duration(*idleFlag) * time.Second
	idleTimer := time.NewTimer(timeout)

	// FIXME still in line mode, so only complete lines will be shown
	//  look into bufio.Reader for a possible solution
	go func() {
		for s := range c.lines {
			idleTimer.Reset(timeout)
			fmt.Println(s)
		}
	}()

	// put stdin in raw mode
	oldState, err := terminal.MakeRaw(0)
	Check(err)
	defer terminal.Restore(0, oldState)

	// cleanup when program is terminated via a signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
	go func() {
		sigMsg := <-sigChan
		terminal.Restore(0, oldState)
		log.Fatal(sigMsg)
	}()

	// cleanup when idle timer fires, and exit cleanly
	if *idleFlag > 0 {
		go func() {
			<-idleTimer.C
			terminal.Restore(0, oldState)
			fmt.Println("\nidle timeout")
			os.Exit(0)
		}()
	}

	// copy key presses to the serial port
	for {
		var b [1]byte
		n, _ := os.Stdin.Read(b[:])
		idleTimer.Reset(timeout)
		if n < 1 || b[0] == 0x1B { // ESC
			break
		}
		c.serial.Write(b[:n])
	}

}

func Check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/template"
)

const configTemplate string = `[agent]
    ; connection string to the MIG relay. must contain credentials.
    relay            = "{{.RelayAddress}}"

    ; location of the mig api
    api             = "{{.APIURL}}"

    proxies         = "{{.Proxies}}"

    ; location of the local stat socket
    socket           = "127.0.0.1:{{.StatSocketPort}}"

    ; frequency at which heartbeat messages are sent to the MIG relay
    heartbeatfreq    = "120s"

    ; timeout after which a module that has not finished is killed by the agent
    moduletimeout    = "300s"

    ; in immortal mode, the agent that encounter a fatal error
    ; will attempt to restart itself instead of just shutting down
    isimmortal       = {{.Immortal}}

    ; installservice orders the agent to deploy a service init configuration
    ; and start itself during the endpoint's boot process
    installservice   = {{.InstallAsService}}

    ; attempt to retrieve the public IP behind which the agent is running
    discoverpublicip = on

    ; in check-in mode, the agent connects to the relay, runs all pending commands
    ; and exits. this mode is used to run the agent as a cron job, not a daemon.
    checkin = {{.CronMode}}

    refreshenv = "5m"

    ; enable privacy mode
    extraprivacymode = {{.ExtraPrivacy}}

    ; Tags help investigators to target specific agents for queries.
    ; Multiple tags can be supplied as demonstrated below and the
    ; tag name and value are separated by a colon.
    ; tags = "operator:example"
    ; tags = "exampleTag:other"{{ range $tag, $value := .Tags }}
    tags = "{{ $tag }}:{{ $value }}"{{ end }}

[stats]
    maxactions = 15

[certs]
    ca  = "/etc/mig/ca.crt"
    cert = "/etc/mig/agent.crt"
    key = "/etc/mig/agent.key"

[logging]
    mode    = "file" ; stdout | file | syslog
    level   = "debug"
    file    = "/var/log/mig-agent.log"
    maxfilesize = 10485760
`

// Defaults
const (
	defaultAPIURL           = "https://api.mig.mozilla.org/api/v1/"
	defaultStatSocketPort   = 51664
	defaultImmortal         = true
	defaultInstallAsService = true
	defaultCronMode         = false
	defaultExtraPrivacy     = true
)

type proxyList []string
type tagList []string

// Config contains user-supplied configurable values that will be translated
// and injected into `release/mig-agent.cfg.template` to produce a valid
// `mig-agent.cfg`.
type Config struct {
	RelayAddress     string
	APIURL           string
	Proxies          proxyList
  Tags             tagList
	StatSocketPort   uint
	Immortal         bool
	InstallAsService bool
	CronMode         bool
	ExtraPrivacy     bool
}

// translatedConfig contains the same data as `Config` with some
// transformations to make the output of this program consistent
// with what mig agents expect.
type translatedConfig struct {
	RelayAddress     string
	APIURL           string
	Proxies          string
  Tags             map[string]string
	StatSocketPort   uint16
	Immortal         string
	InstallAsService string
	CronMode         string
	ExtraPrivacy     string
}

func onOrOff(on bool) string {
	if on {
		return "on"
	} else {
		return "off"
	}
}

// kvPairsToMap converts strings formatted like `key=value` to a map.
func kvPairsToMap(kvs []string) (map[string]string, error) {
  mapping := map[string]string{}
  for _, pair := range kvs {
    parts := strings.Split(pair, "=")
    if len(parts) != 2 {
      return map[string]string{}, fmt.Errorf("encountered invalidly formatted tag \"%s\". Expected format key=value")
    }
    mapping[parts[0]] = parts[1]
  }
  return mapping, nil
}

func translate(config Config) (translatedConfig, error) {
  tags, err := kvPairsToMap(config.Tags)
  if err != nil {
    return translatedConfig{}, err
  }

  tx := translatedConfig{
		RelayAddress:     config.RelayAddress,
		APIURL:           config.APIURL,
		Proxies:          strings.Join([]string(config.Proxies), ","),
    Tags:             tags,
		StatSocketPort:   uint16(config.StatSocketPort),
		Immortal:         onOrOff(config.Immortal),
		InstallAsService: onOrOff(config.InstallAsService),
		CronMode:         onOrOff(config.CronMode),
		ExtraPrivacy:     onOrOff(config.ExtraPrivacy),
	}

  return tx, nil
}

// IsValid verifies that values that must be supplied for a configuration have
// been supplied and that those whose values cannot be guaranteed to be
// correct by the type system and flag API are valid.
func (config Config) IsValid() bool {
	return config.RelayAddress != "" && config.StatSocketPort <= 65535
}

// String is required by the `flag.Value` interface.
func (proxies *proxyList) String() string {
	return strings.Join([]string(*proxies), ",")
}

// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple proxy addresses, which we collect into an array.
func (proxies *proxyList) Set(value string) error {
	*proxies = append(*proxies, value)
	return nil
}

// String is required by the `flag.Value` interface.
func (tags *tagList) String() string {
  return strings.Join([]string(*tags), ",")
}

// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple tag key=value pairs, which we collect into an array.
func (tags *tagList) Set(kvPair string) error {
  if !strings.Contains(kvPair, "=") {
    return fmt.Errorf("expected tag argument to be formatted key=value")
  }
  *tags = append(*tags, kvPair)
  return nil
}

func main() {
	relayAddr := flag.String(
		"relay",
		"",
		"Connection string to the MIG relay. Must contain credentials")
	apiUrl := flag.String(
		"api",
		defaultAPIURL,
		"URL of the MIG API")
	var proxies proxyList
	flag.Var(
		&proxies,
		"proxy",
		"Address of a proxy to use. Multiple -proxy arguments are accepted")
  var tags tagList
  flag.Var(
    &tags,
    "tag",
    "A tagName=tagValue pair to tag the agent with. Multuple -tag arguments are accepted")
  statPort := flag.Uint(
		"statport",
		defaultStatSocketPort,
		"Port to bind the agent's stat socket to on localhost")
	immortal := flag.Bool(
		"immortal",
		defaultImmortal,
		"Instruct the agent to automatically recover from fatal errors")
	service := flag.Bool(
		"service",
		defaultInstallAsService,
		"Instruct the agent to install itself as a service")
	cronMode := flag.Bool(
		"cron",
		defaultCronMode,
		"Instruct the agent to run all queued actions before terminating instead of running as a daemon")
	extraPrivacy := flag.Bool(
		"extraprivate",
		defaultExtraPrivacy,
		"Instruct the agent to run with extra privacy controls")
	flag.Parse()

	template := template.Must(template.New("mig-agent.cfg").Parse(configTemplate))

	config := Config{
		RelayAddress:     *relayAddr,
		APIURL:           *apiUrl,
		Proxies:          proxies,
    Tags:             tags,
		StatSocketPort:   *statPort,
		Immortal:         *immortal,
		InstallAsService: *service,
		CronMode:         *cronMode,
		ExtraPrivacy:     *extraPrivacy,
	}
	if !config.IsValid() {
		fmt.Fprintf(os.Stderr, "Invalid configuration data supplied. Check that the -relay argument is present and that the -statport argument is a valid port number.\n")
		os.Exit(1)
	}

	translated, err := translate(config)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Error creating configuration: %s\n", err.Error())
    os.Exit(1)
  }

	err = template.Execute(os.Stdout, translated)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error filling configuration template: %s\n", err.Error())
		os.Exit(1)
	}
}
<commit_msg>noise reduction<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/template"
)

const configTemplate string = `[agent]
    ; connection string to the MIG relay. must contain credentials.
    relay            = "{{.RelayAddress}}"

    ; location of the mig api
    api             = "{{.APIURL}}"

    proxies         = "{{.Proxies}}"

    ; location of the local stat socket
    socket           = "127.0.0.1:{{.StatSocketPort}}"

    ; frequency at which heartbeat messages are sent to the MIG relay
    heartbeatfreq    = "120s"

    ; timeout after which a module that has not finished is killed by the agent
    moduletimeout    = "300s"

    ; in immortal mode, the agent that encounter a fatal error
    ; will attempt to restart itself instead of just shutting down
    isimmortal       = {{.Immortal}}

    ; installservice orders the agent to deploy a service init configuration
    ; and start itself during the endpoint's boot process
    installservice   = {{.InstallAsService}}

    ; attempt to retrieve the public IP behind which the agent is running
    discoverpublicip = on

    ; in check-in mode, the agent connects to the relay, runs all pending commands
    ; and exits. this mode is used to run the agent as a cron job, not a daemon.
    checkin = {{.CronMode}}

    refreshenv = "5m"

    ; enable privacy mode
    extraprivacymode = {{.ExtraPrivacy}}

    ; Tags help investigators to target specific agents for queries.
    ; Multiple tags can be supplied as demonstrated below and the tag name and value are separated by a colon.
    ; tags = "operator:example"
    ; tags = "exampleTag:other"{{ range $tag, $value := .Tags }}
    tags = "{{ $tag }}:{{ $value }}"{{ end }}

[stats]
    maxactions = 15

[certs]
    ca  = "/etc/mig/ca.crt"
    cert = "/etc/mig/agent.crt"
    key = "/etc/mig/agent.key"

[logging]
    mode    = "file" ; stdout | file | syslog
    level   = "debug"
    file    = "/var/log/mig-agent.log"
    maxfilesize = 10485760
`

// Defaults
const (
	defaultAPIURL           = "https://api.mig.mozilla.org/api/v1/"
	defaultStatSocketPort   = 51664
	defaultImmortal         = true
	defaultInstallAsService = true
	defaultCronMode         = false
	defaultExtraPrivacy     = true
)

type proxyList []string
type tagList []string

// Config contains user-supplied configurable values that will be translated
// and injected into `release/mig-agent.cfg.template` to produce a valid
// `mig-agent.cfg`.
type Config struct {
	RelayAddress     string
	APIURL           string
	Proxies          proxyList
  Tags             tagList
	StatSocketPort   uint
	Immortal         bool
	InstallAsService bool
	CronMode         bool
	ExtraPrivacy     bool
}

// translatedConfig contains the same data as `Config` with some
// transformations to make the output of this program consistent
// with what mig agents expect.
type translatedConfig struct {
	RelayAddress     string
	APIURL           string
	Proxies          string
  Tags             map[string]string
	StatSocketPort   uint16
	Immortal         string
	InstallAsService string
	CronMode         string
	ExtraPrivacy     string
}

func onOrOff(on bool) string {
	if on {
		return "on"
	} else {
		return "off"
	}
}

// kvPairsToMap converts strings formatted like `key=value` to a map.
func kvPairsToMap(kvs []string) (map[string]string, error) {
  mapping := map[string]string{}
  for _, pair := range kvs {
    parts := strings.Split(pair, "=")
    if len(parts) != 2 {
      return map[string]string{}, fmt.Errorf("encountered invalidly formatted tag \"%s\". Expected format key=value")
    }
    mapping[parts[0]] = parts[1]
  }
  return mapping, nil
}

func translate(config Config) (translatedConfig, error) {
  tags, err := kvPairsToMap(config.Tags)
  if err != nil {
    return translatedConfig{}, err
  }

  tx := translatedConfig{
		RelayAddress:     config.RelayAddress,
		APIURL:           config.APIURL,
		Proxies:          strings.Join([]string(config.Proxies), ","),
    Tags:             tags,
		StatSocketPort:   uint16(config.StatSocketPort),
		Immortal:         onOrOff(config.Immortal),
		InstallAsService: onOrOff(config.InstallAsService),
		CronMode:         onOrOff(config.CronMode),
		ExtraPrivacy:     onOrOff(config.ExtraPrivacy),
	}

  return tx, nil
}

// IsValid verifies that values that must be supplied for a configuration have
// been supplied and that those whose values cannot be guaranteed to be
// correct by the type system and flag API are valid.
func (config Config) IsValid() bool {
	return config.RelayAddress != "" && config.StatSocketPort <= 65535
}

// String is required by the `flag.Value` interface.
func (proxies *proxyList) String() string {
	return strings.Join([]string(*proxies), ",")
}

// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple proxy addresses, which we collect into an array.
func (proxies *proxyList) Set(value string) error {
	*proxies = append(*proxies, value)
	return nil
}

// String is required by the `flag.Value` interface.
func (tags *tagList) String() string {
  return strings.Join([]string(*tags), ",")
}

// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple tag key=value pairs, which we collect into an array.
func (tags *tagList) Set(kvPair string) error {
  if !strings.Contains(kvPair, "=") {
    return fmt.Errorf("expected tag argument to be formatted key=value")
  }
  *tags = append(*tags, kvPair)
  return nil
}

func main() {
	relayAddr := flag.String(
		"relay",
		"",
		"Connection string to the MIG relay. Must contain credentials")
	apiUrl := flag.String(
		"api",
		defaultAPIURL,
		"URL of the MIG API")
	var proxies proxyList
	flag.Var(
		&proxies,
		"proxy",
		"Address of a proxy to use. Multiple -proxy arguments are accepted")
  var tags tagList
  flag.Var(
    &tags,
    "tag",
    "A tagName=tagValue pair to tag the agent with. Multuple -tag arguments are accepted")
  statPort := flag.Uint(
		"statport",
		defaultStatSocketPort,
		"Port to bind the agent's stat socket to on localhost")
	immortal := flag.Bool(
		"immortal",
		defaultImmortal,
		"Instruct the agent to automatically recover from fatal errors")
	service := flag.Bool(
		"service",
		defaultInstallAsService,
		"Instruct the agent to install itself as a service")
	cronMode := flag.Bool(
		"cron",
		defaultCronMode,
		"Instruct the agent to run all queued actions before terminating instead of running as a daemon")
	extraPrivacy := flag.Bool(
		"extraprivate",
		defaultExtraPrivacy,
		"Instruct the agent to run with extra privacy controls")
	flag.Parse()

	template := template.Must(template.New("mig-agent.cfg").Parse(configTemplate))

	config := Config{
		RelayAddress:     *relayAddr,
		APIURL:           *apiUrl,
		Proxies:          proxies,
    Tags:             tags,
		StatSocketPort:   *statPort,
		Immortal:         *immortal,
		InstallAsService: *service,
		CronMode:         *cronMode,
		ExtraPrivacy:     *extraPrivacy,
	}
	if !config.IsValid() {
		fmt.Fprintf(os.Stderr, "Invalid configuration data supplied. Check that the -relay argument is present and that the -statport argument is a valid port number.\n")
		os.Exit(1)
	}

	translated, err := translate(config)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Error creating configuration: %s\n", err.Error())
    os.Exit(1)
  }

	err = template.Execute(os.Stdout, translated)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error filling configuration template: %s\n", err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


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

package ogletest

import (
	"github.com/jacobsa/oglemock"
)

// failureRecord represents a single failed expectation for a test.
type failureRecord struct {
	// The file name within which the expectation failed, e.g. "foo_test.go".
	FileName string

	// The line number at which the expectation failed.
	LineNumber int

	// The error generated by the testing framework. For example:
	//
	//     Expected: 17
	//     Actual:   "taco", which is not numeric
	//
	GeneratedError string

	// A user-specified string to print out with the error, if any.
	UserError string
}

// TestInfo represents information about a currently running or previously-run
// test.
type TestInfo struct {
	// A mock controller that is set up to report errors to the ogletest test
	// runner. This can be used for setting up mock expectations and handling
	// mock calls. The Finish method should not be run by the user; ogletest will
	// do that automatically after the test's TearDown method is run.
	MockController oglemock.Controller

	// A set of failure records that the test has produced.
	failureRecords []*failureRecord
}

// newTestInfo creates a valid but empty TestInfo struct.
func newTestInfo() *TestInfo {
	return &TestInfo{failureRecords: make([]*failureRecord, 0)}
}

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

package ogletest

import (
	"github.com/jacobsa/oglemock"
)

// failureRecord represents a single failed expectation for a test.
type failureRecord struct {
	// The file name within which the expectation failed, e.g. "foo_test.go".
	FileName string

	// The line number at which the expectation failed.
	LineNumber int

	// The error generated by the testing framework. For example:
	//
	//     Expected: 17
	//     Actual:   "taco", which is not numeric
	//
	GeneratedError string

	// A user-specified string to print out with the error, if any.
	UserError string
}

// TestInfo represents information about a currently running or previously-run
// test.
type TestInfo struct {
	// A mock controller that is set up to report errors to the ogletest test
	// runner. This can be used for setting up mock expectations and handling
	// mock calls. The Finish method should not be run by the user; ogletest will
	// do that automatically after the test's TearDown method is run.
	//
	// Note that this feature is still experimental, and is subject to change.
	MockController oglemock.Controller

	// A set of failure records that the test has produced.
	failureRecords []*failureRecord
}

// newTestInfo creates a valid but empty TestInfo struct.
func newTestInfo() *TestInfo {
	return &TestInfo{failureRecords: make([]*failureRecord, 0)}
}

// currentlyRunningTest is the state for the currently running test, if any.
var currentlyRunningTest *TestInfo
<|endoftext|>


<commit_before>// $G $F.go && $L $F.$A && ./$A.out

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Correct short declarations and redeclarations.

package main

func f1() int { return 1 }
func f2() (float, int) { return 1, 2 }
func f3() (float, int, string) { return 1, 2, "3" }

func main() {
	i, f, s := f3();
	j, f := f2();	// redeclare f
	k := f1();
	m, g, s := f3();
	m, h, s := f3();
	{
		// new block should be ok.
		i, f, s := f3();
		j, f := f2();	// redeclare f
		k := f1();
		m, g, s := f3();
		m, h, s := f3();
	}
}
<commit_msg>add another test to decl to see that result vars are redeclarable.<commit_after>// $G $F.go && $L $F.$A && ./$A.out

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Correct short declarations and redeclarations.

package main

func f1() int { return 1 }
func f2() (float, int) { return 1, 2 }
func f3() (float, int, string) { return 1, 2, "3" }

func x() (s string) {
	a, b, s := f3();
	return	// tests that result var is in scope for redeclaration
}

func main() {
	i, f, s := f3();
	j, f := f2();	// redeclare f
	k := f1();
	m, g, s := f3();
	m, h, s := f3();
	{
		// new block should be ok.
		i, f, s := f3();
		j, f := f2();	// redeclare f
		k := f1();
		m, g, s := f3();
		m, h, s := f3();
	}
	if x() != "3" {
		println("g() failed");
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2021 gRPC authors.
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 runner contains code for a test runner that can run a list of
// load tests, wait for them to complete, and report on the results.
package runner

import (
	"fmt"
	"log"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	grpcv1 "github.com/grpc/test-infra/api/v1"
	clientset "github.com/grpc/test-infra/clientset"
)

// AfterIntervalFunction returns a function that stops for a time interval.
// This function is provided so it can be replaced with a fake for testing.
func AfterIntervalFunction(d time.Duration) func() {
	return func() {
		<-time.After(d)
	}
}

// Runner contains the information needed to run multiple sets of LoadTests.
type Runner struct {
	Done           chan string
	loadTestGetter clientset.LoadTestGetter
	afterInterval  func()
	retries        uint
}

// NewRunner creates a new Runner object.
func NewRunner(loadTestGetter clientset.LoadTestGetter, afterInterval func(), retries uint) *Runner {
	return &Runner{
		Done:           make(chan string),
		loadTestGetter: loadTestGetter,
		afterInterval:  afterInterval,
		retries:        retries,
	}
}

// Run runs a set of LoadTests at a given concurrency level.
func (r *Runner) Run(qName string, configs []*grpcv1.LoadTest, concurrencyLevel int) {
	done := make(chan int)
	n := 0
	for i, config := range configs {
		if n < concurrencyLevel {
			go r.runTest(qName, config, i, done)
			n++
			continue
		}
		<-done
		n--
	}
	for n > 0 {
		<-done
		n--
	}
	r.Done <- qName
}

// runTest creates a single LoadTest and monitors it to completion.
func (r *Runner) runTest(qName string, config *grpcv1.LoadTest, i int, done chan int) {
	id := fmt.Sprintf("%-14s %3d", qName, i)
	name := nameString(config)
	var status string
	var retries uint

	for {
		loadTest, err := r.loadTestGetter.Create(config, metav1.CreateOptions{})
		if err != nil {
			log.Printf("[%s] Failed to create test %s", id, name)
			if retries < r.retries {
				retries++
				log.Printf("[%s] Scheduling retry %d/%d to create test", id, retries, r.retries)
				r.afterInterval()
				continue
			}
			log.Printf("[%s] Aborting after %d retries to create test %s", id, r.retries, name)
			done <- i
			return
		}
		retries = 0
		config.Status = loadTest.Status
		log.Printf("[%s] Created test %s", id, name)
		break
	}

	for {
		loadTest, err := r.loadTestGetter.Get(config.Name, metav1.GetOptions{})
		if err != nil {
			log.Printf("[%s] Failed to poll test %s", id, name)
			if retries < r.retries {
				retries++
				log.Printf("[%s] Scheduling retry %d/%d to poll test", id, retries, r.retries)
				r.afterInterval()
				continue
			}
			log.Printf("[%s] Aborting test after %d retries to poll test %s", id, r.retries, name)
			done <- i
			return
		}
		retries = 0
		config.Status = loadTest.Status
		s := status
		status := statusString(config)
		switch {
		case loadTest.Status.State.IsTerminated():
			log.Printf("[%s] %s", id, status)
			done <- i
			return
		case loadTest.Status.State == grpcv1.Running:
			log.Printf("[%s] %s", id, status)
			r.afterInterval()
		default:
			if s != status {
				log.Printf("[%s] %s", id, status)
			}
			// Use a longer polling interval for tests that have not started.
			r.afterInterval()
			r.afterInterval()
		}
	}
}

// nameString returns a string to represent the test name in logs.
// This string consists of two names: (1) the test name in the LoadTest
// metadata, (2) a test name derived from the prefix, scenario and uniquifier
// (if these elements are present in labels and annotations). This is a
// workaround for the fact that we cannot use the second name in the metadata.
// The LoadTest name is currently used as a label in pods, to refer back to the
// correspondingLoadTest (instead of the LoadTest UID). Labels are limited to
// 63 characters, while names themselves can go up to 253.
func nameString(config *grpcv1.LoadTest) string {
	var prefix, scenario string
	var ok bool
	if prefix, ok = config.Labels["prefix"]; !ok {
		return config.Name
	}
	if scenario, ok = config.Annotations["scenario"]; !ok {
		return config.Name
	}
	elems := []string{prefix}
	if scenario != "" {
		elems = append(elems, strings.Split(scenario, "_")...)
	}
	if uniquifier := config.Annotations["uniquifier"]; uniquifier != "" {
		elems = append(elems, uniquifier)
	}
	name := strings.Join(elems, "-")
	if name == config.Name {
		return config.Name
	}
	return fmt.Sprintf("%s [%s]", name, config.Name)
}

// statusString returns a string to represent the test status in logs.
// The string consists of state, reason and message (each omitted if empty).
func statusString(config *grpcv1.LoadTest) string {
	s := []string{string(config.Status.State)}
	if reason := strings.TrimSpace(config.Status.Reason); reason != "" {
		s = append(s, reason)
	}
	if message := strings.TrimSpace(config.Status.Message); message != "" {
		s = append(s, message)
	}
	return strings.Join(s, "; ")
}
<commit_msg>Only report status of tests that are not running when the status changes.<commit_after>/*
Copyright 2021 gRPC authors.
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 runner contains code for a test runner that can run a list of
// load tests, wait for them to complete, and report on the results.
package runner

import (
	"fmt"
	"log"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	grpcv1 "github.com/grpc/test-infra/api/v1"
	clientset "github.com/grpc/test-infra/clientset"
)

// AfterIntervalFunction returns a function that stops for a time interval.
// This function is provided so it can be replaced with a fake for testing.
func AfterIntervalFunction(d time.Duration) func() {
	return func() {
		<-time.After(d)
	}
}

// Runner contains the information needed to run multiple sets of LoadTests.
type Runner struct {
	Done           chan string
	loadTestGetter clientset.LoadTestGetter
	afterInterval  func()
	retries        uint
}

// NewRunner creates a new Runner object.
func NewRunner(loadTestGetter clientset.LoadTestGetter, afterInterval func(), retries uint) *Runner {
	return &Runner{
		Done:           make(chan string),
		loadTestGetter: loadTestGetter,
		afterInterval:  afterInterval,
		retries:        retries,
	}
}

// Run runs a set of LoadTests at a given concurrency level.
func (r *Runner) Run(qName string, configs []*grpcv1.LoadTest, concurrencyLevel int) {
	done := make(chan int)
	n := 0
	for i, config := range configs {
		if n < concurrencyLevel {
			go r.runTest(qName, config, i, done)
			n++
			continue
		}
		<-done
		n--
	}
	for n > 0 {
		<-done
		n--
	}
	r.Done <- qName
}

// runTest creates a single LoadTest and monitors it to completion.
func (r *Runner) runTest(qName string, config *grpcv1.LoadTest, i int, done chan int) {
	id := fmt.Sprintf("%-14s %3d", qName, i)
	name := nameString(config)
	var s, status string
	var retries uint

	for {
		loadTest, err := r.loadTestGetter.Create(config, metav1.CreateOptions{})
		if err != nil {
			log.Printf("[%s] Failed to create test %s", id, name)
			if retries < r.retries {
				retries++
				log.Printf("[%s] Scheduling retry %d/%d to create test", id, retries, r.retries)
				r.afterInterval()
				continue
			}
			log.Printf("[%s] Aborting after %d retries to create test %s", id, r.retries, name)
			done <- i
			return
		}
		retries = 0
		config.Status = loadTest.Status
		log.Printf("[%s] Created test %s", id, name)
		break
	}

	for {
		loadTest, err := r.loadTestGetter.Get(config.Name, metav1.GetOptions{})
		if err != nil {
			log.Printf("[%s] Failed to poll test %s", id, name)
			if retries < r.retries {
				retries++
				log.Printf("[%s] Scheduling retry %d/%d to poll test", id, retries, r.retries)
				r.afterInterval()
				continue
			}
			log.Printf("[%s] Aborting test after %d retries to poll test %s", id, r.retries, name)
			done <- i
			return
		}
		retries = 0
		config.Status = loadTest.Status
		s = status
		status = statusString(config)
		switch {
		case loadTest.Status.State.IsTerminated():
			log.Printf("[%s] %s", id, status)
			done <- i
			return
		case loadTest.Status.State == grpcv1.Running:
			log.Printf("[%s] %s", id, status)
			r.afterInterval()
		default:
			if s != status {
				log.Printf("[%s] %s", id, status)
			}
			// Use a longer polling interval for tests that have not started.
			r.afterInterval()
			r.afterInterval()
		}
	}
}

// nameString returns a string to represent the test name in logs.
// This string consists of two names: (1) the test name in the LoadTest
// metadata, (2) a test name derived from the prefix, scenario and uniquifier
// (if these elements are present in labels and annotations). This is a
// workaround for the fact that we cannot use the second name in the metadata.
// The LoadTest name is currently used as a label in pods, to refer back to the
// correspondingLoadTest (instead of the LoadTest UID). Labels are limited to
// 63 characters, while names themselves can go up to 253.
func nameString(config *grpcv1.LoadTest) string {
	var prefix, scenario string
	var ok bool
	if prefix, ok = config.Labels["prefix"]; !ok {
		return config.Name
	}
	if scenario, ok = config.Annotations["scenario"]; !ok {
		return config.Name
	}
	elems := []string{prefix}
	if scenario != "" {
		elems = append(elems, strings.Split(scenario, "_")...)
	}
	if uniquifier := config.Annotations["uniquifier"]; uniquifier != "" {
		elems = append(elems, uniquifier)
	}
	name := strings.Join(elems, "-")
	if name == config.Name {
		return config.Name
	}
	return fmt.Sprintf("%s [%s]", name, config.Name)
}

// statusString returns a string to represent the test status in logs.
// The string consists of state, reason and message (each omitted if empty).
func statusString(config *grpcv1.LoadTest) string {
	s := []string{string(config.Status.State)}
	if reason := strings.TrimSpace(config.Status.Reason); reason != "" {
		s = append(s, reason)
	}
	if message := strings.TrimSpace(config.Status.Message); message != "" {
		s = append(s, message)
	}
	return strings.Join(s, "; ")
}
<|endoftext|>


<commit_before>package mocks

import (
	"io"
	"io/ioutil"
	"net/url"
	"reflect"
	"testing"
)

type Request struct {
	T *testing.T

	StatusCode int
	// ResponseBody is the bytes that will be returned from Run/MarshalAndRun.
	// all JSON requests should use ResponseObject instead since ResponseBody
	// will not be unmarshalled into `out`
	ResponseBody []byte
	// ResponseObject is the object that will be assigned to `out` when
	// MarshalAndRun is called.
	// All non-JSON requests should use ResponseBody instead since
	// ResponseObject will not be marshalled and returned as responseBody.
	ResponseObject interface{}
	Err            error

	// requestBody and requestObject are set by Run and MarshalAndRun respectively.
	// make assertions against them with AssertRequestBodyEqual or
	// AssertRequestObjectEqual
	requestBody   io.Reader
	requestObject interface{}
}

func (r *Request) AssertRequestBodyEqual(expected string) {
	if r.requestBody == nil {
		if expected == "" {
			return
		}
		r.T.Fatalf("AssertRequestBodyEqual: Expected was not blank, but requestBody was nil. Was Request.Run actually called?")
	}
	actual, err := ioutil.ReadAll(r.requestBody)
	if err != nil {
		r.T.Fatalf("Couldn't read from requestBody - %s", err)
	}
	if string(actual) != expected {
		r.T.Fatalf("Request body did not equal expected:\nexpected: %q \n  actual: %q", expected, actual)
	}
}

func (r *Request) AssertRequestObjectEqual(expected interface{}) {
	if !reflect.DeepEqual(expected, r.requestObject) {
		r.T.Fatalf("Request body did not equal expected:\nexpected: %#v \n  actual: %#v", expected, r.requestObject)
	}
}

func (r *Request) GetURL() url.URL {
	return url.URL{
		Scheme: "HTTP",
		Host:   "fake-host",
		Path:   "fake-path",
	}
}

func (r *Request) AllowInsecure() {

}

func (r *Request) fillOut(out interface{}) {
	if out == nil || r.ResponseObject == nil {
		return
	}
	resVal := reflect.ValueOf(r.ResponseObject)
	if resVal.Kind() == reflect.Ptr {
		resVal = resVal.Elem()
	}
	outVal := reflect.ValueOf(out)
	if resVal.Type().AssignableTo(outVal.Type()) {
		r.T.Fatalf("ResponseBody %s was not assignable to out %s", resVal.Type(), outVal.Type())
	}
	outVal.Elem().Set(resVal)
}

func (r *Request) MarshalAndRun(in interface{}, out interface{}) (statusCode int, responseBody []byte, err error) {
	r.requestObject = in
	r.fillOut(out)
	return r.StatusCode, r.ResponseBody, r.Err
}
func (r *Request) Run(body io.Reader, out interface{}) (statusCode int, responseBody []byte, err error) {
	r.fillOut(out)
	r.requestBody = body
	return r.StatusCode, r.ResponseBody, r.Err
}
<commit_msg>workaround for bug in mocks.Request.AssertRequestObjectEqual()<commit_after>package mocks

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"reflect"
	"testing"
)

type Request struct {
	T *testing.T

	StatusCode int
	// ResponseBody is the bytes that will be returned from Run/MarshalAndRun.
	// all JSON requests should use ResponseObject instead since ResponseBody
	// will not be unmarshalled into `out`
	ResponseBody []byte
	// ResponseObject is the object that will be assigned to `out` when
	// MarshalAndRun is called.
	// All non-JSON requests should use ResponseBody instead since
	// ResponseObject will not be marshalled and returned as responseBody.
	ResponseObject interface{}
	Err            error

	// requestBody and requestObject are set by Run and MarshalAndRun respectively.
	// make assertions against them with AssertRequestBodyEqual or
	// AssertRequestObjectEqual
	requestBody   io.Reader
	requestObject interface{}
}

func (r *Request) AssertRequestBodyEqual(expected string) {
	if r.requestBody == nil {
		if expected == "" {
			return
		}
		r.T.Fatalf("AssertRequestBodyEqual: Expected was not blank, but requestBody was nil. Was Request.Run actually called?")
	}
	actual, err := ioutil.ReadAll(r.requestBody)
	if err != nil {
		r.T.Fatalf("Couldn't read from requestBody - %s", err)
	}
	if string(actual) != expected {
		r.T.Fatalf("Request body did not equal expected:\nexpected: %q \n  actual: %q", expected, actual)
	}
}

func (r *Request) AssertRequestObjectEqual(expected interface{}) {
	if !reflect.DeepEqual(expected, r.requestObject) {
		// FIXME: the Fatalf panics - using Printf for now to get
		// usable debug output
		fmt.Printf("Request body did not equal expected:\nexpected: %#v \n  actual: %#v\n", expected, r.requestObject)
		r.T.Fatalf("Request body did not equal expected:\nexpected: %#v \n  actual: %#v", expected, r.requestObject)
	}
}

func (r *Request) GetURL() url.URL {
	return url.URL{
		Scheme: "HTTP",
		Host:   "fake-host",
		Path:   "fake-path",
	}
}

func (r *Request) AllowInsecure() {

}

func (r *Request) fillOut(out interface{}) {
	if out == nil || r.ResponseObject == nil {
		return
	}
	resVal := reflect.ValueOf(r.ResponseObject)
	if resVal.Kind() == reflect.Ptr {
		resVal = resVal.Elem()
	}
	outVal := reflect.ValueOf(out)
	if resVal.Type().AssignableTo(outVal.Type()) {
		r.T.Fatalf("ResponseBody %s was not assignable to out %s", resVal.Type(), outVal.Type())
	}
	outVal.Elem().Set(resVal)
}

func (r *Request) MarshalAndRun(in interface{}, out interface{}) (statusCode int, responseBody []byte, err error) {
	r.requestObject = in
	r.fillOut(out)
	return r.StatusCode, r.ResponseBody, r.Err
}
func (r *Request) Run(body io.Reader, out interface{}) (statusCode int, responseBody []byte, err error) {
	r.fillOut(out)
	r.requestBody = body
	return r.StatusCode, r.ResponseBody, r.Err
}
<|endoftext|>