<commit_before>package commands

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	consulapi "github.com/hashicorp/consul/api"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func newAclCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "acl",
		Short: "Consul /acl endpoint interface",
		Long:  "Consul /acl endpoint interface",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.HelpFunc()(cmd, []string{})
		},
	}

	cmd.AddCommand(newAclCloneCommand())
	cmd.AddCommand(newAclCreateCommand())
	cmd.AddCommand(newAclDestroyCommand())
	cmd.AddCommand(newAclInfoCommand())
	cmd.AddCommand(newAclListCommand())
	cmd.AddCommand(newAclReplicationCommand())
	cmd.AddCommand(newAclUpdateCommand())

	return cmd
}

type rulePath struct {
	Policy string
}

type aclRule struct {
	Key     map[string]*rulePath `json:"key,omitempty"`
	Service map[string]*rulePath `json:"service,omitempty"`
	Event   map[string]*rulePath `json:"event,omitempty"`
	Query   map[string]*rulePath `json:"query,omitempty"`
	Keyring string `json:"keyring,omitempty"`
        Operator string `json:"operator,omitempty"`
}

func readRawAcl(raw string) (string, error) {
	if raw == "-" {
		rules, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return "", err
		}
		return string(rules), nil
	}

	rules, err := ioutil.ReadFile(raw)
	if err != nil {
		return "", err
	}

	return string(rules), nil
}

// getPolicy return "read" if the index i is not set in the
// rs array.
func getPolicy(rs []string, i int) string {
	if i >= len(rs) {
		return "read"
	}

	return rs[i]
}

// getPath returns "" if the inde i is not set in the rs array
func getPath(rs []string, i int) string {
	if i >= len(rs) {
		return ""
	}

	return rs[i]
}

// Convert a list of Rules to a JSON string
func getRulesString(rs []string) (string, error) {
	if len(rs) <= 0 {
		return "", errors.New("No ACL rules specified")
	}

	rules := &aclRule{
		Key: make(map[string]*rulePath),
		Service: make(map[string]*rulePath),
		Event: make(map[string]*rulePath),
		Query: make(map[string]*rulePath),
	}

	for _, r := range rs {
		if len(strings.TrimSpace(r)) < 1 {
			return "", errors.New("cannot specify empty rule declaration")
		}

		parts := strings.Split(r, ":")
		switch strings.ToLower(parts[0]) {
		case "operator":
			rules.Operator = getPolicy(parts, 1)
		case "keyring":
			rules.Keyring = getPolicy(parts, 1)
		case "key":
			rules.Key[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "service":
			rules.Service[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "event":
			rules.Event[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "query":
			rules.Query[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		}
	}

	ruleBytes, err := json.Marshal(rules)
	if err != nil {
		return "", err
	}

	return string(ruleBytes), nil
}

// Clone functions

func newAclCloneCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "clone <token>",
		Short: "Create a new token from an existing one",
		Long:  "Create a new token from an existing one",
		RunE:  aclClone,
	}

	return cmd
}

func aclClone(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("A single ACL id must be specified")
	}

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	writeOpts := writeOptions()

	newid, _, err := client.Clone(args[0], writeOpts)
	if err != nil {
		return err
	}

	fmt.Println(newid)

	return nil
}

// Create functions

func newAclCreateCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "create [<id>]",
		Short: "Create an ACL. Requires a management token.",
		Long:  "Create an ACL. Requires a management token.",
		RunE:  aclCreate,
	}

	cmd.Flags().Bool("management", false, "Create a management token")
	cmd.Flags().String("name", "", "Name of the ACL")
	cmd.Flags().StringSlice("rule", nil, "Rule to create. Can be multiple rules on a command line. Format is type:path:policy")
	cmd.Flags().String("raw", "", "Raw ACL rule definition")

	return cmd
}

func aclCreate(cmd *cobra.Command, args []string) error {
	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	entry := new(consulapi.ACLEntry)
	entry.Name = viper.GetString("name")

	switch {
	case len(args) == 1:
		entry.ID = args[0]
	case len(args) > 1:
		return fmt.Errorf("Only one ACL identified can be specified")
	}

	if viper.GetBool("management") {
		entry.Type = consulapi.ACLManagementType
	} else {
		entry.Type = consulapi.ACLClientType
	}

	if raw := viper.GetString("raw"); raw != "" {
		rules, err := readRawAcl(raw)
		if err != nil {
			return err
		}
		entry.Rules = rules
	} else {
		rules, err := getRulesString(viper.GetStringSlice("rule"))
		if err != nil {
			return err
		}
		entry.Rules = rules
	}

	writeOpts := writeOptions()
	id, _, err := client.Create(entry, writeOpts)
	if err != nil {
		return err
	}

	fmt.Println(id)

	return nil
}

// Destroy functions

func newAclDestroyCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "destroy <token>",
		Short: "Destroy an ACL",
		Long:  "Destroy an ACL",
		RunE:  aclDestroy,
	}

	return cmd
}

func aclDestroy(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("A single ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	writeOpts := writeOptions()
	_, err = client.Destroy(id, writeOpts)
	if err != nil {
		return err
	}

	return nil
}

// Info functions

func newAclInfoCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "info <token>",
		Short: "Query information about an ACL token",
		Long:  "Query information about an ACL token",
		RunE:  aclInfo,
	}

	addTemplateOption(cmd)

	return cmd
}

func aclInfo(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("An ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	queryOpts := queryOptions()
	acl, _, err := client.Info(id, queryOpts)
	if err != nil {
		return err
	}

	return output(acl)
}

// List functions

func newAclListCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "list",
		Short: "List all active ACL tokens",
		Long:  "List all active ACL tokens",
		RunE:  aclList,
	}

	addTemplateOption(cmd)

	return cmd
}

func aclList(cmd *cobra.Command, args []string) error {
	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	queryOpts := queryOptions()
	acls, _, err := client.List(queryOpts)
	if err != nil {
		return err
	}

	return output(acls)
}

// Replication functions

func newAclReplicationCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use: "replication",
		Short: "Get the status of the ACL replication process",
		Long: "Get the status of the ACL replication process",
		RunE: aclReplication,
		Hidden: true,
	}

	addDatacenterOption(cmd)
	addTemplateOption(cmd)

	return cmd
}

func aclReplication(cmd *cobra.Command, args []string) error {
	return fmt.Errorf("ACL replication status not available in Consul API")

//	viper.BindPFlags(cmd.Flags())
//
//	client, err := newACL()
//	if err != nil {
//		return err
//	}
}

// Update functions

func newAclUpdateCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "update <token>",
		Short: "Update an ACL. Will be created if it doesn't exist",
		Long:  "Update an ACL. Will be created if it doesn't exist",
		RunE:  aclUpdate,
	}

	cmd.Flags().Bool("management", false, "Create a management token")
	cmd.Flags().String("name", "", "Name of the ACL")
	cmd.Flags().StringSlice("rule", nil, "Rule to update. Can be multiple rules on a command line. Format is type:path:policy")
	cmd.Flags().String("raw", "", "Raw ACL rule definition")

	return cmd
}

func aclUpdate(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("An ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	entry := new(consulapi.ACLEntry)
	entry.Name = viper.GetString("name")
	entry.ID = id

	if viper.GetBool("management") {
		entry.Type = consulapi.ACLManagementType
	} else {
		entry.Type = consulapi.ACLClientType
	}

	if raw := viper.GetString("raw"); raw != "" {
		rules, err := readRawAcl(raw)
		if err != nil {
			return err
		}
		entry.Rules = rules
	} else {
		rules, err := getRulesString(viper.GetStringSlice("rule"))
		if err != nil {
			return err
		}

		entry.Rules = rules
	}

	writeOpts := writeOptions()
	_, err = client.Update(entry, writeOpts)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Remove viper.GetStringSlice() and use internal getStringSlice() function<commit_after>package commands

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	consulapi "github.com/hashicorp/consul/api"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func newAclCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "acl",
		Short: "Consul /acl endpoint interface",
		Long:  "Consul /acl endpoint interface",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.HelpFunc()(cmd, []string{})
		},
	}

	cmd.AddCommand(newAclCloneCommand())
	cmd.AddCommand(newAclCreateCommand())
	cmd.AddCommand(newAclDestroyCommand())
	cmd.AddCommand(newAclInfoCommand())
	cmd.AddCommand(newAclListCommand())
	cmd.AddCommand(newAclReplicationCommand())
	cmd.AddCommand(newAclUpdateCommand())

	return cmd
}

type rulePath struct {
	Policy string
}

type aclRule struct {
	Key     map[string]*rulePath `json:"key,omitempty"`
	Service map[string]*rulePath `json:"service,omitempty"`
	Event   map[string]*rulePath `json:"event,omitempty"`
	Query   map[string]*rulePath `json:"query,omitempty"`
	Keyring string `json:"keyring,omitempty"`
        Operator string `json:"operator,omitempty"`
}

func readRawAcl(raw string) (string, error) {
	if raw == "-" {
		rules, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return "", err
		}
		return string(rules), nil
	}

	rules, err := ioutil.ReadFile(raw)
	if err != nil {
		return "", err
	}

	return string(rules), nil
}

// getPolicy return "read" if the index i is not set in the
// rs array.
func getPolicy(rs []string, i int) string {
	if i >= len(rs) {
		return "read"
	}

	return rs[i]
}

// getPath returns "" if the inde i is not set in the rs array
func getPath(rs []string, i int) string {
	if i >= len(rs) {
		return ""
	}

	return rs[i]
}

// Convert a list of Rules to a JSON string
func getRulesString(rs []string) (string, error) {
	if len(rs) <= 0 {
		return "", errors.New("No ACL rules specified")
	}

	fmt.Println(rs)

	rules := &aclRule{
		Key: make(map[string]*rulePath),
		Service: make(map[string]*rulePath),
		Event: make(map[string]*rulePath),
		Query: make(map[string]*rulePath),
	}

	for _, r := range rs {
		if len(strings.TrimSpace(r)) < 1 {
			return "", errors.New("cannot specify empty rule declaration")
		}

		parts := strings.Split(r, ":")
		switch strings.ToLower(parts[0]) {
		case "operator":
			rules.Operator = getPolicy(parts, 1)
		case "keyring":
			rules.Keyring = getPolicy(parts, 1)
		case "key":
			rules.Key[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "service":
			rules.Service[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "event":
			rules.Event[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		case "query":
			rules.Query[getPath(parts, 1)] = &rulePath{ Policy: getPolicy(parts, 2) }
		}
	}

	ruleBytes, err := json.Marshal(rules)
	if err != nil {
		return "", err
	}

	return string(ruleBytes), nil
}

// Clone functions

func newAclCloneCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "clone <token>",
		Short: "Create a new token from an existing one",
		Long:  "Create a new token from an existing one",
		RunE:  aclClone,
	}

	return cmd
}

func aclClone(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("A single ACL id must be specified")
	}

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	writeOpts := writeOptions()

	newid, _, err := client.Clone(args[0], writeOpts)
	if err != nil {
		return err
	}

	fmt.Println(newid)

	return nil
}

// Create functions

func newAclCreateCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "create [<id>]",
		Short: "Create an ACL. Requires a management token.",
		Long:  "Create an ACL. Requires a management token.",
		RunE:  aclCreate,
	}

	cmd.Flags().Bool("management", false, "Create a management token")
	cmd.Flags().String("name", "", "Name of the ACL")
	cmd.Flags().StringSlice("rule", nil, "Rule to create. Can be multiple rules on a command line. Format is type:path:policy")
	cmd.Flags().String("raw", "", "Raw ACL rule definition")

	return cmd
}

func aclCreate(cmd *cobra.Command, args []string) error {
	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	entry := new(consulapi.ACLEntry)
	entry.Name = viper.GetString("name")

	switch {
	case len(args) == 1:
		entry.ID = args[0]
	case len(args) > 1:
		return fmt.Errorf("Only one ACL identified can be specified")
	}

	if viper.GetBool("management") {
		entry.Type = consulapi.ACLManagementType
	} else {
		entry.Type = consulapi.ACLClientType
	}

	if raw := viper.GetString("raw"); raw != "" {
		rules, err := readRawAcl(raw)
		if err != nil {
			return err
		}
		entry.Rules = rules
	} else {
		rules, err := getRulesString(getStringSlice(cmd, "rule"))
		if err != nil {
			return err
		}
		entry.Rules = rules
	}

	writeOpts := writeOptions()
	id, _, err := client.Create(entry, writeOpts)
	if err != nil {
		return err
	}

	fmt.Println(id)

	return nil
}

// Destroy functions

func newAclDestroyCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "destroy <token>",
		Short: "Destroy an ACL",
		Long:  "Destroy an ACL",
		RunE:  aclDestroy,
	}

	return cmd
}

func aclDestroy(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("A single ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	writeOpts := writeOptions()
	_, err = client.Destroy(id, writeOpts)
	if err != nil {
		return err
	}

	return nil
}

// Info functions

func newAclInfoCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "info <token>",
		Short: "Query information about an ACL token",
		Long:  "Query information about an ACL token",
		RunE:  aclInfo,
	}

	addTemplateOption(cmd)

	return cmd
}

func aclInfo(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("An ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	queryOpts := queryOptions()
	acl, _, err := client.Info(id, queryOpts)
	if err != nil {
		return err
	}

	return output(acl)
}

// List functions

func newAclListCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "list",
		Short: "List all active ACL tokens",
		Long:  "List all active ACL tokens",
		RunE:  aclList,
	}

	addTemplateOption(cmd)

	return cmd
}

func aclList(cmd *cobra.Command, args []string) error {
	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	queryOpts := queryOptions()
	acls, _, err := client.List(queryOpts)
	if err != nil {
		return err
	}

	return output(acls)
}

// Replication functions

func newAclReplicationCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use: "replication",
		Short: "Get the status of the ACL replication process",
		Long: "Get the status of the ACL replication process",
		RunE: aclReplication,
		Hidden: true,
	}

	addDatacenterOption(cmd)
	addTemplateOption(cmd)

	return cmd
}

func aclReplication(cmd *cobra.Command, args []string) error {
	return fmt.Errorf("ACL replication status not available in Consul API")

//	viper.BindPFlags(cmd.Flags())
//
//	client, err := newACL()
//	if err != nil {
//		return err
//	}
}

// Update functions

func newAclUpdateCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "update <token>",
		Short: "Update an ACL. Will be created if it doesn't exist",
		Long:  "Update an ACL. Will be created if it doesn't exist",
		RunE:  aclUpdate,
	}

	cmd.Flags().Bool("management", false, "Create a management token")
	cmd.Flags().String("name", "", "Name of the ACL")
	cmd.Flags().StringSlice("rule", nil, "Rule to update. Can be multiple rules on a command line. Format is type:path:policy")
	cmd.Flags().String("raw", "", "Raw ACL rule definition")

	return cmd
}

func aclUpdate(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("An ACL id must be specified")
	}
	id := args[0]

	viper.BindPFlags(cmd.Flags())

	client, err := newACL()
	if err != nil {
		return err
	}

	entry := new(consulapi.ACLEntry)
	entry.Name = viper.GetString("name")
	entry.ID = id

	if viper.GetBool("management") {
		entry.Type = consulapi.ACLManagementType
	} else {
		entry.Type = consulapi.ACLClientType
	}

	if raw := viper.GetString("raw"); raw != "" {
		rules, err := readRawAcl(raw)
		if err != nil {
			return err
		}
		entry.Rules = rules
	} else {
		rules, err := getRulesString(getStringSlice(cmd, "rule"))
		if err != nil {
			return err
		}
		entry.Rules = rules
	}

	writeOpts := writeOptions()
	_, err = client.Update(entry, writeOpts)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package ires

import (
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/goware/urlx"
)

// Input image type is Local or HTTP
func IsLocalFile(path string) bool {
	if strings.Index(path, "http") == -1 {
		return true
	} else {
		return false
	}
}

// Generate image name
func ImageName(i *Ires, mode int) string {
	splitPath 	:= strings.Split(i.Uri, "/")

	// ex. sample.jpg
	fileName := splitPath[len(splitPath)-1]
	// ex. .jpg
	ext := filepath.Ext(fileName)

	name := strings.Replace(fileName, ext, "", 1)

	extInfo := strings.Split(ext, "?")
	if len(extInfo) > 1 {
		ext = extInfo[0]
		name += "_" + strings.Join(extInfo[1:], "")
	}

	var prefix string
	if mode == 3 {
		prefix = "original"
	} else {
		prefix = PrefixSize(i.Size)
	}

	return i.Expire + "_" + name + "_" + prefix + ext
}

// Generate image path
func (i *Ires) ImagePath(mode int) string {
	paths := []rune(i.Dir)
	pathsLastIndex := len(paths) - 1
	lastChar := string(paths[pathsLastIndex])
	dir := i.Dir
	if lastChar == "/" {
		dir = string(paths[:pathsLastIndex])
	}

	var oDir string
	if i.IsLocal {
		oDir = LocalPath(mode)
	} else {
		oDir = RemotePath(i)
	}

	// Create directory
	oPath := filepath.Join(dir, oDir)
	if _, err := os.Stat(oPath); err != nil {
		if err := os.MkdirAll(oPath, 0777); err != nil {
			panic(err)
		}
	}

	name := ImageName(i, mode)
	return filepath.Join(oPath, name)
}

// Create prefix by size
// ex. 640x480
func PrefixSize(s Size) string {
	return strconv.Itoa(s.Width) + "x" + strconv.Itoa(s.Height)
}

// リサイズ済みのファイルがあれば、処理せず返す
func IsExistsImage(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	} else {
		return false
	}
}

// Read directory
func (i *Ires) ReadImageDir(mode int) string {
	var dir string
	if i.IsLocal {
		dir = LocalPath(mode)
	} else {
		dir = RemotePath(i)
	}
	return filepath.Join(i.Dir, dir)
}

// if local image, create ires directory
func LocalPath(mode int) string {
	var dir string
	switch mode {
	case 0: dir = "ires/resize"
	case 1: dir = "ires/crop"
	case 2: dir = "ires/resize_to_crop"
	case 3: dir = "ires/original"
	}
	return dir
}

// if http image, parse URL & make directory
func RemotePath(i *Ires) string {
	u, err := urlx.Parse(i.Uri)
	dir := []string{"ires"}
	if err != nil {
		panic(err)
	}

	dir = append(dir, u.Host)
	path := strings.Split(u.Path, "/")
	dir = append(dir, path[1:len(path)-1]...)

	return strings.Join(dir, "/")
}

// Optimize image path
func (i *Ires) TargetImagePath(path string) string {
	return strings.Replace(path, i.Dir, "", -1)
}
<commit_msg>各画像編集ごとにパスのプレフィックスを修正<commit_after>package ires

import (
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/goware/urlx"
)

// Input image type is Local or HTTP
func IsLocalFile(path string) bool {
	if strings.Index(path, "http") == -1 {
		return true
	} else {
		return false
	}
}

// Generate image name
func ImageName(i *Ires, mode int) string {
	splitPath 	:= strings.Split(i.Uri, "/")

	// ex. sample.jpg
	fileName := splitPath[len(splitPath)-1]
	// ex. .jpg
	ext := filepath.Ext(fileName)

	name := strings.Replace(fileName, ext, "", 1)

	extInfo := strings.Split(ext, "?")
	if len(extInfo) > 1 {
		ext = extInfo[0]
		name += "_" + strings.Join(extInfo[1:], "")
	}

	var prefix string
	switch mode {
	case 0: prefix = PrefixSize(i.Size) + "_resize"
	case 1: prefix = PrefixSize(i.Size) + "_crop"
	case 2: prefix = PrefixSize(i.Size) + "_resize_to_crop"
	case 3: prefix = "original"
	}

	return i.Expire + "_" + name + "_" + prefix + ext
}

// Generate image path
func (i *Ires) ImagePath(mode int) string {
	paths := []rune(i.Dir)
	pathsLastIndex := len(paths) - 1
	lastChar := string(paths[pathsLastIndex])
	dir := i.Dir
	if lastChar == "/" {
		dir = string(paths[:pathsLastIndex])
	}

	var oDir string
	if i.IsLocal {
		oDir = LocalPath(mode)
	} else {
		oDir = RemotePath(i)
	}

	// Create directory
	oPath := filepath.Join(dir, oDir)
	if _, err := os.Stat(oPath); err != nil {
		if err := os.MkdirAll(oPath, 0777); err != nil {
			panic(err)
		}
	}

	name := ImageName(i, mode)
	return filepath.Join(oPath, name)
}

// Create prefix by size
// ex. 640x480
func PrefixSize(s Size) string {
	return strconv.Itoa(s.Width) + "x" + strconv.Itoa(s.Height)
}

// リサイズ済みのファイルがあれば、処理せず返す
func IsExistsImage(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	} else {
		return false
	}
}

// Read directory
func (i *Ires) ReadImageDir(mode int) string {
	var dir string
	if i.IsLocal {
		dir = LocalPath(mode)
	} else {
		dir = RemotePath(i)
	}
	return filepath.Join(i.Dir, dir)
}

// if local image, create ires directory
func LocalPath(mode int) string {
	var dir string
	switch mode {
	case 0: dir = "ires/resize"
	case 1: dir = "ires/crop"
	case 2: dir = "ires/resize_to_crop"
	case 3: dir = "ires/original"
	}
	return dir
}

// if http image, parse URL & make directory
func RemotePath(i *Ires) string {
	u, err := urlx.Parse(i.Uri)
	dir := []string{"ires"}
	if err != nil {
		panic(err)
	}

	dir = append(dir, u.Host)
	path := strings.Split(u.Path, "/")
	dir = append(dir, path[1:len(path)-1]...)

	return strings.Join(dir, "/")
}

// Optimize image path
func (i *Ires) TargetImagePath(path string) string {
	return strings.Replace(path, i.Dir, "", -1)
}
<|endoftext|>


<commit_before>package workflow

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/fsamin/go-dump"
	"github.com/go-gorp/gorp"

	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/engine/api/repositoriesmanager"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/interpolate"
	"github.com/ovh/cds/sdk/log"
)

func getNodeJobRunParameters(db gorp.SqlExecutor, j sdk.Job, run *sdk.WorkflowNodeRun, stage *sdk.Stage) ([]sdk.Parameter, *sdk.MultiError) {
	params := run.BuildParameters
	tmp := map[string]string{}

	tmp["cds.stage"] = stage.Name
	tmp["cds.job"] = j.Action.Name
	errm := &sdk.MultiError{}

	for k, v := range tmp {
		s, err := interpolate.Do(v, tmp)
		if err != nil {
			errm.Append(err)
			continue
		}
		sdk.AddParameter(&params, k, sdk.StringParameter, s)
	}

	if errm.IsEmpty() {
		return params, nil
	}
	return params, errm
}

// GetNodeBuildParameters returns build parameters with default values for cds.version, cds.run, cds.run.number, cds.run.subnumber
func GetNodeBuildParameters(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowNode, pipelineParameters []sdk.Parameter, payload interface{}) ([]sdk.Parameter, error) {
	vars := map[string]string{}
	tmpProj := sdk.ParametersFromProjectVariables(proj)
	for k, v := range tmpProj {
		vars[k] = v
	}

	// compute application variables
	if n.Context != nil && n.Context.Application != nil {
		vars["cds.application"] = n.Context.Application.Name
		tmp := sdk.ParametersFromApplicationVariables(n.Context.Application)
		for k, v := range tmp {
			vars[k] = v
		}

		// Get GitUrl
		projectVCSServer := repositoriesmanager.GetProjectVCSServer(proj, n.Context.Application.VCSServer)
		if projectVCSServer != nil {
			client, errclient := repositoriesmanager.AuthorizedClient(db, store, projectVCSServer)
			if errclient != nil {
				return nil, sdk.WrapError(errclient, "GetNodeBuildParameters> Cannot connect get repository manager client")
			}
			r, errR := client.RepoByFullname(n.Context.Application.RepositoryFullname)
			if errR != nil {
				return nil, sdk.WrapError(errR, "GetNodeBuildParameters> Cannot get git.url")
			}
			vars["git.url"] = r.SSHCloneURL
			vars["git.http_url"] = r.HTTPCloneURL

			branches, errB := client.Branches(r.Fullname)
			if errB != nil {
				return nil, sdk.WrapError(errB, "GetNodeBuildParameters> Cannot get branches on %s, app:%s", r.SSHCloneURL, n.Context.Application.Name)
			}
			for _, b := range branches {
				if b.Default {
					vars["git.default_branch"] = b.DisplayID
					break
				}
			}
		}
	}

	// compute environment variables
	if n.Context != nil && n.Context.Environment != nil {
		vars["cds.environment"] = n.Context.Environment.Name
		tmp := sdk.ParametersFromEnvironmentVariables(n.Context.Environment)
		for k, v := range tmp {
			vars[k] = v
		}
	}

	// compute pipeline parameters
	tmpPip := sdk.ParametersFromPipelineParameters(pipelineParameters)
	for k, v := range tmpPip {
		vars[k] = v
	}

	// compute payload
	e := dump.NewDefaultEncoder(new(bytes.Buffer))
	e.Formatters = []dump.KeyFormatterFunc{dump.WithDefaultLowerCaseFormatter()}
	e.ExtraFields.DetailedMap = false
	e.ExtraFields.DetailedStruct = false
	e.ExtraFields.Len = false
	e.ExtraFields.Type = false
	tmpVars, errdump := e.ToStringMap(payload)
	if errdump != nil {
		log.Error("GetNodeBuildParameters> do-dump error: %v", errdump)
	}

	//Merge the dumped payload with vars
	vars = sdk.ParametersMapMerge(vars, tmpVars)

	log.Debug("GetNodeBuildParameters> compute payload :%#v", payload)

	// TODO Update suggest.go  with new variable

	vars["cds.project"] = w.ProjectKey
	vars["cds.workflow"] = w.Name
	vars["cds.pipeline"] = n.Pipeline.Name

	if n.Context != nil && n.Context.Application != nil && n.Context.Application.RepositoryStrategy.ConnectionType != "" {
		vars["git.connection.type"] = n.Context.Application.RepositoryStrategy.ConnectionType
		if n.Context.Application.RepositoryStrategy.SSHKey != "" {
			vars["git.ssh.key"] = n.Context.Application.RepositoryStrategy.SSHKey
		}
		if n.Context.Application.RepositoryStrategy.PGPKey != "" {
			vars["git.pgp.key"] = n.Context.Application.RepositoryStrategy.PGPKey
		}
		if n.Context.Application.RepositoryStrategy.User != "" {
			vars["git.http.user"] = n.Context.Application.RepositoryStrategy.User
		}

		if _, ok := vars["git.branch"]; !ok && n.Context.Application.RepositoryStrategy.Branch != "" {
			vars["git.branch"] = n.Context.Application.RepositoryStrategy.Branch
		}
		if _, ok := vars["git.default_branch"]; !ok && n.Context.Application.RepositoryStrategy.DefaultBranch != "" {
			vars["git.default_branch"] = n.Context.Application.RepositoryStrategy.Branch
		}
	} else {
		// remove vcs strategy variable
		delete(vars, "git.ssh.key")
		delete(vars, "git.pgp.key")
		delete(vars, "git.http.user")
	}

	params := []sdk.Parameter{}
	for k, v := range vars {
		sdk.AddParameter(&params, k, sdk.StringParameter, v)
	}

	return params, errdump
}

func getParentParameters(db gorp.SqlExecutor, run *sdk.WorkflowNodeRun, nodeRunIds []int64, payload map[string]string) ([]sdk.Parameter, error) {
	//Load workflow run
	w, err := LoadRunByID(db, run.WorkflowRunID, false)
	if err != nil {
		return nil, sdk.WrapError(err, "getParentParameters> Unable to load workflow run")
	}

	params := []sdk.Parameter{}
	for _, nodeRunID := range nodeRunIds {
		parentNodeRun, errNR := LoadNodeRunByID(db, nodeRunID, false)
		if errNR != nil {
			return nil, sdk.WrapError(errNR, "getParentParameters> Cannot get parent node run")
		}

		node := w.Workflow.GetNode(parentNodeRun.WorkflowNodeID)
		if node == nil {
			return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", parentNodeRun.WorkflowNodeID), "getParentParameters>")
		}

		for i := range parentNodeRun.BuildParameters {
			p := &parentNodeRun.BuildParameters[i]

			if p.Name == "" || p.Name == "cds.semver" || p.Name == "cds.release.version" ||
				strings.HasPrefix(p.Name, "cds.proj") || strings.HasPrefix(p.Name, "workflow.") ||
				strings.HasPrefix(p.Name, "cds.version") || strings.HasPrefix(p.Name, "cds.run.number") ||
				strings.HasPrefix(p.Name, "cds.workflow") || strings.HasPrefix(p.Name, "job.requirement") {
				continue
			}

			// Do not duplicate variable from payload
			if _, ok := payload[p.Name]; ok {
				continue
			}

			prefix := "workflow." + node.Name + "."
			if strings.HasPrefix(p.Name, "cds.") {
				p.Name = strings.Replace(p.Name, "cds.", prefix, 1)
			} else {
				p.Name = prefix + p.Name
			}
		}
		params = append(params, parentNodeRun.BuildParameters...)
	}
	return params, nil
}

func getNodeRunBuildParameters(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, run *sdk.WorkflowNodeRun) ([]sdk.Parameter, error) {
	//Load workflow run
	w, err := LoadRunByID(db, run.WorkflowRunID, false)
	if err != nil {
		return nil, sdk.WrapError(err, "getNodeRunParameters> Unable to load workflow run")
	}

	//Load node definition
	n := w.Workflow.GetNode(run.WorkflowNodeID)
	if n == nil {
		return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", run.WorkflowNodeID), "getNodeRunParameters>")
	}

	//Get node build parameters
	errm := &sdk.MultiError{}
	params, errparam := GetNodeBuildParameters(db, store, proj, &w.Workflow, n, run.PipelineParameters, run.Payload)
	if errparam != nil {
		err, ok := errparam.(*sdk.MultiError)
		if ok {
			errm = err
		} else {
			return nil, sdk.WrapError(err, "getNodeRunParameters> Unable to compute node build parameters")
		}
	}

	//override default parameters value
	tmp := sdk.ParametersToMap(params)
	tmp["cds.version"] = fmt.Sprintf("%d", run.Number)
	tmp["cds.run"] = fmt.Sprintf("%d.%d", run.Number, run.SubNumber)
	tmp["cds.run.number"] = fmt.Sprintf("%d", run.Number)
	tmp["cds.run.subnumber"] = fmt.Sprintf("%d", run.SubNumber)

	params = []sdk.Parameter{}
	for k, v := range tmp {
		s, err := interpolate.Do(v, tmp)
		if err != nil {
			errm.Append(err)
			continue
		}
		sdk.AddParameter(&params, k, sdk.StringParameter, s)
	}

	if errm.IsEmpty() {
		return params, nil
	}

	return params, errm
}
<commit_msg>fix(api): getNodeRunBuildParameters multiError (#2446)<commit_after>package workflow

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/fsamin/go-dump"
	"github.com/go-gorp/gorp"

	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/engine/api/repositoriesmanager"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/interpolate"
	"github.com/ovh/cds/sdk/log"
)

func getNodeJobRunParameters(db gorp.SqlExecutor, j sdk.Job, run *sdk.WorkflowNodeRun, stage *sdk.Stage) ([]sdk.Parameter, *sdk.MultiError) {
	params := run.BuildParameters
	tmp := map[string]string{}

	tmp["cds.stage"] = stage.Name
	tmp["cds.job"] = j.Action.Name
	errm := &sdk.MultiError{}

	for k, v := range tmp {
		s, err := interpolate.Do(v, tmp)
		if err != nil {
			errm.Append(err)
			continue
		}
		sdk.AddParameter(&params, k, sdk.StringParameter, s)
	}

	if errm.IsEmpty() {
		return params, nil
	}
	return params, errm
}

// GetNodeBuildParameters returns build parameters with default values for cds.version, cds.run, cds.run.number, cds.run.subnumber
func GetNodeBuildParameters(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowNode, pipelineParameters []sdk.Parameter, payload interface{}) ([]sdk.Parameter, error) {
	vars := map[string]string{}
	tmpProj := sdk.ParametersFromProjectVariables(proj)
	for k, v := range tmpProj {
		vars[k] = v
	}

	// compute application variables
	if n.Context != nil && n.Context.Application != nil {
		vars["cds.application"] = n.Context.Application.Name
		tmp := sdk.ParametersFromApplicationVariables(n.Context.Application)
		for k, v := range tmp {
			vars[k] = v
		}

		// Get GitUrl
		projectVCSServer := repositoriesmanager.GetProjectVCSServer(proj, n.Context.Application.VCSServer)
		if projectVCSServer != nil {
			client, errclient := repositoriesmanager.AuthorizedClient(db, store, projectVCSServer)
			if errclient != nil {
				return nil, sdk.WrapError(errclient, "GetNodeBuildParameters> Cannot connect get repository manager client")
			}
			r, errR := client.RepoByFullname(n.Context.Application.RepositoryFullname)
			if errR != nil {
				return nil, sdk.WrapError(errR, "GetNodeBuildParameters> Cannot get git.url")
			}
			vars["git.url"] = r.SSHCloneURL
			vars["git.http_url"] = r.HTTPCloneURL

			branches, errB := client.Branches(r.Fullname)
			if errB != nil {
				return nil, sdk.WrapError(errB, "GetNodeBuildParameters> Cannot get branches on %s, app:%s", r.SSHCloneURL, n.Context.Application.Name)
			}
			for _, b := range branches {
				if b.Default {
					vars["git.default_branch"] = b.DisplayID
					break
				}
			}
		}
	}

	// compute environment variables
	if n.Context != nil && n.Context.Environment != nil {
		vars["cds.environment"] = n.Context.Environment.Name
		tmp := sdk.ParametersFromEnvironmentVariables(n.Context.Environment)
		for k, v := range tmp {
			vars[k] = v
		}
	}

	// compute pipeline parameters
	tmpPip := sdk.ParametersFromPipelineParameters(pipelineParameters)
	for k, v := range tmpPip {
		vars[k] = v
	}

	// compute payload
	e := dump.NewDefaultEncoder(new(bytes.Buffer))
	e.Formatters = []dump.KeyFormatterFunc{dump.WithDefaultLowerCaseFormatter()}
	e.ExtraFields.DetailedMap = false
	e.ExtraFields.DetailedStruct = false
	e.ExtraFields.Len = false
	e.ExtraFields.Type = false
	tmpVars, errdump := e.ToStringMap(payload)
	if errdump != nil {
		return nil, sdk.WrapError(errdump, "GetNodeBuildParameters> do-dump error")
	}

	//Merge the dumped payload with vars
	vars = sdk.ParametersMapMerge(vars, tmpVars)

	log.Debug("GetNodeBuildParameters> compute payload :%#v", payload)

	// TODO Update suggest.go  with new variable

	vars["cds.project"] = w.ProjectKey
	vars["cds.workflow"] = w.Name
	vars["cds.pipeline"] = n.Pipeline.Name

	if n.Context != nil && n.Context.Application != nil && n.Context.Application.RepositoryStrategy.ConnectionType != "" {
		vars["git.connection.type"] = n.Context.Application.RepositoryStrategy.ConnectionType
		if n.Context.Application.RepositoryStrategy.SSHKey != "" {
			vars["git.ssh.key"] = n.Context.Application.RepositoryStrategy.SSHKey
		}
		if n.Context.Application.RepositoryStrategy.PGPKey != "" {
			vars["git.pgp.key"] = n.Context.Application.RepositoryStrategy.PGPKey
		}
		if n.Context.Application.RepositoryStrategy.User != "" {
			vars["git.http.user"] = n.Context.Application.RepositoryStrategy.User
		}

		if _, ok := vars["git.branch"]; !ok && n.Context.Application.RepositoryStrategy.Branch != "" {
			vars["git.branch"] = n.Context.Application.RepositoryStrategy.Branch
		}
		if _, ok := vars["git.default_branch"]; !ok && n.Context.Application.RepositoryStrategy.DefaultBranch != "" {
			vars["git.default_branch"] = n.Context.Application.RepositoryStrategy.Branch
		}
	} else {
		// remove vcs strategy variable
		delete(vars, "git.ssh.key")
		delete(vars, "git.pgp.key")
		delete(vars, "git.http.user")
	}

	params := []sdk.Parameter{}
	for k, v := range vars {
		sdk.AddParameter(&params, k, sdk.StringParameter, v)
	}

	return params, nil
}

func getParentParameters(db gorp.SqlExecutor, run *sdk.WorkflowNodeRun, nodeRunIds []int64, payload map[string]string) ([]sdk.Parameter, error) {
	//Load workflow run
	w, err := LoadRunByID(db, run.WorkflowRunID, false)
	if err != nil {
		return nil, sdk.WrapError(err, "getParentParameters> Unable to load workflow run")
	}

	params := []sdk.Parameter{}
	for _, nodeRunID := range nodeRunIds {
		parentNodeRun, errNR := LoadNodeRunByID(db, nodeRunID, false)
		if errNR != nil {
			return nil, sdk.WrapError(errNR, "getParentParameters> Cannot get parent node run")
		}

		node := w.Workflow.GetNode(parentNodeRun.WorkflowNodeID)
		if node == nil {
			return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", parentNodeRun.WorkflowNodeID), "getParentParameters>")
		}

		for i := range parentNodeRun.BuildParameters {
			p := &parentNodeRun.BuildParameters[i]

			if p.Name == "" || p.Name == "cds.semver" || p.Name == "cds.release.version" ||
				strings.HasPrefix(p.Name, "cds.proj") || strings.HasPrefix(p.Name, "workflow.") ||
				strings.HasPrefix(p.Name, "cds.version") || strings.HasPrefix(p.Name, "cds.run.number") ||
				strings.HasPrefix(p.Name, "cds.workflow") || strings.HasPrefix(p.Name, "job.requirement") {
				continue
			}

			// Do not duplicate variable from payload
			if _, ok := payload[p.Name]; ok {
				continue
			}

			prefix := "workflow." + node.Name + "."
			if strings.HasPrefix(p.Name, "cds.") {
				p.Name = strings.Replace(p.Name, "cds.", prefix, 1)
			} else {
				p.Name = prefix + p.Name
			}
		}
		params = append(params, parentNodeRun.BuildParameters...)
	}
	return params, nil
}

func getNodeRunBuildParameters(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, run *sdk.WorkflowNodeRun) ([]sdk.Parameter, error) {
	//Load workflow run
	w, err := LoadRunByID(db, run.WorkflowRunID, false)
	if err != nil {
		return nil, sdk.WrapError(err, "getNodeRunParameters> Unable to load workflow run")
	}

	//Load node definition
	n := w.Workflow.GetNode(run.WorkflowNodeID)
	if n == nil {
		return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", run.WorkflowNodeID), "getNodeRunParameters>")
	}

	//Get node build parameters
	params, errparam := GetNodeBuildParameters(db, store, proj, &w.Workflow, n, run.PipelineParameters, run.Payload)
	if errparam != nil {
		return nil, sdk.WrapError(err, "getNodeRunParameters> Unable to compute node build parameters")
	}

	errm := &sdk.MultiError{}
	//override default parameters value
	tmp := sdk.ParametersToMap(params)
	tmp["cds.version"] = fmt.Sprintf("%d", run.Number)
	tmp["cds.run"] = fmt.Sprintf("%d.%d", run.Number, run.SubNumber)
	tmp["cds.run.number"] = fmt.Sprintf("%d", run.Number)
	tmp["cds.run.subnumber"] = fmt.Sprintf("%d", run.SubNumber)

	params = []sdk.Parameter{}
	for k, v := range tmp {
		s, err := interpolate.Do(v, tmp)
		if err != nil {
			errm.Append(err)
			continue
		}
		sdk.AddParameter(&params, k, sdk.StringParameter, s)
	}

	if errm.IsEmpty() {
		return params, nil
	}

	return params, errm
}
<|endoftext|>


<commit_before>package sirius

import (
	"strings"
)

type Replacer struct{}

var words = map[string]string{
	"overwatch": "abovelook",
	"cancer": "Does this dress make me look fat?",
}

func (r *Replacer) Run(m Message) (error, MessageAction) {
	edit := TextEdit()

	for s, r := range words {
		edit.Substitute(strings.ToLower(s), r)
	}

	return nil, edit
}
<commit_msg>Improve variable naming<commit_after>package sirius

import (
	"strings"
)

type Replacer struct{}

var phrases = map[string]string{
	"overwatch": "abovelook",
	"cancer":    "Does this dress make me look fat?",
}

func (r *Replacer) Run(m Message) (error, MessageAction) {
	edit := TextEdit()

	for search, replace := range phrases {
		edit.Substitute(strings.ToLower(search), replace)
	}

	return nil, edit
}
<|endoftext|>


<commit_before>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* 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 template

import (
	dyn "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic"
	"github.com/OpenNebula/one/src/oca/go/src/goca/schemas/shared"
)

// Pool represents an OpenNebula Template pool
type Pool struct {
	Templates []Template `xml:"VMTEMPLATE"`
}

// Template represents an OpenNebula Template
type Template struct {
	ID          int                 `xml:"ID"`
	UID         int                 `xml:"UID"`
	GID         int                 `xml:"GID"`
	UName       string              `xml:"UNAME"`
	GName       string              `xml:"GNAME"`
	Name        string              `xml:"NAME"`
	LockInfos   *shared.Lock        `xml:"LOCK"`
	Permissions *shared.Permissions `xml:"PERMISSIONS"`
	RegTime     int                 `xml:"REGTIME"`
	Template    TemplateTpl         `xml:"TEMPLATE"`
}

// Template represent the template part of the OpenNebula Template
type TemplateTpl struct {
	CPU        float64                `xml:"CPU"`
	Memory     int                    `xml:"MEMORY"`
	Context    *Context               `xml:"CONTEXT"`
	Disk       []Disk                 `xml:"DISK"`
	Graphics   *Graphics              `xml:"GRAPHICS"`
	NICDefault *NicDefault            `xml:"NIC_DEFAULT"`
	OS         *OS                    `xml:"OS"`
	UserInputs UserInputs             `xml:"USER_INPUTS"`
	Dynamic    dyn.UnmatchedTagsSlice `xml:",any"`
}

type Context struct {
	Dynamic dyn.UnmatchedTagsMap `xml:",any"`
}

type Disk struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type Graphics struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type UserInputs struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type NicDefault struct {
	Model string `xml:"MODEL"`
}

type OS struct {
	Arch string `xml:"ARCH"`
	Boot string `xml:"BOOT"`
}
<commit_msg>B #3520: Add NIC field to Template schema in GOCA (#3521)<commit_after>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* 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 template

import (
	dyn "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic"
	"github.com/OpenNebula/one/src/oca/go/src/goca/schemas/shared"
)

// Pool represents an OpenNebula Template pool
type Pool struct {
	Templates []Template `xml:"VMTEMPLATE"`
}

// Template represents an OpenNebula Template
type Template struct {
	ID          int                 `xml:"ID"`
	UID         int                 `xml:"UID"`
	GID         int                 `xml:"GID"`
	UName       string              `xml:"UNAME"`
	GName       string              `xml:"GNAME"`
	Name        string              `xml:"NAME"`
	LockInfos   *shared.Lock        `xml:"LOCK"`
	Permissions *shared.Permissions `xml:"PERMISSIONS"`
	RegTime     int                 `xml:"REGTIME"`
	Template    TemplateTpl         `xml:"TEMPLATE"`
}

// Template represent the template part of the OpenNebula Template
type TemplateTpl struct {
	CPU        float64                `xml:"CPU"`
	Memory     int                    `xml:"MEMORY"`
	Context    *Context               `xml:"CONTEXT"`
	Disk       []Disk                 `xml:"DISK"`
	NIC        []NIC                  `xml:"NIC"`
	Graphics   *Graphics              `xml:"GRAPHICS"`
	NICDefault *NicDefault            `xml:"NIC_DEFAULT"`
	OS         *OS                    `xml:"OS"`
	UserInputs UserInputs             `xml:"USER_INPUTS"`
	Dynamic    dyn.UnmatchedTagsSlice `xml:",any"`
}

type Context struct {
	Dynamic dyn.UnmatchedTagsMap `xml:",any"`
}

type Disk struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type NIC struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type Graphics struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type UserInputs struct {
	Dynamic dyn.UnmatchedTagsSlice `xml:",any"`
}

type NicDefault struct {
	Model string `xml:"MODEL"`
}

type OS struct {
	Arch string `xml:"ARCH"`
	Boot string `xml:"BOOT"`
}
<|endoftext|>


<commit_before>package db

import (
    "errors"
    "fmt"
    "strconv"
)

type Job struct {
    Id        int    `json:"job_id"`
    Name      string `json:"name"`
    Timeout   int    `json:"timeout"`
    SchedAt   int    `json:"sched_at"`
    Status    string `json:"status"`
}

func (job *Job) Save() (err error) {
    var tableName = GetTableName(*job)
    var key string
    if job.Id > 0 {
        var old Job
        key = tableName + ":" + strconv.Itoa(job.Id)
        err = GetObject(key, &old)
        if err != nil || old.Id < 1 {
            err = errors.New(fmt.Sprintf("Update Job %d fail, the old job is not exists.", job.Id))
            return
        }
        if old.Name != job.Name {
            DelIndex(tableName + ":name", old.Name)
        }
        if old.Status != job.Status {
            DelIndex(tableName + ":" + old.Status + ":sched", strconv.Itoa(job.Id))
        }
    } else {
        job.Id, err = NextSequence(tableName)
        if err != nil {
            return
        }
    }
    idx, _ := GetIndex(tableName + ":name", job.Name)
    if idx > 0 && idx != job.Id {
        err = errors.New("Duplicate Job name: " + job.Name)
        return
    }
    key = tableName + ":" + strconv.Itoa(job.Id)
    err = SetObject(key, job)
    if err == nil {
        AddIndex(tableName, strconv.Itoa(job.Id), job.Id)
        AddIndex(tableName + ":" + job.Status + ":sched", strconv.Itoa(job.Id), job.SchedAt)
        AddIndex(tableName + ":name", job.Name, job.Id)
    }
    return
}

func (job *Job) Delete() (err error) {
    var tableName = GetTableName(*job)
    var key = tableName + ":" + strconv.Itoa(job.Id)
    err = DelObject(key)
    DelIndex(tableName, strconv.Itoa(job.Id))
    DelIndex(tableName + ":" + job.Status + ":sched", strconv.Itoa(job.Id))
    DelIndex(tableName + ":name", job.Name)
    return
}

func GetJob(id int) (job Job, err error) {
    var tableName = GetTableName(job)
    var key = tableName + ":" +  strconv.Itoa(id)
    err = GetObject(key, &job)
    return
}

func DelJob(id int) (err error) {
    var job Job
    job, err = GetJob(id)
    if err != nil {
        return err
    }
    err = job.Delete()
    return err
}

func CountJob() (count int, err error) {
    var tableName = GetTableName(Job{})
    count, err = CountIndex(tableName)
    return
}

func RangeJob(start, stop int, rev ...bool) (jobs []Job, err error) {
    var tableName = GetTableName(Job{})
    var idxs []Index
    idxs, err = RangeIndex(tableName, start, stop, rev...)
    jobs = make([]Job, len(idxs))

    for k, idx := range idxs {
        job, _ :=  GetJob(idx.Score)
        job.Id = idx.Score
        jobs[k] = job
    }
    return
}

func RangeSchedJob(status string, start, stop int) (jobs []Job, err error) {
    var tableName = GetTableName(Job{})
    var idxs []Index
    idxs, err = RangeIndex(tableName + ":" + status + ":sched", start, stop)
    jobs = make([]Job, len(idxs))

    for k, idx := range idxs {
        jobId, _ := strconv.Atoi(idx.Name)
        job, _ :=  GetJob(jobId)
        job.Id = jobId
        job.Status = status
        jobs[k] = job
    }
    return
}

func CountSchedJob(status string) (count int, err error) {
    var tableName = GetTableName(Job{})
    count, err = CountIndex(tableName + ":" + status + ":sched")
    return
}
<commit_msg>Add some log<commit_after>package db

import (
    "log"
    "fmt"
    "errors"
    "strconv"
)

type Job struct {
    Id        int    `json:"job_id"`
    Name      string `json:"name"`
    Timeout   int    `json:"timeout"`
    SchedAt   int    `json:"sched_at"`
    Status    string `json:"status"`
}

func (job *Job) Save() (err error) {
    var tableName = GetTableName(*job)
    var key string
    if job.Id > 0 {
        var old Job
        key = tableName + ":" + strconv.Itoa(job.Id)
        err = GetObject(key, &old)
        if err != nil || old.Id < 1 {
            err = errors.New(fmt.Sprintf("Update Job %d fail, the old job is not exists.", job.Id))
            return
        }
        if old.Name != job.Name {
            if e := DelIndex(tableName + ":name", old.Name); e != nil {
                log.Printf("DelIndex Error: %s %s\n", tableName + ":name", old.Name)
            }
        }
        if old.Status != job.Status {
            if e := DelIndex(tableName + ":" + old.Status + ":sched", strconv.Itoa(job.Id)); e != nil {
                log.Printf("DelIndex Error: %s %d\n", tableName + ":" + old.Status + ":sched", old.Id)
            }
        }
    } else {
        job.Id, err = NextSequence(tableName)
        if err != nil {
            return
        }
    }
    idx, _ := GetIndex(tableName + ":name", job.Name)
    if idx > 0 && idx != job.Id {
        err = errors.New("Duplicate Job name: " + job.Name)
        return
    }
    key = tableName + ":" + strconv.Itoa(job.Id)
    err = SetObject(key, job)
    if err == nil {
        if e := AddIndex(tableName, strconv.Itoa(job.Id), job.Id); e != nil {
            log.Printf("AddIndex Error: %s %d\n", tableName, job.Id)
        }
        if e := AddIndex(tableName + ":" + job.Status + ":sched", strconv.Itoa(job.Id), job.SchedAt); e != nil {
            log.Printf("AddIndex Error: %s %d\n", tableName + ":" + job.Status + ":sched", job.Id)
        }
        if e := AddIndex(tableName + ":name", job.Name, job.Id); e != nil {
            log.Printf("DelIndex Error: %s %s\n", tableName + ":name", job.Name)
        }
    }
    return
}

func (job *Job) Delete() (err error) {
    var tableName = GetTableName(*job)
    var key = tableName + ":" + strconv.Itoa(job.Id)
    err = DelObject(key)
    DelIndex(tableName, strconv.Itoa(job.Id))
    DelIndex(tableName + ":" + job.Status + ":sched", strconv.Itoa(job.Id))
    DelIndex(tableName + ":name", job.Name)
    return
}

func GetJob(id int) (job Job, err error) {
    var tableName = GetTableName(job)
    var key = tableName + ":" +  strconv.Itoa(id)
    err = GetObject(key, &job)
    return
}

func DelJob(id int) (err error) {
    var job Job
    job, err = GetJob(id)
    if err != nil {
        return err
    }
    err = job.Delete()
    return err
}

func CountJob() (count int, err error) {
    var tableName = GetTableName(Job{})
    count, err = CountIndex(tableName)
    return
}

func RangeJob(start, stop int, rev ...bool) (jobs []Job, err error) {
    var tableName = GetTableName(Job{})
    var idxs []Index
    idxs, err = RangeIndex(tableName, start, stop, rev...)
    jobs = make([]Job, len(idxs))

    for k, idx := range idxs {
        job, _ :=  GetJob(idx.Score)
        job.Id = idx.Score
        jobs[k] = job
    }
    return
}

func RangeSchedJob(status string, start, stop int) (jobs []Job, err error) {
    var tableName = GetTableName(Job{})
    var idxs []Index
    idxs, err = RangeIndex(tableName + ":" + status + ":sched", start, stop)
    jobs = make([]Job, len(idxs))

    for k, idx := range idxs {
        jobId, _ := strconv.Atoi(idx.Name)
        job, _ :=  GetJob(jobId)
        job.Id = jobId
        job.Status = status
        jobs[k] = job
    }
    return
}

func CountSchedJob(status string) (count int, err error) {
    var tableName = GetTableName(Job{})
    count, err = CountIndex(tableName + ":" + status + ":sched")
    return
}
<|endoftext|>


<commit_before>package irmaserver

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-errors/errors"
	"strings"
	"sync"
	"time"

	"github.com/bsm/redislock"

	"github.com/alexandrevicenzi/go-sse"
	"github.com/go-redis/redis/v8"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/privacybydesign/irmago/server"

	"github.com/sirupsen/logrus"
)

type session struct {
	sync.Mutex
	sse            *sse.Server
	locked         bool
	lock           *redislock.Lock
	sessions       sessionStore
	conf           *server.Configuration
	request        irma.SessionRequest
	statusChannels []chan irma.ServerStatus

	sessionData
}

type sessionData struct {
	Action             irma.Action
	RequestorToken     irma.RequestorToken
	ClientToken        irma.ClientToken
	Version            *irma.ProtocolVersion `json:",omitempty"`
	Rrequest           irma.RequestorRequest
	LegacyCompatible   bool // if the request is convertible to pre-condiscon format
	Status             irma.ServerStatus
	PrevStatus         irma.ServerStatus
	ResponseCache      responseCache
	LastActive         time.Time
	Result             *server.SessionResult
	KssProofs          map[irma.SchemeManagerIdentifier]*gabi.ProofP
	Next               *irma.Qr
	FrontendAuth       irma.FrontendAuthorization
	ImplicitDisclosure irma.AttributeConDisCon
	Options            irma.SessionOptions
	ClientAuth         irma.ClientAuthorization
}

type responseCache struct {
	Endpoint      string
	Message       []byte
	Response      []byte
	Status        int
	SessionStatus irma.ServerStatus
}

type sessionStore interface {
	get(token irma.RequestorToken) (*session, error)
	clientGet(token irma.ClientToken) (*session, error)
	add(session *session) error
	update(session *session) error
	lock(session *session) error
	unlock(session *session) error
	stop()
}

type memorySessionStore struct {
	sync.RWMutex
	conf *server.Configuration

	requestor map[irma.RequestorToken]*session
	client    map[irma.ClientToken]*session
}

type redisSessionStore struct {
	client *redis.Client
	locker *redislock.Client
	conf   *server.Configuration
}

type RedisError struct {
	err error
}

func (err *RedisError) Error() string {
	return fmt.Sprintf("redis error: %s", err.err)
}

type UnknownSessionError struct {
	requestorToken irma.RequestorToken
	clientToken    irma.ClientToken
}

func (err *UnknownSessionError) Error() string {
	if err.requestorToken != "" {
		return fmt.Sprintf("session result requested of unknown session %s", err.requestorToken)
	} else {
		return fmt.Sprintf("session result requested of unknown session with clientToken %s", err.clientToken)
	}
}

const (
	maxLockLifetime            = 500 * time.Millisecond // After this the Redis lock self-deletes, preventing a deadlock
	minLockRetryTime           = 30 * time.Millisecond
	maxLockRetryTime           = 2 * time.Second
	requestorTokenLookupPrefix = "token:"
	clientTokenLookupPrefix    = "session:"
	lockPrefix                 = "lock:"
)

var (
	minProtocolVersion = irma.NewVersion(2, 4)
	maxProtocolVersion = irma.NewVersion(2, 8)

	minFrontendProtocolVersion = irma.NewVersion(1, 0)
	maxFrontendProtocolVersion = irma.NewVersion(1, 1)

	lockingRetryOptions = &redislock.Options{RetryStrategy: redislock.ExponentialBackoff(minLockRetryTime, maxLockRetryTime)}
)

func (s *memorySessionStore) get(t irma.RequestorToken) (*session, error) {
	s.RLock()
	defer s.RUnlock()
	if s.requestor[t] != nil {
		return s.requestor[t], nil
	} else {
		return nil, server.LogError(&UnknownSessionError{t, ""})
	}
}

func (s *memorySessionStore) clientGet(t irma.ClientToken) (*session, error) {
	s.RLock()
	defer s.RUnlock()

	if s.client[t] != nil {
		return s.client[t], nil
	} else {
		return nil, server.LogError(&UnknownSessionError{"", t})
	}
}

func (s *memorySessionStore) add(session *session) error {
	s.Lock()
	defer s.Unlock()
	s.requestor[session.RequestorToken] = session
	s.client[session.ClientToken] = session
	return nil
}

func (s *memorySessionStore) update(_ *session) error {
	return nil
}

func (s *memorySessionStore) lock(session *session) error {
	session.Lock()
	session.locked = true

	return nil
}

func (s *memorySessionStore) unlock(session *session) error {
	session.locked = false
	session.Unlock()

	return nil
}

func (s *memorySessionStore) stop() {
	s.Lock()
	defer s.Unlock()
	for _, session := range s.requestor {
		if session.sse != nil {
			session.sse.CloseChannel("session/" + string(session.RequestorToken))
			session.sse.CloseChannel("session/" + string(session.ClientToken))
			session.sse.CloseChannel("frontendsession/" + string(session.ClientToken))
		}
	}
}

func (s *memorySessionStore) deleteExpired() {
	// First check which sessions have expired
	// We don't need a write lock for this yet, so postpone that for actual deleting
	s.RLock()
	toCheck := make(map[irma.RequestorToken]*session, len(s.requestor))
	for token, session := range s.requestor {
		toCheck[token] = session
	}
	s.RUnlock()

	expired := make([]irma.RequestorToken, 0, len(toCheck))
	for token, session := range toCheck {
		session.Lock()

		timeout := s.conf.MaxSessionLifetime
		if session.Status == irma.ServerStatusInitialized && session.Rrequest.Base().ClientTimeout != 0 {
			timeout = time.Duration(session.Rrequest.Base().ClientTimeout) * time.Second
		}

		if session.LastActive.Add(timeout).Before(time.Now()) {
			if !session.Status.Finished() {
				s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Session expired")
				session.markAlive()
				session.setStatus(irma.ServerStatusTimeout)
			} else {
				s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Deleting session")
				expired = append(expired, token)
			}
		}
		session.Unlock()
	}

	// Using a write lock, delete the expired sessions
	s.Lock()
	for _, token := range expired {
		session := s.requestor[token]
		if session.sse != nil {
			session.sse.CloseChannel("session/" + string(session.RequestorToken))
			session.sse.CloseChannel("session/" + string(session.ClientToken))
			session.sse.CloseChannel("frontendsession/" + string(session.ClientToken))
		}
		delete(s.client, session.ClientToken)
		delete(s.requestor, token)
	}
	s.Unlock()
}

func (s *redisSessionStore) get(t irma.RequestorToken) (*session, error) {
	val, err := s.client.Get(context.Background(), requestorTokenLookupPrefix+string(t)).Result()
	if err == redis.Nil {
		return nil, server.LogError(&UnknownSessionError{t, ""})
	} else if err != nil {
		return nil, logAsRedisError(err)
	}

	clientToken, err := irma.ParseClientToken(val)
	if err != nil {
		return nil, logAsRedisError(err)
	}
	s.conf.Logger.WithFields(logrus.Fields{"session": t}).Debugf("clientToken %s found in Redis datastore", clientToken)

	return s.clientGet(clientToken)
}

func (s *redisSessionStore) clientGet(t irma.ClientToken) (*session, error) {
	val, err := s.client.Get(context.Background(), clientTokenLookupPrefix+string(t)).Result()
	if err == redis.Nil {
		return nil, server.LogError(&UnknownSessionError{"", t})
	} else if err != nil {
		return nil, logAsRedisError(err)
	}

	var session session
	session.conf = s.conf
	session.sessions = s
	if err := json.Unmarshal([]byte(val), &session.sessionData); err != nil {
		return nil, logAsRedisError(err)
	}
	session.request = session.Rrequest.SessionRequest()
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("Session received from Redis datastore")

	if session.LastActive.Add(s.conf.MaxSessionLifetime).Before(time.Now()) && !session.Status.Finished() {
		s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Session expired")
		session.markAlive()
		session.setStatus(irma.ServerStatusTimeout)
	}

	return &session, nil
}

func (s *redisSessionStore) add(session *session) error {
	timeout := 2 * s.conf.MaxSessionLifetime // logic similar to memory store
	if session.Status == irma.ServerStatusInitialized && session.Rrequest.Base().ClientTimeout != 0 {
		timeout = time.Duration(session.Rrequest.Base().ClientTimeout) * time.Second
	} else if session.Status.Finished() {
		timeout = s.conf.MaxSessionLifetime
	}

	sessionJSON, err := json.Marshal(session.sessionData)
	if err != nil {
		return server.LogError(err)
	}

	err = s.client.Set(context.Background(), requestorTokenLookupPrefix+string(session.sessionData.RequestorToken), string(session.sessionData.ClientToken), timeout).Err()
	if err != nil {
		return logAsRedisError(err)
	}
	err = s.client.Set(context.Background(), clientTokenLookupPrefix+string(session.sessionData.ClientToken), sessionJSON, timeout).Err()
	if err != nil {
		return logAsRedisError(err)
	}

	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session added to Redis datastore")
	return nil
}

func (s *redisSessionStore) update(session *session) error {
	// Time passes between acquiring the lock and writing to Redis. Check before write action that lock is still valid.
	if session.lock == nil {
		return logAsRedisError(errors.Errorf("lock is not set for session with requestorToken %s", session.RequestorToken))
	} else if ttl, err := session.lock.TTL(context.Background()); err != nil {
		return logAsRedisError(err)
	} else if ttl == 0 {
		return logAsRedisError(errors.Errorf("no session lock available for session with requestorToken %s", session.RequestorToken))
	}
	return s.add(session)
}

func (s *redisSessionStore) lock(session *session) error {
	lock, err := s.locker.Obtain(context.Background(), lockPrefix+string(session.ClientToken), maxLockLifetime, lockingRetryOptions)
	if err == redislock.ErrNotObtained {
		// It is possible that the session is already locked. However, it should not happen often. If you get this warning often,
		// you should investigate why.
		return server.LogWarning(&RedisError{err})
	} else if err != nil {
		return logAsRedisError(err)
	}
	session.locked = true
	session.lock = lock
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session locked successfully")

	return nil
}

func (s *redisSessionStore) unlock(session *session) error {
	session.locked = false
	err := session.lock.Release(context.Background())
	if err == redislock.ErrLockNotHeld {
		s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Info("lock could not be released as the lock was not held")
		return nil
	} else if err != nil {
		return logAsRedisError(err)
	}
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session unlocked successfully")

	return nil
}

func (s *redisSessionStore) stop() {
	err := s.client.Close()
	if err != nil {
		_ = logAsRedisError(err)
	}
	s.conf.Logger.Info("Redis client closed successfully")
}

var one *big.Int = big.NewInt(1)

func (s *Server) newSession(action irma.Action, request irma.RequestorRequest, disclosed irma.AttributeConDisCon, FrontendAuth irma.FrontendAuthorization) (*session, error) {
	clientToken := irma.ClientToken(common.NewSessionToken())
	requestorToken := irma.RequestorToken(common.NewSessionToken())
	if len(FrontendAuth) == 0 {
		FrontendAuth = irma.FrontendAuthorization(common.NewSessionToken())
	}

	base := request.SessionRequest().Base()
	if s.conf.AugmentClientReturnURL && base.AugmentReturnURL && base.ClientReturnURL != "" {
		if strings.Contains(base.ClientReturnURL, "?") {
			base.ClientReturnURL += "&token=" + string(requestorToken)
		} else {
			base.ClientReturnURL += "?token=" + string(requestorToken)
		}
	}

	sd := sessionData{
		Action:         action,
		Rrequest:       request,
		LastActive:     time.Now(),
		RequestorToken: requestorToken,
		ClientToken:    clientToken,
		Status:         irma.ServerStatusInitialized,
		PrevStatus:     irma.ServerStatusInitialized,
		Result: &server.SessionResult{
			LegacySession: request.SessionRequest().Base().Legacy(),
			Token:         requestorToken,
			Type:          action,
			Status:        irma.ServerStatusInitialized,
		},
		Options: irma.SessionOptions{
			LDContext:     irma.LDContextSessionOptions,
			PairingMethod: irma.PairingMethodNone,
		},
		FrontendAuth:       FrontendAuth,
		ImplicitDisclosure: disclosed,
	}
	ses := &session{
		sessionData: sd,
		sessions:    s.sessions,
		sse:         s.serverSentEvents,
		conf:        s.conf,
		request:     request.SessionRequest(),
	}

	s.conf.Logger.WithFields(logrus.Fields{"session": ses.RequestorToken}).Debug("New session started")
	nonce, _ := gabi.GenerateNonce()
	base.Nonce = nonce
	base.Context = one

	err := s.sessions.add(ses)
	if err != nil {
		return nil, err
	}

	return ses, nil
}

func logAsRedisError(err error) error {
	return server.LogError(&RedisError{err})
}
<commit_msg>refactor: log redislock.ErrNotObtained as Redis error.<commit_after>package irmaserver

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-errors/errors"
	"strings"
	"sync"
	"time"

	"github.com/bsm/redislock"

	"github.com/alexandrevicenzi/go-sse"
	"github.com/go-redis/redis/v8"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/privacybydesign/irmago/server"

	"github.com/sirupsen/logrus"
)

type session struct {
	sync.Mutex
	sse            *sse.Server
	locked         bool
	lock           *redislock.Lock
	sessions       sessionStore
	conf           *server.Configuration
	request        irma.SessionRequest
	statusChannels []chan irma.ServerStatus

	sessionData
}

type sessionData struct {
	Action             irma.Action
	RequestorToken     irma.RequestorToken
	ClientToken        irma.ClientToken
	Version            *irma.ProtocolVersion `json:",omitempty"`
	Rrequest           irma.RequestorRequest
	LegacyCompatible   bool // if the request is convertible to pre-condiscon format
	Status             irma.ServerStatus
	PrevStatus         irma.ServerStatus
	ResponseCache      responseCache
	LastActive         time.Time
	Result             *server.SessionResult
	KssProofs          map[irma.SchemeManagerIdentifier]*gabi.ProofP
	Next               *irma.Qr
	FrontendAuth       irma.FrontendAuthorization
	ImplicitDisclosure irma.AttributeConDisCon
	Options            irma.SessionOptions
	ClientAuth         irma.ClientAuthorization
}

type responseCache struct {
	Endpoint      string
	Message       []byte
	Response      []byte
	Status        int
	SessionStatus irma.ServerStatus
}

type sessionStore interface {
	get(token irma.RequestorToken) (*session, error)
	clientGet(token irma.ClientToken) (*session, error)
	add(session *session) error
	update(session *session) error
	lock(session *session) error
	unlock(session *session) error
	stop()
}

type memorySessionStore struct {
	sync.RWMutex
	conf *server.Configuration

	requestor map[irma.RequestorToken]*session
	client    map[irma.ClientToken]*session
}

type redisSessionStore struct {
	client *redis.Client
	locker *redislock.Client
	conf   *server.Configuration
}

type RedisError struct {
	err error
}

func (err *RedisError) Error() string {
	return fmt.Sprintf("redis error: %s", err.err)
}

type UnknownSessionError struct {
	requestorToken irma.RequestorToken
	clientToken    irma.ClientToken
}

func (err *UnknownSessionError) Error() string {
	if err.requestorToken != "" {
		return fmt.Sprintf("session result requested of unknown session %s", err.requestorToken)
	} else {
		return fmt.Sprintf("session result requested of unknown session with clientToken %s", err.clientToken)
	}
}

const (
	maxLockLifetime            = 500 * time.Millisecond // After this the Redis lock self-deletes, preventing a deadlock
	minLockRetryTime           = 30 * time.Millisecond
	maxLockRetryTime           = 2 * time.Second
	requestorTokenLookupPrefix = "token:"
	clientTokenLookupPrefix    = "session:"
	lockPrefix                 = "lock:"
)

var (
	minProtocolVersion = irma.NewVersion(2, 4)
	maxProtocolVersion = irma.NewVersion(2, 8)

	minFrontendProtocolVersion = irma.NewVersion(1, 0)
	maxFrontendProtocolVersion = irma.NewVersion(1, 1)

	lockingRetryOptions = &redislock.Options{RetryStrategy: redislock.ExponentialBackoff(minLockRetryTime, maxLockRetryTime)}
)

func (s *memorySessionStore) get(t irma.RequestorToken) (*session, error) {
	s.RLock()
	defer s.RUnlock()
	if s.requestor[t] != nil {
		return s.requestor[t], nil
	} else {
		return nil, server.LogError(&UnknownSessionError{t, ""})
	}
}

func (s *memorySessionStore) clientGet(t irma.ClientToken) (*session, error) {
	s.RLock()
	defer s.RUnlock()

	if s.client[t] != nil {
		return s.client[t], nil
	} else {
		return nil, server.LogError(&UnknownSessionError{"", t})
	}
}

func (s *memorySessionStore) add(session *session) error {
	s.Lock()
	defer s.Unlock()
	s.requestor[session.RequestorToken] = session
	s.client[session.ClientToken] = session
	return nil
}

func (s *memorySessionStore) update(_ *session) error {
	return nil
}

func (s *memorySessionStore) lock(session *session) error {
	session.Lock()
	session.locked = true

	return nil
}

func (s *memorySessionStore) unlock(session *session) error {
	session.locked = false
	session.Unlock()

	return nil
}

func (s *memorySessionStore) stop() {
	s.Lock()
	defer s.Unlock()
	for _, session := range s.requestor {
		if session.sse != nil {
			session.sse.CloseChannel("session/" + string(session.RequestorToken))
			session.sse.CloseChannel("session/" + string(session.ClientToken))
			session.sse.CloseChannel("frontendsession/" + string(session.ClientToken))
		}
	}
}

func (s *memorySessionStore) deleteExpired() {
	// First check which sessions have expired
	// We don't need a write lock for this yet, so postpone that for actual deleting
	s.RLock()
	toCheck := make(map[irma.RequestorToken]*session, len(s.requestor))
	for token, session := range s.requestor {
		toCheck[token] = session
	}
	s.RUnlock()

	expired := make([]irma.RequestorToken, 0, len(toCheck))
	for token, session := range toCheck {
		session.Lock()

		timeout := s.conf.MaxSessionLifetime
		if session.Status == irma.ServerStatusInitialized && session.Rrequest.Base().ClientTimeout != 0 {
			timeout = time.Duration(session.Rrequest.Base().ClientTimeout) * time.Second
		}

		if session.LastActive.Add(timeout).Before(time.Now()) {
			if !session.Status.Finished() {
				s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Session expired")
				session.markAlive()
				session.setStatus(irma.ServerStatusTimeout)
			} else {
				s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Deleting session")
				expired = append(expired, token)
			}
		}
		session.Unlock()
	}

	// Using a write lock, delete the expired sessions
	s.Lock()
	for _, token := range expired {
		session := s.requestor[token]
		if session.sse != nil {
			session.sse.CloseChannel("session/" + string(session.RequestorToken))
			session.sse.CloseChannel("session/" + string(session.ClientToken))
			session.sse.CloseChannel("frontendsession/" + string(session.ClientToken))
		}
		delete(s.client, session.ClientToken)
		delete(s.requestor, token)
	}
	s.Unlock()
}

func (s *redisSessionStore) get(t irma.RequestorToken) (*session, error) {
	val, err := s.client.Get(context.Background(), requestorTokenLookupPrefix+string(t)).Result()
	if err == redis.Nil {
		return nil, server.LogError(&UnknownSessionError{t, ""})
	} else if err != nil {
		return nil, logAsRedisError(err)
	}

	clientToken, err := irma.ParseClientToken(val)
	if err != nil {
		return nil, logAsRedisError(err)
	}
	s.conf.Logger.WithFields(logrus.Fields{"session": t}).Debugf("clientToken %s found in Redis datastore", clientToken)

	return s.clientGet(clientToken)
}

func (s *redisSessionStore) clientGet(t irma.ClientToken) (*session, error) {
	val, err := s.client.Get(context.Background(), clientTokenLookupPrefix+string(t)).Result()
	if err == redis.Nil {
		return nil, server.LogError(&UnknownSessionError{"", t})
	} else if err != nil {
		return nil, logAsRedisError(err)
	}

	var session session
	session.conf = s.conf
	session.sessions = s
	if err := json.Unmarshal([]byte(val), &session.sessionData); err != nil {
		return nil, logAsRedisError(err)
	}
	session.request = session.Rrequest.SessionRequest()
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("Session received from Redis datastore")

	if session.LastActive.Add(s.conf.MaxSessionLifetime).Before(time.Now()) && !session.Status.Finished() {
		s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Infof("Session expired")
		session.markAlive()
		session.setStatus(irma.ServerStatusTimeout)
	}

	return &session, nil
}

func (s *redisSessionStore) add(session *session) error {
	timeout := 2 * s.conf.MaxSessionLifetime // logic similar to memory store
	if session.Status == irma.ServerStatusInitialized && session.Rrequest.Base().ClientTimeout != 0 {
		timeout = time.Duration(session.Rrequest.Base().ClientTimeout) * time.Second
	} else if session.Status.Finished() {
		timeout = s.conf.MaxSessionLifetime
	}

	sessionJSON, err := json.Marshal(session.sessionData)
	if err != nil {
		return server.LogError(err)
	}

	err = s.client.Set(context.Background(), requestorTokenLookupPrefix+string(session.sessionData.RequestorToken), string(session.sessionData.ClientToken), timeout).Err()
	if err != nil {
		return logAsRedisError(err)
	}
	err = s.client.Set(context.Background(), clientTokenLookupPrefix+string(session.sessionData.ClientToken), sessionJSON, timeout).Err()
	if err != nil {
		return logAsRedisError(err)
	}

	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session added to Redis datastore")
	return nil
}

func (s *redisSessionStore) update(session *session) error {
	// Time passes between acquiring the lock and writing to Redis. Check before write action that lock is still valid.
	if session.lock == nil {
		return logAsRedisError(errors.Errorf("lock is not set for session with requestorToken %s", session.RequestorToken))
	} else if ttl, err := session.lock.TTL(context.Background()); err != nil {
		return logAsRedisError(err)
	} else if ttl == 0 {
		return logAsRedisError(errors.Errorf("no session lock available for session with requestorToken %s", session.RequestorToken))
	}
	return s.add(session)
}

func (s *redisSessionStore) lock(session *session) error {
	lock, err := s.locker.Obtain(context.Background(), lockPrefix+string(session.ClientToken), maxLockLifetime, lockingRetryOptions)
	if err != nil {
		// It is possible that the session is already locked. However, it should not happen often.
		// If you get the redislock.ErrNotObtained error often, you should investigate why.
		return logAsRedisError(err)
	}
	session.locked = true
	session.lock = lock
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session locked successfully")

	return nil
}

func (s *redisSessionStore) unlock(session *session) error {
	session.locked = false
	err := session.lock.Release(context.Background())
	if err == redislock.ErrLockNotHeld {
		s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Info("lock could not be released as the lock was not held")
		return nil
	} else if err != nil {
		return logAsRedisError(err)
	}
	s.conf.Logger.WithFields(logrus.Fields{"session": session.RequestorToken}).Debugf("session unlocked successfully")

	return nil
}

func (s *redisSessionStore) stop() {
	err := s.client.Close()
	if err != nil {
		_ = logAsRedisError(err)
	}
	s.conf.Logger.Info("Redis client closed successfully")
}

var one *big.Int = big.NewInt(1)

func (s *Server) newSession(action irma.Action, request irma.RequestorRequest, disclosed irma.AttributeConDisCon, FrontendAuth irma.FrontendAuthorization) (*session, error) {
	clientToken := irma.ClientToken(common.NewSessionToken())
	requestorToken := irma.RequestorToken(common.NewSessionToken())
	if len(FrontendAuth) == 0 {
		FrontendAuth = irma.FrontendAuthorization(common.NewSessionToken())
	}

	base := request.SessionRequest().Base()
	if s.conf.AugmentClientReturnURL && base.AugmentReturnURL && base.ClientReturnURL != "" {
		if strings.Contains(base.ClientReturnURL, "?") {
			base.ClientReturnURL += "&token=" + string(requestorToken)
		} else {
			base.ClientReturnURL += "?token=" + string(requestorToken)
		}
	}

	sd := sessionData{
		Action:         action,
		Rrequest:       request,
		LastActive:     time.Now(),
		RequestorToken: requestorToken,
		ClientToken:    clientToken,
		Status:         irma.ServerStatusInitialized,
		PrevStatus:     irma.ServerStatusInitialized,
		Result: &server.SessionResult{
			LegacySession: request.SessionRequest().Base().Legacy(),
			Token:         requestorToken,
			Type:          action,
			Status:        irma.ServerStatusInitialized,
		},
		Options: irma.SessionOptions{
			LDContext:     irma.LDContextSessionOptions,
			PairingMethod: irma.PairingMethodNone,
		},
		FrontendAuth:       FrontendAuth,
		ImplicitDisclosure: disclosed,
	}
	ses := &session{
		sessionData: sd,
		sessions:    s.sessions,
		sse:         s.serverSentEvents,
		conf:        s.conf,
		request:     request.SessionRequest(),
	}

	s.conf.Logger.WithFields(logrus.Fields{"session": ses.RequestorToken}).Debug("New session started")
	nonce, _ := gabi.GenerateNonce()
	base.Nonce = nonce
	base.Context = one

	err := s.sessions.add(ses)
	if err != nil {
		return nil, err
	}

	return ses, nil
}

func logAsRedisError(err error) error {
	return server.LogError(&RedisError{err})
}
<|endoftext|>


<commit_before>package marathon

import (
	"encoding/json"
	"github.com/QubitProducts/bamboo/configuration"
	"io/ioutil"
	"net/http"
	"sort"
	"strings"
)

// Describes an app process running
type Task struct {
	Host  string
	Port  int
	Ports []int
}

// A health check on the application
type HealthCheck struct {
	// One of TCP, HTTP or COMMAND
	Protocol string
	// The path (if Protocol is HTTP)
	Path string
	// The position of the port targeted in the ports array
	PortIndex int
}

// An app may have multiple processes
type App struct {
	Id              string
	EscapedId       string
	HealthCheckPath string
	HealthChecks    []HealthCheck
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
	Labels          map[string]string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonTaskList []marathonTask

type marathonTasks struct {
	Tasks marathonTaskList `json:"tasks"`
}

type marathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice marathonTaskList) Len() int {
	return len(slice)
}

func (slice marathonTaskList) Less(i, j int) bool {
	return slice[i].StagedAt < slice[j].StagedAt
}

func (slice marathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonApps struct {
	Apps []marathonApp `json:"apps"`
}

type marathonApp struct {
	Id           string                `json:"id"`
	HealthChecks []marathonHealthCheck `json:"healthChecks"`
	Ports        []int                 `json:"ports"`
	Env          map[string]string     `json:"env"`
	Labels       map[string]string     `json:"labels"`
}

type marathonHealthCheck struct {
	Path      string `json:"path"`
	Protocol  string `json:"protocol"`
	PortIndex int    `json:"portIndex"`
}

func fetchMarathonApps(endpoint string, conf *configuration.Configuration) (map[string]marathonApp, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/apps", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	var appResponse marathonApps

	contents, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &appResponse)
	if err != nil {
		return nil, err
	}

	dataById := map[string]marathonApp{}

	for _, appConfig := range appResponse.Apps {
		dataById[appConfig.Id] = appConfig
	}

	return dataById, nil
}

func fetchTasks(endpoint string, conf *configuration.Configuration) (map[string][]marathonTask, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	var tasks marathonTasks

	if err != nil {
		return nil, err
	}

	contents, err := ioutil.ReadAll(response.Body)
	defer response.Body.Close()
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &tasks)
	if err != nil {
		return nil, err
	}

	taskList := tasks.Tasks
	sort.Sort(taskList)

	tasksById := map[string][]marathonTask{}
	for _, task := range taskList {
		if tasksById[task.AppId] == nil {
			tasksById[task.AppId] = []marathonTask{}
		}
		tasksById[task.AppId] = append(tasksById[task.AppId], task)
	}

	return tasksById, nil
}

func createApps(tasksById map[string][]marathonTask, marathonApps map[string]marathonApp) AppList {

	apps := AppList{}

	for appId, mApp := range marathonApps {

		// Try to handle old app id format without slashes
		appPath := appId
		if !strings.HasPrefix(appId, "/") {
			appPath = "/" + appId
		}

		// build App from marathonApp
		app := App{
			Id:              appPath,
			EscapedId:       strings.Replace(appId, "/", "::", -1),
			HealthCheckPath: parseHealthCheckPath(mApp.HealthChecks),
			Env:             mApp.Env,
			Labels:          mApp.Labels,
		}

		app.HealthChecks = make([]HealthCheck, 0, len(mApp.HealthChecks))
		for _, marathonCheck := range mApp.HealthChecks {
			check := HealthCheck{
				Protocol:  marathonCheck.Protocol,
				Path:      marathonCheck.Path,
				PortIndex: marathonCheck.PortIndex,
			}
			app.HealthChecks = append(app.HealthChecks, check)
		}

		if len(mApp.Ports) > 0 {
			app.ServicePort = mApp.Ports[0]
			app.ServicePorts = mApp.Ports
		}

		// build Tasks for this App
		tasks := []Task{}
		for _, mTask := range tasksById[appId] {
			if len(mTask.Ports) > 0 {
				t := Task{
					Host:  mTask.Host,
					Port:  mTask.Ports[0],
					Ports: mTask.Ports,
				}
				tasks = append(tasks, t)
			}
		}
		app.Tasks = tasks

		apps = append(apps, app)
	}
	return apps
}

func parseHealthCheckPath(checks []marathonHealthCheck) string {
	for _, check := range checks {
		if check.Protocol != "HTTP" {
			continue
		}
		return check.Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon, conf *configuration.Configuration) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url, conf)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string, conf *configuration.Configuration) (AppList, error) {
	tasks, err := fetchTasks(url, conf)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url, conf)
	if err != nil {
		return nil, err
	}

	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<commit_msg>sort tasks by .Id, not .StagedAt. fixes #177<commit_after>package marathon

import (
	"encoding/json"
	"github.com/QubitProducts/bamboo/configuration"
	"io/ioutil"
	"net/http"
	"sort"
	"strings"
)

// Describes an app process running
type Task struct {
	Host  string
	Port  int
	Ports []int
}

// A health check on the application
type HealthCheck struct {
	// One of TCP, HTTP or COMMAND
	Protocol string
	// The path (if Protocol is HTTP)
	Path string
	// The position of the port targeted in the ports array
	PortIndex int
}

// An app may have multiple processes
type App struct {
	Id              string
	EscapedId       string
	HealthCheckPath string
	HealthChecks    []HealthCheck
	Tasks           []Task
	ServicePort     int
	ServicePorts    []int
	Env             map[string]string
	Labels          map[string]string
}

type AppList []App

func (slice AppList) Len() int {
	return len(slice)
}

func (slice AppList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice AppList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonTaskList []marathonTask

type marathonTasks struct {
	Tasks marathonTaskList `json:"tasks"`
}

type marathonTask struct {
	AppId        string
	Id           string
	Host         string
	Ports        []int
	ServicePorts []int
	StartedAt    string
	StagedAt     string
	Version      string
}

func (slice marathonTaskList) Len() int {
	return len(slice)
}

func (slice marathonTaskList) Less(i, j int) bool {
	return slice[i].Id < slice[j].Id
}

func (slice marathonTaskList) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

type marathonApps struct {
	Apps []marathonApp `json:"apps"`
}

type marathonApp struct {
	Id           string                `json:"id"`
	HealthChecks []marathonHealthCheck `json:"healthChecks"`
	Ports        []int                 `json:"ports"`
	Env          map[string]string     `json:"env"`
	Labels       map[string]string     `json:"labels"`
}

type marathonHealthCheck struct {
	Path      string `json:"path"`
	Protocol  string `json:"protocol"`
	PortIndex int    `json:"portIndex"`
}

func fetchMarathonApps(endpoint string, conf *configuration.Configuration) (map[string]marathonApp, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/apps", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	var appResponse marathonApps

	contents, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &appResponse)
	if err != nil {
		return nil, err
	}

	dataById := map[string]marathonApp{}

	for _, appConfig := range appResponse.Apps {
		dataById[appConfig.Id] = appConfig
	}

	return dataById, nil
}

func fetchTasks(endpoint string, conf *configuration.Configuration) (map[string]marathonTaskList, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")
	if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
		req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
	}
	response, err := client.Do(req)

	var tasks marathonTasks

	if err != nil {
		return nil, err
	}

	contents, err := ioutil.ReadAll(response.Body)
	defer response.Body.Close()
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(contents, &tasks)
	if err != nil {
		return nil, err
	}

	taskList := tasks.Tasks
	sort.Sort(taskList)

	tasksById := map[string]marathonTaskList{}
	for _, task := range taskList {
		if tasksById[task.AppId] == nil {
			tasksById[task.AppId] = marathonTaskList{}
		}
		tasksById[task.AppId] = append(tasksById[task.AppId], task)
	}

  for _, task_list := range tasksById {
    sort.Sort(task_list)
  }

	return tasksById, nil
}

func createApps(tasksById map[string]marathonTaskList, marathonApps map[string]marathonApp) AppList {

	apps := AppList{}

	for appId, mApp := range marathonApps {

		// Try to handle old app id format without slashes
		appPath := appId
		if !strings.HasPrefix(appId, "/") {
			appPath = "/" + appId
		}

		// build App from marathonApp
		app := App{
			Id:              appPath,
			EscapedId:       strings.Replace(appId, "/", "::", -1),
			HealthCheckPath: parseHealthCheckPath(mApp.HealthChecks),
			Env:             mApp.Env,
			Labels:          mApp.Labels,
		}

		app.HealthChecks = make([]HealthCheck, 0, len(mApp.HealthChecks))
		for _, marathonCheck := range mApp.HealthChecks {
			check := HealthCheck{
				Protocol:  marathonCheck.Protocol,
				Path:      marathonCheck.Path,
				PortIndex: marathonCheck.PortIndex,
			}
			app.HealthChecks = append(app.HealthChecks, check)
		}

		if len(mApp.Ports) > 0 {
			app.ServicePort = mApp.Ports[0]
			app.ServicePorts = mApp.Ports
		}

		// build Tasks for this App
		tasks := []Task{}
		for _, mTask := range tasksById[appId] {
			if len(mTask.Ports) > 0 {
				t := Task{
					Host:  mTask.Host,
					Port:  mTask.Ports[0],
					Ports: mTask.Ports,
				}
				tasks = append(tasks, t)
			}
		}
		app.Tasks = tasks

		apps = append(apps, app)
	}
	return apps
}

func parseHealthCheckPath(checks []marathonHealthCheck) string {
	for _, check := range checks {
		if check.Protocol != "HTTP" {
			continue
		}
		return check.Path
	}
	return ""
}

/*
	Apps returns a struct that describes Marathon current app and their
	sub tasks information.

	Parameters:
		endpoint: Marathon HTTP endpoint, e.g. http://localhost:8080
*/
func FetchApps(maraconf configuration.Marathon, conf *configuration.Configuration) (AppList, error) {

	var applist AppList
	var err error

	// try all configured endpoints until one succeeds
	for _, url := range maraconf.Endpoints() {
		applist, err = _fetchApps(url, conf)
		if err == nil {
			return applist, err
		}
	}
	// return last error
	return nil, err
}

func _fetchApps(url string, conf *configuration.Configuration) (AppList, error) {
	tasks, err := fetchTasks(url, conf)
	if err != nil {
		return nil, err
	}

	marathonApps, err := fetchMarathonApps(url, conf)
	if err != nil {
		return nil, err
	}

	apps := createApps(tasks, marathonApps)
	sort.Sort(apps)
	return apps, nil
}
<|endoftext|>


<commit_before>/*
Package dncomp implements domain name compression according to RFC 1035
section 4.1.4.

Compressed domain names are composed by a sequence of label size and labels,
ending with 0 or a pointer to an offset in compressed data. Pointers are
encoded in a two-octet word with the most significant bits set to 1, and the
rest of the word containing the pointer offset. The maximum size of a label
is 63 octets, and pointer codes 10 and 01 are reserved. Pointers should
always point backwards to data that has already been processed.

Example:

	1, 'F',
	3, 'I', 'S', 'I',
	4, 'A', 'R', 'P', 'A', 0,
	3, 'F', 'O', 'O', 0xc0, 0,
	0xc0, 6, 0

decodes to:

	"F.ISI.ARPA"
	"FOO.F.ISI.ARPA"
	"ARPA"
	"" (root domain name, no labels)

*/
package dncomp

import (
	"bytes"
	"errors"
)

// ErrMalformedCompressedData is returned when compressed domain names
// cannot be properly parsed due to invalid offsets, label sizes, or
// character set.
var ErrMalformedCompressedData = errors.New("dncomp: malformed compressed data")

// decodeLabel adds a label from offset lstart of compressed data to byte
// buffer b, for the domain name starting at start.
func decodeLabel(b *bytes.Buffer, data []byte, start, off int) int {
	if off >= len(data) {
		return -1
	}

	size := int(data[off])
	off++

	// check for pointer
	switch size & 0xc0 {
	case 0x00:
		// check end of domain name
		if size == 0 {
			return off
		}
		// sanity check: invalid label length
			if off+size > len(data) {
			return -1
		}

	case 0xc0:
		// check if second octet is available
		if off >= len(data) {
			return -1
		}

		// offset
		ptr := (size&0x3f)<<8 | int(data[off])
		if ptr >= start || decodeLabel(b, data, start, ptr) < 0 {
			return -1
		}
		return off + 1

	case 0x80, 0x40:
		// reserved codes 10 and 01
		return -1
	}

	end := off + size

	// write label to domain name string
	_, err := b.Write(data[off:end])
	if err != nil {
		return -1
	}

	// sanity check: missing end marker
	if end >= len(data) {
		return -1
	}

	// add dot if we have more labels
	if data[end] != 0 {
		b.WriteByte(byte('.'))
	}

	return decodeLabel(b, data, start, end)
}

// Decode uncompresses a list of domain names encoded according to RFC 1035
// section 4.1.4, returning a list of uncompressed domain names or an error
// if data is inconsistent.
func Decode(data []byte) ([]string, error) {
	var s []string

	for i := 0; ; {
		if i >= len(data) {
			break
		}

		var b bytes.Buffer
		i = decodeLabel(&b, data, i, i)
		if i < 0 {
			return nil, ErrMalformedCompressedData
		}

		s = append(s, b.String())
	}

	return s, nil
}
<commit_msg>Refactor encoding code<commit_after>/*
Package dncomp implements domain name compression according to RFC 1035
section 4.1.4.

Compressed domain names are composed by a sequence of label size and labels,
ending with 0 or a pointer to an offset in compressed data. Pointers are
encoded in a two-octet word with the most significant bits set to 1, and the
rest of the word containing the pointer offset. The maximum size of a label
is 63 octets, and pointer codes 10 and 01 are reserved. Pointers should
always point backwards to data that has already been processed.

Example:

	1, 'F',
	3, 'I', 'S', 'I',
	4, 'A', 'R', 'P', 'A', 0,
	3, 'F', 'O', 'O', 0xc0, 0,
	0xc0, 6, 0

decodes to:

	"F.ISI.ARPA"
	"FOO.F.ISI.ARPA"
	"ARPA"
	"" (root domain name, no labels)

*/
package dncomp

import (
	"bytes"
	"errors"
)

// ErrMalformedCompressedData is returned when compressed domain names
// cannot be properly parsed due to invalid offsets, label sizes, or
// character set.
var ErrMalformedCompressedData = errors.New("dncomp: malformed compressed data")

// decodeLabel adds a label from offset off of compressed data to byte
// buffer b, for the name record starting at start.
func decodeLabel(b *bytes.Buffer, data []byte, start, off int) int {
	for {
		if off >= len(data) {
			return -1
		}

		size := int(data[off])
		off++

		// check for pointer
		switch size & 0xc0 {
		case 0x00:
			// end of domain name
			if size == 0 {
				return off
			}
			// sanity check: invalid label length
			if off+size > len(data) {
				return -1
			}

			// write label to domain name string
			end := off + size
			_, err := b.Write(data[off:end])
			if err != nil {
				return -1
			}
			off = end

			// sanity check: missing end marker
			if end >= len(data) {
				return -1
			}

			// add dot if we have more labels
			if data[end] != 0 {
				b.WriteByte(byte('.'))
			}

		case 0xc0:
			// sanity check: second octet available
			if off >= len(data) {
				return -1
			}

			// pointer offset
			ptr := (size&0x3f)<<8 | int(data[off])
			if ptr >= start || decodeLabel(b, data, start, ptr) < 0 {
				return -1
			}
			return off + 1

		default:
			// reserved codes 10 and 01
			return -1
		}
	}
}

// Decode uncompresses a list of domain names encoded according to RFC 1035
// section 4.1.4, returning a list of uncompressed domain names or an error
// if data is inconsistent.
func Decode(data []byte) ([]string, error) {
	var s []string

	for i := 0; ; {
		if i >= len(data) {
			break
		}

		var b bytes.Buffer
		i = decodeLabel(&b, data, i, i)
		if i < 0 {
			return nil, ErrMalformedCompressedData
		}

		s = append(s, b.String())
	}

	return s, nil
}
<|endoftext|>