<commit_before>// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.

package command

import (
	"fmt"
	"strings"

	"github.com/sgeb/go-acd"
)

type DownloadCommand struct {
	Meta
}

func (c *DownloadCommand) Help() string {
	return ""
}

func (c *DownloadCommand) Run(args []string) int {
	client, err := c.NewAcdClient()
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	if len(args) == 0 {
		c.Ui.Error("Source path must be provided")
		return 4
	}
	if strings.HasSuffix(args[0], "/") {
		c.Ui.Error(fmt.Sprintf("Source path must not end with '/'"))
		return 4
	}

	// prepare the names to walk in node hierarchy
	names := make([]string, 0)
	for _, s := range strings.Split(args[0], "/") {
		if s != "" {
			names = append(names, s)
		}
	}

	// Start with root folder
	root, _, err := client.Nodes.GetRoot()
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}

	// walk the hiearchy
	targetNode, _, err := root.WalkNodes(names...)
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}
	if !targetNode.IsFile() {
		c.Ui.Error(fmt.Sprintf("Node '%s' is not a file", *targetNode.Name))
		return 4
	}

	// determine output file path
	outputPath := *targetNode.Name
	if len(args) > 1 {
		outputPath = args[1]
	}

	// download
	_, err = targetNode.Typed().(*acd.File).Download(outputPath)
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}

	return 0
}

func (c *DownloadCommand) Synopsis() string {
	return "Download files"
}
<commit_msg>Download to target directory without explicit file<commit_after>// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.

package command

import (
	"fmt"
	"strings"

	"github.com/sgeb/go-acd"
	"os"
)

type DownloadCommand struct {
	Meta
}

func (c *DownloadCommand) Help() string {
	return ""
}

func (c *DownloadCommand) Run(args []string) int {
	client, err := c.NewAcdClient()
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	if len(args) == 0 {
		c.Ui.Error("Source path must be provided")
		return 4
	}
	if strings.HasSuffix(args[0], "/") {
		c.Ui.Error(fmt.Sprintf("Source path must not end with '/'"))
		return 4
	}

	// prepare the names to walk in node hierarchy
	names := make([]string, 0)
	for _, s := range strings.Split(args[0], "/") {
		if s != "" {
			names = append(names, s)
		}
	}

	// Start with root folder
	root, _, err := client.Nodes.GetRoot()
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}

	// walk the hiearchy
	targetNode, _, err := root.WalkNodes(names...)
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}
	if !targetNode.IsFile() {
		c.Ui.Error(fmt.Sprintf("Node '%s' is not a file", *targetNode.Name))
		return 4
	}

	// determine output file path
	outputPath := *targetNode.Name
	if len(args) > 1 {
		outputPath = args[1]

		fi, err := os.Stat(outputPath)
		if err == nil {
			if fi.IsDir() {
				outputPath += fmt.Sprintf("/%s", *targetNode.Name)
			}
		}
	}

	// download
	_, err = targetNode.Typed().(*acd.File).Download(outputPath)
	if err != nil {
		c.Ui.Error(err.Error())
		return 3
	}

	return 0
}

func (c *DownloadCommand) Synopsis() string {
	return "Download files"
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*]+)\\*"
	MessageTextItalicRegexp            = "_([^_]+)_"
	MessageTextStrikethroughRegexp     = "~([^~]+)~"
	MessageTextInlineCodeRegexp        = "`([^`]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						authTest, err := slackClient.AuthTest()
						if err == nil {
							control = fmt.Sprintf("%s/team/%s", authTest.URL, user.Name)
						} else {
							log.Printf("Could get team URL: %s", err)
						}
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetChannelInfo(channelId)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					authTest, err := slackClient.AuthTest()
					if err == nil {
						control = fmt.Sprintf("%s/team/%s", authTest.URL, channel.Name)
					} else {
						log.Printf("Could get team URL: %s", err)
					}
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			return fmt.Sprintf("%s",
				control, Style("message.link"), anchorText)
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Println("Could not parse timestamp \"%s\".", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if m.File == nil {
		return nil
	}
	return &MessageFile{m.File, m.slackClient, m.account}
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<commit_msg>Tweak inline formatting regular expressions to avoid breaking links.<commit_after>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*<<>]+)\\*"
	MessageTextItalicRegexp            = "_([^_<>]+)_"
	MessageTextStrikethroughRegexp     = "~([^~<>]+)~"
	MessageTextInlineCodeRegexp        = "`([^`<>]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						authTest, err := slackClient.AuthTest()
						if err == nil {
							control = fmt.Sprintf("%s/team/%s", authTest.URL, user.Name)
						} else {
							log.Printf("Could get team URL: %s", err)
						}
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetChannelInfo(channelId)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					authTest, err := slackClient.AuthTest()
					if err == nil {
						control = fmt.Sprintf("%s/team/%s", authTest.URL, channel.Name)
					} else {
						log.Printf("Could get team URL: %s", err)
					}
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			return fmt.Sprintf("%s",
				control, Style("message.link"), anchorText)
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Println("Could not parse timestamp \"%s\".", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if m.File == nil {
		return nil
	}
	return &MessageFile{m.File, m.slackClient, m.account}
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 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 gcs

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/jacobsa/gcloud/httputil"
	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
)

func (b *bucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// Construct an appropriate URL.
	//
	// The documentation (http://goo.gl/JKf25r) is extremely vague about how this
	// is supposed to work. As of 2015-03-25, it has no prose but gives the
	// example:
	//
	//     GET https://www.googleapis.com/storage/v1/b/<bucket>/o/<object>
	//
	// In Google-internal bug 19718068, it was clarified that the intent is that
	// each of the bucket and object names are encoded into a single path
	// segment, as defined by RFC 3986.
	bucketSegment := httputil.EncodePathSegment(b.name)
	objectSegment := httputil.EncodePathSegment(req.Name)
	opaque := fmt.Sprintf(
		"//www.googleapis.com/storage/v1/b/%s/o/%s",
		bucketSegment,
		objectSegment)

	query := make(url.Values)
	query.Set("alt", "media")

	if req.Generation != 0 {
		query.Set("generation", fmt.Sprintf("%v", req.Generation))
	}

	url := &url.URL{
		Scheme:   "https",
		Host:     "www.googleapis.com",
		Opaque:   opaque,
		RawQuery: query.Encode(),
	}

	// Create an HTTP request.
	httpReq, err := httputil.NewRequest("GET", url, nil, b.userAgent)
	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set a Range header, if appropriate.
	var bodyLimit int64
	if req.Range != nil {
		var v string
		v, bodyLimit = makeRangeHeaderValue(*req.Range)
		httpReq.Header.Set("Range", v)
	}

	// Call the server.
	httpRes, err := httputil.Do(ctx, b.client, httpReq)
	if err != nil {
		return
	}

	// Close the body if we're returning in error.
	defer func() {
		if err != nil {
			googleapi.CloseBody(httpRes)
		}
	}()

	// Check for HTTP error statuses.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		if typed, ok := err.(*googleapi.Error); ok {
			// Special case: handle not found errors.
			if typed.Code == http.StatusNotFound {
				err = &NotFoundError{Err: typed}
			}

			// Special case: if the user requested a range and we received HTTP 416
			// from the server, treat this as an empty body. See makeRangeHeaderValue
			// for more details.
			if req.Range != nil &&
				typed.Code == http.StatusRequestedRangeNotSatisfiable {
				err = nil
				googleapi.CloseBody(httpRes)
				rc = ioutil.NopCloser(strings.NewReader(""))
			}
		}

		return
	}

	// The body contains the object data.
	rc = httpRes.Body

	// If the user requested a range and we didn't see HTTP 416 above, we require
	// an HTTP 206 response and must truncate the body. See the notes on
	// makeRangeHeaderValue.
	if req.Range != nil {
		if httpRes.StatusCode != http.StatusPartialContent {
			err = fmt.Errorf(
				"Received unexpected status code %d instead of HTTP 206",
				httpRes.StatusCode)

			return
		}

		rc = &limitReadCloser{
			n:       bodyLimit,
			wrapped: rc,
		}
	}

	return
}

// Given a [start, limit) range, create an HTTP 1.1 Range header which ensures
// that the resulting body is what the user intended, given the following
// protocol:
//
//  *  If GCS returns HTTP 416 (Requested range not satisfiable), treat the
//     body as if it is empty.
//
//  *  If GCS returns HTTP 206 (Partial Content), truncate the body to at most
//     the returned number of bytes. Do not treat the body already being
//     shorter than this length as an error.
//
//  *  If GCS returns any other status code, regard it as an error.
//
// This monkeying around is necessary because of various shitty aspects of the
// HTTP 1.1 Range header:
//
//  *  Ranges are [min, max] and require min <= max.
//
//  *  Therefore users cannot request empty ranges, even though the status code
//     and headers may still be meaningful without actual entity body content.
//
//  *  min must be less than the entity body length, unless the server is
//     polite enough to send HTTP 416 when this is not the case. Luckily GCS
//     appears to be.
//
// Cf. http://tools.ietf.org/html/rfc2616#section-14.35.1
func makeRangeHeaderValue(br ByteRange) (hdr string, n int64) {
	// Canonicalize ranges that the server will not like. We must do this because
	// RFC 2616 §14.35.1 requires the last byte position to be greater than or
	// equal to the first byte position.
	if br.Limit < br.Start {
		br.Start = 0
		br.Limit = 0
	}

	// HTTP byte range specifiers are [min, max] double-inclusive, ugh. But we
	// require the user to truncate, so there is no harm in requesting one byte
	// extra at the end. If the range GCS sees goes past the end of the object,
	// it truncates. If the range starts after the end of the object, it returns
	// HTTP 416, which we require the user to handle.
	hdr = fmt.Sprintf("bytes=%d-%d", br.Start, br.Limit)
	n = int64(br.Limit - br.Start)

	return
}
<commit_msg>newLimitReadCloser<commit_after>// Copyright 2015 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 gcs

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/jacobsa/gcloud/httputil"
	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
)

func (b *bucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// Construct an appropriate URL.
	//
	// The documentation (http://goo.gl/JKf25r) is extremely vague about how this
	// is supposed to work. As of 2015-03-25, it has no prose but gives the
	// example:
	//
	//     GET https://www.googleapis.com/storage/v1/b/<bucket>/o/<object>
	//
	// In Google-internal bug 19718068, it was clarified that the intent is that
	// each of the bucket and object names are encoded into a single path
	// segment, as defined by RFC 3986.
	bucketSegment := httputil.EncodePathSegment(b.name)
	objectSegment := httputil.EncodePathSegment(req.Name)
	opaque := fmt.Sprintf(
		"//www.googleapis.com/storage/v1/b/%s/o/%s",
		bucketSegment,
		objectSegment)

	query := make(url.Values)
	query.Set("alt", "media")

	if req.Generation != 0 {
		query.Set("generation", fmt.Sprintf("%v", req.Generation))
	}

	url := &url.URL{
		Scheme:   "https",
		Host:     "www.googleapis.com",
		Opaque:   opaque,
		RawQuery: query.Encode(),
	}

	// Create an HTTP request.
	httpReq, err := httputil.NewRequest("GET", url, nil, b.userAgent)
	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set a Range header, if appropriate.
	var bodyLimit int64
	if req.Range != nil {
		var v string
		v, bodyLimit = makeRangeHeaderValue(*req.Range)
		httpReq.Header.Set("Range", v)
	}

	// Call the server.
	httpRes, err := httputil.Do(ctx, b.client, httpReq)
	if err != nil {
		return
	}

	// Close the body if we're returning in error.
	defer func() {
		if err != nil {
			googleapi.CloseBody(httpRes)
		}
	}()

	// Check for HTTP error statuses.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		if typed, ok := err.(*googleapi.Error); ok {
			// Special case: handle not found errors.
			if typed.Code == http.StatusNotFound {
				err = &NotFoundError{Err: typed}
			}

			// Special case: if the user requested a range and we received HTTP 416
			// from the server, treat this as an empty body. See makeRangeHeaderValue
			// for more details.
			if req.Range != nil &&
				typed.Code == http.StatusRequestedRangeNotSatisfiable {
				err = nil
				googleapi.CloseBody(httpRes)
				rc = ioutil.NopCloser(strings.NewReader(""))
			}
		}

		return
	}

	// The body contains the object data.
	rc = httpRes.Body

	// If the user requested a range and we didn't see HTTP 416 above, we require
	// an HTTP 206 response and must truncate the body. See the notes on
	// makeRangeHeaderValue.
	if req.Range != nil {
		if httpRes.StatusCode != http.StatusPartialContent {
			err = fmt.Errorf(
				"Received unexpected status code %d instead of HTTP 206",
				httpRes.StatusCode)

			return
		}

		rc = newLimitReadCloser(rc, bodyLimit)
	}

	return
}

// Given a [start, limit) range, create an HTTP 1.1 Range header which ensures
// that the resulting body is what the user intended, given the following
// protocol:
//
//  *  If GCS returns HTTP 416 (Requested range not satisfiable), treat the
//     body as if it is empty.
//
//  *  If GCS returns HTTP 206 (Partial Content), truncate the body to at most
//     the returned number of bytes. Do not treat the body already being
//     shorter than this length as an error.
//
//  *  If GCS returns any other status code, regard it as an error.
//
// This monkeying around is necessary because of various shitty aspects of the
// HTTP 1.1 Range header:
//
//  *  Ranges are [min, max] and require min <= max.
//
//  *  Therefore users cannot request empty ranges, even though the status code
//     and headers may still be meaningful without actual entity body content.
//
//  *  min must be less than the entity body length, unless the server is
//     polite enough to send HTTP 416 when this is not the case. Luckily GCS
//     appears to be.
//
// Cf. http://tools.ietf.org/html/rfc2616#section-14.35.1
func makeRangeHeaderValue(br ByteRange) (hdr string, n int64) {
	// Canonicalize ranges that the server will not like. We must do this because
	// RFC 2616 §14.35.1 requires the last byte position to be greater than or
	// equal to the first byte position.
	if br.Limit < br.Start {
		br.Start = 0
		br.Limit = 0
	}

	// HTTP byte range specifiers are [min, max] double-inclusive, ugh. But we
	// require the user to truncate, so there is no harm in requesting one byte
	// extra at the end. If the range GCS sees goes past the end of the object,
	// it truncates. If the range starts after the end of the object, it returns
	// HTTP 416, which we require the user to handle.
	hdr = fmt.Sprintf("bytes=%d-%d", br.Start, br.Limit)
	n = int64(br.Limit - br.Start)

	return
}

type separateReadCloser struct {
	reader io.Reader
	closer io.Closer
}

func (rc *separateReadCloser) Read(p []byte) (n int, err error) {
	n, err = rc.reader.Read(p)
	return
}

func (rc *separateReadCloser) Close() (err error) {
	err = rc.closer.Close()
	return
}

// Create an io.ReadCloser that limits the amount of data returned by a wrapped
// io.ReadCloser. Like io.LimitReader, but supports closing.
func newLimitReadCloser(wrapped io.ReadCloser, n int64) (rc io.ReadCloser) {
	rc = &separateReadCloser{
		reader: io.LimitReader(wrapped, n),
		closer: wrapped,
	}

	return
}
<|endoftext|>


<commit_before>package jcapi

import (
	"encoding/json"
	"fmt"
)

type JCSystem struct {
	Os                             string  `json:os`
	TemplateName                   string  `json:templateName`
	AllowSshRootLogin              bool    `json:allowSshRootLogin`
	Id                             string  `json:id`
	LastContact                    string  `json:lastContact`
	RemoteIP                       string  `json:remoteIP`
	Active                         bool    `json:active`
	SshRootEnabled                 bool    `json:sshRootEnabled`
	AmazonInstanceID               string  `json:amazonInstanceID,omitempty`
	SshPassEnabled                 bool    `json:sshPassEnabled`
	Version                        string  `json:version`
	AgentVersion                   string  `json:agentVersion`
	AllowPublicKeyAuth             bool    `json:allowPublicKeyAuthentication`
	Organization                   string  `json:organization`
	Created                        string  `json:created`
	Arch                           string  `json:arch`
	SystemTimezone                 float64 `json:systemTimeZone`
	AllowSshPasswordAuthentication bool    `json:allowSshPasswordAuthentication`
	DisplayName                    string  `json:displayName`
	ModifySSHDConfig               bool    `json:modifySSHDConfig`
	AllowMultiFactorAuthentication bool    `json:allowMultiFactorAuthentication`
	Hostname                       string  `json:hostname`

	TagList               []string `json:"tags"`
	Patches               []string `json:patches`
	SshParamList          []string `json:sshParams`
	PatchAlarmList        []string `json:patchAlarms`
	NetworkInterfaceList  []string `json:networkInterfaces`
	ConnectionHistoryList []string `json:connectionHistory`

	Tags              []JCTag
	SshdParams        []JCSSHDParam
	NetworkInterfaces []JCNetworkInterface
}

type JCSSHDParam struct {
	Name  string `json:name`
	Value string `json:value`
}

type JCNetworkInterface struct {
	Name     string `json:name`
	Internal bool   `json:internal`
	Family   string `json:family`
	Address  string `json:address`
}

func SystemsToString(systems []JCSystem) string {
	returnVal := ""

	for _, system := range systems {
		returnVal += system.ToString()
	}

	return returnVal
}

func (jcsystem JCSystem) ToString() string {
	returnVal := fmt.Sprintf("JCSystem: OS=[%s] - TemplateName=[%s] - ID=[%s] - RemoteIP=[%s] - LastContact=[%v] - Version=%s - DisplayName=%s - Hostname=%s - Arch=%s\n",
		jcsystem.Os, jcsystem.TemplateName, jcsystem.Id, jcsystem.RemoteIP, jcsystem.LastContact,
		jcsystem.Version, jcsystem.DisplayName, jcsystem.Hostname, jcsystem.Arch)

	for _, tag := range jcsystem.Tags {
		returnVal += fmt.Sprintf("\t%s\n", tag.ToString())
	}

	return returnVal
}

func (jcsystem JCSystem) SystemHasTag(tagName string) (hasTag bool, tagId string) {
	for _, tag := range jcsystem.Tags {
		if tag.Name == tagName {
			hasTag = true
			tagId = tag.Id
			return
		}
	}

	return
}

func getJCSSHDParamFieldsFromInterface(fields map[string]interface{}, params *JCSSHDParam) {
	params.Name = fields["name"].(string)
	params.Value = fields["value"].(string)
}

func getJCSSHDParamFromArray(paramArray []interface{}) []JCSSHDParam {
	var returnVal []JCSSHDParam
	returnVal = make([]JCSSHDParam, len(paramArray))

	for idx, rec := range paramArray {
		getJCSSHDParamFieldsFromInterface(rec.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal

}

func getJCNetworkInterfaceFieldsFromInterface(fields map[string]interface{}, nic *JCNetworkInterface) {
	if _, exists := fields["address"]; exists {
		nic.Address = fields["address"].(string)
	}
	if _, exists := fields["family"]; exists {
		nic.Family = fields["family"].(string)
	}
	if _, exists := fields["internal"]; exists {
		nic.Internal = fields["internal"].(bool)
	}
	if _, exists := fields["name"]; exists {
		nic.Name = fields["name"].(string)
	}
}

func getJCNetworkInterfacesFromArray(nicArray []interface{}) []JCNetworkInterface {
	var returnVal []JCNetworkInterface
	returnVal = make([]JCNetworkInterface, len(nicArray))

	for idx, rec := range nicArray {
		getJCNetworkInterfaceFieldsFromInterface(rec.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

func getJCSystemFieldsFromInterface(fields map[string]interface{}, system *JCSystem) {
	// doing this b/c the jsonSysRec that's returned from the update only has a subset
	// of the fields
	if _, exists := fields["os"]; exists {
		system.Os = fields["os"].(string)
	}
	if _, exists := fields["templateName"]; exists {
		system.TemplateName = fields["templateName"].(string)
	}
	if _, exists := fields["allowSsgRootLogin"]; exists {
		system.AllowSshRootLogin = fields["allowSshRootLogin"].(bool)
	}
	if _, exists := fields["id"]; exists {
		system.Id = fields["id"].(string)
	} else if _, exists := fields["_id"]; exists {
		system.Id = fields["_id"].(string)

	}
	if _, exists := fields["lastContact"]; exists {
		system.LastContact = fields["lastContact"].(string)
	}
	if _, exists := fields["remoteIP"]; exists {
		system.RemoteIP = fields["remoteIP"].(string)
	}
	if _, exists := fields["active"]; exists {
		system.Active = fields["active"].(bool)
	}
	if _, exists := fields["sshRootEnabled"]; exists {
		system.SshRootEnabled = fields["sshRootEnabled"].(bool)
	}
	if _, exists := fields["sshPassEnabled"]; exists {
		system.SshPassEnabled = fields["sshPassEnabled"].(bool)
	}
	if _, exists := fields["version"]; exists {
		system.Version = fields["version"].(string)
	}
	if _, exists := fields["agentVersion"]; exists {
		system.AgentVersion = fields["agentVersion"].(string)
	}
	if _, exists := fields["allowPublicKeyAuthentication"]; exists {
		system.AllowPublicKeyAuth = fields["allowPublicKeyAuthentication"].(bool)
	}
	if _, exists := fields["organization"]; exists {
		system.Organization = fields["organization"].(string)
	}
	if _, exists := fields["created"]; exists {
		system.Created = fields["created"].(string)
	}
	if _, exists := fields["arch"]; exists {
		system.Arch = fields["arch"].(string)
	}
	if _, exists := fields["systemTimeZone"]; exists {
		system.SystemTimezone = fields["systemTimezone"].(float64)
	}
	if _, exists := fields["allowSshPasswordAuthentication"]; exists {
		system.AllowSshPasswordAuthentication = fields["allowSshPasswordAuthentication"].(bool)
	}
	if _, exists := fields["displayName"]; exists {
		system.DisplayName = fields["displayName"].(string)
	}
	if _, exists := fields["modifySSHDConfig"]; exists {
		system.ModifySSHDConfig = fields["modifySSHDConfig"].(bool)
	}
	if _, exists := fields["allowMultiFactorAuthentication"]; exists {
		system.AllowMultiFactorAuthentication = fields["allowMultiFactorAuthentication"].(bool)
	}
	if _, exists := fields["hostname"]; exists {
		system.Hostname = fields["hostname"].(string)
	}

	if _, exists := fields["sshdParams"]; exists {
		system.SshdParams = getJCSSHDParamFromArray(fields["sshdParams"].([]interface{}))
	}
	if _, exists := fields["networkInterfaces"]; exists {
		system.NetworkInterfaces = getJCNetworkInterfacesFromArray(fields["networkInterfaces"].([]interface{}))
	}

	if _, exists := fields["amazonInstanceID"]; exists {
		system.AmazonInstanceID = fields["amazonInstanceID"].(string)
	}
}

func getJCSystemsFromInterface(systemInt interface{}) []JCSystem {

	var returnVal []JCSystem

	recMap := systemInt.(map[string]interface{})

	results := recMap["results"].([]interface{})

	returnVal = make([]JCSystem, len(results))

	for idx, result := range results {
		getJCSystemFieldsFromInterface(result.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

// Executes a search by hostname via the JumpCloud API
func (jc JCAPI) GetSystemByHostName(hostname string, withTags bool) ([]JCSystem, JCError) {
	var returnVal []JCSystem

	jcSystemRec, err := jc.Post("/search/systems", jc.hostnameFilter(hostname))

	if err != nil {
		return nil, fmt.Errorf("ERROR: Post to JumpCloud failed, err='%s'", err)
	}

	if jcSystemRec == nil {
		return nil, fmt.Errorf("ERROR: No systems found")
	}

	returnVal = getJCSystemsFromInterface(jcSystemRec)

	if withTags {
		tags, err := jc.GetAllTags()
		if err != nil {
			return nil, fmt.Errorf("ERROR: Could not get tags, err='%s'", err)
		}

		for idx, _ := range returnVal {
			returnVal[idx].AddJCTagsToSystem(tags)
		}
	}

	return returnVal, nil
}

func (jc JCAPI) GetSystemById(systemId string, withTags bool) (system JCSystem, err JCError) {
	url := fmt.Sprintf("/systems/%s", systemId)

	retVal, err := jc.Get(url)
	if err != nil {
		err = fmt.Errorf("ERROR: Could not get system by ID '%s', err='%s'", systemId, err)
	}

	if retVal != nil {
		getJCSystemFieldsFromInterface(retVal.(map[string]interface{}), &system)

		if withTags {
			// I should be able to use err below as the err return value, but there's
			// a compiler bug here in that it thinks a := of err is shadowed here,
			// even though tags should be the only variable declared with the :=
			tags, err2 := jc.GetAllTags()
			if err != nil {
				err = fmt.Errorf("ERROR: Could not get tags, err='%s'", err2)
				return
			}

			system.AddJCTagsToSystem(tags)
		}
	}

	return
}

func (jc JCAPI) GetSystems(withTags bool) ([]JCSystem, JCError) {
	var returnVal []JCSystem

	for skip := 0; skip == 0 || len(returnVal) == searchLimit; skip += searchSkipInterval {
		url := fmt.Sprintf("/systems?sort=hostname&skip=%d&limit=%d", skip, searchLimit)

		jcSysRec, err2 := jc.Get(url)

		if err2 != nil {
			return nil, fmt.Errorf("ERROR: Get to JumpCloud failed, err='%s'", err2)
		}

		if jcSysRec == nil {
			return nil, fmt.Errorf("ERROR: No systems found")
		}

		returnVal = getJCSystemsFromInterface(jcSysRec)

		if withTags {
			tags, err := jc.GetAllTags()
			if err != nil {
				return nil, fmt.Errorf("ERROR: Could not get tags, err='%s'", err)
			}

			for idx, _ := range returnVal {
				returnVal[idx].AddJCTagsToSystem(tags)
			}
		}

	}
	return returnVal, nil
}

//
// Update a system
//
func (jc JCAPI) UpdateSystem(system JCSystem) (systemId string, err JCError) {
	data, err := json.Marshal(system)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not marshal JCSystem object, err='%s'", err)
	}
	url := "/systems/" + system.Id

	jcSysRec, err := jc.Do(MapJCOpToHTTP(Update), url, data)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not update JCSystem object, err='%s'", err)
	}
	var returnSystem JCSystem
	getJCSystemFieldsFromInterface(jcSysRec.(map[string]interface{}), &returnSystem)

	if returnSystem.Id != system.Id {
		return "", fmt.Errorf("ERROR: JumpCloud did not return the same ID - this should never happen!")
	}
	systemId = returnSystem.Id
	return systemId, nil
}

//!!!!!!!!!!!!WARNING!!!!!!!!!!!!
//This will cause JumpCloud to uninstall the agent on this system
//You will lose control of the system after the call returns
//Seriously, It'll be gone
func (jc JCAPI) DeleteSystem(system JCSystem) JCError {
	_, err := jc.Delete(fmt.Sprintf("/system/%s", system.Id))
	if err != nil {
		return fmt.Errorf("ERROR: Could not delete system '%s': err='%s'", system.Hostname, err)
	}

	return nil
}
<commit_msg>Another field fix during unmarshalling<commit_after>package jcapi

import (
	"encoding/json"
	"fmt"
)

type JCSystem struct {
	Os                             string  `json:os`
	TemplateName                   string  `json:templateName`
	AllowSshRootLogin              bool    `json:allowSshRootLogin`
	Id                             string  `json:id`
	LastContact                    string  `json:lastContact`
	RemoteIP                       string  `json:remoteIP`
	Active                         bool    `json:active`
	SshRootEnabled                 bool    `json:sshRootEnabled`
	AmazonInstanceID               string  `json:amazonInstanceID,omitempty`
	SshPassEnabled                 bool    `json:sshPassEnabled`
	Version                        string  `json:version`
	AgentVersion                   string  `json:agentVersion`
	AllowPublicKeyAuth             bool    `json:allowPublicKeyAuthentication`
	Organization                   string  `json:organization`
	Created                        string  `json:created`
	Arch                           string  `json:arch`
	SystemTimezone                 float64 `json:systemTimeZone`
	AllowSshPasswordAuthentication bool    `json:allowSshPasswordAuthentication`
	DisplayName                    string  `json:displayName`
	ModifySSHDConfig               bool    `json:modifySSHDConfig`
	AllowMultiFactorAuthentication bool    `json:allowMultiFactorAuthentication`
	Hostname                       string  `json:hostname`

	TagList               []string `json:"tags"`
	Patches               []string `json:patches`
	SshParamList          []string `json:sshParams`
	PatchAlarmList        []string `json:patchAlarms`
	NetworkInterfaceList  []string `json:networkInterfaces`
	ConnectionHistoryList []string `json:connectionHistory`

	Tags              []JCTag
	SshdParams        []JCSSHDParam
	NetworkInterfaces []JCNetworkInterface
}

type JCSSHDParam struct {
	Name  string `json:name`
	Value string `json:value`
}

type JCNetworkInterface struct {
	Name     string `json:name`
	Internal bool   `json:internal`
	Family   string `json:family`
	Address  string `json:address`
}

func SystemsToString(systems []JCSystem) string {
	returnVal := ""

	for _, system := range systems {
		returnVal += system.ToString()
	}

	return returnVal
}

func (jcsystem JCSystem) ToString() string {
	returnVal := fmt.Sprintf("JCSystem: OS=[%s] - TemplateName=[%s] - ID=[%s] - RemoteIP=[%s] - LastContact=[%v] - Version=%s - DisplayName=%s - Hostname=%s - Arch=%s\n",
		jcsystem.Os, jcsystem.TemplateName, jcsystem.Id, jcsystem.RemoteIP, jcsystem.LastContact,
		jcsystem.Version, jcsystem.DisplayName, jcsystem.Hostname, jcsystem.Arch)

	for _, tag := range jcsystem.Tags {
		returnVal += fmt.Sprintf("\t%s\n", tag.ToString())
	}

	return returnVal
}

func (jcsystem JCSystem) SystemHasTag(tagName string) (hasTag bool, tagId string) {
	for _, tag := range jcsystem.Tags {
		if tag.Name == tagName {
			hasTag = true
			tagId = tag.Id
			return
		}
	}

	return
}

func getJCSSHDParamFieldsFromInterface(fields map[string]interface{}, params *JCSSHDParam) {
	params.Name = fields["name"].(string)
	params.Value = fields["value"].(string)
}

func getJCSSHDParamFromArray(paramArray []interface{}) []JCSSHDParam {
	var returnVal []JCSSHDParam
	returnVal = make([]JCSSHDParam, len(paramArray))

	for idx, rec := range paramArray {
		getJCSSHDParamFieldsFromInterface(rec.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal

}

func getJCNetworkInterfaceFieldsFromInterface(fields map[string]interface{}, nic *JCNetworkInterface) {
	if _, exists := fields["address"]; exists {
		nic.Address = fields["address"].(string)
	}
	if _, exists := fields["family"]; exists {
		nic.Family = fields["family"].(string)
	}
	if _, exists := fields["internal"]; exists {
		nic.Internal = fields["internal"].(bool)
	}
	if _, exists := fields["name"]; exists {
		nic.Name = fields["name"].(string)
	}
}

func getJCNetworkInterfacesFromArray(nicArray []interface{}) []JCNetworkInterface {
	var returnVal []JCNetworkInterface
	returnVal = make([]JCNetworkInterface, len(nicArray))

	for idx, rec := range nicArray {
		getJCNetworkInterfaceFieldsFromInterface(rec.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

func getJCSystemFieldsFromInterface(fields map[string]interface{}, system *JCSystem) {
	// doing this b/c the jsonSysRec that's returned from the update only has a subset
	// of the fields
	if _, exists := fields["os"]; exists {
		system.Os = fields["os"].(string)
	}
	if _, exists := fields["templateName"]; exists {
		system.TemplateName = fields["templateName"].(string)
	}
	if _, exists := fields["allowSshRootLogin"]; exists {
		system.AllowSshRootLogin = fields["allowSshRootLogin"].(bool)
	}
	if _, exists := fields["id"]; exists {
		system.Id = fields["id"].(string)
	} else if _, exists := fields["_id"]; exists {
		system.Id = fields["_id"].(string)

	}
	if _, exists := fields["lastContact"]; exists {
		system.LastContact = fields["lastContact"].(string)
	}
	if _, exists := fields["remoteIP"]; exists {
		system.RemoteIP = fields["remoteIP"].(string)
	}
	if _, exists := fields["active"]; exists {
		system.Active = fields["active"].(bool)
	}
	if _, exists := fields["sshRootEnabled"]; exists {
		system.SshRootEnabled = fields["sshRootEnabled"].(bool)
	}
	if _, exists := fields["sshPassEnabled"]; exists {
		system.SshPassEnabled = fields["sshPassEnabled"].(bool)
	}
	if _, exists := fields["version"]; exists {
		system.Version = fields["version"].(string)
	}
	if _, exists := fields["agentVersion"]; exists {
		system.AgentVersion = fields["agentVersion"].(string)
	}
	if _, exists := fields["allowPublicKeyAuthentication"]; exists {
		system.AllowPublicKeyAuth = fields["allowPublicKeyAuthentication"].(bool)
	}
	if _, exists := fields["organization"]; exists {
		system.Organization = fields["organization"].(string)
	}
	if _, exists := fields["created"]; exists {
		system.Created = fields["created"].(string)
	}
	if _, exists := fields["arch"]; exists {
		system.Arch = fields["arch"].(string)
	}
	if _, exists := fields["systemTimeZone"]; exists {
		system.SystemTimezone = fields["systemTimezone"].(float64)
	}
	if _, exists := fields["allowSshPasswordAuthentication"]; exists {
		system.AllowSshPasswordAuthentication = fields["allowSshPasswordAuthentication"].(bool)
	}
	if _, exists := fields["displayName"]; exists {
		system.DisplayName = fields["displayName"].(string)
	}
	if _, exists := fields["modifySSHDConfig"]; exists {
		system.ModifySSHDConfig = fields["modifySSHDConfig"].(bool)
	}
	if _, exists := fields["allowMultiFactorAuthentication"]; exists {
		system.AllowMultiFactorAuthentication = fields["allowMultiFactorAuthentication"].(bool)
	}
	if _, exists := fields["hostname"]; exists {
		system.Hostname = fields["hostname"].(string)
	}

	if _, exists := fields["sshdParams"]; exists {
		system.SshdParams = getJCSSHDParamFromArray(fields["sshdParams"].([]interface{}))
	}
	if _, exists := fields["networkInterfaces"]; exists {
		system.NetworkInterfaces = getJCNetworkInterfacesFromArray(fields["networkInterfaces"].([]interface{}))
	}

	if _, exists := fields["amazonInstanceID"]; exists {
		system.AmazonInstanceID = fields["amazonInstanceID"].(string)
	}
}

func getJCSystemsFromInterface(systemInt interface{}) []JCSystem {

	var returnVal []JCSystem

	recMap := systemInt.(map[string]interface{})

	results := recMap["results"].([]interface{})

	returnVal = make([]JCSystem, len(results))

	for idx, result := range results {
		getJCSystemFieldsFromInterface(result.(map[string]interface{}), &returnVal[idx])
	}

	return returnVal
}

// Executes a search by hostname via the JumpCloud API
func (jc JCAPI) GetSystemByHostName(hostname string, withTags bool) ([]JCSystem, JCError) {
	var returnVal []JCSystem

	jcSystemRec, err := jc.Post("/search/systems", jc.hostnameFilter(hostname))

	if err != nil {
		return nil, fmt.Errorf("ERROR: Post to JumpCloud failed, err='%s'", err)
	}

	if jcSystemRec == nil {
		return nil, fmt.Errorf("ERROR: No systems found")
	}

	returnVal = getJCSystemsFromInterface(jcSystemRec)

	if withTags {
		tags, err := jc.GetAllTags()
		if err != nil {
			return nil, fmt.Errorf("ERROR: Could not get tags, err='%s'", err)
		}

		for idx, _ := range returnVal {
			returnVal[idx].AddJCTagsToSystem(tags)
		}
	}

	return returnVal, nil
}

func (jc JCAPI) GetSystemById(systemId string, withTags bool) (system JCSystem, err JCError) {
	url := fmt.Sprintf("/systems/%s", systemId)

	retVal, err := jc.Get(url)
	if err != nil {
		err = fmt.Errorf("ERROR: Could not get system by ID '%s', err='%s'", systemId, err)
	}

	if retVal != nil {
		getJCSystemFieldsFromInterface(retVal.(map[string]interface{}), &system)

		if withTags {
			// I should be able to use err below as the err return value, but there's
			// a compiler bug here in that it thinks a := of err is shadowed here,
			// even though tags should be the only variable declared with the :=
			tags, err2 := jc.GetAllTags()
			if err != nil {
				err = fmt.Errorf("ERROR: Could not get tags, err='%s'", err2)
				return
			}

			system.AddJCTagsToSystem(tags)
		}
	}

	return
}

func (jc JCAPI) GetSystems(withTags bool) ([]JCSystem, JCError) {
	var returnVal []JCSystem

	for skip := 0; skip == 0 || len(returnVal) == searchLimit; skip += searchSkipInterval {
		url := fmt.Sprintf("/systems?sort=hostname&skip=%d&limit=%d", skip, searchLimit)

		jcSysRec, err2 := jc.Get(url)

		if err2 != nil {
			return nil, fmt.Errorf("ERROR: Get to JumpCloud failed, err='%s'", err2)
		}

		if jcSysRec == nil {
			return nil, fmt.Errorf("ERROR: No systems found")
		}

		returnVal = getJCSystemsFromInterface(jcSysRec)

		if withTags {
			tags, err := jc.GetAllTags()
			if err != nil {
				return nil, fmt.Errorf("ERROR: Could not get tags, err='%s'", err)
			}

			for idx, _ := range returnVal {
				returnVal[idx].AddJCTagsToSystem(tags)
			}
		}

	}
	return returnVal, nil
}

//
// Update a system
//
func (jc JCAPI) UpdateSystem(system JCSystem) (systemId string, err JCError) {
	data, err := json.Marshal(system)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not marshal JCSystem object, err='%s'", err)
	}
	url := "/systems/" + system.Id

	jcSysRec, err := jc.Do(MapJCOpToHTTP(Update), url, data)
	if err != nil {
		return "", fmt.Errorf("ERROR: Could not update JCSystem object, err='%s'", err)
	}
	var returnSystem JCSystem
	getJCSystemFieldsFromInterface(jcSysRec.(map[string]interface{}), &returnSystem)

	if returnSystem.Id != system.Id {
		return "", fmt.Errorf("ERROR: JumpCloud did not return the same ID - this should never happen!")
	}
	systemId = returnSystem.Id
	return systemId, nil
}

//!!!!!!!!!!!!WARNING!!!!!!!!!!!!
//This will cause JumpCloud to uninstall the agent on this system
//You will lose control of the system after the call returns
//Seriously, It'll be gone
func (jc JCAPI) DeleteSystem(system JCSystem) JCError {
	_, err := jc.Delete(fmt.Sprintf("/system/%s", system.Id))
	if err != nil {
		return fmt.Errorf("ERROR: Could not delete system '%s': err='%s'", system.Hostname, err)
	}

	return nil
}
<|endoftext|>


<commit_before>package registry

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/utils"
)

var (
	ErrAlreadyExists         = errors.New("Image already exists")
	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
	ErrDoesNotExist          = errors.New("Image does not exist")
	errLoginRequired         = errors.New("Authentication is required.")
	validHex                 = regexp.MustCompile(`^([a-f0-9]{64})$`)
	validNamespace           = regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
)

type TimeoutType uint32

const (
	NoTimeout TimeoutType = iota
	ReceiveTimeout
	ConnectTimeout
)

func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
	tlsConfig := tls.Config{
		RootCAs: roots,
		// Avoid fallback to SSL protocols < TLS1.0
		MinVersion: tls.VersionTLS10,
	}

	if cert != nil {
		tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
	}

	if !secure {
		tlsConfig.InsecureSkipVerify = true
	}

	httpTransport := &http.Transport{
		DisableKeepAlives: true,
		Proxy:             http.ProxyFromEnvironment,
		TLSClientConfig:   &tlsConfig,
	}

	switch timeout {
	case ConnectTimeout:
		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
			// Set the connect timeout to 5 seconds
			d := net.Dialer{Timeout: 5 * time.Second, DualStack: true}

			conn, err := d.Dial(proto, addr)
			if err != nil {
				return nil, err
			}
			// Set the recv timeout to 10 seconds
			conn.SetDeadline(time.Now().Add(10 * time.Second))
			return conn, nil
		}
	case ReceiveTimeout:
		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
			d := net.Dialer{DualStack: true}

			conn, err := d.Dial(proto, addr)
			if err != nil {
				return nil, err
			}
			conn = utils.NewTimeoutConn(conn, 1*time.Minute)
			return conn, nil
		}
	}

	return &http.Client{
		Transport:     httpTransport,
		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
		Jar:           jar,
	}
}

func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
	var (
		pool  *x509.CertPool
		certs []*tls.Certificate
	)

	if secure && req.URL.Scheme == "https" {
		hasFile := func(files []os.FileInfo, name string) bool {
			for _, f := range files {
				if f.Name() == name {
					return true
				}
			}
			return false
		}

		hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
		log.Debugf("hostDir: %s", hostDir)
		fs, err := ioutil.ReadDir(hostDir)
		if err != nil && !os.IsNotExist(err) {
			return nil, nil, err
		}

		for _, f := range fs {
			if strings.HasSuffix(f.Name(), ".crt") {
				if pool == nil {
					pool = x509.NewCertPool()
				}
				log.Debugf("crt: %s", hostDir+"/"+f.Name())
				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
				if err != nil {
					return nil, nil, err
				}
				pool.AppendCertsFromPEM(data)
			}
			if strings.HasSuffix(f.Name(), ".cert") {
				certName := f.Name()
				keyName := certName[:len(certName)-5] + ".key"
				log.Debugf("cert: %s", hostDir+"/"+f.Name())
				if !hasFile(fs, keyName) {
					return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
				}
				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
				if err != nil {
					return nil, nil, err
				}
				certs = append(certs, &cert)
			}
			if strings.HasSuffix(f.Name(), ".key") {
				keyName := f.Name()
				certName := keyName[:len(keyName)-4] + ".cert"
				log.Debugf("key: %s", hostDir+"/"+f.Name())
				if !hasFile(fs, certName) {
					return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
				}
			}
		}
	}

	if len(certs) == 0 {
		client := newClient(jar, pool, nil, timeout, secure)
		res, err := client.Do(req)
		if err != nil {
			return nil, nil, err
		}
		return res, client, nil
	}

	for i, cert := range certs {
		client := newClient(jar, pool, cert, timeout, secure)
		res, err := client.Do(req)
		// If this is the last cert, otherwise, continue to next cert if 403 or 5xx
		if i == len(certs)-1 || err == nil &&
			res.StatusCode != 403 &&
			res.StatusCode != 404 &&
			res.StatusCode < 500 {
			return res, client, err
		}
	}

	return nil, nil, nil
}

func validateRepositoryName(repositoryName string) error {
	var (
		namespace string
		name      string
	)
	nameParts := strings.SplitN(repositoryName, "/", 2)
	if len(nameParts) < 2 {
		namespace = "library"
		name = nameParts[0]

		if validHex.MatchString(name) {
			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
		}
	} else {
		namespace = nameParts[0]
		name = nameParts[1]
	}
	if !validNamespace.MatchString(namespace) {
		return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace)
	}
	if !validRepo.MatchString(name) {
		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
	}
	return nil
}

// Resolves a repository name to a hostname + name
func ResolveRepositoryName(reposName string) (string, string, error) {
	if strings.Contains(reposName, "://") {
		// It cannot contain a scheme!
		return "", "", ErrInvalidRepositoryName
	}
	nameParts := strings.SplitN(reposName, "/", 2)
	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") &&
		nameParts[0] != "localhost") {
		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
		err := validateRepositoryName(reposName)
		return IndexServerAddress(), reposName, err
	}
	hostname := nameParts[0]
	reposName = nameParts[1]
	if strings.Contains(hostname, "index.docker.io") {
		return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
	}
	if err := validateRepositoryName(reposName); err != nil {
		return "", "", err
	}

	return hostname, reposName, nil
}

func trustedLocation(req *http.Request) bool {
	var (
		trusteds = []string{"docker.com", "docker.io"}
		hostname = strings.SplitN(req.Host, ":", 2)[0]
	)
	if req.URL.Scheme != "https" {
		return false
	}

	for _, trusted := range trusteds {
		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
			return true
		}
	}
	return false
}

func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
	if via != nil && via[0] != nil {
		if trustedLocation(req) && trustedLocation(via[0]) {
			req.Header = via[0].Header
			return nil
		}
		for k, v := range via[0].Header {
			if k != "Authorization" {
				for _, vv := range v {
					req.Header.Add(k, vv)
				}
			}
		}
	}
	return nil
}
<commit_msg>registry: don't iterate through certs<commit_after>package registry

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/utils"
)

var (
	ErrAlreadyExists         = errors.New("Image already exists")
	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
	ErrDoesNotExist          = errors.New("Image does not exist")
	errLoginRequired         = errors.New("Authentication is required.")
	validHex                 = regexp.MustCompile(`^([a-f0-9]{64})$`)
	validNamespace           = regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
)

type TimeoutType uint32

const (
	NoTimeout TimeoutType = iota
	ReceiveTimeout
	ConnectTimeout
)

func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
	tlsConfig := tls.Config{
		RootCAs: roots,
		// Avoid fallback to SSL protocols < TLS1.0
		MinVersion:   tls.VersionTLS10,
		Certificates: certs,
	}

	if !secure {
		tlsConfig.InsecureSkipVerify = true
	}

	httpTransport := &http.Transport{
		DisableKeepAlives: true,
		Proxy:             http.ProxyFromEnvironment,
		TLSClientConfig:   &tlsConfig,
	}

	switch timeout {
	case ConnectTimeout:
		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
			// Set the connect timeout to 5 seconds
			d := net.Dialer{Timeout: 5 * time.Second, DualStack: true}

			conn, err := d.Dial(proto, addr)
			if err != nil {
				return nil, err
			}
			// Set the recv timeout to 10 seconds
			conn.SetDeadline(time.Now().Add(10 * time.Second))
			return conn, nil
		}
	case ReceiveTimeout:
		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
			d := net.Dialer{DualStack: true}

			conn, err := d.Dial(proto, addr)
			if err != nil {
				return nil, err
			}
			conn = utils.NewTimeoutConn(conn, 1*time.Minute)
			return conn, nil
		}
	}

	return &http.Client{
		Transport:     httpTransport,
		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
		Jar:           jar,
	}
}

func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
	var (
		pool  *x509.CertPool
		certs []tls.Certificate
	)

	if secure && req.URL.Scheme == "https" {
		hasFile := func(files []os.FileInfo, name string) bool {
			for _, f := range files {
				if f.Name() == name {
					return true
				}
			}
			return false
		}

		hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
		log.Debugf("hostDir: %s", hostDir)
		fs, err := ioutil.ReadDir(hostDir)
		if err != nil && !os.IsNotExist(err) {
			return nil, nil, err
		}

		for _, f := range fs {
			if strings.HasSuffix(f.Name(), ".crt") {
				if pool == nil {
					pool = x509.NewCertPool()
				}
				log.Debugf("crt: %s", hostDir+"/"+f.Name())
				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
				if err != nil {
					return nil, nil, err
				}
				pool.AppendCertsFromPEM(data)
			}
			if strings.HasSuffix(f.Name(), ".cert") {
				certName := f.Name()
				keyName := certName[:len(certName)-5] + ".key"
				log.Debugf("cert: %s", hostDir+"/"+f.Name())
				if !hasFile(fs, keyName) {
					return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
				}
				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
				if err != nil {
					return nil, nil, err
				}
				certs = append(certs, cert)
			}
			if strings.HasSuffix(f.Name(), ".key") {
				keyName := f.Name()
				certName := keyName[:len(keyName)-4] + ".cert"
				log.Debugf("key: %s", hostDir+"/"+f.Name())
				if !hasFile(fs, certName) {
					return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
				}
			}
		}
	}

	if len(certs) == 0 {
		client := newClient(jar, pool, nil, timeout, secure)
		res, err := client.Do(req)
		if err != nil {
			return nil, nil, err
		}
		return res, client, nil
	}

	client := newClient(jar, pool, certs, timeout, secure)
	res, err := client.Do(req)
	return res, client, err
}

func validateRepositoryName(repositoryName string) error {
	var (
		namespace string
		name      string
	)
	nameParts := strings.SplitN(repositoryName, "/", 2)
	if len(nameParts) < 2 {
		namespace = "library"
		name = nameParts[0]

		if validHex.MatchString(name) {
			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
		}
	} else {
		namespace = nameParts[0]
		name = nameParts[1]
	}
	if !validNamespace.MatchString(namespace) {
		return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace)
	}
	if !validRepo.MatchString(name) {
		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
	}
	return nil
}

// Resolves a repository name to a hostname + name
func ResolveRepositoryName(reposName string) (string, string, error) {
	if strings.Contains(reposName, "://") {
		// It cannot contain a scheme!
		return "", "", ErrInvalidRepositoryName
	}
	nameParts := strings.SplitN(reposName, "/", 2)
	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") &&
		nameParts[0] != "localhost") {
		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
		err := validateRepositoryName(reposName)
		return IndexServerAddress(), reposName, err
	}
	hostname := nameParts[0]
	reposName = nameParts[1]
	if strings.Contains(hostname, "index.docker.io") {
		return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
	}
	if err := validateRepositoryName(reposName); err != nil {
		return "", "", err
	}

	return hostname, reposName, nil
}

func trustedLocation(req *http.Request) bool {
	var (
		trusteds = []string{"docker.com", "docker.io"}
		hostname = strings.SplitN(req.Host, ":", 2)[0]
	)
	if req.URL.Scheme != "https" {
		return false
	}

	for _, trusted := range trusteds {
		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
			return true
		}
	}
	return false
}

func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
	if via != nil && via[0] != nil {
		if trustedLocation(req) && trustedLocation(via[0]) {
			req.Header = via[0].Header
			return nil
		}
		for k, v := range via[0].Header {
			if k != "Authorization" {
				for _, vv := range v {
					req.Header.Add(k, vv)
				}
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Package registry is the micro registry
package registry

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

	"github.com/google/uuid"
	"github.com/micro/cli"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/registry/service"
	"github.com/micro/go-micro/registry/service/handler"
	pb "github.com/micro/go-micro/registry/service/proto"
	"github.com/micro/go-micro/util/backoff"
	"github.com/micro/go-micro/util/log"
	rcli "github.com/micro/micro/cli"
)

var (
	// Name of the registry
	Name = "go.micro.registry"
	// The address of the registry
	Address = ":8000"
	// Topic to publish registry events to
	Topic = "go.micro.registry.events"
	// SyncTime defines time interval to periodically sync registries
	SyncTime = 5 * time.Second
)

func ActionToEventType(action string) registry.EventType {
	switch action {
	case "create":
		return registry.Create
	case "delete":
		return registry.Delete
	default:
		return registry.Update
	}
}

// Sub processes registry events
type sub struct {
	// id is registry id
	id string
	// registry is service registry
	registry registry.Registry
}

// Process processes registry events
func (s *sub) Process(ctx context.Context, event *pb.Event) error {
	if event.Id == s.id {
		log.Tracef("skipping own %s event: %s for: %s", registry.EventType(event.Type), event.Id, event.Service.Name)
		return nil
	}

	log.Debugf("received %s event from: %s for: %s", registry.EventType(event.Type), event.Id, event.Service.Name)

	// no service
	if event.Service == nil {
		return nil
	}

	// decode protobuf to registry.Service
	svc := service.ToService(event.Service)

	// default ttl to 1 minute
	ttl := time.Minute

	// set ttl if it exists
	if opts := event.Service.Options; opts != nil {
		if opts.Ttl > 0 {
			ttl = time.Duration(opts.Ttl) * time.Second
		}
	}

	switch registry.EventType(event.Type) {
	case registry.Create, registry.Update:
		log.Debugf("registering service: %s", svc.Name)
		if err := s.registry.Register(svc, registry.RegisterTTL(ttl)); err != nil {
			log.Debugf("failed to register service: %s", svc.Name)
			return err
		}
	case registry.Delete:
		log.Debugf("deregistering service: %s", svc.Name)
		if err := s.registry.Deregister(svc); err != nil {
			log.Debugf("failed to deregister service: %s", svc.Name)
			return err
		}
	}

	return nil
}

// reg is micro registry
type reg struct {
	// registry is micro registry
	registry.Registry
	// id is registry id
	id string
	// client is service client
	client client.Client
	// nodes to sync with
	nodes []string
	// exit stops the registry
	exit chan bool
}

// newRegsitry creates new micro registry and returns it
func newRegistry(service micro.Service, registry registry.Registry, nodes []string) *reg {
	id := uuid.New().String()
	// register subscriber
	// disabled until we understand broadcast storms better
	/*
		s := &sub{
			id:       id,
			registry: registry,
		}

		if err := micro.RegisterSubscriber(Topic, service.Server(), s); err != nil {
			log.Debugf("failed to subscribe to events: %s", err)
			os.Exit(1)
		}
	*/

	return &reg{
		Registry: registry,
		id:       id,
		client:   service.Client(),
		nodes:    nodes,
		exit:     make(chan bool),
	}
}

// Publish publishes registry events to other registries to consume
func (r *reg) PublishEvents(reg registry.Registry) error {
	// create registry watcher
	w, err := reg.Watch()
	if err != nil {
		return err
	}
	defer w.Stop()

	// create a publisher
	p := micro.NewPublisher(Topic, r.client)
	// track watcher errors
	var watchErr error

	for {
		res, err := w.Next()
		if err != nil {
			if err != registry.ErrWatcherStopped {
				watchErr = err
			}
			break
		}

		// encode *registry.Service into protobuf messag
		svc := service.ToProto(res.Service)

		// TODO: timestamp should be read from received event
		// Right now registry.Result does not contain timestamp
		event := &pb.Event{
			Id:        r.id,
			Type:      pb.EventType(ActionToEventType(res.Action)),
			Timestamp: time.Now().UnixNano(),
			Service:   svc,
		}

		log.Debugf("publishing event %s for action %s", event.Id, res.Action)

		select {
		case <-r.exit:
			return nil
		default:
			ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
			if err := p.Publish(ctx, event); err != nil {
				log.Debugf("error publishing event: %v", err)
				return fmt.Errorf("error publishing event: %v", err)
			}
		}
	}

	return watchErr
}

func (r *reg) syncRecords(nodes []string) error {
	if len(nodes) == 0 {
		log.Tracef("no nodes to sync with...skipping")
		return nil
	}

	log.Debugf("syncing records from %v", nodes)

	c := pb.NewRegistryService(Name, r.client)
	resp, err := c.ListServices(context.Background(), &pb.ListRequest{}, client.WithAddress(nodes...))
	if err != nil {
		log.Debugf("failed sync: %v", err)
		return err
	}

	for _, pbService := range resp.Services {
		// default ttl to 1 minute
		ttl := time.Minute

		// set ttl if it exists
		if opts := pbService.Options; opts != nil {
			if opts.Ttl > 0 {
				ttl = time.Duration(opts.Ttl) * time.Second
			}
		}

		svc := service.ToService(pbService)
		log.Debugf("registering service: %s", svc.Name)
		if err := r.Register(svc, registry.RegisterTTL(ttl)); err != nil {
			log.Debugf("failed to register service: %v", svc.Name)
			return err
		}
	}

	return nil
}

func (r *reg) Sync() error {
	sync := time.NewTicker(SyncTime)
	defer sync.Stop()

	for {
		select {
		case <-r.exit:
			return nil
		case <-sync.C:
			if err := r.syncRecords(r.nodes); err != nil {
				log.Debugf("failed to sync registry records: %v", err)
			}
		}
	}

	return nil
}

func runManager(reg *reg, errChan chan error) {
	go func() {
		var i int

		// loop creating the watcher until exit
		for {
			select {
			case <-reg.exit:
				errChan <- nil
				return
			default:
				if err := reg.PublishEvents(reg.Registry); err != nil {
					sleep := backoff.Do(i)

					log.Debugf("failed to publish events: %v backing off for %v", err, sleep)

					// backoff for a period of time
					time.Sleep(sleep)

					// reset the counter
					if i > 3 {
						i = 0
					}
				}

				// update the counter
				i++
			}
		}
	}()

	go func() {
		errChan <- reg.Sync()
	}()

}

func run(ctx *cli.Context, srvOpts ...micro.Option) {
	log.Name("registry")

	if len(ctx.GlobalString("server_name")) > 0 {
		Name = ctx.GlobalString("server_name")
	}
	if len(ctx.String("address")) > 0 {
		Address = ctx.String("address")
	}

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	// service opts
	srvOpts = append(srvOpts, micro.Name(Name))
	if i := time.Duration(ctx.GlobalInt("register_ttl")); i > 0 {
		srvOpts = append(srvOpts, micro.RegisterTTL(i*time.Second))
	}
	if i := time.Duration(ctx.GlobalInt("register_interval")); i > 0 {
		srvOpts = append(srvOpts, micro.RegisterInterval(i*time.Second))
	}
	var nodes []string
	if len(ctx.String("nodes")) > 0 {
		nodes = strings.Split(ctx.String("nodes"), ",")
	}

	// set address
	if len(Address) > 0 {
		srvOpts = append(srvOpts, micro.Address(Address))
	}

	// new service
	service := micro.NewService(srvOpts...)

	pb.RegisterRegistryHandler(service.Server(), &handler.Registry{
		// using the mdns registry
		Registry: service.Options().Registry,
	})

	reg := newRegistry(service, service.Options().Registry, nodes)
	errChan := make(chan error, 3)

	// kickoff the registry manager
	// disabled until we understand broadcast storms better
	// runManager(reg, errChan)

	go func() {
		// we block here until either service or server fails
		if err := <-errChan; err != nil {
			log.Logf("error running the registry: %v", err)
			os.Exit(1)
		}
	}()

	// run the service inline
	if err := service.Run(); err != nil {
		errChan <- err
	}

	// stop everything
	close(reg.exit)

	log.Debugf("successfully stopped")
}

func Commands(options ...micro.Option) []cli.Command {
	command := cli.Command{
		Name:  "registry",
		Usage: "Run the service registry",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:   "address",
				Usage:  "Set the registry http address e.g 0.0.0.0:8000",
				EnvVar: "MICRO_SERVER_ADDRESS",
			},
			cli.StringFlag{
				Name:   "nodes",
				Usage:  "Set the micro registry nodes to connect to. This can be a comma separated list.",
				EnvVar: "MICRO_REGISTRY_NODES",
			},
		},
		Action: func(ctx *cli.Context) {
			run(ctx, options...)
		},
		Subcommands: rcli.RegistryCommands(),
	}

	for _, p := range Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	return []cli.Command{command}
}
<commit_msg>Small go vet cleanup<commit_after>// Package registry is the micro registry
package registry

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

	"github.com/google/uuid"
	"github.com/micro/cli"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/registry/service"
	"github.com/micro/go-micro/registry/service/handler"
	pb "github.com/micro/go-micro/registry/service/proto"
	"github.com/micro/go-micro/util/backoff"
	"github.com/micro/go-micro/util/log"
	rcli "github.com/micro/micro/cli"
)

var (
	// Name of the registry
	Name = "go.micro.registry"
	// The address of the registry
	Address = ":8000"
	// Topic to publish registry events to
	Topic = "go.micro.registry.events"
	// SyncTime defines time interval to periodically sync registries
	SyncTime = 5 * time.Second
)

func ActionToEventType(action string) registry.EventType {
	switch action {
	case "create":
		return registry.Create
	case "delete":
		return registry.Delete
	default:
		return registry.Update
	}
}

// Sub processes registry events
type sub struct {
	// id is registry id
	id string
	// registry is service registry
	registry registry.Registry
}

// Process processes registry events
func (s *sub) Process(ctx context.Context, event *pb.Event) error {
	if event.Id == s.id {
		log.Tracef("skipping own %s event: %s for: %s", registry.EventType(event.Type), event.Id, event.Service.Name)
		return nil
	}

	log.Debugf("received %s event from: %s for: %s", registry.EventType(event.Type), event.Id, event.Service.Name)

	// no service
	if event.Service == nil {
		return nil
	}

	// decode protobuf to registry.Service
	svc := service.ToService(event.Service)

	// default ttl to 1 minute
	ttl := time.Minute

	// set ttl if it exists
	if opts := event.Service.Options; opts != nil {
		if opts.Ttl > 0 {
			ttl = time.Duration(opts.Ttl) * time.Second
		}
	}

	switch registry.EventType(event.Type) {
	case registry.Create, registry.Update:
		log.Debugf("registering service: %s", svc.Name)
		if err := s.registry.Register(svc, registry.RegisterTTL(ttl)); err != nil {
			log.Debugf("failed to register service: %s", svc.Name)
			return err
		}
	case registry.Delete:
		log.Debugf("deregistering service: %s", svc.Name)
		if err := s.registry.Deregister(svc); err != nil {
			log.Debugf("failed to deregister service: %s", svc.Name)
			return err
		}
	}

	return nil
}

// reg is micro registry
type reg struct {
	// registry is micro registry
	registry.Registry
	// id is registry id
	id string
	// client is service client
	client client.Client
	// nodes to sync with
	nodes []string
	// exit stops the registry
	exit chan bool
}

// newRegsitry creates new micro registry and returns it
func newRegistry(service micro.Service, registry registry.Registry, nodes []string) *reg {
	id := uuid.New().String()
	// register subscriber
	// disabled until we understand broadcast storms better
	/*
		s := &sub{
			id:       id,
			registry: registry,
		}

		if err := micro.RegisterSubscriber(Topic, service.Server(), s); err != nil {
			log.Debugf("failed to subscribe to events: %s", err)
			os.Exit(1)
		}
	*/

	return &reg{
		Registry: registry,
		id:       id,
		client:   service.Client(),
		nodes:    nodes,
		exit:     make(chan bool),
	}
}

// Publish publishes registry events to other registries to consume
func (r *reg) PublishEvents(reg registry.Registry) error {
	// create registry watcher
	w, err := reg.Watch()
	if err != nil {
		return err
	}
	defer w.Stop()

	// create a publisher
	p := micro.NewPublisher(Topic, r.client)
	// track watcher errors
	var watchErr error

	for {
		res, err := w.Next()
		if err != nil {
			if err != registry.ErrWatcherStopped {
				watchErr = err
			}
			break
		}

		// encode *registry.Service into protobuf messag
		svc := service.ToProto(res.Service)

		// TODO: timestamp should be read from received event
		// Right now registry.Result does not contain timestamp
		event := &pb.Event{
			Id:        r.id,
			Type:      pb.EventType(ActionToEventType(res.Action)),
			Timestamp: time.Now().UnixNano(),
			Service:   svc,
		}

		log.Debugf("publishing event %s for action %s", event.Id, res.Action)

		select {
		case <-r.exit:
			return nil
		default:
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()
			if err := p.Publish(ctx, event); err != nil {
				log.Debugf("error publishing event: %v", err)
				return fmt.Errorf("error publishing event: %v", err)
			}
		}
	}

	return watchErr
}

func (r *reg) syncRecords(nodes []string) error {
	if len(nodes) == 0 {
		log.Tracef("no nodes to sync with...skipping")
		return nil
	}

	log.Debugf("syncing records from %v", nodes)

	c := pb.NewRegistryService(Name, r.client)
	resp, err := c.ListServices(context.Background(), &pb.ListRequest{}, client.WithAddress(nodes...))
	if err != nil {
		log.Debugf("failed sync: %v", err)
		return err
	}

	for _, pbService := range resp.Services {
		// default ttl to 1 minute
		ttl := time.Minute

		// set ttl if it exists
		if opts := pbService.Options; opts != nil {
			if opts.Ttl > 0 {
				ttl = time.Duration(opts.Ttl) * time.Second
			}
		}

		svc := service.ToService(pbService)
		log.Debugf("registering service: %s", svc.Name)
		if err := r.Register(svc, registry.RegisterTTL(ttl)); err != nil {
			log.Debugf("failed to register service: %v", svc.Name)
			return err
		}
	}

	return nil
}

func (r *reg) Sync() error {
	sync := time.NewTicker(SyncTime)
	defer sync.Stop()

	for {
		select {
		case <-r.exit:
			return nil
		case <-sync.C:
			if err := r.syncRecords(r.nodes); err != nil {
				log.Debugf("failed to sync registry records: %v", err)
			}
		}
	}
}

func runManager(reg *reg, errChan chan error) {
	go func() {
		var i int

		// loop creating the watcher until exit
		for {
			select {
			case <-reg.exit:
				errChan <- nil
				return
			default:
				if err := reg.PublishEvents(reg.Registry); err != nil {
					sleep := backoff.Do(i)

					log.Debugf("failed to publish events: %v backing off for %v", err, sleep)

					// backoff for a period of time
					time.Sleep(sleep)

					// reset the counter
					if i > 3 {
						i = 0
					}
				}

				// update the counter
				i++
			}
		}
	}()

	go func() {
		errChan <- reg.Sync()
	}()

}

func run(ctx *cli.Context, srvOpts ...micro.Option) {
	log.Name("registry")

	if len(ctx.GlobalString("server_name")) > 0 {
		Name = ctx.GlobalString("server_name")
	}
	if len(ctx.String("address")) > 0 {
		Address = ctx.String("address")
	}

	// Init plugins
	for _, p := range Plugins() {
		p.Init(ctx)
	}

	// service opts
	srvOpts = append(srvOpts, micro.Name(Name))
	if i := time.Duration(ctx.GlobalInt("register_ttl")); i > 0 {
		srvOpts = append(srvOpts, micro.RegisterTTL(i*time.Second))
	}
	if i := time.Duration(ctx.GlobalInt("register_interval")); i > 0 {
		srvOpts = append(srvOpts, micro.RegisterInterval(i*time.Second))
	}
	var nodes []string
	if len(ctx.String("nodes")) > 0 {
		nodes = strings.Split(ctx.String("nodes"), ",")
	}

	// set address
	if len(Address) > 0 {
		srvOpts = append(srvOpts, micro.Address(Address))
	}

	// new service
	service := micro.NewService(srvOpts...)

	pb.RegisterRegistryHandler(service.Server(), &handler.Registry{
		// using the mdns registry
		Registry: service.Options().Registry,
	})

	reg := newRegistry(service, service.Options().Registry, nodes)
	errChan := make(chan error, 3)

	// kickoff the registry manager
	// disabled until we understand broadcast storms better
	// runManager(reg, errChan)

	go func() {
		// we block here until either service or server fails
		if err := <-errChan; err != nil {
			log.Logf("error running the registry: %v", err)
			os.Exit(1)
		}
	}()

	// run the service inline
	if err := service.Run(); err != nil {
		errChan <- err
	}

	// stop everything
	close(reg.exit)

	log.Debugf("successfully stopped")
}

func Commands(options ...micro.Option) []cli.Command {
	command := cli.Command{
		Name:  "registry",
		Usage: "Run the service registry",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:   "address",
				Usage:  "Set the registry http address e.g 0.0.0.0:8000",
				EnvVar: "MICRO_SERVER_ADDRESS",
			},
			cli.StringFlag{
				Name:   "nodes",
				Usage:  "Set the micro registry nodes to connect to. This can be a comma separated list.",
				EnvVar: "MICRO_REGISTRY_NODES",
			},
		},
		Action: func(ctx *cli.Context) {
			run(ctx, options...)
		},
		Subcommands: rcli.RegistryCommands(),
	}

	for _, p := range Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	return []cli.Command{command}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/showwin/speedtest-go/speedtest"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	showList   = kingpin.Flag("list", "Show available speedtest.net servers.").Short('l').Bool()
	serverIds  = kingpin.Flag("server", "Select server id to speedtest.").Short('s').Ints()
	savingMode = kingpin.Flag("saving-mode", "Using less memory (≒10MB), though low accuracy (especially > 30Mbps).").Bool()
)

func main() {
	kingpin.Version("1.0.5")
	kingpin.Parse()

	user, err := speedtest.FetchUserInfo()
	if err != nil {
		fmt.Println("Warning: Cannot fetch user information. http://www.speedtest.net/speedtest-config.php is temporarily unavailable.")
	}
	showUser(user)

	serverList, err := speedtest.FetchServerList(user)
	checkError(err)
	if *showList {
		showServerList(serverList)
		return
	}

	targets, err := serverList.FindServer(*serverIds)
	checkError(err)

	startTest(targets, *savingMode)
}

func startTest(servers speedtest.Servers, savingMode bool) {
	for _, s := range servers {
		showServer(s)

		err := s.PingTest()
		checkError(err)
		showLatencyResult(s)

		err = testDownload(s, savingMode)
		checkError(err)
		err = testUpload(s, savingMode)
		checkError(err)

		showServerResult(s)
	}

	if len(servers) > 1 {
		showAverageServerResult(servers)
	}
}

func testDownload(server *speedtest.Server, savingMode bool) error {
	quit := make(chan bool)
	fmt.Printf("Download Test: ")
	go dots(quit)
	err := server.DownloadTest(savingMode)
	quit <- true
	if err != nil {
		return err
	}
	fmt.Println()
	return err
}

func testUpload(server *speedtest.Server, savingMode bool) error {
	quit := make(chan bool)
	fmt.Printf("Upload Test: ")
	go dots(quit)
	err := server.UploadTest(savingMode)
	quit <- true
	if err != nil {
		return err
	}
	fmt.Println()
	return nil
}

func dots(quit chan bool) {
	for {
		select {
		case <-quit:
			return
		default:
			time.Sleep(time.Second)
			fmt.Print(".")
		}
	}
}

func showUser(user *speedtest.User) {
	if user.IP != "" {
		fmt.Printf("Testing From IP: %s\n", user.String())
	}
}

func showServerList(serverList speedtest.ServerList) {
	for _, s := range serverList.Servers {
		fmt.Printf("[%4s] %8.2fkm ", s.ID, s.Distance)
		fmt.Printf(s.Name + " (" + s.Country + ") by " + s.Sponsor + "\n")
	}
}

func showServer(s *speedtest.Server) {
	fmt.Printf(" \n")
	fmt.Printf("Target Server: [%4s] %8.2fkm ", s.ID, s.Distance)
	fmt.Printf(s.Name + " (" + s.Country + ") by " + s.Sponsor + "\n")
}

func showLatencyResult(server *speedtest.Server) {
	fmt.Println("Latency:", server.Latency)
}

// ShowResult : show testing result
func showServerResult(server *speedtest.Server) {
	fmt.Printf(" \n")

	fmt.Printf("Download: %5.2f Mbit/s\n", server.DLSpeed)
	fmt.Printf("Upload: %5.2f Mbit/s\n\n", server.ULSpeed)
	valid := server.CheckResultValid()
	if !valid {
		fmt.Println("Warning: Result seems to be wrong. Please speedtest again.")
	}
}

func showAverageServerResult(servers speedtest.Servers) {
	avgDL := 0.0
	avgUL := 0.0
	for _, s := range servers {
		avgDL = avgDL + s.DLSpeed
		avgUL = avgUL + s.ULSpeed
	}
	fmt.Printf("Download Avg: %5.2f Mbit/s\n", avgDL/float64(len(servers)))
	fmt.Printf("Upload Avg: %5.2f Mbit/s\n", avgUL/float64(len(servers)))
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}
<commit_msg>Release v1.1.0<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/showwin/speedtest-go/speedtest"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	showList   = kingpin.Flag("list", "Show available speedtest.net servers.").Short('l').Bool()
	serverIds  = kingpin.Flag("server", "Select server id to speedtest.").Short('s').Ints()
	savingMode = kingpin.Flag("saving-mode", "Using less memory (≒10MB), though low accuracy (especially > 30Mbps).").Bool()
)

func main() {
	kingpin.Version("1.1.0")
	kingpin.Parse()

	user, err := speedtest.FetchUserInfo()
	if err != nil {
		fmt.Println("Warning: Cannot fetch user information. http://www.speedtest.net/speedtest-config.php is temporarily unavailable.")
	}
	showUser(user)

	serverList, err := speedtest.FetchServerList(user)
	checkError(err)
	if *showList {
		showServerList(serverList)
		return
	}

	targets, err := serverList.FindServer(*serverIds)
	checkError(err)

	startTest(targets, *savingMode)
}

func startTest(servers speedtest.Servers, savingMode bool) {
	for _, s := range servers {
		showServer(s)

		err := s.PingTest()
		checkError(err)
		showLatencyResult(s)

		err = testDownload(s, savingMode)
		checkError(err)
		err = testUpload(s, savingMode)
		checkError(err)

		showServerResult(s)
	}

	if len(servers) > 1 {
		showAverageServerResult(servers)
	}
}

func testDownload(server *speedtest.Server, savingMode bool) error {
	quit := make(chan bool)
	fmt.Printf("Download Test: ")
	go dots(quit)
	err := server.DownloadTest(savingMode)
	quit <- true
	if err != nil {
		return err
	}
	fmt.Println()
	return err
}

func testUpload(server *speedtest.Server, savingMode bool) error {
	quit := make(chan bool)
	fmt.Printf("Upload Test: ")
	go dots(quit)
	err := server.UploadTest(savingMode)
	quit <- true
	if err != nil {
		return err
	}
	fmt.Println()
	return nil
}

func dots(quit chan bool) {
	for {
		select {
		case <-quit:
			return
		default:
			time.Sleep(time.Second)
			fmt.Print(".")
		}
	}
}

func showUser(user *speedtest.User) {
	if user.IP != "" {
		fmt.Printf("Testing From IP: %s\n", user.String())
	}
}

func showServerList(serverList speedtest.ServerList) {
	for _, s := range serverList.Servers {
		fmt.Printf("[%4s] %8.2fkm ", s.ID, s.Distance)
		fmt.Printf(s.Name + " (" + s.Country + ") by " + s.Sponsor + "\n")
	}
}

func showServer(s *speedtest.Server) {
	fmt.Printf(" \n")
	fmt.Printf("Target Server: [%4s] %8.2fkm ", s.ID, s.Distance)
	fmt.Printf(s.Name + " (" + s.Country + ") by " + s.Sponsor + "\n")
}

func showLatencyResult(server *speedtest.Server) {
	fmt.Println("Latency:", server.Latency)
}

// ShowResult : show testing result
func showServerResult(server *speedtest.Server) {
	fmt.Printf(" \n")

	fmt.Printf("Download: %5.2f Mbit/s\n", server.DLSpeed)
	fmt.Printf("Upload: %5.2f Mbit/s\n\n", server.ULSpeed)
	valid := server.CheckResultValid()
	if !valid {
		fmt.Println("Warning: Result seems to be wrong. Please speedtest again.")
	}
}

func showAverageServerResult(servers speedtest.Servers) {
	avgDL := 0.0
	avgUL := 0.0
	for _, s := range servers {
		avgDL = avgDL + s.DLSpeed
		avgUL = avgUL + s.ULSpeed
	}
	fmt.Printf("Download Avg: %5.2f Mbit/s\n", avgDL/float64(len(servers)))
	fmt.Printf("Upload Avg: %5.2f Mbit/s\n", avgUL/float64(len(servers)))
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}
<|endoftext|>