<commit_before>package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"net/textproto"
	"strings"
	"time"
)

func (bot *Bot) reduceJoins() {
	bot.joins--
}

// Bot struct for main config
type Bot struct {
	server          string
	groupserver     string
	port            string
	groupport       string
	oauth           string
	nick            string
	inconn          net.Conn
	mainconn        net.Conn
	connlist        []Connection
	connactive      bool
	groupconn       net.Conn
	groupconnactive bool
	joins           int
	toJoin          []string
}

// NewBot main config
func NewBot() *Bot {
	return &Bot{
		server:          "irc.chat.twitch.tv",
		groupserver:     "group.tmi.twitch.tv",
		port:            "80",
		groupport:       "6667",
		oauth:           "",
		nick:            "",
		inconn:          nil,
		mainconn:        nil,
		connlist:        make([]Connection, 0),
		connactive:      false,
		groupconn:       nil,
		groupconnactive: false,
		joins:           0,
	}
}

func (bot *Bot) join(channel string) {
	for !bot.connactive {
		log.Printf("chat connection not active yet")
		time.Sleep(time.Second)
	}

	if bot.joins < 45 {
		fmt.Fprintf(bot.mainconn, "JOIN %s\r\n", channel)
		bot.joins++
		time.AfterFunc(10*time.Second, bot.reduceJoins)
	} else {
		bot.join(channel)
	}
}

// ListenToConnection listen
func (bot *Bot) ListenToConnection(conn net.Conn) {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			log.Printf("Error reading from chat connection: %s", err)
			bot.CreateConnection()
			break // break loop on errors
		}
		if strings.Contains(line, "tmi.twitch.tv 001") {
			bot.connactive = true
		}
		if strings.Contains(line, "PING ") {
			fmt.Fprintf(conn, "PONG tmi.twitch.tv\r\n")
			log.Printf("PONG tmi.twitch.tv\r\n")
		}
		bot.inconn.Write([]byte(line + "\r\n"))
	}
}

// ListenToGroupConnection validate connection is running and listen to it
func (bot *Bot) ListenToGroupConnection(conn net.Conn) {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			log.Printf("Error reading from group connection: %s", err)
			bot.CreateGroupConnection()
			break
		}
		if strings.Contains(line, "tmi.twitch.tv 001") {
			bot.groupconnactive = true
		}
		if strings.Contains(line, "PING ") {
			fmt.Fprintf(conn, "PONG tmi.twitch.tv\r\n")
			log.Printf("PONG tmi.twitch.tv\r\n")
		}
		bot.inconn.Write([]byte(line + "\r\n"))
	}
}

// CreateConnection Add a new connection
func (bot *Bot) CreateConnection() {
	conn, err := net.Dial("tcp", bot.server+":"+bot.port)
	if err != nil {
		log.Println("unable to connect to chat IRC server ", err)
		bot.CreateConnection()
		return
	}
	fmt.Fprintf(conn, "PASS %s\r\n", bot.oauth)
	fmt.Fprintf(conn, "USER %s\r\n", bot.nick)
	fmt.Fprintf(conn, "NICK %s\r\n", bot.nick)
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/tags\r\n")     // enable ircv3 tags
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/commands\r\n") // enable roomstate and such
	log.Printf("new connection to chat IRC server %s (%s)\n", bot.server, conn.RemoteAddr())

	connnection := NewConnection(conn)
	bot.connlist = append(bot.connlist, connnection)

	if len(bot.connlist) == 1 {
		bot.mainconn = conn
	}

	go bot.ListenToConnection(conn)

}

// CreateGroupConnection creates connection to recevie and send whispers
func (bot *Bot) CreateGroupConnection() {
	conn, err := net.Dial("tcp", bot.groupserver+":"+bot.groupport)
	if err != nil {
		log.Println("unable to connect to group IRC server ", err)
		bot.CreateGroupConnection()
		return
	}
	fmt.Fprintf(conn, "PASS %s\r\n", bot.oauth)
	fmt.Fprintf(conn, "USER %s\r\n", bot.nick)
	fmt.Fprintf(conn, "NICK %s\r\n", bot.nick)
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/tags\r\n")     // enable ircv3 tags
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/commands\r\n") // enable roomstate and such
	log.Printf("new connection to group IRC server %s (%s)\n", bot.groupserver, conn.RemoteAddr())

	bot.groupconn = conn

	go bot.ListenToGroupConnection(conn)
}

// Message to send a message
func (bot *Bot) Message(message string) {
	for !bot.connactive {
		// wait for connection to become active
	}

	for i := 0; i < len(bot.connlist); i++ {
		if bot.connlist[i].messages < 90 {
			bot.connlist[i].Message(message)
			return
		}
	}
	// open new connection when others too full
	log.Printf("opened new connection, total: %d", len(bot.connlist))
	bot.CreateConnection()
	bot.Message(message)
}

// Whisper to send whispers
func (bot *Bot) Whisper(message string) {
	for !bot.groupconnactive {
		// wait for connection to become active
	}
	fmt.Fprintf(bot.groupconn, "PRIVMSG #jtv :"+message+"\r\n")
	log.Printf(message)
}
<commit_msg>improved joining of channels and added goroutine for it<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"net/textproto"
	"strings"
	"time"
)

func (bot *Bot) reduceJoins() {
	bot.joins--
}

// Bot struct for main config
type Bot struct {
	server          string
	groupserver     string
	port            string
	groupport       string
	oauth           string
	nick            string
	inconn          net.Conn
	mainconn        net.Conn
	connlist        []Connection
	connactive      bool
	groupconn       net.Conn
	groupconnactive bool
	joins           int
	toJoin          []string
}

// NewBot main config
func NewBot() *Bot {
	return &Bot{
		server:          "irc.chat.twitch.tv",
		groupserver:     "group.tmi.twitch.tv",
		port:            "80",
		groupport:       "6667",
		oauth:           "",
		nick:            "",
		inconn:          nil,
		mainconn:        nil,
		connlist:        make([]Connection, 0),
		connactive:      false,
		groupconn:       nil,
		groupconnactive: false,
		joins:           0,
	}
}

func (bot *Bot) join(channel string) {
	for !bot.connactive {
		log.Printf("chat connection not active yet")
		time.Sleep(time.Second)
	}

	if bot.joins < 45 {
		fmt.Fprintf(bot.mainconn, "JOIN %s\r\n", channel)
		log.Printf("[chat] joined %s", channel)
		bot.joins++
		time.AfterFunc(10*time.Second, bot.reduceJoins)
	} else {
		log.Printf("[chat] in queue to join %s", channel)
		time.Sleep(time.Second)
		bot.join(channel)
	}
}

// ListenToConnection listen
func (bot *Bot) ListenToConnection(conn net.Conn) {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			log.Printf("Error reading from chat connection: %s", err)
			bot.CreateConnection()
			break // break loop on errors
		}
		if strings.Contains(line, "tmi.twitch.tv 001") {
			bot.connactive = true
		}
		if strings.Contains(line, "PING ") {
			fmt.Fprintf(conn, "PONG tmi.twitch.tv\r\n")
			log.Printf("PONG tmi.twitch.tv\r\n")
		}
		bot.inconn.Write([]byte(line + "\r\n"))
	}
}

// ListenToGroupConnection validate connection is running and listen to it
func (bot *Bot) ListenToGroupConnection(conn net.Conn) {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			log.Printf("Error reading from group connection: %s", err)
			bot.CreateGroupConnection()
			break
		}
		if strings.Contains(line, "tmi.twitch.tv 001") {
			bot.groupconnactive = true
		}
		if strings.Contains(line, "PING ") {
			fmt.Fprintf(conn, "PONG tmi.twitch.tv\r\n")
			log.Printf("PONG tmi.twitch.tv\r\n")
		}
		bot.inconn.Write([]byte(line + "\r\n"))
	}
}

// CreateConnection Add a new connection
func (bot *Bot) CreateConnection() {
	conn, err := net.Dial("tcp", bot.server+":"+bot.port)
	if err != nil {
		log.Println("unable to connect to chat IRC server ", err)
		bot.CreateConnection()
		return
	}
	fmt.Fprintf(conn, "PASS %s\r\n", bot.oauth)
	fmt.Fprintf(conn, "USER %s\r\n", bot.nick)
	fmt.Fprintf(conn, "NICK %s\r\n", bot.nick)
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/tags\r\n")     // enable ircv3 tags
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/commands\r\n") // enable roomstate and such
	log.Printf("new connection to chat IRC server %s (%s)\n", bot.server, conn.RemoteAddr())

	connnection := NewConnection(conn)
	bot.connlist = append(bot.connlist, connnection)

	if len(bot.connlist) == 1 {
		bot.mainconn = conn
	}

	go bot.ListenToConnection(conn)

}

// CreateGroupConnection creates connection to recevie and send whispers
func (bot *Bot) CreateGroupConnection() {
	conn, err := net.Dial("tcp", bot.groupserver+":"+bot.groupport)
	if err != nil {
		log.Println("unable to connect to group IRC server ", err)
		bot.CreateGroupConnection()
		return
	}
	fmt.Fprintf(conn, "PASS %s\r\n", bot.oauth)
	fmt.Fprintf(conn, "USER %s\r\n", bot.nick)
	fmt.Fprintf(conn, "NICK %s\r\n", bot.nick)
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/tags\r\n")     // enable ircv3 tags
	fmt.Fprintf(conn, "CAP REQ :twitch.tv/commands\r\n") // enable roomstate and such
	log.Printf("new connection to group IRC server %s (%s)\n", bot.groupserver, conn.RemoteAddr())

	bot.groupconn = conn

	go bot.ListenToGroupConnection(conn)
}

// Message to send a message
func (bot *Bot) Message(message string) {
	for !bot.connactive {
		// wait for connection to become active
	}

	for i := 0; i < len(bot.connlist); i++ {
		if bot.connlist[i].messages < 90 {
			bot.connlist[i].Message(message)
			return
		}
	}
	// open new connection when others too full
	log.Printf("opened new connection, total: %d", len(bot.connlist))
	bot.CreateConnection()
	bot.Message(message)
}

// Whisper to send whispers
func (bot *Bot) Whisper(message string) {
	for !bot.groupconnactive {
		// wait for connection to become active
	}
	fmt.Fprintf(bot.groupconn, "PRIVMSG #jtv :"+message+"\r\n")
	log.Printf(message)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/ElvisChiang/tgwalkrbot/process"

	"bitbucket.org/mrd0ll4r/tbotapi"
	"bitbucket.org/mrd0ll4r/tbotapi/examples/boilerplate"
)

// DEBUG show verbose debug msg
const DEBUG = true

var idFile = "./data/id.csv"
var planetFile = "./data/planet.csv"

var playerData []process.PlayerName
var gameData []process.GameData

func main() {
	ok := false
	gameData, ok = process.LoadGameData(planetFile)
	if !ok {
		fmt.Printf("Game data loading fail\n")
		return
	}
	if DEBUG {
		for _, data := range gameData {
			fmt.Printf("#%d: %s/%s %s/%s %s\n",
				data.Number,
				data.Planet, data.PlanetFile,
				data.Satellite, data.SatelliteFile,
				data.Resource)
		}
	}
	startBot()
}

func startBot() {
	updateFunc := func(update tbotapi.Update, api *tbotapi.TelegramBotAPI) {
		switch update.Type() {
		case tbotapi.MessageUpdate:
			msg := update.Message
			typ := msg.Type()
			text := "(nil)"
			if typ == tbotapi.StickerMessage {
				sticker := update.Message.Sticker
				fmt.Printf("\tSticker id: %s size: %d\n",
					sticker.FileBase.ID, sticker.FileBase.Size)
			}
			if typ == tbotapi.TextMessage {
				text = *msg.Text
			}
			fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, text)
			if typ != tbotapi.TextMessage {
				//ignore non-text messages for now
				fmt.Println("Ignoring non-text message")
				return
			}
			// Note: Bots cannot receive from channels, at least no text messages. So we don't have to distinguish anything here

			// display the incoming message
			// msg.Chat implements fmt.Stringer, so it'll display nicely
			// we know it's a text message, so we can safely use the Message.Text pointer
			// fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, *msg.Text)

			Command(api, &msg.Chat, *msg.Text)
		case tbotapi.InlineQueryUpdate:
			fmt.Println("Ignoring received inline query: ", update.InlineQuery.Query)
		case tbotapi.ChosenInlineResultUpdate:
			fmt.Println("Ignoring chosen inline query result (ID): ", update.ChosenInlineResult.ID)
		default:
			fmt.Printf("Ignoring unknown Update type.")
		}
	}

	// run the bot, this will block
	boilerplate.RunBot(apiToken, updateFunc, "WalkrBot", "Reply Walkr information")
}

func sendPlanetPic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, planet process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(planet.PlanetFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "planet.png", file)
	captain := fmt.Sprintf("# %d: %s\n命定衛星: %s\n生產資源: %s",
		planet.Number, planet.Planet, planet.Satellite, planet.Resource)
	fmt.Println(captain)
	photo.SetCaption(captain)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendSatelitePic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, planet process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(planet.SatelliteFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "satelite.png", file)
	captain := fmt.Sprintf("命定衛星: %s\n對應星球\n # %d: %s\n生產資源: %s",
		planet.Satellite, planet.Number, planet.Planet, planet.Resource)
	if planet.Planet == "-" {
		captain = fmt.Sprintf("命定衛星: %s\n對應星球: 任意",
			planet.Satellite)
	}
	fmt.Println(captain)
	photo.SetCaption(captain)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendText(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, text string) (ok bool) {
	outMsg, err := api.NewOutgoingMessage(tbotapi.NewRecipientFromChat(*chat), text).Send()
	if err != nil {
		fmt.Printf("Error sending text: %s, err = %s\n", text, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, %s\n", outMsg.Message.ID, outMsg.Message.Chat, text)
	return true
}

func sendSticker(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, id string) (ok bool) {
	outMsg, err := api.NewOutgoingStickerResend(tbotapi.NewRecipientFromChat(*chat), id).Send()
	if err != nil {
		fmt.Printf("Error sending sticker: %s, err = %s\n", id, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, sticker %s\n", outMsg.Message.ID, outMsg.Message.Chat, id)
	return true
}

// Command parse tg command line
func Command(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, msg string) (ok bool) {
	ok = false
	result := strings.Fields(msg)
	if len(result) == 0 {
		return
	}
	command := result[0]
	lowerCmd := strings.ToLower(command)
	if lowerCmd == "/help" || lowerCmd == "/start" {
		text := "/wp 星球編號\n/wp 星球名字(模糊搜尋第一個)\n/wr 資源\n" +
			"\n/wp 1\n/wp 地球\n/wr 笑料"
		sendText(api, chat, text)
		// sendSticker(api, chat, "BQADBQADPwAD_HMCBpRNwQvxuQoDAg")
		ok = true
		return
	}

	msg = strings.TrimPrefix(msg, command)
	msg = strings.TrimSpace(msg)
	if len(msg) == 0 {
		return
	}

	switch lowerCmd {
	case "/wp":
		planet, found := process.FindPlanet(gameData, msg)
		if !found {
			text := "醒醒吧，你沒有" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wn":
		ok = true
	case "/wr":
		planet, found := process.FindPlanetByResource(gameData, msg)
		if !found {
			text := "沒有生產" + msg + "的星球"
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wsp": // Find Satelite picture
		satelite, found := process.FindSatellite(gameData, msg)
		if !found {
			text := "醒醒吧，你沒有" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendSatelitePic(api, chat, satelite)
	case "/ws":
		planet, found := process.FindPlanetBySatellite(gameData, msg)
		if !found {
			text := "沒有星球喜歡" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wm":
		uri, found := process.GetWidURL(msg)
		if found {
			text := "想要在家賺錢嗎？點下面網址吧\n" + uri
			sendText(api, chat, text)
			fmt.Println(text)
			ok = true
		} else {
			text := "要產生 wid 網址的話，用空格或換行隔開喔！"
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
	default:
		fmt.Printf("我看不懂!! %s\n", msg)
	}
	return ok
}
<commit_msg>handle satellite to suit everyone<commit_after>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/ElvisChiang/tgwalkrbot/process"

	"bitbucket.org/mrd0ll4r/tbotapi"
	"bitbucket.org/mrd0ll4r/tbotapi/examples/boilerplate"
)

// DEBUG show verbose debug msg
const DEBUG = true

var idFile = "./data/id.csv"
var planetFile = "./data/planet.csv"

var playerData []process.PlayerName
var gameData []process.GameData

func main() {
	ok := false
	gameData, ok = process.LoadGameData(planetFile)
	if !ok {
		fmt.Printf("Game data loading fail\n")
		return
	}
	if DEBUG {
		for _, data := range gameData {
			fmt.Printf("#%d: %s/%s %s/%s %s\n",
				data.Number,
				data.Planet, data.PlanetFile,
				data.Satellite, data.SatelliteFile,
				data.Resource)
		}
	}
	startBot()
}

func startBot() {
	updateFunc := func(update tbotapi.Update, api *tbotapi.TelegramBotAPI) {
		switch update.Type() {
		case tbotapi.MessageUpdate:
			msg := update.Message
			typ := msg.Type()
			text := "(nil)"
			if typ == tbotapi.StickerMessage {
				sticker := update.Message.Sticker
				fmt.Printf("\tSticker id: %s size: %d\n",
					sticker.FileBase.ID, sticker.FileBase.Size)
			}
			if typ == tbotapi.TextMessage {
				text = *msg.Text
			}
			fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, text)
			if typ != tbotapi.TextMessage {
				//ignore non-text messages for now
				fmt.Println("Ignoring non-text message")
				return
			}
			// Note: Bots cannot receive from channels, at least no text messages. So we don't have to distinguish anything here

			// display the incoming message
			// msg.Chat implements fmt.Stringer, so it'll display nicely
			// we know it's a text message, so we can safely use the Message.Text pointer
			// fmt.Printf("<-%d, From:\t%s, Text: %s \n", msg.ID, msg.Chat, *msg.Text)

			Command(api, &msg.Chat, *msg.Text)
		case tbotapi.InlineQueryUpdate:
			fmt.Println("Ignoring received inline query: ", update.InlineQuery.Query)
		case tbotapi.ChosenInlineResultUpdate:
			fmt.Println("Ignoring chosen inline query result (ID): ", update.ChosenInlineResult.ID)
		default:
			fmt.Printf("Ignoring unknown Update type.")
		}
	}

	// run the bot, this will block
	boilerplate.RunBot(apiToken, updateFunc, "WalkrBot", "Reply Walkr information")
}

func sendPlanetPic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, planet process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(planet.PlanetFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "planet.png", file)
	captain := fmt.Sprintf("# %d: %s\n命定衛星: %s\n生產資源: %s",
		planet.Number, planet.Planet, planet.Satellite, planet.Resource)
	fmt.Println(captain)
	photo.SetCaption(captain)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendSatelitePic(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, planet process.GameData) (ok bool) {
	ok = false
	// send a photo
	file, err := os.Open(planet.SatelliteFile)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		ok = false
		return
	}
	defer file.Close()
	photo := api.NewOutgoingPhoto(tbotapi.NewRecipientFromChat(*chat), "satelite.png", file)
	captain := fmt.Sprintf("命定衛星: %s\n對應星球\n # %d: %s\n生產資源: %s",
		planet.Satellite, planet.Number, planet.Planet, planet.Resource)
	if planet.Planet == "-" {
		captain = fmt.Sprintf("命定衛星: %s\n對應星球: 任意",
			planet.Satellite)
	}
	fmt.Println(captain)
	photo.SetCaption(captain)
	outMsg, err := photo.Send()
	if err != nil {
		fmt.Printf("Error sending photo: %s\n", err)
		return
	}
	fmt.Printf("->%d, To:\t%s, (Photo)\n", outMsg.Message.ID, outMsg.Message.Chat)
	ok = true
	return
}

func sendText(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, text string) (ok bool) {
	outMsg, err := api.NewOutgoingMessage(tbotapi.NewRecipientFromChat(*chat), text).Send()
	if err != nil {
		fmt.Printf("Error sending text: %s, err = %s\n", text, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, %s\n", outMsg.Message.ID, outMsg.Message.Chat, text)
	return true
}

func sendSticker(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, id string) (ok bool) {
	outMsg, err := api.NewOutgoingStickerResend(tbotapi.NewRecipientFromChat(*chat), id).Send()
	if err != nil {
		fmt.Printf("Error sending sticker: %s, err = %s\n", id, err)
		return false
	}
	fmt.Printf("->%d, To:\t%s, sticker %s\n", outMsg.Message.ID, outMsg.Message.Chat, id)
	return true
}

// Command parse tg command line
func Command(api *tbotapi.TelegramBotAPI, chat *tbotapi.Chat, msg string) (ok bool) {
	ok = false
	result := strings.Fields(msg)
	if len(result) == 0 {
		return
	}
	command := result[0]
	lowerCmd := strings.ToLower(command)
	if lowerCmd == "/help" || lowerCmd == "/start" {
		text := "/wp 星球編號\n/wp 星球名字(模糊搜尋第一個)\n/wr 資源\n" +
			"\n/wp 1\n/wp 地球\n/wr 笑料"
		sendText(api, chat, text)
		// sendSticker(api, chat, "BQADBQADPwAD_HMCBpRNwQvxuQoDAg")
		ok = true
		return
	}

	msg = strings.TrimPrefix(msg, command)
	msg = strings.TrimSpace(msg)
	if len(msg) == 0 {
		return
	}

	switch lowerCmd {
	case "/wp":
		planet, found := process.FindPlanet(gameData, msg)
		if !found {
			text := "醒醒吧，你沒有" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wn":
		ok = true
	case "/wr":
		planet, found := process.FindPlanetByResource(gameData, msg)
		if !found {
			text := "沒有生產" + msg + "的星球"
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wsp": // Find Satelite picture
		satelite, found := process.FindSatellite(gameData, msg)
		if !found {
			text := "醒醒吧，你沒有" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		ok = sendSatelitePic(api, chat, satelite)
	case "/ws":
		planet, found := process.FindPlanetBySatellite(gameData, msg)
		if !found {
			text := "沒有星球喜歡" + msg
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
		if planet.Planet == "-" {
			text := msg + "很亂, 每個都喜歡"
			sendText(api, chat, text)
			fmt.Println(text)
			ok = true
			return
		}
		ok = sendPlanetPic(api, chat, planet)
	case "/wm":
		uri, found := process.GetWidURL(msg)
		if found {
			text := "想要在家賺錢嗎？點下面網址吧\n" + uri
			sendText(api, chat, text)
			fmt.Println(text)
			ok = true
		} else {
			text := "要產生 wid 網址的話，用空格或換行隔開喔！"
			sendText(api, chat, text)
			fmt.Println(text)
			return
		}
	default:
		fmt.Printf("我看不懂!! %s\n", msg)
	}
	return ok
}
<|endoftext|>


<commit_before>package filesystem

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"
	"unicode/utf8"
)

// TestPathSeparatorSingleByte verifies that the platform path separator rune is
// encoded as a single byte in UTF-8. We rely on this assumption for high
// performance in ensureValidName.
func TestPathSeparatorSingleByte(t *testing.T) {
	if utf8.RuneLen(os.PathSeparator) != 1 {
		t.Fatal("OS path separator does not have single-byte UTF-8 encoding")
	}
}

func TestDirectoryContentsNotExist(t *testing.T) {
	if _, err := DirectoryContentsByPath("/does/not/exist"); err == nil {
		t.Error("directory listing succeedeed for non-existent path")
	}
}

func TestDirectoryContentsFile(t *testing.T) {
	// Create an empty temporary file and defer its cleanup.
	file, err := ioutil.TempFile("", "mutagen_filesystem")
	if err != nil {
		t.Fatal("unable to create temporary file:", err)
	} else if err = file.Close(); err != nil {
		t.Error("unable to close temporary file:", err)
	}
	defer os.Remove(file.Name())

	// Ensure that directory listing fails.
	if _, err := DirectoryContentsByPath(file.Name()); err == nil {
		t.Error("directory listing succeedeed for non-directory path")
	}
}

func TestDirectoryContentsGOROOT(t *testing.T) {
	if contents, err := DirectoryContentsByPath(runtime.GOROOT()); err != nil {
		t.Fatal("directory listing failed for GOROOT:", err)
	} else if contents == nil {
		t.Fatal("directory contents nil for GOROOT")
	}
}

// TestDirectorySymbolicLinkRemoval tests that removal of symbolic links that
// point to directories works as expected.
func TestDirectorySymbolicLinkRemoval(t *testing.T) {
	// Create a temporary directory and defer its cleanup.
	temporaryDirectoryPath, err := ioutil.TempDir("", "mutagen_filesystem_test")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(temporaryDirectoryPath)

	// Create a directory handle for this temporary directory and defer its
	// closure.
	directory, _, err := OpenDirectory(temporaryDirectoryPath, false)
	if err != nil {
		t.Fatal("unable to open directory handle:", err)
	}
	defer directory.Close()

	// Create a directory that will serve as our target.
	if err := directory.CreateDirectory("target"); err != nil {
		t.Fatal("unable to create target directory:", err)
	}

	// Open the target directory and defer its closure.
	target, err := directory.OpenDirectory("target")
	if err != nil {
		t.Fatal("unable to open target directory:", err)
	}
	defer target.Close()

	// Create content within the target.
	if err := target.CreateDirectory("content"); err != nil {
		t.Fatal("unable to create content in target directory:", err)
	}

	// Create a symbolic link to the target.
	if err := directory.CreateSymbolicLink("link", "target"); err != nil {
		t.Fatal("unable to create symbolic link:", err)
	}

	// Remove the symbolic link.
	if err := directory.RemoveSymbolicLink("link"); err != nil {
		t.Fatal("unable to remove symbolic link:", err)
	}

	// Grab the target metadata to ensure that it still exists. As an additional
	// sanity check, also ensure that the content path still exists, but do so
	// using path-based access.
	if _, err := directory.ReadContentMetadata("target"); err != nil {
		t.Error("unable to read target metadata:", err)
	} else if _, err = os.Lstat(filepath.Join(temporaryDirectoryPath, "target", "content")); err != nil {
		t.Error("unable to read target content metadata:", err)
	}
}
<commit_msg>Added test for non-empty directory removal failure.<commit_after>package filesystem

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"
	"unicode/utf8"
)

// TestPathSeparatorSingleByte verifies that the platform path separator rune is
// encoded as a single byte in UTF-8. We rely on this assumption for high
// performance in ensureValidName.
func TestPathSeparatorSingleByte(t *testing.T) {
	if utf8.RuneLen(os.PathSeparator) != 1 {
		t.Fatal("OS path separator does not have single-byte UTF-8 encoding")
	}
}

func TestDirectoryContentsNotExist(t *testing.T) {
	if _, err := DirectoryContentsByPath("/does/not/exist"); err == nil {
		t.Error("directory listing succeedeed for non-existent path")
	}
}

func TestDirectoryContentsFile(t *testing.T) {
	// Create an empty temporary file and defer its cleanup.
	file, err := ioutil.TempFile("", "mutagen_filesystem")
	if err != nil {
		t.Fatal("unable to create temporary file:", err)
	} else if err = file.Close(); err != nil {
		t.Error("unable to close temporary file:", err)
	}
	defer os.Remove(file.Name())

	// Ensure that directory listing fails.
	if _, err := DirectoryContentsByPath(file.Name()); err == nil {
		t.Error("directory listing succeedeed for non-directory path")
	}
}

func TestDirectoryContentsGOROOT(t *testing.T) {
	if contents, err := DirectoryContentsByPath(runtime.GOROOT()); err != nil {
		t.Fatal("directory listing failed for GOROOT:", err)
	} else if contents == nil {
		t.Fatal("directory contents nil for GOROOT")
	}
}

// TestNonEmptyDirectoryRemovalFailure tests that removal of a non-empty
// directory results in failure.
func TestNonEmptyDirectoryRemovalFailure(t *testing.T) {
	// Create a temporary directory and defer its cleanup.
	temporaryDirectoryPath, err := ioutil.TempDir("", "mutagen_filesystem_test")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(temporaryDirectoryPath)

	// Create a directory handle for this temporary directory and defer its
	// closure.
	directory, _, err := OpenDirectory(temporaryDirectoryPath, false)
	if err != nil {
		t.Fatal("unable to open directory handle:", err)
	}
	defer directory.Close()

	// Create a directory that will serve as our target.
	if err := directory.CreateDirectory("target"); err != nil {
		t.Fatal("unable to create target directory:", err)
	}

	// Create content inside the directory.
	if target, err := directory.OpenDirectory("target"); err != nil {
		t.Fatal("unable to open target directory:", err)
	} else if err = target.CreateDirectory("content"); err != nil {
		target.Close()
		t.Fatal("unable to create content in target directory:", err)
	} else if err = target.Close(); err != nil {
		t.Fatal("unable to close target directory:", err)
	}

	// Attempt to remove the target directory.
	if directory.RemoveDirectory("target") == nil {
		t.Error("able to remove non-empty directory")
	}
}

// TestDirectorySymbolicLinkRemoval tests that removal of symbolic links that
// point to directories works as expected.
func TestDirectorySymbolicLinkRemoval(t *testing.T) {
	// Create a temporary directory and defer its cleanup.
	temporaryDirectoryPath, err := ioutil.TempDir("", "mutagen_filesystem_test")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(temporaryDirectoryPath)

	// Create a directory handle for this temporary directory and defer its
	// closure.
	directory, _, err := OpenDirectory(temporaryDirectoryPath, false)
	if err != nil {
		t.Fatal("unable to open directory handle:", err)
	}
	defer directory.Close()

	// Create a directory that will serve as our target.
	if err := directory.CreateDirectory("target"); err != nil {
		t.Fatal("unable to create target directory:", err)
	}

	// Open the target directory and defer its closure.
	target, err := directory.OpenDirectory("target")
	if err != nil {
		t.Fatal("unable to open target directory:", err)
	}
	defer target.Close()

	// Create content within the target.
	if err := target.CreateDirectory("content"); err != nil {
		t.Fatal("unable to create content in target directory:", err)
	}

	// Create a symbolic link to the target.
	if err := directory.CreateSymbolicLink("link", "target"); err != nil {
		t.Fatal("unable to create symbolic link:", err)
	}

	// Remove the symbolic link.
	if err := directory.RemoveSymbolicLink("link"); err != nil {
		t.Fatal("unable to remove symbolic link:", err)
	}

	// Grab the target metadata to ensure that it still exists. As an additional
	// sanity check, also ensure that the content path still exists, but do so
	// using path-based access.
	if _, err := directory.ReadContentMetadata("target"); err != nil {
		t.Error("unable to read target metadata:", err)
	} else if _, err = os.Lstat(filepath.Join(temporaryDirectoryPath, "target", "content")); err != nil {
		t.Error("unable to read target content metadata:", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"common/models"
	"errors"
	"os"
	"regexp"
	"strings"

	"gopkg.in/vmihailenco/msgpack.v2"

	log "github.com/Sirupsen/logrus"
	"golang.org/x/net/context"

	db "lib/db/mongodb"
	"lib/proto/auth"
	"lib/proto/snowflake"
	sp "lib/services"
	"lib/utils"
)

const (
	SERVICE           = "[AUTH]"
	DefaultMongodbURL = "mongodb://172.17.0.1/account"
	EnvMongodb        = "MONGODB_URL"
	DBAccount         = "account"
)

var (
	ErrMethodNotSupported    = errors.New("method not supported")
	ErrUsernameAlreadyExists = errors.New("username already exists")
	ErrPasswordInvalid       = errors.New("password invalid")

	AuthFailResult = &auth.Auth_Result{OK: false, UserId: 0, Body: nil}

	uuidRegexp = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
)

var (
	sfCli snowflake.SnowflakeServiceClient
	accDB *db.DialContext
)

type server struct {
}

func (s *server) init() {
	// 连接snowflake
	conn, _ := sp.GetService(sp.DefaultServicePath + "/snowflake")
	if conn == nil {
		log.Panic("cannot get snowflake service")
		os.Exit(-1)
	}
	sfCli = snowflake.NewSnowflakeServiceClient(conn)

	// 连接db
	mongodbURL := DefaultMongodbURL
	mkey, mval := sp.GetExtraService(sp.DefaultServicePath + "/mongo")
	if mkey == "" || mval == "" {
		if env := os.Getenv(EnvMongodb); env != "" {
			mongodbURL = env
		}
	} else {
		mongodbURL = "mongodb://" + mval
	}

	var err error
	accDB, err = db.Dial(mongodbURL, db.DefaultConcurrent)
	if err != nil {
		log.Panicf("mongodb: cannot connect to %v, err: %v", mongodbURL, err)
		os.Exit(-1)
	}
}

func (s *server) Auth(ctx context.Context, cert *auth.Auth_Certificate) (*auth.Auth_Result, error) {
	switch cert.Type {
	case auth.Auth_UUID:
		if uuidRegexp.MatchString(strings.ToLower(string(cert.Proof))) {
			return AuthFailResult, nil
		}
		return &auth.Auth_Result{OK: true, UserId: 0, Body: nil}, nil
	case auth.Auth_PLAIN:
		var p = models.Account{}
		if err := msgpack.Unmarshal(cert.Proof, &p); err != nil {
			log.Error("Auth plain invalid, proof:", utils.Bytes2Str(cert.Proof))
			return AuthFailResult, nil
		}

		// 用户名密码验证
		acc, err := authOrReg(&p)
		if err != nil {
			return AuthFailResult, nil
		}

		return &auth.Auth_Result{
			OK:     true,
			UserId: p.UserId,
			Body:   utils.Str2Bytes(acc.Token)}, nil

	case auth.Auth_TOKEN:
	case auth.Auth_FACEBOOK:
	default:
		return nil, ErrMethodNotSupported
	}
	return nil, ErrMethodNotSupported
}
<commit_msg>update auth service<commit_after>package main

import (
	"common/models"
	"errors"
	"os"
	"regexp"
	"strings"

	"gopkg.in/vmihailenco/msgpack.v2"

	log "github.com/Sirupsen/logrus"
	"golang.org/x/net/context"

	db "lib/db/mongodb"
	"lib/proto/auth"
	"lib/proto/snowflake"
	sp "lib/services"
	"lib/utils"
)

const (
	SERVICE           = "[AUTH]"
	DefaultMongodbURL = "mongodb://172.17.0.1/account"
	EnvMongodb        = "MONGODB_URL"
	DBAccount         = "account"
)

var (
	ErrMethodNotSupported    = errors.New("method not supported")
	ErrUsernameAlreadyExists = errors.New("username already exists")
	ErrPasswordInvalid       = errors.New("password invalid")

	AuthFailResult = &auth.Auth_Result{OK: false, UserId: 0, Body: nil}

	uuidRegexp = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
)

var (
	sfCli snowflake.SnowflakeServiceClient
	accDB *db.DialContext
)

type server struct {
}

func (s *server) init() {
	// 连接snowflake
	conn, _ := sp.GetService(sp.DefaultServicePath + "/snowflake")
	if conn == nil {
		log.Panic("cannot get snowflake service")
		os.Exit(-1)
	}
	sfCli = snowflake.NewSnowflakeServiceClient(conn)

	// 连接db
	mongodbURL := DefaultMongodbURL
	mkey, mval := sp.GetExtraService(sp.DefaultServicePath + "/mongo")
	if mkey == "" || mval == "" {
		if env := os.Getenv(EnvMongodb); env != "" {
			mongodbURL = env
		}
	} else {
		mongodbURL = "mongodb://" + mval
	}

	var err error
	accDB, err = db.Dial(mongodbURL, db.DefaultConcurrent)
	if err != nil {
		log.Panicf("mongodb: cannot connect to %v, err: %v", mongodbURL, err)
		os.Exit(-1)
	}

	log.Info("connected mongodb:", mongodbURL)
}

func (s *server) Auth(ctx context.Context, cert *auth.Auth_Certificate) (*auth.Auth_Result, error) {

	log.Debug("auth request, type:", cert.Type)

	switch cert.Type {
	case auth.Auth_UUID:
		if uuidRegexp.MatchString(strings.ToLower(string(cert.Proof))) {
			return AuthFailResult, nil
		}
		return &auth.Auth_Result{OK: true, UserId: 0, Body: nil}, nil
	case auth.Auth_PLAIN:
		var p = models.Account{}
		if err := msgpack.Unmarshal(cert.Proof, &p); err != nil {
			log.Error("Auth plain invalid, proof:", utils.Bytes2Str(cert.Proof))
			return AuthFailResult, nil
		}

		// 用户名密码验证
		acc, err := authOrReg(&p)
		if err != nil {
			log.Error("auth plain error:", err)
			return AuthFailResult, nil
		}

		return &auth.Auth_Result{
			OK:     true,
			UserId: p.UserId,
			Body:   utils.Str2Bytes(acc.Token)}, nil

	case auth.Auth_TOKEN:
	case auth.Auth_FACEBOOK:
	default:
		return nil, ErrMethodNotSupported
	}
	return nil, ErrMethodNotSupported
}
<|endoftext|>


<commit_before>package auth

import (
    "crypto/rand"
    "encoding/base64"
	"log"
	"strconv"
)

type SessionInfo struct {
	UserId int
    Username string
}

// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    _, err := rand.Read(b)
    // Note that err == nil only if we read len(b) bytes.
    if err != nil {
        return nil, err
    }

    return b, nil
}

// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
func GenerateRandomString(s int) (string, error) {
    b, err := GenerateRandomBytes(s)
    return base64.URLEncoding.EncodeToString(b), err
}

// GenerateUID creates a random session ID, for use with the session store.
func GenerateUID() (string, error) {
	return GenerateRandomString(26)
}

// SetSession stores the given value under the given name in the database.
// If the given name is already used, its value is overwritten.
func SetSession(name string, value SessionInfo) {
	result := RedisClient.HMSet("session:" + name, map[string]string{
		"userId": strconv.Itoa(value.UserId),
		"username": value.Username,
	})
	if result.Err() != nil {
		log.Println("Error while setting session: ")
		log.Println(result.Err())
		return
	}
}

// GetSession retrieves the session information for the given name
func GetSession(name string) (SessionInfo) {
	result := RedisClient.HGetAll("session:" + name)
	if result.Err() != nil {
		log.Println("Error while getting session: ")
		log.Println(result.Err())
		return SessionInfo{-1, ""}
	}

	resultMap, err := result.Result()
	if err != nil {
		log.Println("Error while getting session: ")
		log.Println(err)
		return SessionInfo{-1, ""}
	}

	retval := SessionInfo{-1, ""}

	retval.UserId, err = strconv.Atoi(resultMap["userId"])
	retval.Username = resultMap["username"]

	return retval
}
<commit_msg>actually do server-side expiry of redis sessions<commit_after>package auth

import (
    "crypto/rand"
    "encoding/base64"
	"log"
	"strconv"
	"time"
)

type SessionInfo struct {
	UserId int
    Username string
}

// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    _, err := rand.Read(b)
    // Note that err == nil only if we read len(b) bytes.
    if err != nil {
        return nil, err
    }

    return b, nil
}

// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
func GenerateRandomString(s int) (string, error) {
    b, err := GenerateRandomBytes(s)
    return base64.URLEncoding.EncodeToString(b), err
}

// GenerateUID creates a random session ID, for use with the session store.
func GenerateUID() (string, error) {
	return GenerateRandomString(26)
}

// SetSession stores the given value under the given name in the database.
// If the given name is already used, its value is overwritten.
func SetSession(name string, value SessionInfo) {
	result := RedisClient.HMSet("session:" + name, map[string]string{
		"userId": strconv.Itoa(value.UserId),
		"username": value.Username,
	})

	if result.Err() != nil {
		log.Println("Error while setting session: ")
		log.Println(result.Err())
		return
	}

	expireResult := RedisClient.Expire("session:" + name, 7*24*time.Hour)
	if expireResult.Err() != nil {
		log.Println("Error while setting session: ")
		log.Println(expireResult.Err())
		return
	}
}

// GetSession retrieves the session information for the given name
func GetSession(name string) (SessionInfo) {
	result := RedisClient.HGetAll("session:" + name)
	if result.Err() != nil {
		log.Println("Error while getting session: ")
		log.Println(result.Err())
		return SessionInfo{-1, ""}
	}

	resultMap, err := result.Result()
	if err != nil {
		log.Println("Error while getting session: ")
		log.Println(err)
		return SessionInfo{-1, ""}
	}

	retval := SessionInfo{-1, ""}

	retval.UserId, err = strconv.Atoi(resultMap["userId"])
	retval.Username = resultMap["username"]

	return retval
}
<|endoftext|>


<commit_before>// Copyright 2014 Matthew Baird, Andrew Mussey
//
// 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 saml

import (
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/nu7hatch/gouuid"
)

func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSettings) *AuthorizationRequest {
	myIdUUID, err := uuid.NewV4()
	if err != nil {
		fmt.Println("Error is UUID Generation:", err)
	}
	//yyyy-MM-dd'T'H:mm:ss
	//layout := "2006-01-02T15:04:05"
	//layout := "2014-12-04T21:13:49Z"
	//t := time.Now().Format(layout)
	t := time.Now().UTC().Format(time.RFC3339Nano)

	return &AuthorizationRequest{AccountSettings: accountSettings, AppSettings: appSettings, Id: "_" + myIdUUID.String(), IssueInstant: t}
}

// GetRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) {
	d := AuthnRequest{
		XMLName: xml.Name{
			Local: "samlp:AuthnRequest",
		},
		SAMLP:                       "urn:oasis:names:tc:SAML:2.0:protocol",
		SAML:                        "urn:oasis:names:tc:SAML:2.0:assertion",
		ID:                          ar.Id,
		ProtocolBinding:             "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
		Version:                     "2.0",
		AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
		Issuer: Issuer{
			XMLName: xml.Name{
				Local: "saml:Issuer",
			},
			Url:  ar.AppSettings.Issuer,
			SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
		},
		IssueInstant: ar.IssueInstant,
		NameIDPolicy: NameIDPolicy{
			XMLName: xml.Name{
				Local: "samlp:NameIDPolicy",
			},
			AllowCreate: true,
			Format:      "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
		},
		RequestedAuthnContext: RequestedAuthnContext{
			XMLName: xml.Name{
				Local: "samlp:RequestedAuthnContext",
			},
			SAMLP:      "urn:oasis:names:tc:SAML:2.0:protocol",
			Comparison: "exact",
			AuthnContextClassRef: AuthnContextClassRef{
				XMLName: xml.Name{
					Local: "saml:AuthnContextClassRef",
				},
				SAML:      "urn:oasis:names:tc:SAML:2.0:assertion",
				Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
			},
		},
	}
	b, err := xml.MarshalIndent(d, "", "    ")
	if err != nil {
		return "", err
	}

	xmlAuthnRequest := fmt.Sprintf("<?xml version='1.0' encoding='UTF-8'?>\n%s", b)

	if base64Encode {
		data := []byte(xmlAuthnRequest)
		return base64.StdEncoding.EncodeToString(data), nil
	} else {
		return string(xmlAuthnRequest), nil
	}
}

// GetSignedRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert string, privateCert string) (string, error) {
	cert, err := LoadCertificate(publicCert)
	if err != nil {
		return "", err
	}

	d := AuthnSignedRequest{
		XMLName: xml.Name{
			Local: "samlp:AuthnRequest",
		},
		SAMLP:                       "urn:oasis:names:tc:SAML:2.0:protocol",
		SAML:                        "urn:oasis:names:tc:SAML:2.0:assertion",
		SAMLSIG:                     "http://www.w3.org/2000/09/xmldsig#",
		ID:                          ar.Id,
		ProtocolBinding:             "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
		Version:                     "2.0",
		AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
		Issuer: Issuer{
			XMLName: xml.Name{
				Local: "saml:Issuer",
			},
			Url: "https://sp.example.com/SAML2",
		},
		IssueInstant: ar.IssueInstant,
		NameIDPolicy: NameIDPolicy{
			XMLName: xml.Name{
				Local: "samlp:NameIDPolicy",
			},
			AllowCreate: true,
			Format:      "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
		},
		RequestedAuthnContext: RequestedAuthnContext{
			XMLName: xml.Name{
				Local: "samlp:RequestedAuthnContext",
			},
			SAMLP:      "urn:oasis:names:tc:SAML:2.0:protocol",
			Comparison: "exact",
			AuthnContextClassRef: AuthnContextClassRef{
				XMLName: xml.Name{
					Local: "saml:AuthnContextClassRef",
				},
				SAML:      "urn:oasis:names:tc:SAML:2.0:assertion",
				Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
			},
		},

		Signature: Signature{
			XMLName: xml.Name{
				Local: "samlsig:Signature",
			},
			Id: "Signature1",
			SignedInfo: SignedInfo{
				XMLName: xml.Name{
					Local: "samlsig:SignedInfo",
				},
				CanonicalizationMethod: CanonicalizationMethod{
					XMLName: xml.Name{
						Local: "samlsig:CanonicalizationMethod",
					},
					Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
				},
				SignatureMethod: SignatureMethod{
					XMLName: xml.Name{
						Local: "samlsig:SignatureMethod",
					},
					Algorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
				},
				SamlsigReference: SamlsigReference{
					XMLName: xml.Name{
						Local: "samlsig:Reference",
					},
					URI: "#" + ar.Id,
					Transforms: Transforms{
						XMLName: xml.Name{
							Local: "samlsig:Transforms",
						},
						Transform: Transform{
							XMLName: xml.Name{
								Local: "samlsig:Transform",
							},
							Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
						},
					},
					DigestMethod: DigestMethod{
						XMLName: xml.Name{
							Local: "samlsig:DigestMethod",
						},
						Algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
					},
					DigestValue: DigestValue{
						XMLName: xml.Name{
							Local: "samlsig:DigestValue",
						},
					},
				},
			},
			SignatureValue: SignatureValue{
				XMLName: xml.Name{
					Local: "samlsig:SignatureValue",
				},
			},
			KeyInfo: KeyInfo{
				XMLName: xml.Name{
					Local: "samlsig:KeyInfo",
				},
				X509Data: X509Data{
					XMLName: xml.Name{
						Local: "samlsig:X509Data",
					},
					X509Certificate: X509Certificate{
						XMLName: xml.Name{
							Local: "samlsig:X509Certificate",
						},
						Cert: cert,
					},
				},
			},
		},
	}
	b, err := xml.MarshalIndent(d, "", "    ")
	if err != nil {
		return "", err
	}

	samlAuthnRequest := string(b)
	// Write the SAML to a file.

	samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs")
	if err != nil {
		return "", err
	}
	samlXmlsecOutput, err := ioutil.TempFile(os.TempDir(), "tmpgs")
	if err != nil {
		return "", err
	}

	samlXmlsecOutput.Close()

	samlXmlsecInput.WriteString("<?xml version='1.0' encoding='UTF-8'?>\n")
	samlXmlsecInput.WriteString(samlAuthnRequest)
	samlXmlsecInput.Close()

	_, errOut := exec.Command("xmlsec1", "--sign", "--privkey-pem", privateCert,
		"--id-attr:ID", "urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest",
		"--output", samlXmlsecOutput.Name(), samlXmlsecInput.Name()).Output()
	if errOut != nil {
		return "", errOut
	}

	samlSignedRequest, err := ioutil.ReadFile(samlXmlsecOutput.Name())
	if err != nil {
		return "", err
	}
	samlSignedRequestXml := strings.Trim(string(samlSignedRequest), "\n")

	if base64Encode {
		data := []byte(samlSignedRequestXml)
		return base64.StdEncoding.EncodeToString(data), nil
	} else {
		return string(samlSignedRequestXml), nil
	}
}

// String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" +
// AuthRequest.getRidOfCRLF(URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8"));
func (ar AuthorizationRequest) GetRequestUrl() (string, error) {
	u, err := url.Parse(ar.AccountSettings.IDP_SSO_Target_URL)
	if err != nil {
		return "", err
	}
	base64EncodedUTF8SamlRequest, err := ar.GetRequest(true)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Add("SAMLRequest", base64EncodedUTF8SamlRequest)

	u.RawQuery = q.Encode()
	return u.String(), nil
}
<commit_msg>Unhardcode issuer in authn xml<commit_after>// Copyright 2014 Matthew Baird, Andrew Mussey
//
// 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 saml

import (
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/nu7hatch/gouuid"
)

func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSettings) *AuthorizationRequest {
	myIdUUID, err := uuid.NewV4()
	if err != nil {
		fmt.Println("Error is UUID Generation:", err)
	}
	//yyyy-MM-dd'T'H:mm:ss
	//layout := "2006-01-02T15:04:05"
	//layout := "2014-12-04T21:13:49Z"
	//t := time.Now().Format(layout)
	t := time.Now().UTC().Format(time.RFC3339Nano)

	return &AuthorizationRequest{AccountSettings: accountSettings, AppSettings: appSettings, Id: "_" + myIdUUID.String(), IssueInstant: t}
}

// GetRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) {
	d := AuthnRequest{
		XMLName: xml.Name{
			Local: "samlp:AuthnRequest",
		},
		SAMLP:                       "urn:oasis:names:tc:SAML:2.0:protocol",
		SAML:                        "urn:oasis:names:tc:SAML:2.0:assertion",
		ID:                          ar.Id,
		ProtocolBinding:             "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
		Version:                     "2.0",
		AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
		Issuer: Issuer{
			XMLName: xml.Name{
				Local: "saml:Issuer",
			},
			Url:  ar.AppSettings.Issuer,
			SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
		},
		IssueInstant: ar.IssueInstant,
		NameIDPolicy: NameIDPolicy{
			XMLName: xml.Name{
				Local: "samlp:NameIDPolicy",
			},
			AllowCreate: true,
			Format:      "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
		},
		RequestedAuthnContext: RequestedAuthnContext{
			XMLName: xml.Name{
				Local: "samlp:RequestedAuthnContext",
			},
			SAMLP:      "urn:oasis:names:tc:SAML:2.0:protocol",
			Comparison: "exact",
			AuthnContextClassRef: AuthnContextClassRef{
				XMLName: xml.Name{
					Local: "saml:AuthnContextClassRef",
				},
				SAML:      "urn:oasis:names:tc:SAML:2.0:assertion",
				Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
			},
		},
	}
	b, err := xml.MarshalIndent(d, "", "    ")
	if err != nil {
		return "", err
	}

	xmlAuthnRequest := fmt.Sprintf("<?xml version='1.0' encoding='UTF-8'?>\n%s", b)

	if base64Encode {
		data := []byte(xmlAuthnRequest)
		return base64.StdEncoding.EncodeToString(data), nil
	} else {
		return string(xmlAuthnRequest), nil
	}
}

// GetSignedRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert string, privateCert string) (string, error) {
	cert, err := LoadCertificate(publicCert)
	if err != nil {
		return "", err
	}

	d := AuthnSignedRequest{
		XMLName: xml.Name{
			Local: "samlp:AuthnRequest",
		},
		SAMLP:                       "urn:oasis:names:tc:SAML:2.0:protocol",
		SAML:                        "urn:oasis:names:tc:SAML:2.0:assertion",
		SAMLSIG:                     "http://www.w3.org/2000/09/xmldsig#",
		ID:                          ar.Id,
		ProtocolBinding:             "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
		Version:                     "2.0",
		AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
		Issuer: Issuer{
			XMLName: xml.Name{
				Local: "saml:Issuer",
			},
			Url: ar.AppSettings.AssertionConsumerServiceURL,
		},
		IssueInstant: ar.IssueInstant,
		NameIDPolicy: NameIDPolicy{
			XMLName: xml.Name{
				Local: "samlp:NameIDPolicy",
			},
			AllowCreate: true,
			Format:      "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
		},
		RequestedAuthnContext: RequestedAuthnContext{
			XMLName: xml.Name{
				Local: "samlp:RequestedAuthnContext",
			},
			SAMLP:      "urn:oasis:names:tc:SAML:2.0:protocol",
			Comparison: "exact",
			AuthnContextClassRef: AuthnContextClassRef{
				XMLName: xml.Name{
					Local: "saml:AuthnContextClassRef",
				},
				SAML:      "urn:oasis:names:tc:SAML:2.0:assertion",
				Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
			},
		},

		Signature: Signature{
			XMLName: xml.Name{
				Local: "samlsig:Signature",
			},
			Id: "Signature1",
			SignedInfo: SignedInfo{
				XMLName: xml.Name{
					Local: "samlsig:SignedInfo",
				},
				CanonicalizationMethod: CanonicalizationMethod{
					XMLName: xml.Name{
						Local: "samlsig:CanonicalizationMethod",
					},
					Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
				},
				SignatureMethod: SignatureMethod{
					XMLName: xml.Name{
						Local: "samlsig:SignatureMethod",
					},
					Algorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
				},
				SamlsigReference: SamlsigReference{
					XMLName: xml.Name{
						Local: "samlsig:Reference",
					},
					URI: "#" + ar.Id,
					Transforms: Transforms{
						XMLName: xml.Name{
							Local: "samlsig:Transforms",
						},
						Transform: Transform{
							XMLName: xml.Name{
								Local: "samlsig:Transform",
							},
							Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
						},
					},
					DigestMethod: DigestMethod{
						XMLName: xml.Name{
							Local: "samlsig:DigestMethod",
						},
						Algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
					},
					DigestValue: DigestValue{
						XMLName: xml.Name{
							Local: "samlsig:DigestValue",
						},
					},
				},
			},
			SignatureValue: SignatureValue{
				XMLName: xml.Name{
					Local: "samlsig:SignatureValue",
				},
			},
			KeyInfo: KeyInfo{
				XMLName: xml.Name{
					Local: "samlsig:KeyInfo",
				},
				X509Data: X509Data{
					XMLName: xml.Name{
						Local: "samlsig:X509Data",
					},
					X509Certificate: X509Certificate{
						XMLName: xml.Name{
							Local: "samlsig:X509Certificate",
						},
						Cert: cert,
					},
				},
			},
		},
	}
	b, err := xml.MarshalIndent(d, "", "    ")
	if err != nil {
		return "", err
	}

	samlAuthnRequest := string(b)
	// Write the SAML to a file.

	samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs")
	if err != nil {
		return "", err
	}
	samlXmlsecOutput, err := ioutil.TempFile(os.TempDir(), "tmpgs")
	if err != nil {
		return "", err
	}

	samlXmlsecOutput.Close()

	samlXmlsecInput.WriteString("<?xml version='1.0' encoding='UTF-8'?>\n")
	samlXmlsecInput.WriteString(samlAuthnRequest)
	samlXmlsecInput.Close()

	_, errOut := exec.Command("xmlsec1", "--sign", "--privkey-pem", privateCert,
		"--id-attr:ID", "urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest",
		"--output", samlXmlsecOutput.Name(), samlXmlsecInput.Name()).Output()
	if errOut != nil {
		return "", errOut
	}

	samlSignedRequest, err := ioutil.ReadFile(samlXmlsecOutput.Name())
	if err != nil {
		return "", err
	}
	samlSignedRequestXml := strings.Trim(string(samlSignedRequest), "\n")

	if base64Encode {
		data := []byte(samlSignedRequestXml)
		return base64.StdEncoding.EncodeToString(data), nil
	} else {
		return string(samlSignedRequestXml), nil
	}
}

// String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" +
// AuthRequest.getRidOfCRLF(URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8"));
func (ar AuthorizationRequest) GetRequestUrl() (string, error) {
	u, err := url.Parse(ar.AccountSettings.IDP_SSO_Target_URL)
	if err != nil {
		return "", err
	}
	base64EncodedUTF8SamlRequest, err := ar.GetRequest(true)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Add("SAMLRequest", base64EncodedUTF8SamlRequest)

	u.RawQuery = q.Encode()
	return u.String(), nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

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

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

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

package cache

import (
	"context"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/pkg/errors"
)

// For testing
var (
	hashFunction           = cacheHasher
	artifactConfigFunction = artifactConfig
)

func getHashForArtifact(ctx context.Context, depLister DependencyLister, a *latest.Artifact) (string, error) {
	var inputs []string

	// Append the artifact's configuration
	config, err := artifactConfigFunction(a)
	if err != nil {
		return "", errors.Wrapf(err, "getting artifact's configuration for %s", a.ImageName)
	}
	inputs = append(inputs, config)

	// Append the digest of each input file
	deps, err := depLister.DependenciesForArtifact(ctx, a)
	if err != nil {
		return "", errors.Wrapf(err, "getting dependencies for %s", a.ImageName)
	}
	sort.Strings(deps)

	for _, d := range deps {
		h, err := hashFunction(d)
		if err != nil {
			return "", errors.Wrapf(err, "getting hash for %s", d)
		}
		inputs = append(inputs, h)
	}

	// add build args for the artifact if specified
	if buildArgs := retrieveBuildArgs(a); buildArgs != nil {
		buildArgs, err := docker.EvaluateBuildArgs(buildArgs)
		if err != nil {
			return "", errors.Wrap(err, "evaluating build args")
		}
		args := convertBuildArgsToStringArray(buildArgs)
		inputs = append(inputs, args...)
	}

	// get a key for the hashes
	hasher := sha256.New()
	enc := json.NewEncoder(hasher)
	if err := enc.Encode(inputs); err != nil {
		return "", err
	}

	return hex.EncodeToString(hasher.Sum(nil)), nil
}

func artifactConfig(a *latest.Artifact) (string, error) {
	buf, err := json.Marshal(a.ArtifactType)
	if err != nil {
		return "", errors.Wrapf(err, "marshalling the artifact's configuration for %s", a.ImageName)
	}

	return string(buf), nil
}

func retrieveBuildArgs(a *latest.Artifact) map[string]*string {
	if a.ArtifactType.DockerArtifact != nil {
		return a.ArtifactType.DockerArtifact.BuildArgs
	}
	if a.ArtifactType.KanikoArtifact != nil {
		return a.KanikoArtifact.BuildArgs
	}
	customArtifact := a.ArtifactType.CustomArtifact
	if customArtifact != nil && customArtifact.Dependencies.Dockerfile != nil {
		return customArtifact.Dependencies.Dockerfile.BuildArgs
	}
	return nil
}

func convertBuildArgsToStringArray(buildArgs map[string]*string) []string {
	var args []string
	for k, v := range buildArgs {
		if v == nil {
			args = append(args, k)
			continue
		}
		args = append(args, fmt.Sprintf("%s=%s", k, *v))
	}
	sort.Strings(args)
	return args
}

// cacheHasher takes hashes the contents and name of a file
func cacheHasher(p string) (string, error) {
	h := md5.New()
	fi, err := os.Lstat(p)
	if err != nil {
		return "", err
	}
	h.Write([]byte(fi.Mode().String()))
	h.Write([]byte(fi.Name()))
	if fi.Mode().IsRegular() {
		f, err := os.Open(p)
		if err != nil {
			return "", err
		}
		defer f.Close()
		if _, err := io.Copy(h, f); err != nil {
			return "", err
		}
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}
<commit_msg>Use a switch instead of if<commit_after>/*
Copyright 2019 The Skaffold Authors

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

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

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

package cache

import (
	"context"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/pkg/errors"
)

// For testing
var (
	hashFunction           = cacheHasher
	artifactConfigFunction = artifactConfig
)

func getHashForArtifact(ctx context.Context, depLister DependencyLister, a *latest.Artifact) (string, error) {
	var inputs []string

	// Append the artifact's configuration
	config, err := artifactConfigFunction(a)
	if err != nil {
		return "", errors.Wrapf(err, "getting artifact's configuration for %s", a.ImageName)
	}
	inputs = append(inputs, config)

	// Append the digest of each input file
	deps, err := depLister.DependenciesForArtifact(ctx, a)
	if err != nil {
		return "", errors.Wrapf(err, "getting dependencies for %s", a.ImageName)
	}
	sort.Strings(deps)

	for _, d := range deps {
		h, err := hashFunction(d)
		if err != nil {
			return "", errors.Wrapf(err, "getting hash for %s", d)
		}
		inputs = append(inputs, h)
	}

	// add build args for the artifact if specified
	if buildArgs := retrieveBuildArgs(a); buildArgs != nil {
		buildArgs, err := docker.EvaluateBuildArgs(buildArgs)
		if err != nil {
			return "", errors.Wrap(err, "evaluating build args")
		}
		args := convertBuildArgsToStringArray(buildArgs)
		inputs = append(inputs, args...)
	}

	// get a key for the hashes
	hasher := sha256.New()
	enc := json.NewEncoder(hasher)
	if err := enc.Encode(inputs); err != nil {
		return "", err
	}

	return hex.EncodeToString(hasher.Sum(nil)), nil
}

func artifactConfig(a *latest.Artifact) (string, error) {
	buf, err := json.Marshal(a.ArtifactType)
	if err != nil {
		return "", errors.Wrapf(err, "marshalling the artifact's configuration for %s", a.ImageName)
	}

	return string(buf), nil
}

func retrieveBuildArgs(artifact *latest.Artifact) map[string]*string {
	switch {
	case artifact.DockerArtifact != nil:
		return artifact.DockerArtifact.BuildArgs

	case artifact.KanikoArtifact != nil:
		return artifact.KanikoArtifact.BuildArgs

	case artifact.CustomArtifact != nil && artifact.CustomArtifact.Dependencies.Dockerfile != nil:
		return artifact.CustomArtifact.Dependencies.Dockerfile.BuildArgs

	default:
		return nil
	}
}

func convertBuildArgsToStringArray(buildArgs map[string]*string) []string {
	var args []string
	for k, v := range buildArgs {
		if v == nil {
			args = append(args, k)
			continue
		}
		args = append(args, fmt.Sprintf("%s=%s", k, *v))
	}
	sort.Strings(args)
	return args
}

// cacheHasher takes hashes the contents and name of a file
func cacheHasher(p string) (string, error) {
	h := md5.New()
	fi, err := os.Lstat(p)
	if err != nil {
		return "", err
	}
	h.Write([]byte(fi.Mode().String()))
	h.Write([]byte(fi.Name()))
	if fi.Mode().IsRegular() {
		f, err := os.Open(p)
		if err != nil {
			return "", err
		}
		defer f.Close()
		if _, err := io.Copy(h, f); err != nil {
			return "", err
		}
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Skaffold Authors

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

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

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

package kaniko

import (
	"context"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"

	cstorage "cloud.google.com/go/storage"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

const kanikoContainerName = "kaniko"

func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cfg *latest.KanikoBuild) (string, error) {
	dockerfilePath := artifact.DockerArtifact.DockerfilePath

	initialTag := util.RandomID()

	logrus.Debug("Upload sources to GCS")
	tarName := fmt.Sprintf("context-%s.tar.gz", initialTag)
	if err := docker.UploadContextToGCS(ctx, artifact.Workspace, artifact.DockerArtifact, cfg.BuildContext.GCSBucket, tarName); err != nil {
		return "", errors.Wrap(err, "uploading sources to GCS")
	}
	defer gcsDelete(ctx, cfg.BuildContext.GCSBucket, tarName)

	client, err := kubernetes.GetClientset()
	if err != nil {
		return "", errors.Wrap(err, "")
	}
	pods := client.CoreV1().Pods(cfg.Namespace)

	imageDst := fmt.Sprintf("%s:%s", artifact.ImageName, initialTag)
	args := []string{
		fmt.Sprintf("--dockerfile=%s", dockerfilePath),
		fmt.Sprintf("--context=gs://%s/%s", cfg.BuildContext.GCSBucket, tarName),
		fmt.Sprintf("--destination=%s", imageDst),
		fmt.Sprintf("-v=%s", logrus.GetLevel().String()),
	}
	args = append(args, docker.GetBuildArgs(artifact.DockerArtifact)...)

	logrus.Debug("Creating pod")
	p, err := pods.Create(&v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "kaniko",
			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
			Namespace:    cfg.Namespace,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:            kanikoContainerName,
					Image:           constants.DefaultKanikoImage,
					ImagePullPolicy: v1.PullIfNotPresent,
					Args:            args,
					VolumeMounts: []v1.VolumeMount{{
						Name:      constants.DefaultKanikoSecretName,
						MountPath: "/secret",
					}},
					Env: []v1.EnvVar{{
						Name:  "GOOGLE_APPLICATION_CREDENTIALS",
						Value: "/secret/kaniko-secret",
					}},
				},
			},
			Volumes: []v1.Volume{{
				Name: constants.DefaultKanikoSecretName,
				VolumeSource: v1.VolumeSource{
					Secret: &v1.SecretVolumeSource{
						SecretName: cfg.PullSecretName,
					},
				},
			}},
			RestartPolicy: v1.RestartPolicyNever,
		},
	})
	if err != nil {
		return "", errors.Wrap(err, "creating kaniko pod")
	}

	waitForLogs := streamLogs(out, p.Name, pods)

	defer func() {
		if err := pods.Delete(p.Name, &metav1.DeleteOptions{
			GracePeriodSeconds: new(int64),
		}); err != nil {
			logrus.Fatalf("deleting pod: %s", err)
		}
	}()

	timeout, err := time.ParseDuration(cfg.Timeout)
	if err != nil {
		return "", errors.Wrap(err, "parsing timeout")
	}

	if err := kubernetes.WaitForPodComplete(pods, p.Name, timeout); err != nil {
		return "", errors.Wrap(err, "waiting for pod to complete")
	}

	waitForLogs()

	return imageDst, nil
}

func streamLogs(out io.Writer, name string, pods corev1.PodInterface) func() {
	var wg sync.WaitGroup
	wg.Add(1)

	var retry int32 = 1
	go func() {
		defer wg.Done()

		for atomic.LoadInt32(&retry) == 1 {
			r, err := pods.GetLogs(name, &v1.PodLogOptions{
				Follow:    true,
				Container: kanikoContainerName,
			}).Stream()
			if err == nil {
				io.Copy(out, r)
				return
			}

			logrus.Debugln("unable to get kaniko pod logs:", err)
			time.Sleep(1 * time.Second)
		}
	}()

	return func() {
		atomic.StoreInt32(&retry, 0)
		wg.Wait()
	}
}

func gcsDelete(ctx context.Context, bucket, path string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return err
	}
	defer c.Close()

	return c.Bucket(bucket).Object(path).Delete(ctx)
}
<commit_msg>Auto-detect GCP project/bucket<commit_after>/*
Copyright 2018 The Skaffold Authors

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

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

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

package kaniko

import (
	"context"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"

	cstorage "cloud.google.com/go/storage"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/gcp"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

const kanikoContainerName = "kaniko"

func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cfg *latest.KanikoBuild) (string, error) {
	initialTag := util.RandomID()
	dockerfilePath := artifact.DockerArtifact.DockerfilePath

	bucket := cfg.BuildContext.GCSBucket
	if bucket == "" {
		guessedProjectID, err := gcp.ExtractProjectID(artifact.ImageName)
		if err != nil {
			return "", errors.Wrap(err, "extracting projectID from image name")
		}

		bucket = guessedProjectID
	}
	logrus.Debugln("Upload sources to", bucket, "GCS bucket")

	tarName := fmt.Sprintf("context-%s.tar.gz", initialTag)
	if err := docker.UploadContextToGCS(ctx, artifact.Workspace, artifact.DockerArtifact, bucket, tarName); err != nil {
		return "", errors.Wrap(err, "uploading sources to GCS")
	}
	defer gcsDelete(ctx, bucket, tarName)

	client, err := kubernetes.GetClientset()
	if err != nil {
		return "", errors.Wrap(err, "")
	}
	pods := client.CoreV1().Pods(cfg.Namespace)

	imageDst := fmt.Sprintf("%s:%s", artifact.ImageName, initialTag)
	args := []string{
		fmt.Sprintf("--dockerfile=%s", dockerfilePath),
		fmt.Sprintf("--context=gs://%s/%s", bucket, tarName),
		fmt.Sprintf("--destination=%s", imageDst),
		fmt.Sprintf("-v=%s", logrus.GetLevel().String()),
	}
	args = append(args, docker.GetBuildArgs(artifact.DockerArtifact)...)

	logrus.Debug("Creating pod")
	p, err := pods.Create(&v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "kaniko",
			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
			Namespace:    cfg.Namespace,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:            kanikoContainerName,
					Image:           constants.DefaultKanikoImage,
					ImagePullPolicy: v1.PullIfNotPresent,
					Args:            args,
					VolumeMounts: []v1.VolumeMount{{
						Name:      constants.DefaultKanikoSecretName,
						MountPath: "/secret",
					}},
					Env: []v1.EnvVar{{
						Name:  "GOOGLE_APPLICATION_CREDENTIALS",
						Value: "/secret/kaniko-secret",
					}},
				},
			},
			Volumes: []v1.Volume{{
				Name: constants.DefaultKanikoSecretName,
				VolumeSource: v1.VolumeSource{
					Secret: &v1.SecretVolumeSource{
						SecretName: cfg.PullSecretName,
					},
				},
			}},
			RestartPolicy: v1.RestartPolicyNever,
		},
	})
	if err != nil {
		return "", errors.Wrap(err, "creating kaniko pod")
	}

	waitForLogs := streamLogs(out, p.Name, pods)

	defer func() {
		if err := pods.Delete(p.Name, &metav1.DeleteOptions{
			GracePeriodSeconds: new(int64),
		}); err != nil {
			logrus.Fatalf("deleting pod: %s", err)
		}
	}()

	timeout, err := time.ParseDuration(cfg.Timeout)
	if err != nil {
		return "", errors.Wrap(err, "parsing timeout")
	}

	if err := kubernetes.WaitForPodComplete(pods, p.Name, timeout); err != nil {
		return "", errors.Wrap(err, "waiting for pod to complete")
	}

	waitForLogs()

	return imageDst, nil
}

func streamLogs(out io.Writer, name string, pods corev1.PodInterface) func() {
	var wg sync.WaitGroup
	wg.Add(1)

	var retry int32 = 1
	go func() {
		defer wg.Done()

		for atomic.LoadInt32(&retry) == 1 {
			r, err := pods.GetLogs(name, &v1.PodLogOptions{
				Follow:    true,
				Container: kanikoContainerName,
			}).Stream()
			if err == nil {
				io.Copy(out, r)
				return
			}

			logrus.Debugln("unable to get kaniko pod logs:", err)
			time.Sleep(1 * time.Second)
		}
	}()

	return func() {
		atomic.StoreInt32(&retry, 0)
		wg.Wait()
	}
}

func gcsDelete(ctx context.Context, bucket, path string) error {
	c, err := cstorage.NewClient(ctx)
	if err != nil {
		return err
	}
	defer c.Close()

	return c.Bucket(bucket).Object(path).Delete(ctx)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Skaffold Authors

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

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

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

package schema

import (
	"fmt"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config/transform"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1"
	"github.com/pkg/errors"
)

type Transform func(vc util.VersionedConfig) (util.VersionedConfig, error)

// Map of schema version to transform functions
// If there are n schema versions, this should always contain (n-1) entries,
// since the last schema version should not have a transform
var transformers map[string]Transform = map[string]Transform{
	v1alpha1.Version: transform.ToV1Alpha2,
}

func RunTransform(vc util.VersionedConfig) (util.VersionedConfig, error) {
	for i, version := range config.Versions {
		if version == vc.GetVersion() {
			return transformToLatest(vc, i)
		}
	}
	return nil, fmt.Errorf("Unsupported version: %s", vc.GetVersion())
}

func transformToLatest(vc util.VersionedConfig, pos int) (util.VersionedConfig, error) {
	if pos == len(config.Versions)-1 {
		// if there are n versions, there are (n-1) transforms
		return vc, nil
	}
	transformer := transformers[config.Versions[pos]]
	newConfig, err := transformer(vc)
	if err != nil {
		return nil, errors.Wrapf(err, "transforming skaffold config")
	}
	return transformToLatest(newConfig, pos+1)
}
<commit_msg>should omit type map[string]Transform from declaration of var transformers; it will be inferred from the right-hand side<commit_after>/*
Copyright 2018 The Skaffold Authors

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

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

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

package schema

import (
	"fmt"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config/transform"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1"
	"github.com/pkg/errors"
)

type Transform func(vc util.VersionedConfig) (util.VersionedConfig, error)

// Map of schema version to transform functions
// If there are n schema versions, this should always contain (n-1) entries,
// since the last schema version should not have a transform
var transformers = map[string]Transform{
	v1alpha1.Version: transform.ToV1Alpha2,
}

func RunTransform(vc util.VersionedConfig) (util.VersionedConfig, error) {
	for i, version := range config.Versions {
		if version == vc.GetVersion() {
			return transformToLatest(vc, i)
		}
	}
	return nil, fmt.Errorf("Unsupported version: %s", vc.GetVersion())
}

func transformToLatest(vc util.VersionedConfig, pos int) (util.VersionedConfig, error) {
	if pos == len(config.Versions)-1 {
		// if there are n versions, there are (n-1) transforms
		return vc, nil
	}
	transformer := transformers[config.Versions[pos]]
	newConfig, err := transformer(vc)
	if err != nil {
		return nil, errors.Wrapf(err, "transforming skaffold config")
	}
	return transformToLatest(newConfig, pos+1)
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

import (
	"regexp/syntax"

	"github.com/google/mtail/metrics"
)

type astNode interface {
	Pos() *position // Returns the position of the node from the original source
	Type() Type     // Returns the type of the expression in this node
}

type stmtlistNode struct {
	s        *Scope // Pointer to the local scope for this enclosing block
	children []astNode
}

func (n *stmtlistNode) Pos() *position {
	return mergepositionlist(n.children)
}

func (n *stmtlistNode) Type() Type {
	return None
}

type exprlistNode struct {
	children []astNode
}

func (n *exprlistNode) Pos() *position {
	return mergepositionlist(n.children)
}

func (n *exprlistNode) Type() Type {
	return None
}

type condNode struct {
	cond      astNode
	truthNode astNode
	elseNode  astNode
	s         *Scope // a conditional expression can cause new variables to be defined
}

func (n *condNode) Pos() *position {
	return mergepositionlist([]astNode{n.cond, n.truthNode, n.elseNode})
}

func (n *condNode) Type() Type {
	return None
}

type regexNode struct {
	astNode
	pos     position
	pattern string
	addr    int
	re_ast  *syntax.Regexp
}

func (n *regexNode) Pos() *position {
	return &n.pos
}

func (n *regexNode) Type() Type {
	return None
}

type idNode struct {
	pos  position
	name string
	sym  *Symbol
}

func (n *idNode) Pos() *position {
	return &n.pos
}

func (n *idNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type
	}
	return None // Bugs
}

type caprefNode struct {
	pos     position
	name    string
	isNamed bool // true if the capref is a named reference, not positional
	sym     *Symbol
}

func (n *caprefNode) Pos() *position {
	return &n.pos
}

func (n *caprefNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type
	}
	return None // sym not defined due to undefined capref error
}

type builtinNode struct {
	pos  position
	name string
	args astNode
}

func (n *builtinNode) Pos() *position {
	return &n.pos
}

func (n *builtinNode) Type() Type {
	return Int
}

type binaryExprNode struct {
	lhs, rhs astNode
	op       int
	typ      Type
}

func (n *binaryExprNode) Pos() *position {
	return MergePosition(n.lhs.Pos(), n.rhs.Pos())
}

func (n *binaryExprNode) Type() Type {
	return n.typ
}

type unaryExprNode struct {
	pos  position // pos is the position of the op
	expr astNode
	op   int
	typ  Type
}

func (n *unaryExprNode) Pos() *position {
	return MergePosition(&n.pos, n.expr.Pos())
}

func (n *unaryExprNode) Type() Type {
	return n.typ
}

type indexedExprNode struct {
	lhs, index astNode
}

func (n *indexedExprNode) Pos() *position {
	return MergePosition(n.lhs.Pos(), n.index.Pos())
}

func (n *indexedExprNode) Type() Type {
	return n.lhs.Type()
}

type declNode struct {
	pos          position
	name         string
	hidden       bool
	keys         []string
	kind         metrics.Kind
	exportedName string
	sym          *Symbol
}

func (n *declNode) Pos() *position {
	return &n.pos
}

func (n *declNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type
	}
	return Undef
}

type stringConstNode struct {
	pos  position
	text string
}

func (n *stringConstNode) Pos() *position {
	return &n.pos
}
func (n *stringConstNode) Type() Type {
	return String
}

type intConstNode struct {
	pos position
	i   int64
}

func (n *intConstNode) Pos() *position {
	return &n.pos
}
func (n *intConstNode) Type() Type {
	return Int
}

type floatConstNode struct {
	pos position
	f   float64
}

func (n *floatConstNode) Pos() *position {
	return &n.pos
}
func (n *floatConstNode) Type() Type {
	return Float
}

type defNode struct {
	pos   position
	name  string
	block astNode
	sym   *Symbol
}

func (n *defNode) Pos() *position {
	return MergePosition(&n.pos, n.block.Pos())
}

func (n *defNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type
	}
	return Int
}

type decoNode struct {
	pos   position
	name  string
	block astNode
	def   *defNode
}

func (n *decoNode) Pos() *position {
	return MergePosition(&n.pos, n.block.Pos())
}

func (n *decoNode) Type() Type {
	return None
}

type nextNode struct {
	pos position
}

func (n *nextNode) Pos() *position {
	return &n.pos
}

func (n *nextNode) Type() Type {
	return None
}

type otherwiseNode struct {
	pos position
}

func (n *otherwiseNode) Pos() *position {
	return &n.pos
}

func (n *otherwiseNode) Type() Type {
	return None
}

type delNode struct {
	pos position
	n   astNode
}

func (d *delNode) Pos() *position {
	return &d.pos
}

func (d *delNode) Type() Type {
	return None
}
<commit_msg>Return the root type of objects from their symbols after unification.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

import (
	"regexp/syntax"

	"github.com/google/mtail/metrics"
)

type astNode interface {
	Pos() *position // Returns the position of the node from the original source
	Type() Type     // Returns the type of the expression in this node
}

type stmtlistNode struct {
	s        *Scope // Pointer to the local scope for this enclosing block
	children []astNode
}

func (n *stmtlistNode) Pos() *position {
	return mergepositionlist(n.children)
}

func (n *stmtlistNode) Type() Type {
	return None
}

type exprlistNode struct {
	children []astNode
}

func (n *exprlistNode) Pos() *position {
	return mergepositionlist(n.children)
}

func (n *exprlistNode) Type() Type {
	return None
}

type condNode struct {
	cond      astNode
	truthNode astNode
	elseNode  astNode
	s         *Scope // a conditional expression can cause new variables to be defined
}

func (n *condNode) Pos() *position {
	return mergepositionlist([]astNode{n.cond, n.truthNode, n.elseNode})
}

func (n *condNode) Type() Type {
	return None
}

type regexNode struct {
	astNode
	pos     position
	pattern string
	addr    int
	re_ast  *syntax.Regexp
}

func (n *regexNode) Pos() *position {
	return &n.pos
}

func (n *regexNode) Type() Type {
	return None
}

type idNode struct {
	pos  position
	name string
	sym  *Symbol
}

func (n *idNode) Pos() *position {
	return &n.pos
}

func (n *idNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type.Root()
	}
	return None // Bugs
}

type caprefNode struct {
	pos     position
	name    string
	isNamed bool // true if the capref is a named reference, not positional
	sym     *Symbol
}

func (n *caprefNode) Pos() *position {
	return &n.pos
}

func (n *caprefNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type.Root()
	}
	return None // sym not defined due to undefined capref error
}

type builtinNode struct {
	pos  position
	name string
	args astNode
}

func (n *builtinNode) Pos() *position {
	return &n.pos
}

func (n *builtinNode) Type() Type {
	return Int
}

type binaryExprNode struct {
	lhs, rhs astNode
	op       int
	typ      Type
}

func (n *binaryExprNode) Pos() *position {
	return MergePosition(n.lhs.Pos(), n.rhs.Pos())
}

func (n *binaryExprNode) Type() Type {
	return n.typ
}

type unaryExprNode struct {
	pos  position // pos is the position of the op
	expr astNode
	op   int
	typ  Type
}

func (n *unaryExprNode) Pos() *position {
	return MergePosition(&n.pos, n.expr.Pos())
}

func (n *unaryExprNode) Type() Type {
	return n.typ
}

type indexedExprNode struct {
	lhs, index astNode
}

func (n *indexedExprNode) Pos() *position {
	return MergePosition(n.lhs.Pos(), n.index.Pos())
}

func (n *indexedExprNode) Type() Type {
	return n.lhs.Type()
}

type declNode struct {
	pos          position
	name         string
	hidden       bool
	keys         []string
	kind         metrics.Kind
	exportedName string
	sym          *Symbol
}

func (n *declNode) Pos() *position {
	return &n.pos
}

func (n *declNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type.Root()
	}
	return Undef
}

type stringConstNode struct {
	pos  position
	text string
}

func (n *stringConstNode) Pos() *position {
	return &n.pos
}
func (n *stringConstNode) Type() Type {
	return String
}

type intConstNode struct {
	pos position
	i   int64
}

func (n *intConstNode) Pos() *position {
	return &n.pos
}
func (n *intConstNode) Type() Type {
	return Int
}

type floatConstNode struct {
	pos position
	f   float64
}

func (n *floatConstNode) Pos() *position {
	return &n.pos
}
func (n *floatConstNode) Type() Type {
	return Float
}

type defNode struct {
	pos   position
	name  string
	block astNode
	sym   *Symbol
}

func (n *defNode) Pos() *position {
	return MergePosition(&n.pos, n.block.Pos())
}

func (n *defNode) Type() Type {
	if n.sym != nil {
		return n.sym.Type.Root()
	}
	return Int
}

type decoNode struct {
	pos   position
	name  string
	block astNode
	def   *defNode
}

func (n *decoNode) Pos() *position {
	return MergePosition(&n.pos, n.block.Pos())
}

func (n *decoNode) Type() Type {
	return None
}

type nextNode struct {
	pos position
}

func (n *nextNode) Pos() *position {
	return &n.pos
}

func (n *nextNode) Type() Type {
	return None
}

type otherwiseNode struct {
	pos position
}

func (n *otherwiseNode) Pos() *position {
	return &n.pos
}

func (n *otherwiseNode) Type() Type {
	return None
}

type delNode struct {
	pos position
	n   astNode
}

func (d *delNode) Pos() *position {
	return &d.pos
}

func (d *delNode) Type() Type {
	return None
}
<|endoftext|>