<commit_before>package main

import (
	"fmt"
	"koding/klientctl/config"

	"github.com/codegangsta/cli"
)

// cmdDescriptions is the help text shown to user. Note in addition to adding
// new text here you'll need to update main.go to use the description.
var cmdDescriptions = map[string]string{
	"install": fmtDesc(
		"<authToken>",
		fmt.Sprintf("Install the %s. sudo is required.", config.KlientName),
	),
	"mount": fmtDesc(
		"<optional args> <alias:remote path> <local folder>",
		fmt.Sprintf("Mount folder from remote machine to local folder.\n\n    Alias is the local identifer for machine in 'kd list'.\n    Local folder can be relative or absolute path, if\n    folder doesn't exit, it'll be created.\n\n    By default this uses FUSE to mount remote folders.\n    For best I/O performance, especially with commands\n    that does a lot of filesystem operations like git, \n    use --oneway-sync."),
	),
	"ssh": fmtDesc(
		"<alias>", "SSH into the machine.",
	),
	"unmount": fmtDesc(
		"<alias>",
		"Unmount folder which was previously mounted.",
	),
	"remount": fmtDesc(
		"<alias>",
		"Remount machine which was previously mounted using the same settings.",
	),
	"run": fmtDesc(
		"<command> <arguments>",
		fmt.Sprintf("Run command on remote or local machine depending\n    on the location where the command was run.\n\n    All arguments after run are passed to command on\n    remote machine.\n\n    Currently only commands that don't require tty/pty\n    work on remote machines."),
	),
	"list": fmtDesc(
		"", "List running machines for user.",
	),
	"restart": fmtDesc(
		"", fmt.Sprintf("Restart the %s.", config.KlientName),
	),
	"stop": fmtDesc(
		"", fmt.Sprintf("Stop the %s.", config.KlientName),
	),
	"start": fmtDesc(
		"", fmt.Sprintf("Start the %s.", config.KlientName),
	),
	"status": fmtDesc(
		"", fmt.Sprintf("Check status of the %s.", config.KlientName),
	),
	"uninstall": fmtDesc(
		"", fmt.Sprintf("Uninstall the %s.", config.KlientName),
	),
	"update": fmtDesc(
		"", fmt.Sprintf("Update %s to latest version.", config.KlientName),
	),
	"version": fmtDesc(
		"", fmt.Sprintf("Display version information of the %s.", config.KlientName),
	),
}

func init() {
	cli.AppHelpTemplate = `
USAGE:
   {{.Name}} command [command options]

COMMANDS:
   {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
   {{end}}
`

	cli.CommandHelpTemplate = `USAGE:
    kd {{.FullName}}{{if .Description}} {{.Description}}{{end}}{{if .Flags}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}{{end}}
`
}

func fmtDesc(opts, description string) string {
	return fmt.Sprintf("%s\nDESCRIPTION\n    %s", opts, description)
}
<commit_msg>klientctl: use ` to format help strings<commit_after>package main

import (
	"fmt"
	"koding/klientctl/config"

	"github.com/codegangsta/cli"
)

// cmdDescriptions is the help text shown to user. Note in addition to adding
// new text here you'll need to update main.go to use the description.
var cmdDescriptions = map[string]string{
	"install": fmtDesc(
		"<authToken>",
		fmt.Sprintf("Install the %s. sudo is required.", config.KlientName),
	),
	"mount": fmtDesc(
		"<optional args> <alias:remote path> <local folder>",
		fmt.Sprintf(`Mount folder from remote machine to local folder.
    Alias is the local identifer for machine in 'kd list'.

    Local folder can be relative or absolute path, if
    folder doesn't exit, it'll be created.

    By default this uses FUSE to mount remote folders.
    For best I/O performance, especially with commands
    that does a lot of filesystem operations like git,
    use --oneway-sync.`),
	),
	"ssh": fmtDesc(
		"<alias>", "SSH into the machine.",
	),
	"unmount": fmtDesc(
		"<alias>",
		"Unmount folder which was previously mounted.",
	),
	"remount": fmtDesc(
		"<alias>",
		"Remount machine which was previously mounted using the same settings.",
	),
	"run": fmtDesc(
		"<command> <arguments>",
		fmt.Sprintf(`Run command on remote or local machine depending
    on the location where the command was run.

    All arguments after run are passed to command on
    remote machine.

    Currently only commands that don't require tty/pty
    work on remote machines.`),
	),
	"list": fmtDesc(
		"", "List running machines for user.",
	),
	"restart": fmtDesc(
		"", fmt.Sprintf("Restart the %s.", config.KlientName),
	),
	"stop": fmtDesc(
		"", fmt.Sprintf("Stop the %s.", config.KlientName),
	),
	"start": fmtDesc(
		"", fmt.Sprintf("Start the %s.", config.KlientName),
	),
	"status": fmtDesc(
		"", fmt.Sprintf("Check status of the %s.", config.KlientName),
	),
	"uninstall": fmtDesc(
		"", fmt.Sprintf("Uninstall the %s.", config.KlientName),
	),
	"update": fmtDesc(
		"", fmt.Sprintf("Update %s to latest version.", config.KlientName),
	),
	"version": fmtDesc(
		"", fmt.Sprintf("Display version information of the %s.", config.KlientName),
	),
}

func init() {
	cli.AppHelpTemplate = `
USAGE:
   {{.Name}} command [command options]

COMMANDS:
   {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
   {{end}}
`

	cli.CommandHelpTemplate = `USAGE:
    kd {{.FullName}}{{if .Description}} {{.Description}}{{end}}{{if .Flags}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}{{end}}
`
}

func fmtDesc(opts, description string) string {
	return fmt.Sprintf("%s\nDESCRIPTION\n    %s", opts, description)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreedto 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 consultopo implements topo.Server with consul as the backend.
*/
package consultopo

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/hashicorp/consul/api"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

var (
	consulAuthClientStaticFile = flag.String("consul_auth_static_file", "", "JSON File to read the topos/tokens from.")
)

// ClientAuthCred credential to use for consul clusters
type ClientAuthCred struct {
	// ACLToken when provided, the client will use this token when making requests to the Consul server.
	ACLToken string `json:"acl_token,omitempty"`
}

// Factory is the consul topo.Factory implementation.
type Factory struct{}

// HasGlobalReadOnlyCell is part of the topo.Factory interface.
func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
	return false
}

// Create is part of the topo.Factory interface.
func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
	return NewServer(cell, serverAddr, root)
}

func getClientCreds() (creds map[string]*ClientAuthCred, err error) {
	creds = make(map[string]*ClientAuthCred)

	if *consulAuthClientStaticFile == "" {
		// Not configured, nothing to do.
		log.Infof("Consul client auth is not set up. consul_auth_static_file was not provided")
		return nil, nil
	}

	data, err := ioutil.ReadFile(*consulAuthClientStaticFile)
	if err != nil {
		err = fmt.Errorf("Failed to read consul_auth_static_file file: %v", err)
		return creds, err
	}

	if err := json.Unmarshal(data, &creds); err != nil {
		err = fmt.Errorf(fmt.Sprintf("Error parsing consul_auth_static_file: %v", err))
		return creds, err
	}
	return creds, nil
}

// Server is the implementation of topo.Server for consul.
type Server struct {
	// client is the consul api client.
	client *api.Client
	kv     *api.KV

	// root is the root path for this client.
	root string

	// mu protects the following fields.
	mu sync.Mutex
	// locks is a map of *lockInstance structures.
	// The key is the filepath of the Lock file.
	locks map[string]*lockInstance
}

// lockInstance keeps track of one lock held by this client.
type lockInstance struct {
	// lock has the api.Lock structure.
	lock *api.Lock

	// done is closed when the lock is release by this process.
	done chan struct{}
}

// NewServer returns a new consultopo.Server.
func NewServer(cell, serverAddr, root string) (*Server, error) {
	creds, err := getClientCreds()
	if err != nil {
		return nil, err
	}
	cfg := api.DefaultConfig()
	cfg.Address = serverAddr
	if creds != nil && creds[cell] != nil {
		cfg.Token = creds[cell].ACLToken
	} else {
		log.Warningf("Client auth not configured for cell: %v", cell)
	}
	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, err
	}

	return &Server{
		client: client,
		kv:     client.KV(),
		root:   root,
		locks:  make(map[string]*lockInstance),
	}, nil
}

// Close implements topo.Server.Close.
// It will nil out the global and cells fields, so any attempt to
// re-use this server will panic.
func (s *Server) Close() {
	s.client = nil
	s.kv = nil
	s.mu.Lock()
	defer s.mu.Unlock()
	s.locks = nil
}

func init() {
	topo.RegisterFactory("consul", Factory{})
}
<commit_msg>Improve warning so it's not that verbose.<commit_after>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreedto 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 consultopo implements topo.Server with consul as the backend.
*/
package consultopo

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/hashicorp/consul/api"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

var (
	consulAuthClientStaticFile = flag.String("consul_auth_static_file", "", "JSON File to read the topos/tokens from.")
)

// ClientAuthCred credential to use for consul clusters
type ClientAuthCred struct {
	// ACLToken when provided, the client will use this token when making requests to the Consul server.
	ACLToken string `json:"acl_token,omitempty"`
}

// Factory is the consul topo.Factory implementation.
type Factory struct{}

// HasGlobalReadOnlyCell is part of the topo.Factory interface.
func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
	return false
}

// Create is part of the topo.Factory interface.
func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
	return NewServer(cell, serverAddr, root)
}

func getClientCreds() (creds map[string]*ClientAuthCred, err error) {
	creds = make(map[string]*ClientAuthCred)

	if *consulAuthClientStaticFile == "" {
		// Not configured, nothing to do.
		log.Infof("Consul client auth is not set up. consul_auth_static_file was not provided")
		return nil, nil
	}

	data, err := ioutil.ReadFile(*consulAuthClientStaticFile)
	if err != nil {
		err = fmt.Errorf("Failed to read consul_auth_static_file file: %v", err)
		return creds, err
	}

	if err := json.Unmarshal(data, &creds); err != nil {
		err = fmt.Errorf(fmt.Sprintf("Error parsing consul_auth_static_file: %v", err))
		return creds, err
	}
	return creds, nil
}

// Server is the implementation of topo.Server for consul.
type Server struct {
	// client is the consul api client.
	client *api.Client
	kv     *api.KV

	// root is the root path for this client.
	root string

	// mu protects the following fields.
	mu sync.Mutex
	// locks is a map of *lockInstance structures.
	// The key is the filepath of the Lock file.
	locks map[string]*lockInstance
}

// lockInstance keeps track of one lock held by this client.
type lockInstance struct {
	// lock has the api.Lock structure.
	lock *api.Lock

	// done is closed when the lock is release by this process.
	done chan struct{}
}

// NewServer returns a new consultopo.Server.
func NewServer(cell, serverAddr, root string) (*Server, error) {
	creds, err := getClientCreds()
	if err != nil {
		return nil, err
	}
	cfg := api.DefaultConfig()
	cfg.Address = serverAddr
	if creds != nil {
		if creds[cell] != nil {
			cfg.Token = creds[cell].ACLToken
		} else {
			log.Warningf("Client auth not configured for cell: %v", cell)
		}
	}

	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, err
	}

	return &Server{
		client: client,
		kv:     client.KV(),
		root:   root,
		locks:  make(map[string]*lockInstance),
	}, nil
}

// Close implements topo.Server.Close.
// It will nil out the global and cells fields, so any attempt to
// re-use this server will panic.
func (s *Server) Close() {
	s.client = nil
	s.kv = nil
	s.mu.Lock()
	defer s.mu.Unlock()
	s.locks = nil
}

func init() {
	topo.RegisterFactory("consul", Factory{})
}
<|endoftext|>


<commit_before>package bmsteams

import (
	"context"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"

	//	"github.com/davecgh/go-spew/spew"
	"github.com/mattn/godown"
	msgraph "github.com/yaegashi/msgraph.go/beta"
	"github.com/yaegashi/msgraph.go/msauth"

	"golang.org/x/oauth2"
)

var defaultScopes = []string{} //"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)

type Bmsteams struct {
	gc    *msgraph.GraphServiceRequestBuilder
	ctx   context.Context
	botID string
	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bmsteams{Config: cfg}
}

func (b *Bmsteams) Connect() error {
	tokenCachePath := b.GetString("sessionFile")
	if tokenCachePath == "" {
		tokenCachePath = "msteams_session.json"
	}
	ctx := context.Background()
	m := msauth.NewManager()
	m.LoadFile(tokenCachePath) //nolint:errcheck
	ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil)
	if err != nil {
		return err
	}
	err = m.SaveFile(tokenCachePath)
	if err != nil {
		b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
	}
	// make file readable only for matterbridge user
	err = os.Chmod(tokenCachePath, 0600)
	if err != nil {
		b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
	}
	httpClient := oauth2.NewClient(ctx, ts)
	graphClient := msgraph.NewClient(httpClient)
	b.gc = graphClient
	b.ctx = ctx

	err = b.setBotID()
	if err != nil {
		return err
	}
	b.Log.Info("Connection succeeded")
	return nil
}

func (b *Bmsteams) Disconnect() error {
	return nil
}

func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
	go b.poll(channel.Name)
	return nil
}

func (b *Bmsteams) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)
	if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
		return b.sendReply(msg)
	}
	if msg.ParentID == "msg-parent-not-found" {
		msg.ParentID = ""
		msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
	}
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
	text := msg.Username + msg.Text
	content := &msgraph.ItemBody{Content: &text}
	rmsg := &msgraph.ChatMessage{Body: content}
	res, err := ct.Add(b.ctx, rmsg)
	if err != nil {
		return "", err
	}
	return *res.ID, nil
}

func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
	// Handle prefix hint for unthreaded messages.

	text := msg.Username + msg.Text
	content := &msgraph.ItemBody{Content: &text}
	rmsg := &msgraph.ChatMessage{Body: content}
	res, err := ct.Add(b.ctx, rmsg)
	if err != nil {
		return "", err
	}
	return *res.ID, nil
}

func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request()
	rct, err := ct.Get(b.ctx)
	if err != nil {
		return nil, err
	}
	b.Log.Debugf("got %#v messages", len(rct))
	return rct, nil
}

func (b *Bmsteams) poll(channelName string) {
	msgmap := make(map[string]time.Time)
	b.Log.Debug("getting initial messages")
	res, err := b.getMessages(channelName)
	if err != nil {
		panic(err)
	}
	for _, msg := range res {
		msgmap[*msg.ID] = *msg.CreatedDateTime
		if msg.LastModifiedDateTime != nil {
			msgmap[*msg.ID] = *msg.LastModifiedDateTime
		}
	}
	time.Sleep(time.Second * 5)
	b.Log.Debug("polling for messages")
	for {
		res, err := b.getMessages(channelName)
		if err != nil {
			panic(err)
		}
		for i := len(res) - 1; i >= 0; i-- {
			msg := res[i]
			if mtime, ok := msgmap[*msg.ID]; ok {
				if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil {
					continue
				}
				if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime {
					continue
				}
			}
			if *msg.From.User.ID == b.botID {
				b.Log.Debug("skipping own message")
				msgmap[*msg.ID] = *msg.CreatedDateTime
				continue
			}
			msgmap[*msg.ID] = *msg.CreatedDateTime
			if msg.LastModifiedDateTime != nil {
				msgmap[*msg.ID] = *msg.LastModifiedDateTime
			}
			b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account)
			text := b.convertToMD(*msg.Body.Content)
			rmsg := config.Message{
				Username: *msg.From.User.DisplayName,
				Text:     text,
				Channel:  channelName,
				Account:  b.Account,
				Avatar:   "",
				UserID:   *msg.From.User.ID,
				ID:       *msg.ID,
				Extra:    make(map[string][]interface{}),
			}

			b.handleAttachments(&rmsg, msg)
			b.Log.Debugf("<= Message is %#v", rmsg)
			b.Remote <- rmsg
		}
		time.Sleep(time.Second * 5)
	}
}

func (b *Bmsteams) setBotID() error {
	req := b.gc.Me().Request()
	r, err := req.Get(b.ctx)
	if err != nil {
		return err
	}
	b.botID = *r.ID
	return nil
}

func (b *Bmsteams) convertToMD(text string) string {
	if !strings.Contains(text, "<div>") {
		return text
	}
	var sb strings.Builder
	err := godown.Convert(&sb, strings.NewReader(text), nil)
	if err != nil {
		b.Log.Errorf("Couldn't convert message to markdown %s", text)
		return text
	}
	return sb.String()
}
<commit_msg>Add scopes again<commit_after>package bmsteams

import (
	"context"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"

	//	"github.com/davecgh/go-spew/spew"
	"github.com/mattn/godown"
	msgraph "github.com/yaegashi/msgraph.go/beta"
	"github.com/yaegashi/msgraph.go/msauth"

	"golang.org/x/oauth2"
)

var defaultScopes = []string{"openid", "profile", "offline_access", "Group.Read.All", "Group.ReadWrite.All"}
var attachRE = regexp.MustCompile(`<attachment id=.*?attachment>`)

type Bmsteams struct {
	gc    *msgraph.GraphServiceRequestBuilder
	ctx   context.Context
	botID string
	*bridge.Config
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bmsteams{Config: cfg}
}

func (b *Bmsteams) Connect() error {
	tokenCachePath := b.GetString("sessionFile")
	if tokenCachePath == "" {
		tokenCachePath = "msteams_session.json"
	}
	ctx := context.Background()
	m := msauth.NewManager()
	m.LoadFile(tokenCachePath) //nolint:errcheck
	ts, err := m.DeviceAuthorizationGrant(ctx, b.GetString("TenantID"), b.GetString("ClientID"), defaultScopes, nil)
	if err != nil {
		return err
	}
	err = m.SaveFile(tokenCachePath)
	if err != nil {
		b.Log.Errorf("Couldn't save sessionfile in %s: %s", tokenCachePath, err)
	}
	// make file readable only for matterbridge user
	err = os.Chmod(tokenCachePath, 0600)
	if err != nil {
		b.Log.Errorf("Couldn't change permissions for %s: %s", tokenCachePath, err)
	}
	httpClient := oauth2.NewClient(ctx, ts)
	graphClient := msgraph.NewClient(httpClient)
	b.gc = graphClient
	b.ctx = ctx

	err = b.setBotID()
	if err != nil {
		return err
	}
	b.Log.Info("Connection succeeded")
	return nil
}

func (b *Bmsteams) Disconnect() error {
	return nil
}

func (b *Bmsteams) JoinChannel(channel config.ChannelInfo) error {
	go b.poll(channel.Name)
	return nil
}

func (b *Bmsteams) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)
	if msg.ParentID != "" && msg.ParentID != "msg-parent-not-found" {
		return b.sendReply(msg)
	}
	if msg.ParentID == "msg-parent-not-found" {
		msg.ParentID = ""
		msg.Text = fmt.Sprintf("[thread]: %s", msg.Text)
	}
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().Request()
	text := msg.Username + msg.Text
	content := &msgraph.ItemBody{Content: &text}
	rmsg := &msgraph.ChatMessage{Body: content}
	res, err := ct.Add(b.ctx, rmsg)
	if err != nil {
		return "", err
	}
	return *res.ID, nil
}

func (b *Bmsteams) sendReply(msg config.Message) (string, error) {
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(msg.Channel).Messages().ID(msg.ParentID).Replies().Request()
	// Handle prefix hint for unthreaded messages.

	text := msg.Username + msg.Text
	content := &msgraph.ItemBody{Content: &text}
	rmsg := &msgraph.ChatMessage{Body: content}
	res, err := ct.Add(b.ctx, rmsg)
	if err != nil {
		return "", err
	}
	return *res.ID, nil
}

func (b *Bmsteams) getMessages(channel string) ([]msgraph.ChatMessage, error) {
	ct := b.gc.Teams().ID(b.GetString("TeamID")).Channels().ID(channel).Messages().Request()
	rct, err := ct.Get(b.ctx)
	if err != nil {
		return nil, err
	}
	b.Log.Debugf("got %#v messages", len(rct))
	return rct, nil
}

func (b *Bmsteams) poll(channelName string) {
	msgmap := make(map[string]time.Time)
	b.Log.Debug("getting initial messages")
	res, err := b.getMessages(channelName)
	if err != nil {
		panic(err)
	}
	for _, msg := range res {
		msgmap[*msg.ID] = *msg.CreatedDateTime
		if msg.LastModifiedDateTime != nil {
			msgmap[*msg.ID] = *msg.LastModifiedDateTime
		}
	}
	time.Sleep(time.Second * 5)
	b.Log.Debug("polling for messages")
	for {
		res, err := b.getMessages(channelName)
		if err != nil {
			panic(err)
		}
		for i := len(res) - 1; i >= 0; i-- {
			msg := res[i]
			if mtime, ok := msgmap[*msg.ID]; ok {
				if mtime == *msg.CreatedDateTime && msg.LastModifiedDateTime == nil {
					continue
				}
				if msg.LastModifiedDateTime != nil && mtime == *msg.LastModifiedDateTime {
					continue
				}
			}
			if *msg.From.User.ID == b.botID {
				b.Log.Debug("skipping own message")
				msgmap[*msg.ID] = *msg.CreatedDateTime
				continue
			}
			msgmap[*msg.ID] = *msg.CreatedDateTime
			if msg.LastModifiedDateTime != nil {
				msgmap[*msg.ID] = *msg.LastModifiedDateTime
			}
			b.Log.Debugf("<= Sending message from %s on %s to gateway", *msg.From.User.DisplayName, b.Account)
			text := b.convertToMD(*msg.Body.Content)
			rmsg := config.Message{
				Username: *msg.From.User.DisplayName,
				Text:     text,
				Channel:  channelName,
				Account:  b.Account,
				Avatar:   "",
				UserID:   *msg.From.User.ID,
				ID:       *msg.ID,
				Extra:    make(map[string][]interface{}),
			}

			b.handleAttachments(&rmsg, msg)
			b.Log.Debugf("<= Message is %#v", rmsg)
			b.Remote <- rmsg
		}
		time.Sleep(time.Second * 5)
	}
}

func (b *Bmsteams) setBotID() error {
	req := b.gc.Me().Request()
	r, err := req.Get(b.ctx)
	if err != nil {
		return err
	}
	b.botID = *r.ID
	return nil
}

func (b *Bmsteams) convertToMD(text string) string {
	if !strings.Contains(text, "<div>") {
		return text
	}
	var sb strings.Builder
	err := godown.Convert(&sb, strings.NewReader(text), nil)
	if err != nil {
		b.Log.Errorf("Couldn't convert message to markdown %s", text)
		return text
	}
	return sb.String()
}
<|endoftext|>


<commit_before>package pkg_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry/bosh-init/state/pkg"

	birelpkg "github.com/cloudfoundry/bosh-init/release/pkg"
)

var _ = Describe("DependencyResolver", func() {
	It("supports a single dependency", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}

		deps := ResolveDependencies(&a)
		Expect(deps).To(Equal([]*birelpkg.Package{&b}))
	})

	It("supports a transitive dependency", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		c := birelpkg.Package{Name: "c"}
		b.Dependencies = []*birelpkg.Package{&c}

		deps := ResolveDependencies(&a)
		Expect(deps).To(Equal([]*birelpkg.Package{&c, &b}))
	})

	It("supports simple cycles", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		b.Dependencies = []*birelpkg.Package{&a}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&b}))
	})

	It("supports triangular cycles", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		c := birelpkg.Package{Name: "c"}
		b.Dependencies = []*birelpkg.Package{&c}
		c.Dependencies = []*birelpkg.Package{&a}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&c, &b}))
	})

	It("supports sibling dependencies", func(){
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		c := birelpkg.Package{Name: "c"}
		d := birelpkg.Package{Name: "d"}

		a.Dependencies = []*birelpkg.Package{&b, &c}
		b.Dependencies = []*birelpkg.Package{&c, &d}
		c.Dependencies = []*birelpkg.Package{&d}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&d, &c, &b}))
	})
})
<commit_msg>Add new "diamond" dependencies test case<commit_after>package pkg_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry/bosh-init/state/pkg"

	birelpkg "github.com/cloudfoundry/bosh-init/release/pkg"
)

var _ = Describe("DependencyResolver", func() {
	It("supports a single dependency", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}

		deps := ResolveDependencies(&a)
		Expect(deps).To(Equal([]*birelpkg.Package{&b}))
	})

	It("supports a transitive dependency", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		c := birelpkg.Package{Name: "c"}
		b.Dependencies = []*birelpkg.Package{&c}

		deps := ResolveDependencies(&a)
		Expect(deps).To(Equal([]*birelpkg.Package{&c, &b}))
	})

	It("supports simple cycles", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		b.Dependencies = []*birelpkg.Package{&a}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&b}))
	})

	It("supports triangular cycles", func() {
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		a.Dependencies = []*birelpkg.Package{&b}
		c := birelpkg.Package{Name: "c"}
		b.Dependencies = []*birelpkg.Package{&c}
		c.Dependencies = []*birelpkg.Package{&a}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&c, &b}))
	})

	It("supports diamond cycles", func(){
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		c := birelpkg.Package{Name: "c"}
		d := birelpkg.Package{Name: "d"}

		a.Dependencies = []*birelpkg.Package{&c}
		b.Dependencies = []*birelpkg.Package{&a}
		c.Dependencies = []*birelpkg.Package{&d}
		d.Dependencies = []*birelpkg.Package{&b}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&b, &d, &c}))
	})

	It("supports sibling dependencies", func(){
		a := birelpkg.Package{Name: "a"}
		b := birelpkg.Package{Name: "b"}
		c := birelpkg.Package{Name: "c"}
		d := birelpkg.Package{Name: "d"}

		a.Dependencies = []*birelpkg.Package{&b, &c}
		b.Dependencies = []*birelpkg.Package{&c, &d}
		c.Dependencies = []*birelpkg.Package{&d}

		deps := ResolveDependencies(&a)
		Expect(deps).ToNot(ContainElement(&a))
		Expect(deps).To(Equal([]*birelpkg.Package{&d, &c, &b}))
	})
})
<|endoftext|>


<commit_before>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"fmt"
	"testing"

	_ "github.com/go-sql-driver/mysql"
)

const (
	dtypeStorReq = "DATA_PLAT:STOR_REQ"
	dtypeStorRel = "DATA_PLAT:STOR_REL"
)

// Driver test function.
func TestDocTypes01(t *testing.T) {
	// Connect to the database.
	driver, connStr := "mysql", "travis@/flow"
	db, err := sql.Open(driver, connStr)
	if err != nil {
		t.Errorf("could not connect to database : %v\n", err)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil {
		t.Errorf("could not ping the database : %v\n", err)
	}
	RegisterDB(db)

	// List document types.
	t.Run("List", func(t *testing.T) {
		dts, err := DocTypes().List(0, 0)
		if err != nil {
			t.Errorf("error : %v", err)
		}

		for _, dt := range dts {
			fmt.Printf("%#v\n", dt)
		}
	})

	// Register a few new document types.
	t.Run("New", func(t *testing.T) {
		tx, err := db.Begin()
		if err != nil {
			t.Fatalf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		_, err = DocTypes().New(tx, dtypeStorReq)
		if err != nil {
			t.Fatalf("error creating document type '%s' : %v\n", dtypeStorReq, err)
		}
		_, err = DocTypes().New(tx, dtypeStorRel)
		if err != nil {
			t.Fatalf("error creating document type '%s' : %v\n", dtypeStorRel, err)
		}

		err = tx.Commit()
		if err != nil {
			t.Errorf("error committing transaction : %v\n", err)
		}
	})

	// Retrieve a specified document type.
	t.Run("GetByID", func(t *testing.T) {
		dt, err := DocTypes().Get(3)
		if err != nil {
			t.Errorf("error getting document type '3' : %v\n", err)
		}

		fmt.Printf("%#v\n", dt)
	})

	// Verify existence of a specified document type.
	t.Run("GetByName", func(t *testing.T) {
		dt, err := DocTypes().GetByName(dtypeStorRel)
		if err != nil {
			t.Errorf("error getting document type '%s' : %v\n", dtypeStorRel, err)
		}

		fmt.Printf("%#v\n", dt)
	})

	// Rename the given document type to the specified new name.
	t.Run("RenameType", func(t *testing.T) {
		tx, err := db.Begin()
		if err != nil {
			t.Errorf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		err = DocTypes().Rename(tx, 3, "DATA_PLAT:STOR_DEL")
		if err != nil {
			t.Errorf("error renaming document type '3' : %v\n", err)
		}

		if err == nil {
			err = tx.Commit()
			if err != nil {
				t.Errorf("error committing transaction : %v\n", err)
			}
		}
	})

	// Rename the given document type to the specified old name.
	t.Run("UndoRename", func(t *testing.T) {
		tx, err := db.Begin()
		if err != nil {
			t.Errorf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		err = DocTypes().Rename(tx, 3, "DATA_PLAT:STOR_REQ")
		if err != nil {
			t.Errorf("error renaming document type '3' : %v\n", err)
		}

		if err == nil {
			err = tx.Commit()
			if err != nil {
				t.Errorf("error committing transaction : %v\n", err)
			}
		}
	})
}
<commit_msg>Modify tests for `DocType` to be self-contained<commit_after>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"testing"

	_ "github.com/go-sql-driver/mysql"
)

const (
	dtypeStorReq = "DATA_PLAT:STOR_REQ"
	dtypeStorRel = "DATA_PLAT:STOR_REL"
)

// Driver test function.
func TestDocTypes01(t *testing.T) {
	// Connect to the database.
	driver, connStr := "mysql", "travis@/flow"
	db, err := sql.Open(driver, connStr)
	if err != nil {
		t.Fatalf("could not connect to database : %v\n", err)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil {
		t.Fatalf("could not ping the database : %v\n", err)
	}
	RegisterDB(db)

	// Tear down.
	defer func() {
		tx, err := db.Begin()
		if err != nil {
			t.Fatalf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		_, err = tx.Exec(`DELETE FROM wf_doctypes_master`)
		if err != nil {
			t.Fatalf("error running transaction : %v\n", err)
		}

		err = tx.Commit()
		if err != nil {
			t.Fatalf("error committing transaction : %v\n", err)
		}
	}()

	// Test life cycle.
	t.Run("CRUL", func(t *testing.T) {
		// Test creation.
		tx, err := db.Begin()
		if err != nil {
			t.Fatalf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		dtypeStorReqID, err := DocTypes().New(tx, dtypeStorReq)
		if err != nil {
			t.Fatalf("error creating document type '%s' : %v\n", dtypeStorReq, err)
		}
		_, err = DocTypes().New(tx, dtypeStorRel)
		if err != nil {
			t.Fatalf("error creating document type '%s' : %v\n", dtypeStorRel, err)
		}

		err = tx.Commit()
		if err != nil {
			t.Fatalf("error committing transaction : %v\n", err)
		}

		// Test reading.
		_, err = DocTypes().Get(dtypeStorReqID)
		if err != nil {
			t.Fatalf("error getting document type : %v\n", err)
		}

		_, err = DocTypes().GetByName(dtypeStorRel)
		if err != nil {
			t.Fatalf("error getting document type '%s' : %v\n", dtypeStorRel, err)
		}

		_, err = DocTypes().List(0, 0)
		if err != nil {
			t.Fatalf("error : %v", err)
		}

		_, err = DocStates().List(0, 0)
		if err != nil {
			t.Fatalf("error : %v", err)
		}

		// Test renaming.
		tx, err = db.Begin()
		if err != nil {
			t.Fatalf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		err = DocTypes().Rename(tx, dtypeStorReqID, "DATA_PLAT:STOR_DEL")
		if err != nil {
			t.Fatalf("error renaming document type : %v\n", err)
		}

		err = tx.Commit()
		if err != nil {
			t.Fatalf("error committing transaction : %v\n", err)
		}

		tx, err = db.Begin()
		if err != nil {
			t.Fatalf("error starting transaction : %v\n", err)
		}
		defer tx.Rollback()

		err = DocTypes().Rename(tx, dtypeStorReqID, "DATA_PLAT:STOR_REQ")
		if err != nil {
			t.Fatalf("error renaming document type : %v\n", err)
		}

		err = tx.Commit()
		if err != nil {
			t.Fatalf("error committing transaction : %v\n", err)
		}
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 options

import (
	"net"
	"reflect"
	"testing"
	"time"

	"github.com/spf13/pflag"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/diff"
	apiserveroptions "k8s.io/apiserver/pkg/server/options"
	cpconfig "k8s.io/cloud-provider/config"
	serviceconfig "k8s.io/cloud-provider/controllers/service/config"
	componentbaseconfig "k8s.io/component-base/config"
	cmconfig "k8s.io/controller-manager/config"
	cmoptions "k8s.io/controller-manager/options"
)

func TestDefaultFlags(t *testing.T) {
	s, _ := NewCloudControllerManagerOptions()

	expected := &CloudControllerManagerOptions{
		Generic: &cmoptions.GenericControllerManagerConfigurationOptions{
			GenericControllerManagerConfiguration: &cmconfig.GenericControllerManagerConfiguration{
				Port:            DefaultInsecureCloudControllerManagerPort, // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				Address:         "0.0.0.0",                                 // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				MinResyncPeriod: metav1.Duration{Duration: 12 * time.Hour},
				ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
					ContentType: "application/vnd.kubernetes.protobuf",
					QPS:         20.0,
					Burst:       30,
				},
				ControllerStartInterval: metav1.Duration{Duration: 0},
				LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
					ResourceLock:      "leases",
					LeaderElect:       true,
					LeaseDuration:     metav1.Duration{Duration: 15 * time.Second},
					RenewDeadline:     metav1.Duration{Duration: 10 * time.Second},
					RetryPeriod:       metav1.Duration{Duration: 2 * time.Second},
					ResourceName:      "cloud-controller-manager",
					ResourceNamespace: "kube-system",
				},
				Controllers: []string{"*"},
			},
			Debugging: &cmoptions.DebuggingOptions{
				DebuggingConfiguration: &componentbaseconfig.DebuggingConfiguration{
					EnableProfiling:           true,
					EnableContentionProfiling: false,
				},
			},
		},
		KubeCloudShared: &KubeCloudSharedOptions{
			KubeCloudSharedConfiguration: &cpconfig.KubeCloudSharedConfiguration{
				RouteReconciliationPeriod: metav1.Duration{Duration: 10 * time.Second},
				NodeMonitorPeriod:         metav1.Duration{Duration: 5 * time.Second},
				ClusterName:               "kubernetes",
				ClusterCIDR:               "",
				AllocateNodeCIDRs:         false,
				CIDRAllocatorType:         "",
				ConfigureCloudRoutes:      true,
			},
			CloudProvider: &CloudProviderOptions{
				CloudProviderConfiguration: &cpconfig.CloudProviderConfiguration{
					Name:            "",
					CloudConfigFile: "",
				},
			},
		},
		ServiceController: &ServiceControllerOptions{
			ServiceControllerConfiguration: &serviceconfig.ServiceControllerConfiguration{
				ConcurrentServiceSyncs: 1,
			},
		},
		SecureServing: (&apiserveroptions.SecureServingOptions{
			BindPort:    10258,
			BindAddress: net.ParseIP("0.0.0.0"),
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "",
				PairName:      "cloud-controller-manager",
			},
			HTTP2MaxStreamsPerConnection: 0,
		}).WithLoopback(),
		InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{
			BindAddress: net.ParseIP("0.0.0.0"),
			BindPort:    int(0),
			BindNetwork: "tcp",
		}).WithLoopback(),
		Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
			CacheTTL:            10 * time.Second,
			ClientTimeout:       10 * time.Second,
			WebhookRetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			ClientCert:          apiserveroptions.ClientCertAuthenticationOptions{},
			RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
				UsernameHeaders:     []string{"x-remote-user"},
				GroupHeaders:        []string{"x-remote-group"},
				ExtraHeaderPrefixes: []string{"x-remote-extra-"},
			},
			RemoteKubeConfigFileOptional: true,
		},
		Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
			AllowCacheTTL:                10 * time.Second,
			DenyCacheTTL:                 10 * time.Second,
			ClientTimeout:                10 * time.Second,
			WebhookRetryBackoff:          apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			RemoteKubeConfigFileOptional: true,
			AlwaysAllowPaths:             []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/*
			AlwaysAllowGroups:            []string{"system:masters"},
		},
		Kubeconfig:                "",
		Master:                    "",
		NodeStatusUpdateFrequency: metav1.Duration{Duration: 5 * time.Minute},
	}
	if !reflect.DeepEqual(expected, s) {
		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
	}
}

func TestAddFlags(t *testing.T) {
	fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
	s, _ := NewCloudControllerManagerOptions()
	for _, f := range s.Flags([]string{""}, []string{""}).FlagSets {
		fs.AddFlagSet(f)
	}

	args := []string{
		"--address=192.168.4.10",
		"--allocate-node-cidrs=true",
		"--bind-address=192.168.4.21",
		"--cert-dir=/a/b/c",
		"--cloud-config=/cloud-config",
		"--cloud-provider=gce",
		"--cluster-cidr=1.2.3.4/24",
		"--cluster-name=k8s",
		"--configure-cloud-routes=false",
		"--contention-profiling=true",
		"--controller-start-interval=2m",
		"--controllers=foo,bar",
		"--http2-max-streams-per-connection=47",
		"--kube-api-burst=100",
		"--kube-api-content-type=application/vnd.kubernetes.protobuf",
		"--kube-api-qps=50.0",
		"--kubeconfig=/kubeconfig",
		"--leader-elect=false",
		"--leader-elect-lease-duration=30s",
		"--leader-elect-renew-deadline=15s",
		"--leader-elect-resource-lock=configmap",
		"--leader-elect-retry-period=5s",
		"--master=192.168.4.20",
		"--min-resync-period=100m",
		"--node-status-update-frequency=10m",
		"--port=10000",
		"--profiling=false",
		"--route-reconciliation-period=30s",
		"--secure-port=10001",
		"--use-service-account-credentials=false",
	}
	fs.Parse(args)

	expected := &CloudControllerManagerOptions{
		Generic: &cmoptions.GenericControllerManagerConfigurationOptions{
			GenericControllerManagerConfiguration: &cmconfig.GenericControllerManagerConfiguration{
				Port:            DefaultInsecureCloudControllerManagerPort, // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				Address:         "0.0.0.0",                                 // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				MinResyncPeriod: metav1.Duration{Duration: 100 * time.Minute},
				ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
					ContentType: "application/vnd.kubernetes.protobuf",
					QPS:         50.0,
					Burst:       100,
				},
				ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute},
				LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
					ResourceLock:      "configmap",
					LeaderElect:       false,
					LeaseDuration:     metav1.Duration{Duration: 30 * time.Second},
					RenewDeadline:     metav1.Duration{Duration: 15 * time.Second},
					RetryPeriod:       metav1.Duration{Duration: 5 * time.Second},
					ResourceName:      "cloud-controller-manager",
					ResourceNamespace: "kube-system",
				},
				Controllers: []string{"foo", "bar"},
			},
			Debugging: &cmoptions.DebuggingOptions{
				DebuggingConfiguration: &componentbaseconfig.DebuggingConfiguration{
					EnableProfiling:           false,
					EnableContentionProfiling: true,
				},
			},
		},
		KubeCloudShared: &KubeCloudSharedOptions{
			KubeCloudSharedConfiguration: &cpconfig.KubeCloudSharedConfiguration{
				RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second},
				NodeMonitorPeriod:         metav1.Duration{Duration: 5 * time.Second},
				ClusterName:               "k8s",
				ClusterCIDR:               "1.2.3.4/24",
				AllocateNodeCIDRs:         true,
				CIDRAllocatorType:         "RangeAllocator",
				ConfigureCloudRoutes:      false,
			},
			CloudProvider: &CloudProviderOptions{
				CloudProviderConfiguration: &cpconfig.CloudProviderConfiguration{
					Name:            "gce",
					CloudConfigFile: "/cloud-config",
				},
			},
		},
		ServiceController: &ServiceControllerOptions{
			ServiceControllerConfiguration: &serviceconfig.ServiceControllerConfiguration{
				ConcurrentServiceSyncs: 1,
			},
		},
		SecureServing: (&apiserveroptions.SecureServingOptions{
			BindPort:    10001,
			BindAddress: net.ParseIP("192.168.4.21"),
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "/a/b/c",
				PairName:      "cloud-controller-manager",
			},
			HTTP2MaxStreamsPerConnection: 47,
		}).WithLoopback(),
		InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{
			BindAddress: net.ParseIP("192.168.4.10"),
			BindPort:    int(10000),
			BindNetwork: "tcp",
		}).WithLoopback(),
		Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
			CacheTTL:            10 * time.Second,
			ClientTimeout:       10 * time.Second,
			WebhookRetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			ClientCert:          apiserveroptions.ClientCertAuthenticationOptions{},
			RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
				UsernameHeaders:     []string{"x-remote-user"},
				GroupHeaders:        []string{"x-remote-group"},
				ExtraHeaderPrefixes: []string{"x-remote-extra-"},
			},
			RemoteKubeConfigFileOptional: true,
		},
		Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
			AllowCacheTTL:                10 * time.Second,
			DenyCacheTTL:                 10 * time.Second,
			ClientTimeout:                10 * time.Second,
			WebhookRetryBackoff:          apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			RemoteKubeConfigFileOptional: true,
			AlwaysAllowPaths:             []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/*
			AlwaysAllowGroups:            []string{"system:masters"},
		},
		Kubeconfig:                "/kubeconfig",
		Master:                    "192.168.4.20",
		NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute},
	}
	if !reflect.DeepEqual(expected, s) {
		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
	}
}
<commit_msg>add test to ensure that user can clear alwaysallowpaths<commit_after>/*
Copyright 2017 The Kubernetes 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 options

import (
	"net"
	"reflect"
	"testing"
	"time"

	"github.com/spf13/pflag"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/diff"
	apiserveroptions "k8s.io/apiserver/pkg/server/options"
	cpconfig "k8s.io/cloud-provider/config"
	serviceconfig "k8s.io/cloud-provider/controllers/service/config"
	componentbaseconfig "k8s.io/component-base/config"
	cmconfig "k8s.io/controller-manager/config"
	cmoptions "k8s.io/controller-manager/options"
)

func TestDefaultFlags(t *testing.T) {
	s, _ := NewCloudControllerManagerOptions()

	expected := &CloudControllerManagerOptions{
		Generic: &cmoptions.GenericControllerManagerConfigurationOptions{
			GenericControllerManagerConfiguration: &cmconfig.GenericControllerManagerConfiguration{
				Port:            DefaultInsecureCloudControllerManagerPort, // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				Address:         "0.0.0.0",                                 // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				MinResyncPeriod: metav1.Duration{Duration: 12 * time.Hour},
				ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
					ContentType: "application/vnd.kubernetes.protobuf",
					QPS:         20.0,
					Burst:       30,
				},
				ControllerStartInterval: metav1.Duration{Duration: 0},
				LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
					ResourceLock:      "leases",
					LeaderElect:       true,
					LeaseDuration:     metav1.Duration{Duration: 15 * time.Second},
					RenewDeadline:     metav1.Duration{Duration: 10 * time.Second},
					RetryPeriod:       metav1.Duration{Duration: 2 * time.Second},
					ResourceName:      "cloud-controller-manager",
					ResourceNamespace: "kube-system",
				},
				Controllers: []string{"*"},
			},
			Debugging: &cmoptions.DebuggingOptions{
				DebuggingConfiguration: &componentbaseconfig.DebuggingConfiguration{
					EnableProfiling:           true,
					EnableContentionProfiling: false,
				},
			},
		},
		KubeCloudShared: &KubeCloudSharedOptions{
			KubeCloudSharedConfiguration: &cpconfig.KubeCloudSharedConfiguration{
				RouteReconciliationPeriod: metav1.Duration{Duration: 10 * time.Second},
				NodeMonitorPeriod:         metav1.Duration{Duration: 5 * time.Second},
				ClusterName:               "kubernetes",
				ClusterCIDR:               "",
				AllocateNodeCIDRs:         false,
				CIDRAllocatorType:         "",
				ConfigureCloudRoutes:      true,
			},
			CloudProvider: &CloudProviderOptions{
				CloudProviderConfiguration: &cpconfig.CloudProviderConfiguration{
					Name:            "",
					CloudConfigFile: "",
				},
			},
		},
		ServiceController: &ServiceControllerOptions{
			ServiceControllerConfiguration: &serviceconfig.ServiceControllerConfiguration{
				ConcurrentServiceSyncs: 1,
			},
		},
		SecureServing: (&apiserveroptions.SecureServingOptions{
			BindPort:    10258,
			BindAddress: net.ParseIP("0.0.0.0"),
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "",
				PairName:      "cloud-controller-manager",
			},
			HTTP2MaxStreamsPerConnection: 0,
		}).WithLoopback(),
		InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{
			BindAddress: net.ParseIP("0.0.0.0"),
			BindPort:    int(0),
			BindNetwork: "tcp",
		}).WithLoopback(),
		Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
			CacheTTL:            10 * time.Second,
			ClientTimeout:       10 * time.Second,
			WebhookRetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			ClientCert:          apiserveroptions.ClientCertAuthenticationOptions{},
			RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
				UsernameHeaders:     []string{"x-remote-user"},
				GroupHeaders:        []string{"x-remote-group"},
				ExtraHeaderPrefixes: []string{"x-remote-extra-"},
			},
			RemoteKubeConfigFileOptional: true,
		},
		Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
			AllowCacheTTL:                10 * time.Second,
			DenyCacheTTL:                 10 * time.Second,
			ClientTimeout:                10 * time.Second,
			WebhookRetryBackoff:          apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			RemoteKubeConfigFileOptional: true,
			AlwaysAllowPaths:             []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/*
			AlwaysAllowGroups:            []string{"system:masters"},
		},
		Kubeconfig:                "",
		Master:                    "",
		NodeStatusUpdateFrequency: metav1.Duration{Duration: 5 * time.Minute},
	}
	if !reflect.DeepEqual(expected, s) {
		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
	}
}

func TestAddFlags(t *testing.T) {
	fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError)
	s, _ := NewCloudControllerManagerOptions()
	for _, f := range s.Flags([]string{""}, []string{""}).FlagSets {
		fs.AddFlagSet(f)
	}

	args := []string{
		"--address=192.168.4.10",
		"--allocate-node-cidrs=true",
		"--authorization-always-allow-paths=", // this proves that we can clear the default
		"--bind-address=192.168.4.21",
		"--cert-dir=/a/b/c",
		"--cloud-config=/cloud-config",
		"--cloud-provider=gce",
		"--cluster-cidr=1.2.3.4/24",
		"--cluster-name=k8s",
		"--configure-cloud-routes=false",
		"--contention-profiling=true",
		"--controller-start-interval=2m",
		"--controllers=foo,bar",
		"--http2-max-streams-per-connection=47",
		"--kube-api-burst=100",
		"--kube-api-content-type=application/vnd.kubernetes.protobuf",
		"--kube-api-qps=50.0",
		"--kubeconfig=/kubeconfig",
		"--leader-elect=false",
		"--leader-elect-lease-duration=30s",
		"--leader-elect-renew-deadline=15s",
		"--leader-elect-resource-lock=configmap",
		"--leader-elect-retry-period=5s",
		"--master=192.168.4.20",
		"--min-resync-period=100m",
		"--node-status-update-frequency=10m",
		"--port=10000",
		"--profiling=false",
		"--route-reconciliation-period=30s",
		"--secure-port=10001",
		"--use-service-account-credentials=false",
	}
	fs.Parse(args)

	expected := &CloudControllerManagerOptions{
		Generic: &cmoptions.GenericControllerManagerConfigurationOptions{
			GenericControllerManagerConfiguration: &cmconfig.GenericControllerManagerConfiguration{
				Port:            DefaultInsecureCloudControllerManagerPort, // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				Address:         "0.0.0.0",                                 // Note: InsecureServingOptions.ApplyTo will write the flag value back into the component config
				MinResyncPeriod: metav1.Duration{Duration: 100 * time.Minute},
				ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
					ContentType: "application/vnd.kubernetes.protobuf",
					QPS:         50.0,
					Burst:       100,
				},
				ControllerStartInterval: metav1.Duration{Duration: 2 * time.Minute},
				LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
					ResourceLock:      "configmap",
					LeaderElect:       false,
					LeaseDuration:     metav1.Duration{Duration: 30 * time.Second},
					RenewDeadline:     metav1.Duration{Duration: 15 * time.Second},
					RetryPeriod:       metav1.Duration{Duration: 5 * time.Second},
					ResourceName:      "cloud-controller-manager",
					ResourceNamespace: "kube-system",
				},
				Controllers: []string{"foo", "bar"},
			},
			Debugging: &cmoptions.DebuggingOptions{
				DebuggingConfiguration: &componentbaseconfig.DebuggingConfiguration{
					EnableProfiling:           false,
					EnableContentionProfiling: true,
				},
			},
		},
		KubeCloudShared: &KubeCloudSharedOptions{
			KubeCloudSharedConfiguration: &cpconfig.KubeCloudSharedConfiguration{
				RouteReconciliationPeriod: metav1.Duration{Duration: 30 * time.Second},
				NodeMonitorPeriod:         metav1.Duration{Duration: 5 * time.Second},
				ClusterName:               "k8s",
				ClusterCIDR:               "1.2.3.4/24",
				AllocateNodeCIDRs:         true,
				CIDRAllocatorType:         "RangeAllocator",
				ConfigureCloudRoutes:      false,
			},
			CloudProvider: &CloudProviderOptions{
				CloudProviderConfiguration: &cpconfig.CloudProviderConfiguration{
					Name:            "gce",
					CloudConfigFile: "/cloud-config",
				},
			},
		},
		ServiceController: &ServiceControllerOptions{
			ServiceControllerConfiguration: &serviceconfig.ServiceControllerConfiguration{
				ConcurrentServiceSyncs: 1,
			},
		},
		SecureServing: (&apiserveroptions.SecureServingOptions{
			BindPort:    10001,
			BindAddress: net.ParseIP("192.168.4.21"),
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "/a/b/c",
				PairName:      "cloud-controller-manager",
			},
			HTTP2MaxStreamsPerConnection: 47,
		}).WithLoopback(),
		InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{
			BindAddress: net.ParseIP("192.168.4.10"),
			BindPort:    int(10000),
			BindNetwork: "tcp",
		}).WithLoopback(),
		Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
			CacheTTL:            10 * time.Second,
			ClientTimeout:       10 * time.Second,
			WebhookRetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			ClientCert:          apiserveroptions.ClientCertAuthenticationOptions{},
			RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
				UsernameHeaders:     []string{"x-remote-user"},
				GroupHeaders:        []string{"x-remote-group"},
				ExtraHeaderPrefixes: []string{"x-remote-extra-"},
			},
			RemoteKubeConfigFileOptional: true,
		},
		Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
			AllowCacheTTL:                10 * time.Second,
			DenyCacheTTL:                 10 * time.Second,
			ClientTimeout:                10 * time.Second,
			WebhookRetryBackoff:          apiserveroptions.DefaultAuthWebhookRetryBackoff(),
			RemoteKubeConfigFileOptional: true,
			AlwaysAllowPaths:             []string{},
			AlwaysAllowGroups:            []string{"system:masters"},
		},
		Kubeconfig:                "/kubeconfig",
		Master:                    "192.168.4.20",
		NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute},
	}
	if !reflect.DeepEqual(expected, s) {
		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
	}
}
<|endoftext|>


<commit_before>package api

import (
	"testing"

	"github.com/NebulousLabs/Sia/build"
)

// TestVersion checks that /daemon/version is responding with the correct
// version.
func TestVersion(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestVersion")
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.Close()
	var dv DaemonVersion
	st.getAPI("/daemon/version", &dv)
	if dv.Version != build.Version {
		t.Fatalf("/daemon/version reporting bad version: expected %v, got %v", build.Version, dv.Version)
	}
}

// TestUpdate checks that /daemon/update correctly asserts that an update is
// not available for the daemon (since the test build is always up to date).
func TestUpdate(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestUpdate")
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.Close()

	var update UpdateInfo
	if err = st.getAPI("/daemon/update", &update); err != nil {
		t.Fatal(err)
	}
	if update.Available && build.Version == update.Version {
		t.Fatal("daemon should not have an update available")
	}
}

/*
// TODO: enable this test again once proper daemon shutdown is implemented (shutting down modules and listener separately).
// TestStop tests the /daemon/stop handler.
func TestStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestStop")
	if err != nil {
		t.Fatal(err)
	}
	var success struct{ Success bool }
	err = st.getAPI("/daemon/stop", &success)
	if err != nil {
		t.Fatal(err)
	}
	// Sleep to give time for server to close, as /daemon/stop will return success
	// before Server.Close() is called.
	time.Sleep(200 * time.Millisecond)
	err = st.getAPI("/daemon/stop", &success)
	if err == nil {
		t.Fatal("after /daemon/stop, subsequent calls should fail")
	}
}
*/
<commit_msg>skip TestUpdate if update API call fails<commit_after>package api

import (
	"strings"
	"testing"

	"github.com/NebulousLabs/Sia/build"
)

// TestVersion checks that /daemon/version is responding with the correct
// version.
func TestVersion(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestVersion")
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.Close()
	var dv DaemonVersion
	st.getAPI("/daemon/version", &dv)
	if dv.Version != build.Version {
		t.Fatalf("/daemon/version reporting bad version: expected %v, got %v", build.Version, dv.Version)
	}
}

// TestUpdate checks that /daemon/update correctly asserts that an update is
// not available for the daemon (since the test build is always up to date).
func TestUpdate(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestUpdate")
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.Close()

	var update UpdateInfo
	if err = st.getAPI("/daemon/update", &update); err != nil {
		// Notify tester that the API call failed, but allow testing to continue.
		// Otherwise you have to be online to run tests.
		if strings.HasSuffix(err.Error(), errEmptyUpdateResponse.Error()) {
			t.Skip(err)
		}
		t.Fatal(err)
	}
	if update.Available && build.Version == update.Version {
		t.Fatal("daemon should not have an update available")
	}
}

/*
// TODO: enable this test again once proper daemon shutdown is implemented (shutting down modules and listener separately).
// TestStop tests the /daemon/stop handler.
func TestStop(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestStop")
	if err != nil {
		t.Fatal(err)
	}
	var success struct{ Success bool }
	err = st.getAPI("/daemon/stop", &success)
	if err != nil {
		t.Fatal(err)
	}
	// Sleep to give time for server to close, as /daemon/stop will return success
	// before Server.Close() is called.
	time.Sleep(200 * time.Millisecond)
	err = st.getAPI("/daemon/stop", &success)
	if err == nil {
		t.Fatal("after /daemon/stop, subsequent calls should fail")
	}
}
*/
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package api

import (
	stderrs "errors"

	"golang.org/x/net/context"
)

// Context carries values across API boundaries.
type Context interface {
	Value(key interface{}) interface{}
}

// The key type is unexported to prevent collisions
type key int

// namespaceKey is the context key for the request namespace.
const namespaceKey key = 0

// NewContext instantiates a base context object for request flows.
func NewContext() Context {
	return context.TODO()
}

// NewDefaultContext instantiates a base context object for request flows in the default namespace
func NewDefaultContext() Context {
	return WithNamespace(NewContext(), NamespaceDefault)
}

// WithValue returns a copy of parent in which the value associated with key is val.
func WithValue(parent Context, key interface{}, val interface{}) Context {
	internalCtx, ok := parent.(context.Context)
	if !ok {
		panic(stderrs.New("Invalid context type"))
	}
	return context.WithValue(internalCtx, key, val)
}

// WithNamespace returns a copy of parent in which the namespace value is set
func WithNamespace(parent Context, namespace string) Context {
	return WithValue(parent, namespaceKey, namespace)
}

// NamespaceFrom returns the value of the namespace key on the ctx
func NamespaceFrom(ctx Context) (string, bool) {
	namespace, ok := ctx.Value(namespaceKey).(string)
	return namespace, ok
}

// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
func NamespaceValue(ctx Context) string {
	namespace, _ := NamespaceFrom(ctx)
	return namespace
}

// ValidNamespace returns false if the namespace on the context differs from the resource.  If the resource has no namespace, it is set to the value in the context.
func ValidNamespace(ctx Context, resource *ObjectMeta) bool {
	ns, ok := NamespaceFrom(ctx)
	if len(resource.Namespace) == 0 {
		resource.Namespace = ns
	}
	return ns == resource.Namespace && ok
}

// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value
func WithNamespaceDefaultIfNone(parent Context) Context {
	namespace, ok := NamespaceFrom(parent)
	if !ok || len(namespace) == 0 {
		return WithNamespace(parent, NamespaceDefault)
	}
	return parent
}
<commit_msg>Add user helper methods to context<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package api

import (
	stderrs "errors"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
	"golang.org/x/net/context"
)

// Context carries values across API boundaries.
type Context interface {
	Value(key interface{}) interface{}
}

// The key type is unexported to prevent collisions
type key int

// namespaceKey is the context key for the request namespace.
const namespaceKey key = 0

// userKey is the context key for the request user.
const userKey key = 1

// NewContext instantiates a base context object for request flows.
func NewContext() Context {
	return context.TODO()
}

// NewDefaultContext instantiates a base context object for request flows in the default namespace
func NewDefaultContext() Context {
	return WithNamespace(NewContext(), NamespaceDefault)
}

// WithValue returns a copy of parent in which the value associated with key is val.
func WithValue(parent Context, key interface{}, val interface{}) Context {
	internalCtx, ok := parent.(context.Context)
	if !ok {
		panic(stderrs.New("Invalid context type"))
	}
	return context.WithValue(internalCtx, key, val)
}

// WithNamespace returns a copy of parent in which the namespace value is set
func WithNamespace(parent Context, namespace string) Context {
	return WithValue(parent, namespaceKey, namespace)
}

// NamespaceFrom returns the value of the namespace key on the ctx
func NamespaceFrom(ctx Context) (string, bool) {
	namespace, ok := ctx.Value(namespaceKey).(string)
	return namespace, ok
}

// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
func NamespaceValue(ctx Context) string {
	namespace, _ := NamespaceFrom(ctx)
	return namespace
}

// ValidNamespace returns false if the namespace on the context differs from the resource.  If the resource has no namespace, it is set to the value in the context.
func ValidNamespace(ctx Context, resource *ObjectMeta) bool {
	ns, ok := NamespaceFrom(ctx)
	if len(resource.Namespace) == 0 {
		resource.Namespace = ns
	}
	return ns == resource.Namespace && ok
}

// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value
func WithNamespaceDefaultIfNone(parent Context) Context {
	namespace, ok := NamespaceFrom(parent)
	if !ok || len(namespace) == 0 {
		return WithNamespace(parent, NamespaceDefault)
	}
	return parent
}

// WithUser returns a copy of parent in which the user value is set
func WithUser(parent Context, user user.Info) Context {
	return WithValue(parent, userKey, user)
}

// UserFrom returns the value of the user key on the ctx
func UserFrom(ctx Context) (user.Info, bool) {
	user, ok := ctx.Value(userKey).(user.Info)
	return user, ok
}
<|endoftext|>


<commit_before>/*
Package board implement a library for placing stones on a Go game board.

It is inspired by 'Move Prediction in the Game of Go'. A thesis presented by Brett Alexander Harrison.
http://www.eecs.harvard.edu/econcs/pubs/Harrisonthesis.pdf
*/
package board

import (
	"errors"
)

type state int

const (
	black state = iota
	white
	empty
	wall
)

/*
A Board contains data of a Go board.

	7 by 7 board example.

	# # # # # # # #         00 01 02 03 04 05 06 07
	# . . . . . . .         08 09 10 11 12 13 14 15
	# . . . . . . .         16 17 18 19 20 21 22 23
	# . . . . . . .         24 25 26 27 28 29 30 31
	# . . . . . . .         32 33 34 35 36 37 38 39
	# . . . . . . .         40 41 42 43 44 45 46 47
	# . . . . . . .         48 49 50 51 52 53 54 55
	# . . . . . . .         56 57 58 59 60 61 62 63
	# # # # # # # #         64 65 66 67 68 69 70 71
	#                       72
*/
type Board struct {

	// Size of Go board. boardSize = (size+2)*(size+1)+1
	size      int
	boardSize int

	// Max number of previous moves to store.
	maxHistory int

	// Arrays for storing states, chains, and chain representatives.
	// Array length is boardSize.
	// chainReps - Zero if no chain.
	states    []state
	chains    []*chain
	chainReps []int

	// Current ko point if exists, 0 otherwise
	koPoint int

	// Number of stones captured
	blackDead int
	whiteDead int

	// Move history
	histories []*history
	depth     int
}

// NewBoard create a Board object.
func NewBoard(size int) Board {

	bh := Board{
		size:       size,
		maxHistory: 600,
	}
	bh.init()

	return bh
}

func (bd *Board) init() {

	bd.boardSize = (bd.size+2)*(bd.size+1) + 1

	// Index zero is not used.
	bd.histories = make([]*history, bd.maxHistory+1)

	bd.states = make([]state, bd.boardSize)

	bd.chains = make([]*chain, bd.boardSize)

	bd.chainReps = make([]int, bd.boardSize)

	bd.initStates()
}

func (bd *Board) initStates() {

	for i := 0; i <= bd.size+2; i++ {

		lead := i * (bd.size + 1)

		if i == 0 || i == bd.size+1 {

			for j := lead; j < lead+(bd.size+1); j++ {
				bd.states[j] = wall
			}

		} else if i == bd.size+2 {

			bd.states[lead] = wall

		} else {

			bd.states[lead] = wall

			for j := lead + 1; j < lead+(bd.size+1); j++ {
				bd.states[j] = empty
			}
		}
	}
}

// String is the text representation of current board state.
func (bd *Board) String() string {

	var line, result string

	for i, s := range bd.states {

		var c string

		switch s {
		case empty:
			c = "."
		case wall:
			c = "#"
		case black:
			c = "X"
		case white:
			c = "O"
		default:
			c = "?"
		}

		if i%(bd.size+1) == 0 && i != 0 {
			result += line + "\n"
			line = c
		} else {

			line += c
		}
	}

	return result
}

// DoBlack puts a black stone on a point.
func (bd *Board) DoBlack(pt int) error {

	return bd.do(pt, black)
}

// DoWhite puts a white stone on a point.
func (bd *Board) DoWhite(pt int) error {

	return bd.do(pt, white)
}

func (bd *Board) do(pt int, clr state) error {

	err := bd.isLegal(pt, clr)
	if err != nil {
		return err
	}

	h := newHistory(clr, pt, bd.koPoint)

	c := newChain(bd.size)
	c.addPoint(pt)

	// Initalize captured
	cp := newChain(bd.size)

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == empty {

			c.addLiberty(n)

		} else if bd.states[n] == clr && c.hasPoint(n) == false {

			c = *bd.joinChains(&c, bd.chains[n])

			bd.updateLibertiesAndChainReps(&c, clr)

		} else if bd.states[n] == bd.oppositePlayer(clr) {

			nc := bd.chains[n]

			if nc.numLiberties == 1 {

				bd.removeFromBoard(nc)

				bd.updatePrisoners(nc, clr)

				//Push
				for j := 0; j < nc.numPoints; j++ {

					ncp := nc.points[j]

					cp.addPoint(ncp)
				}

				bd.updateNeighboringChainsLiberties(nc)

				h.setCaptureDirections(i)
			}
		}
	}

	bd.updateLibertiesAndChainReps(&c, clr)

	bd.updateNeighboringChainsLiberties(&c)

	if cp.numPoints == 1 && c.numPoints == 1 {

		bd.koPoint = cp.points[0]

	} else {

		bd.koPoint = 0
	}

	bd.depth++

	bd.histories[bd.depth] = &h

	return nil
}

func (bd *Board) isLegal(pt int, clr state) error {

	if bd.depth >= bd.maxHistory {
		return errors.New("depth is larger than maxHistory")
	}

	if bd.isEmpty(pt) == false {
		return errors.New("point is not empty")
	}

	if bd.isKo(pt, clr) == true {
		return errors.New("point is Ko")
	}

	if bd.isSuicide(pt, clr) == true {
		return errors.New("point is suicide")
	}

	return nil
}

func (bd *Board) isEmpty(pt int) bool {

	return bd.states[pt] == empty
}

func (bd *Board) isKo(pt int, clr state) bool {

	result := false

	if pt == bd.koPoint {

		// This is for game ending winner fill in self ko.
		if bd.isAdjacentSelfChainWithTwoPlusLiberties(pt, clr) == false {
			result = true
		}
	}

	return result
}

func (bd *Board) isSuicide(pt int, clr state) bool {

	b1 := bd.isAdjacentEmpty(pt)

	b2 := bd.isAdjacentSelfChainWithTwoPlusLiberties(pt, clr)

	b3 := bd.isAdjacentEnemyChainWithOneLiberty(pt, clr)

	return !(b1 || b2 || b3)
}

func (bd *Board) isAdjacentSelfChainWithTwoPlusLiberties(pt int, clr state) bool {

	r := false

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == clr {

			if bd.chains[n].numLiberties >= 2 {

				r = true

				break
			}
		}
	}

	return r
}

func (bd *Board) isAdjacentEmpty(pt int) bool {

	nb := bd.neighbors(pt)

	return bd.states[nb[0]] == empty ||
		bd.states[nb[1]] == empty ||
		bd.states[nb[2]] == empty ||
		bd.states[nb[3]] == empty
}

func (bd *Board) isAdjacentEnemyChainWithOneLiberty(pt int, clr state) bool {

	r := false

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == bd.oppositePlayer(clr) {

			if bd.chains[n].numLiberties == 1 {

				r = true

				break
			}
		}
	}

	return r
}

func (bd *Board) oppositePlayer(clr state) state {

	r := clr

	if clr == black {
		r = white
	} else {
		r = black
	}

	return r
}

func (bd *Board) joinChains(c1 *chain, c2 *chain) *chain {

	// Add points and liberties of c2 to c1.
	for i := 0; i < c2.numPoints; i++ {
		c1.addPoint(c2.points[i])
	}

	return c1
}

func (bd *Board) updateLibertiesAndChainReps(c *chain, clr state) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		// Update states, chains, chain_reps
		bd.states[pt] = clr

		bd.chains[pt] = c

		bd.chainReps[pt] = c.points[0]
	}

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			if bd.states[n] == empty {
				c.addLiberty(n)
			}
		}
	}
}

func (bd *Board) removeFromBoard(c *chain) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		bd.setEmpty(pt)
	}
}

func (bd *Board) updatePrisoners(nc *chain, clr state) {

	if clr == black {
		bd.blackDead += nc.numPoints
	} else if clr == white {
		bd.whiteDead += nc.numPoints
	}
}

func (bd *Board) updateNeighboringChainsLiberties(c *chain) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			bd.updateLiberties(bd.chains[n])
		}
	}
}

func (bd *Board) setEmpty(pt int) {

	bd.states[pt] = empty
	bd.chains[pt] = nil
	bd.chainReps[pt] = 0
}

func (bd *Board) updateLiberties(c *chain) {

	if c == nil {
		return
	}

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			if bd.states[n] == empty {

				c.addLiberty(n)

			} else {

				c.removeLiberty(n) // This is needed for unknown Neighbors.
			}
		}
	}
}

// Undo remove the last stone placed on the Go board.
func (bd *Board) Undo() error {

	if bd.depth == 0 {
		return errors.New("no history")
	}

	h := bd.histories[bd.depth]

	clr := h.color

	pt := h.point

	bd.setEmpty(pt)

	bd.koPoint = 0

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == bd.oppositePlayer(clr) {

			bd.chains[n].addLiberty(pt)

		} else if bd.states[n] == clr {

			chain := bd.reconstructChain(n, clr, pt)

			bd.updateLibertiesAndChainReps(chain, clr)
		}

		if h.isCaptureDirections(i) == true {

			np := bd.oppositePlayer(clr)

			c := bd.reconstructChain(n, empty, pt)

			for j := 0; j < c.numPoints; j++ {
				bd.states[c.points[j]] = np
			}

			bd.updateLibertiesAndChainReps(c, np)

			bd.updateNeighboringChainsLiberties(c)

			// Update prisoners
			if clr == black {
				bd.blackDead -= c.numPoints
			} else if clr == white {
				bd.whiteDead -= c.numPoints
			}
		}
	}

	bd.koPoint = h.koPoint

	bd.depth--

	return nil
}

func (bd *Board) reconstructChain(pt int, clr state, original int) *chain {

	c := newChain(bd.size)
	c.addPoint(pt)

	// searchPoints
	sps := bd.neighbors(pt)

	for len(sps) != 0 {

		len := len(sps)

		for i := len - 1; i >= 0; i-- {

			sp := sps[i]

			if bd.states[sp] == clr && c.hasPoint(sp) == false && sp != original {

				c.addPoint(sp)

				sps = append(sps, bd.neighbors(sp)...)
			}

			// remove sp
			sps = append(sps[:i], sps[i+1:]...)
		}
	}

	return &c
}

// neighbors returns surrounding points with order north/east/south/west.
func (bd *Board) neighbors(pt int) []int {

	return []int{
		pt - (bd.size + 1),
		pt + 1,
		pt + (bd.size + 1),
		pt - 1}
}
<commit_msg>Revise reconstructChain<commit_after>/*
Package board implement a library for placing stones on a Go game board.

It is inspired by 'Move Prediction in the Game of Go'. A thesis presented by Brett Alexander Harrison.
http://www.eecs.harvard.edu/econcs/pubs/Harrisonthesis.pdf
*/
package board

import (
	"errors"
)

type state int

const (
	black state = iota
	white
	empty
	wall
)

/*
A Board contains data of a Go board.

	7 by 7 board example.

	# # # # # # # #         00 01 02 03 04 05 06 07
	# . . . . . . .         08 09 10 11 12 13 14 15
	# . . . . . . .         16 17 18 19 20 21 22 23
	# . . . . . . .         24 25 26 27 28 29 30 31
	# . . . . . . .         32 33 34 35 36 37 38 39
	# . . . . . . .         40 41 42 43 44 45 46 47
	# . . . . . . .         48 49 50 51 52 53 54 55
	# . . . . . . .         56 57 58 59 60 61 62 63
	# # # # # # # #         64 65 66 67 68 69 70 71
	#                       72
*/
type Board struct {

	// boardSize = (size+2)*(size+1)+1
	size      int
	boardSize int

	// Max number of previous moves to store.
	maxHistory int

	// Arrays for storing states, chains, and chain representatives.
	// Array length is boardSize.
	// chainReps - Zero if no chain.
	states    []state
	chains    []*chain
	chainReps []int

	// Current ko point if exists, 0 otherwise
	koPoint int

	// Number of stones captured
	blackDead int
	whiteDead int

	// Move history
	histories []*history
	depth     int
}

// NewBoard create a Board object.
func NewBoard(size int) Board {

	bh := Board{
		size:       size,
		maxHistory: 600,
	}
	bh.init()

	return bh
}

func (bd *Board) init() {

	bd.boardSize = (bd.size+2)*(bd.size+1) + 1

	// Index zero is not used.
	bd.histories = make([]*history, bd.maxHistory+1)

	bd.states = make([]state, bd.boardSize)

	bd.chains = make([]*chain, bd.boardSize)

	bd.chainReps = make([]int, bd.boardSize)

	bd.initStates()
}

func (bd *Board) initStates() {

	for i := 0; i <= bd.size+2; i++ {

		lead := i * (bd.size + 1)

		if i == 0 || i == bd.size+1 {

			for j := lead; j < lead+(bd.size+1); j++ {
				bd.states[j] = wall
			}

		} else if i == bd.size+2 {

			bd.states[lead] = wall

		} else {

			bd.states[lead] = wall

			for j := lead + 1; j < lead+(bd.size+1); j++ {
				bd.states[j] = empty
			}
		}
	}
}

// String is the text representation of current board state.
func (bd *Board) String() string {

	var line, result string

	for i, s := range bd.states {

		var c string

		switch s {
		case empty:
			c = "."
		case wall:
			c = "#"
		case black:
			c = "X"
		case white:
			c = "O"
		default:
			c = "?"
		}

		if i%(bd.size+1) == 0 && i != 0 {
			result += line + "\n"
			line = c
		} else {

			line += c
		}
	}

	return result
}

// DoBlack puts a black stone on a point.
func (bd *Board) DoBlack(pt int) error {

	return bd.do(pt, black)
}

// DoWhite puts a white stone on a point.
func (bd *Board) DoWhite(pt int) error {

	return bd.do(pt, white)
}

func (bd *Board) do(pt int, clr state) error {

	err := bd.isLegal(pt, clr)
	if err != nil {
		return err
	}

	h := newHistory(clr, pt, bd.koPoint)

	c := newChain(bd.size)
	c.addPoint(pt)

	// Initalize captured
	cp := newChain(bd.size)

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == empty {

			c.addLiberty(n)

		} else if bd.states[n] == clr && c.hasPoint(n) == false {

			c = *bd.joinChains(&c, bd.chains[n])

			bd.updateLibertiesAndChainReps(&c, clr)

		} else if bd.states[n] == bd.oppositePlayer(clr) {

			nc := bd.chains[n]

			if nc.numLiberties == 1 {

				bd.removeFromBoard(nc)

				bd.updatePrisoners(nc, clr)

				//Push
				for j := 0; j < nc.numPoints; j++ {

					ncp := nc.points[j]

					cp.addPoint(ncp)
				}

				bd.updateNeighboringChainsLiberties(nc)

				h.setCaptureDirections(i)
			}
		}
	}

	bd.updateLibertiesAndChainReps(&c, clr)

	bd.updateNeighboringChainsLiberties(&c)

	if cp.numPoints == 1 && c.numPoints == 1 {

		bd.koPoint = cp.points[0]

	} else {

		bd.koPoint = 0
	}

	bd.depth++

	bd.histories[bd.depth] = &h

	return nil
}

func (bd *Board) isLegal(pt int, clr state) error {

	if bd.depth >= bd.maxHistory {
		return errors.New("depth is larger than maxHistory")
	}

	if bd.isEmpty(pt) == false {
		return errors.New("point is not empty")
	}

	if bd.isKo(pt, clr) == true {
		return errors.New("point is Ko")
	}

	if bd.isSuicide(pt, clr) == true {
		return errors.New("point is suicide")
	}

	return nil
}

func (bd *Board) isEmpty(pt int) bool {

	return bd.states[pt] == empty
}

func (bd *Board) isKo(pt int, clr state) bool {

	result := false

	if pt == bd.koPoint {

		// This is for game ending winner fill in self ko.
		if bd.isAdjacentSelfChainWithTwoPlusLiberties(pt, clr) == false {
			result = true
		}
	}

	return result
}

func (bd *Board) isSuicide(pt int, clr state) bool {

	b1 := bd.isAdjacentEmpty(pt)

	b2 := bd.isAdjacentSelfChainWithTwoPlusLiberties(pt, clr)

	b3 := bd.isAdjacentEnemyChainWithOneLiberty(pt, clr)

	return !(b1 || b2 || b3)
}

func (bd *Board) isAdjacentSelfChainWithTwoPlusLiberties(pt int, clr state) bool {

	r := false

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == clr {

			if bd.chains[n].numLiberties >= 2 {

				r = true

				break
			}
		}
	}

	return r
}

func (bd *Board) isAdjacentEmpty(pt int) bool {

	nb := bd.neighbors(pt)

	return bd.states[nb[0]] == empty ||
		bd.states[nb[1]] == empty ||
		bd.states[nb[2]] == empty ||
		bd.states[nb[3]] == empty
}

func (bd *Board) isAdjacentEnemyChainWithOneLiberty(pt int, clr state) bool {

	r := false

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == bd.oppositePlayer(clr) {

			if bd.chains[n].numLiberties == 1 {

				r = true

				break
			}
		}
	}

	return r
}

func (bd *Board) oppositePlayer(clr state) state {

	r := clr

	if clr == black {
		r = white
	} else {
		r = black
	}

	return r
}

func (bd *Board) joinChains(c1 *chain, c2 *chain) *chain {

	// Add points and liberties of c2 to c1.
	for i := 0; i < c2.numPoints; i++ {
		c1.addPoint(c2.points[i])
	}

	return c1
}

func (bd *Board) updateLibertiesAndChainReps(c *chain, clr state) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		// Update states, chains, chain_reps
		bd.states[pt] = clr

		bd.chains[pt] = c

		bd.chainReps[pt] = c.points[0]
	}

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			if bd.states[n] == empty {
				c.addLiberty(n)
			}
		}
	}
}

func (bd *Board) removeFromBoard(c *chain) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		bd.setEmpty(pt)
	}
}

func (bd *Board) updatePrisoners(nc *chain, clr state) {

	if clr == black {
		bd.blackDead += nc.numPoints
	} else if clr == white {
		bd.whiteDead += nc.numPoints
	}
}

func (bd *Board) updateNeighboringChainsLiberties(c *chain) {

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			bd.updateLiberties(bd.chains[n])
		}
	}
}

func (bd *Board) setEmpty(pt int) {

	bd.states[pt] = empty
	bd.chains[pt] = nil
	bd.chainReps[pt] = 0
}

func (bd *Board) updateLiberties(c *chain) {

	if c == nil {
		return
	}

	for i := 0; i < c.numPoints; i++ {

		pt := c.points[i]

		nb := bd.neighbors(pt)

		for j := 0; j < 4; j++ {

			n := nb[j]

			if bd.states[n] == empty {

				c.addLiberty(n)

			} else {

				c.removeLiberty(n) // This is needed for unknown Neighbors.
			}
		}
	}
}

// Undo remove the last stone placed on the Go board.
func (bd *Board) Undo() error {

	if bd.depth == 0 {
		return errors.New("no history")
	}

	h := bd.histories[bd.depth]

	clr := h.color

	pt := h.point

	bd.setEmpty(pt)

	bd.koPoint = 0

	nb := bd.neighbors(pt)

	for i := 0; i < 4; i++ {

		n := nb[i]

		if bd.states[n] == bd.oppositePlayer(clr) {

			bd.chains[n].addLiberty(pt)

		} else if bd.states[n] == clr {

			chain := bd.reconstructChain(n, clr, pt)

			bd.updateLibertiesAndChainReps(&chain, clr)
		}

		if h.isCaptureDirections(i) == true {

			np := bd.oppositePlayer(clr)

			c := bd.reconstructChain(n, empty, pt)

			for j := 0; j < c.numPoints; j++ {
				bd.states[c.points[j]] = np
			}

			bd.updateLibertiesAndChainReps(&c, np)

			bd.updateNeighboringChainsLiberties(&c)

			// Update prisoners
			if clr == black {
				bd.blackDead -= c.numPoints
			} else if clr == white {
				bd.whiteDead -= c.numPoints
			}
		}
	}

	bd.koPoint = h.koPoint

	bd.depth--

	return nil
}

func (bd *Board) reconstructChain(pt int, clr state, original int) chain {

	c := newChain(bd.size)

	c.addPoint(pt)

	sps := []int{pt}

	for len(sps) != 0 {

		len := len(sps)

		for i := len - 1; i >= 0; i-- {

			sp := sps[i]

			if bd.states[sp] == clr && c.hasPoint(sp) == false && sp != original {

				c.addPoint(sp)

				sps = append(sps, bd.neighbors(sp)...)
			}

			// remove sp
			sps = append(sps[:i], sps[i+1:]...)
		}
	}

	return c
}

// neighbors returns surrounding points with order north/east/south/west.
func (bd *Board) neighbors(pt int) []int {

	return []int{
		pt - (bd.size + 1),
		pt + 1,
		pt + (bd.size + 1),
		pt - 1}
}
<|endoftext|>


<commit_before>package config

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path"
	"path/filepath"

	"github.com/google/go-jsonnet"

	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
	"github.com/mbrt/gmailctl/pkg/errors"
)

const (
	// LatestVersion points to the latest version of the config format.
	LatestVersion = v1alpha3.Version

	unsupportedHelp = "Please see https://github.com/mbrt/gmailctl#yaml-config-is-unsupported.\n"
)

// ErrNotFound is returned when a file was not found.
var ErrNotFound = errors.New("config not found")

// ReadFile takes a path and returns the parsed config file.
//
// If the config file needs to have access to additional libraries,
// their location can be specified with cfgDirs.
func ReadFile(path, libPath string) (v1alpha3.Config, error) {
	/* #nosec */
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return v1alpha3.Config{}, errors.WithCause(err, ErrNotFound)
	}
	if ext := filepath.Ext(path); ext == ".yml" || ext == ".yaml" {
		return v1alpha3.Config{}, errors.WithDetails(errors.New("YAML config is unsupported"),
			unsupportedHelp)
	}
	// We pass the libPath to jsonnet, because that is the hint
	// to the libraries location. If no library is specified,
	// we use the original file location.
	if libPath == "" {
		libPath = path
	}
	return ReadJsonnet(path, b)
}

// ReadJsonnet parses a buffer containing a jsonnet config.
//
// The path is used to resolve imports.
func ReadJsonnet(p string, buf []byte) (v1alpha3.Config, error) {
	var res v1alpha3.Config
	vm := jsonnet.MakeVM()
	vm.Importer(&jsonnet.FileImporter{
		JPaths: []string{path.Dir(p)},
	})
	jstr, err := vm.EvaluateAnonymousSnippet(p, string(buf))
	if err != nil {
		return res, fmt.Errorf("parsing jsonnet: %w", err)
	}
	version, err := readJSONVersion(jstr)
	if err != nil {
		return res, fmt.Errorf("parsing the config version: %w", err)
	}
	if version != LatestVersion {
		return res, errors.WithDetails(fmt.Errorf("unsupported config version: %s", version),
			unsupportedHelp)
	}
	err = jsonUnmarshalStrict([]byte(jstr), &res)
	return res, err
}

func readJSONVersion(js string) (string, error) {
	// Try to unmarshal only the version
	v := struct {
		Version string `json:"version"`
	}{}
	err := json.Unmarshal([]byte(js), &v)
	return v.Version, err
}

func jsonUnmarshalStrict(buf []byte, v interface{}) error {
	dec := json.NewDecoder(bytes.NewReader(buf))
	dec.DisallowUnknownFields()
	if err := dec.Decode(v); err != nil {
		// Make the error more informative.
		jctx := contextFromJSONErr(err, buf)
		if jctx == "" {
			return err
		}
		return errors.WithDetails(err,
			fmt.Sprintf("JSON context:\n%s", jctx))
	}
	return nil
}

func contextFromJSONErr(err error, buf []byte) string {
	var (
		jserr  *json.SyntaxError
		juerr  *json.UnmarshalTypeError
		offset int
	)
	switch {
	case errors.As(err, &jserr):
		offset = int(jserr.Offset)
	case errors.As(err, &juerr):
		offset = int(juerr.Offset)
	default:
		return ""
	}

	if offset < 0 || offset >= len(buf) {
		return ""
	}

	// Collect 6 lines of context
	begin, end, count := 0, 0, 0
	for i := offset; i >= 0 && count < 3; i-- {
		if buf[i] == '\n' {
			begin = i + 1
			count++
		}
	}
	for i := offset; i < len(buf) && count < 6; i++ {
		if buf[i] == '\n' {
			end = i
			count++
		}
	}
	return string(buf[begin:end])
}
<commit_msg>Fix bug in ReadFile.<commit_after>package config

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path"
	"path/filepath"

	"github.com/google/go-jsonnet"

	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
	"github.com/mbrt/gmailctl/pkg/errors"
)

const (
	// LatestVersion points to the latest version of the config format.
	LatestVersion = v1alpha3.Version

	unsupportedHelp = "Please see https://github.com/mbrt/gmailctl#yaml-config-is-unsupported.\n"
)

// ErrNotFound is returned when a file was not found.
var ErrNotFound = errors.New("config not found")

// ReadFile takes a path and returns the parsed config file.
//
// If the config file needs to have access to additional libraries,
// their location can be specified with cfgDirs.
func ReadFile(path, libPath string) (v1alpha3.Config, error) {
	/* #nosec */
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return v1alpha3.Config{}, errors.WithCause(err, ErrNotFound)
	}
	if ext := filepath.Ext(path); ext == ".yml" || ext == ".yaml" {
		return v1alpha3.Config{}, errors.WithDetails(errors.New("YAML config is unsupported"),
			unsupportedHelp)
	}
	// We pass the libPath to jsonnet, because that is the hint
	// to the libraries location. If no library is specified,
	// we use the original file location.
	if libPath == "" {
		libPath = path
	}
	return ReadJsonnet(libPath, b)
}

// ReadJsonnet parses a buffer containing a jsonnet config.
//
// The path is used to resolve imports.
func ReadJsonnet(p string, buf []byte) (v1alpha3.Config, error) {
	var res v1alpha3.Config
	vm := jsonnet.MakeVM()
	vm.Importer(&jsonnet.FileImporter{
		JPaths: []string{path.Dir(p)},
	})
	jstr, err := vm.EvaluateAnonymousSnippet(p, string(buf))
	if err != nil {
		return res, fmt.Errorf("parsing jsonnet: %w", err)
	}
	version, err := readJSONVersion(jstr)
	if err != nil {
		return res, fmt.Errorf("parsing the config version: %w", err)
	}
	if version != LatestVersion {
		return res, errors.WithDetails(fmt.Errorf("unsupported config version: %s", version),
			unsupportedHelp)
	}
	err = jsonUnmarshalStrict([]byte(jstr), &res)
	return res, err
}

func readJSONVersion(js string) (string, error) {
	// Try to unmarshal only the version
	v := struct {
		Version string `json:"version"`
	}{}
	err := json.Unmarshal([]byte(js), &v)
	return v.Version, err
}

func jsonUnmarshalStrict(buf []byte, v interface{}) error {
	dec := json.NewDecoder(bytes.NewReader(buf))
	dec.DisallowUnknownFields()
	if err := dec.Decode(v); err != nil {
		// Make the error more informative.
		jctx := contextFromJSONErr(err, buf)
		if jctx == "" {
			return err
		}
		return errors.WithDetails(err,
			fmt.Sprintf("JSON context:\n%s", jctx))
	}
	return nil
}

func contextFromJSONErr(err error, buf []byte) string {
	var (
		jserr  *json.SyntaxError
		juerr  *json.UnmarshalTypeError
		offset int
	)
	switch {
	case errors.As(err, &jserr):
		offset = int(jserr.Offset)
	case errors.As(err, &juerr):
		offset = int(juerr.Offset)
	default:
		return ""
	}

	if offset < 0 || offset >= len(buf) {
		return ""
	}

	// Collect 6 lines of context
	begin, end, count := 0, 0, 0
	for i := offset; i >= 0 && count < 3; i-- {
		if buf[i] == '\n' {
			begin = i + 1
			count++
		}
	}
	for i := offset; i < len(buf) && count < 6; i++ {
		if buf[i] == '\n' {
			end = i
			count++
		}
	}
	return string(buf[begin:end])
}
<|endoftext|>