<commit_before>/*
 * Minio Cloud Storage, (C) 2015-2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or 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 fs

import (
	"errors"
	"hash/fnv"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/minio/minio-xl/pkg/probe"
)

// listObjectsParams - list objects input parameters.
type listObjectsParams struct {
	// Bucket name to list the objects for.
	Bucket string
	// list all objects with this parameter as common prefix.
	Prefix string
	// list all objects starting with object after marker in
	// lexicographical order.
	Marker string
	// list all objects until the first occurrence of the delimtier
	// after the prefix.
	Delimiter string
	// maximum number of objects returned per listObjects()
	// operation.
	MaxKeys int
}

// listServiceReq
type listServiceReq struct {
	reqParams listObjectsParams
	respCh    chan ListObjectsResult
}

type listWorkerReq struct {
	respCh chan ListObjectsResult
}

// listObjects - list objects lists objects upto maxKeys for a given prefix.
func (fs Filesystem) listObjects(bucket, prefix, marker, delimiter string, maxKeys int) (chan<- listWorkerReq, *probe.Error) {
	quitWalker := make(chan bool)
	reqCh := make(chan listWorkerReq)
	walkerCh := make(chan ObjectMetadata)
	go func() {
		defer close(walkerCh)
		var walkPath string
		bucketPath := filepath.Join(fs.path, bucket)
		// Bucket path prefix should always end with a separator.
		bucketPathPrefix := bucketPath + string(os.PathSeparator)
		prefixPath := bucketPathPrefix + prefix
		st, err := os.Stat(prefixPath)
		if err != nil && os.IsNotExist(err) {
			walkPath = bucketPath
		} else {
			if st.IsDir() && !strings.HasSuffix(prefix, delimiter) {
				walkPath = bucketPath
			} else {
				walkPath = prefixPath
			}
		}
		Walk(walkPath, func(path string, info os.FileInfo, err error) error {
			// We don't need to list the walk path.
			if path == walkPath {
				return nil
			}
			// For all incoming directories add a ending separator.
			if info.IsDir() {
				path = path + string(os.PathSeparator)
			}
			// Extract object name.
			objectName := strings.TrimPrefix(path, bucketPathPrefix)
			if strings.HasPrefix(objectName, prefix) {
				// For objectName lesser than marker, ignore.
				if marker >= objectName {
					return nil
				}
				object := ObjectMetadata{
					Object:  objectName,
					Created: info.ModTime(),
					Mode:    info.Mode(),
					Size:    info.Size(),
				}
				select {
				// Send object on walker channel.
				case walkerCh <- object:
				case <-quitWalker:
					// Returning error ends the file tree Walk().
					return errors.New("Quit list worker.")
				}
				// If delimiter is set, we stop if current path is a
				// directory.
				if delimiter != "" && info.IsDir() {
					return ErrSkipDir
				}
			}
			return nil
		})
	}()

	go func() {
		for {
			select {
			// Timeout after 10 seconds if request did not arrive for
			// the given list parameters.
			case <-time.After(10 * time.Second):
				quitWalker <- true // Quit file path walk if running.
				// Send back the hash for this request.
				fs.timeoutReqCh <- fnvSum(bucket, prefix, marker, delimiter)
				return
			case req, ok := <-reqCh:
				if !ok {
					// If the request channel is closed, no more
					// requests return here.
					return
				}
				resp := ListObjectsResult{}
				var count int
				for object := range walkerCh {
					if count == maxKeys {
						resp.IsTruncated = true
						break
					}
					// If object is a directory.
					if object.Mode.IsDir() {
						if delimiter == "" {
							// Skip directories for recursive listing.
							continue
						}
						resp.Prefixes = append(resp.Prefixes, object.Object)
					} else {
						resp.Objects = append(resp.Objects, object)
					}
					// Set the next marker for the next request.
					resp.NextMarker = object.Object
					count++
				}
				req.respCh <- resp
			}
		}
	}()
	return reqCh, nil
}

// fnvSum calculates a hash for concatenation of all input strings.
func fnvSum(elements ...string) uint32 {
	fnvHash := fnv.New32a()
	for _, element := range elements {
		fnvHash.Write([]byte(element))
	}
	return fnvHash.Sum32()
}

// listObjectsService - list objects service manages various incoming
// list object requests by delegating them to an existing listObjects
// routine or initializes a new listObjects routine.
func (fs *Filesystem) listObjectsService() *probe.Error {
	// Initialize list service request channel.
	listServiceReqCh := make(chan listServiceReq)
	fs.listServiceReqCh = listServiceReqCh

	// Initialize timeout request channel to receive request hashes of
	// timed-out requests.
	timeoutReqCh := make(chan uint32)
	fs.timeoutReqCh = timeoutReqCh

	// Initialize request hash to list worker map.
	reqToListWorkerReqCh := make(map[uint32]chan<- listWorkerReq)

	// Start service in a go routine.
	go func() {
		for {
			select {
			case reqHash := <-timeoutReqCh:
				// For requests which have timed-out, close the worker
				// channels proactively, this may happen for idle
				// workers once in 10seconds.
				listWorkerReqCh, ok := reqToListWorkerReqCh[reqHash]
				if ok {
					close(listWorkerReqCh)
				}
				delete(reqToListWorkerReqCh, reqHash)
			case srvReq := <-listServiceReqCh:
				// Save the params for readability.
				bucket := srvReq.reqParams.Bucket
				prefix := srvReq.reqParams.Prefix
				marker := srvReq.reqParams.Marker
				delimiter := srvReq.reqParams.Delimiter
				maxKeys := srvReq.reqParams.MaxKeys

				// Generate hash.
				reqHash := fnvSum(bucket, prefix, marker, delimiter)
				listWorkerReqCh, ok := reqToListWorkerReqCh[reqHash]
				if !ok {
					var err *probe.Error
					listWorkerReqCh, err = fs.listObjects(bucket, prefix, marker, delimiter, maxKeys)
					if err != nil {
						srvReq.respCh <- ListObjectsResult{}
						return
					}
					reqToListWorkerReqCh[reqHash] = listWorkerReqCh
				}
				respCh := make(chan ListObjectsResult)
				listWorkerReqCh <- listWorkerReq{respCh}
				resp, ok := <-respCh
				if !ok {
					srvReq.respCh <- ListObjectsResult{}
					return
				}
				delete(reqToListWorkerReqCh, reqHash)
				if !resp.IsTruncated {
					close(listWorkerReqCh)
				} else {
					nextMarker := resp.NextMarker
					reqHash = fnvSum(bucket, prefix, nextMarker, delimiter)
					reqToListWorkerReqCh[reqHash] = listWorkerReqCh
				}
				srvReq.respCh <- resp
			}
		}
	}()
	return nil
}

// ListObjects - lists all objects for a given prefix, returns upto
// maxKeys number of objects per call.
func (fs Filesystem) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) {
	fs.lock.Lock()
	defer fs.lock.Unlock()

	if !IsValidBucketName(bucket) {
		return ListObjectsResult{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
	}

	bucket = fs.denormalizeBucket(bucket)
	rootPrefix := filepath.Join(fs.path, bucket)
	// check bucket exists
	if _, e := os.Stat(rootPrefix); e != nil {
		if os.IsNotExist(e) {
			return ListObjectsResult{}, probe.NewError(BucketNotFound{Bucket: bucket})
		}
		return ListObjectsResult{}, probe.NewError(e)
	}

	reqParams := listObjectsParams{}
	reqParams.Bucket = bucket
	reqParams.Prefix = filepath.FromSlash(prefix)
	reqParams.Marker = filepath.FromSlash(marker)
	reqParams.Delimiter = filepath.FromSlash(delimiter)
	reqParams.MaxKeys = maxKeys

	respCh := make(chan ListObjectsResult)
	fs.listServiceReqCh <- listServiceReq{reqParams, respCh}
	resp := <-respCh

	for i := range resp.Prefixes {
		resp.Prefixes[i] = filepath.ToSlash(resp.Prefixes[i])
	}
	for i := range resp.Objects {
		resp.Objects[i].Object = filepath.ToSlash(resp.Objects[i].Object)
	}
	if reqParams.Delimiter == "" {
		// This element is set only if you have delimiter set.
		// If response does not include the NextMaker and it is
		// truncated, you can use the value of the last Key in the
		// response as the marker in the subsequent request to get the
		// next set of object keys.
		resp.NextMarker = ""
	}
	return resp, nil
}
<commit_msg>listObjects: Marker should be unescaped before being used internally.<commit_after>/*
 * Minio Cloud Storage, (C) 2015-2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or 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 fs

import (
	"errors"
	"hash/fnv"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/minio/minio-xl/pkg/probe"
)

// listObjectsParams - list objects input parameters.
type listObjectsParams struct {
	// Bucket name to list the objects for.
	Bucket string
	// list all objects with this parameter as common prefix.
	Prefix string
	// list all objects starting with object after marker in
	// lexicographical order.
	Marker string
	// list all objects until the first occurrence of the delimtier
	// after the prefix.
	Delimiter string
	// maximum number of objects returned per listObjects()
	// operation.
	MaxKeys int
}

// listServiceReq
type listServiceReq struct {
	reqParams listObjectsParams
	respCh    chan ListObjectsResult
}

type listWorkerReq struct {
	respCh chan ListObjectsResult
}

// listObjects - list objects lists objects upto maxKeys for a given prefix.
func (fs Filesystem) listObjects(bucket, prefix, marker, delimiter string, maxKeys int) (chan<- listWorkerReq, *probe.Error) {
	quitWalker := make(chan bool)
	reqCh := make(chan listWorkerReq)
	walkerCh := make(chan ObjectMetadata)
	go func() {
		defer close(walkerCh)
		var walkPath string
		bucketPath := filepath.Join(fs.path, bucket)
		// Bucket path prefix should always end with a separator.
		bucketPathPrefix := bucketPath + string(os.PathSeparator)
		prefixPath := bucketPathPrefix + prefix
		st, err := os.Stat(prefixPath)
		if err != nil && os.IsNotExist(err) {
			walkPath = bucketPath
		} else {
			if st.IsDir() && !strings.HasSuffix(prefix, delimiter) {
				walkPath = bucketPath
			} else {
				walkPath = prefixPath
			}
		}
		Walk(walkPath, func(path string, info os.FileInfo, err error) error {
			// We don't need to list the walk path.
			if path == walkPath {
				return nil
			}
			// For all incoming directories add a ending separator.
			if info.IsDir() {
				path = path + string(os.PathSeparator)
			}
			// Extract object name.
			objectName := strings.TrimPrefix(path, bucketPathPrefix)
			if strings.HasPrefix(objectName, prefix) {
				// For objectName lesser than marker, ignore.
				if marker >= objectName {
					return nil
				}
				object := ObjectMetadata{
					Object:  objectName,
					Created: info.ModTime(),
					Mode:    info.Mode(),
					Size:    info.Size(),
				}
				select {
				// Send object on walker channel.
				case walkerCh <- object:
				case <-quitWalker:
					// Returning error ends the file tree Walk().
					return errors.New("Quit list worker.")
				}
				// If delimiter is set, we stop if current path is a
				// directory.
				if delimiter != "" && info.IsDir() {
					return ErrSkipDir
				}
			}
			return nil
		})
	}()

	go func() {
		for {
			select {
			// Timeout after 10 seconds if request did not arrive for
			// the given list parameters.
			case <-time.After(10 * time.Second):
				quitWalker <- true // Quit file path walk if running.
				// Send back the hash for this request.
				fs.timeoutReqCh <- fnvSum(bucket, prefix, marker, delimiter)
				return
			case req, ok := <-reqCh:
				if !ok {
					// If the request channel is closed, no more
					// requests return here.
					return
				}
				resp := ListObjectsResult{}
				var count int
				for object := range walkerCh {
					if count == maxKeys {
						resp.IsTruncated = true
						break
					}
					// If object is a directory.
					if object.Mode.IsDir() {
						if delimiter == "" {
							// Skip directories for recursive listing.
							continue
						}
						resp.Prefixes = append(resp.Prefixes, object.Object)
					} else {
						resp.Objects = append(resp.Objects, object)
					}
					// Set the next marker for the next request.
					resp.NextMarker = object.Object
					count++
				}
				req.respCh <- resp
			}
		}
	}()
	return reqCh, nil
}

// fnvSum calculates a hash for concatenation of all input strings.
func fnvSum(elements ...string) uint32 {
	fnvHash := fnv.New32a()
	for _, element := range elements {
		fnvHash.Write([]byte(element))
	}
	return fnvHash.Sum32()
}

// listObjectsService - list objects service manages various incoming
// list object requests by delegating them to an existing listObjects
// routine or initializes a new listObjects routine.
func (fs *Filesystem) listObjectsService() *probe.Error {
	// Initialize list service request channel.
	listServiceReqCh := make(chan listServiceReq)
	fs.listServiceReqCh = listServiceReqCh

	// Initialize timeout request channel to receive request hashes of
	// timed-out requests.
	timeoutReqCh := make(chan uint32)
	fs.timeoutReqCh = timeoutReqCh

	// Initialize request hash to list worker map.
	reqToListWorkerReqCh := make(map[uint32]chan<- listWorkerReq)

	// Start service in a go routine.
	go func() {
		for {
			select {
			case reqHash := <-timeoutReqCh:
				// For requests which have timed-out, close the worker
				// channels proactively, this may happen for idle
				// workers once in 10seconds.
				listWorkerReqCh, ok := reqToListWorkerReqCh[reqHash]
				if ok {
					close(listWorkerReqCh)
				}
				delete(reqToListWorkerReqCh, reqHash)
			case srvReq := <-listServiceReqCh:
				// Save the params for readability.
				bucket := srvReq.reqParams.Bucket
				prefix := srvReq.reqParams.Prefix
				marker := srvReq.reqParams.Marker
				delimiter := srvReq.reqParams.Delimiter
				maxKeys := srvReq.reqParams.MaxKeys

				// Generate hash.
				reqHash := fnvSum(bucket, prefix, marker, delimiter)
				listWorkerReqCh, ok := reqToListWorkerReqCh[reqHash]
				if !ok {
					var err *probe.Error
					listWorkerReqCh, err = fs.listObjects(bucket, prefix, marker, delimiter, maxKeys)
					if err != nil {
						srvReq.respCh <- ListObjectsResult{}
						return
					}
					reqToListWorkerReqCh[reqHash] = listWorkerReqCh
				}
				respCh := make(chan ListObjectsResult)
				listWorkerReqCh <- listWorkerReq{respCh}
				resp, ok := <-respCh
				if !ok {
					srvReq.respCh <- ListObjectsResult{}
					return
				}
				delete(reqToListWorkerReqCh, reqHash)
				if !resp.IsTruncated {
					close(listWorkerReqCh)
				} else {
					nextMarker := resp.NextMarker
					reqHash = fnvSum(bucket, prefix, nextMarker, delimiter)
					reqToListWorkerReqCh[reqHash] = listWorkerReqCh
				}
				srvReq.respCh <- resp
			}
		}
	}()
	return nil
}

// ListObjects - lists all objects for a given prefix, returns upto
// maxKeys number of objects per call.
func (fs Filesystem) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) {
	fs.lock.Lock()
	defer fs.lock.Unlock()

	if !IsValidBucketName(bucket) {
		return ListObjectsResult{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
	}

	bucket = fs.denormalizeBucket(bucket)
	rootPrefix := filepath.Join(fs.path, bucket)
	// check bucket exists
	if _, e := os.Stat(rootPrefix); e != nil {
		if os.IsNotExist(e) {
			return ListObjectsResult{}, probe.NewError(BucketNotFound{Bucket: bucket})
		}
		return ListObjectsResult{}, probe.NewError(e)
	}

	// Unescape the marker values.
	markerUnescaped, e := url.QueryUnescape(marker)
	if e != nil {
		return ListObjectsResult{}, probe.NewError(e)
	}

	reqParams := listObjectsParams{}
	reqParams.Bucket = bucket
	reqParams.Prefix = filepath.FromSlash(prefix)
	reqParams.Marker = filepath.FromSlash(markerUnescaped)
	reqParams.Delimiter = filepath.FromSlash(delimiter)
	reqParams.MaxKeys = maxKeys

	respCh := make(chan ListObjectsResult)
	fs.listServiceReqCh <- listServiceReq{reqParams, respCh}
	resp := <-respCh

	for i := range resp.Prefixes {
		resp.Prefixes[i] = filepath.ToSlash(resp.Prefixes[i])
	}
	for i := range resp.Objects {
		resp.Objects[i].Object = filepath.ToSlash(resp.Objects[i].Object)
	}
	if reqParams.Delimiter == "" {
		// This element is set only if you have delimiter set.
		// If response does not include the NextMaker and it is
		// truncated, you can use the value of the last Key in the
		// response as the marker in the subsequent request to get the
		// next set of object keys.
		resp.NextMarker = ""
	}
	return resp, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/opencontainers/runtime-spec/specs-go"
	log "github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

// Note these definitions are from moby/tool/src/moby/config.go and should be kept in sync

// Runtime is the type of config processed at runtime, not used to build the OCI spec
type Runtime struct {
	Cgroups    []string      `yaml:"cgroups" json:"cgroups,omitempty"`
	Mounts     []specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
	Mkdir      []string      `yaml:"mkdir" json:"mkdir,omitempty"`
	Interfaces []Interface   `yaml:"interfaces" json:"interfaces,omitempty"`
	BindNS     Namespaces    `yaml:"bindNS" json:"bindNS,omitempty"`
}

// Namespaces is the type for configuring paths to bind namespaces
type Namespaces struct {
	Cgroup string `yaml:"cgroup" json:"cgroup,omitempty"`
	Ipc    string `yaml:"ipc" json:"ipc,omitempty"`
	Mnt    string `yaml:"mnt" json:"mnt,omitempty"`
	Net    string `yaml:"net" json:"net,omitempty"`
	Pid    string `yaml:"pid" json:"pid,omitempty"`
	User   string `yaml:"user" json:"user,omitempty"`
	Uts    string `yaml:"uts" json:"uts,omitempty"`
}

// Interface is the runtime config for network interfaces
type Interface struct {
	Name         string `yaml:"name" json:"name,omitempty"`
	Add          string `yaml:"add" json:"add,omitempty"`
	Peer         string `yaml:"peer" json:"peer,omitempty"`
	CreateInRoot bool   `yaml:"createInRoot" json:"createInRoot"`
}

func getRuntimeConfig(path string) Runtime {
	var runtime Runtime
	conf, err := ioutil.ReadFile(filepath.Join(path, "runtime.json"))
	if err != nil {
		// if it does not exist it is fine to return an empty runtime, to not do anything
		if os.IsNotExist(err) {
			return runtime
		}
		log.Fatalf("Cannot read runtime config: %v", err)
	}
	if err := json.Unmarshal(conf, &runtime); err != nil {
		log.Fatalf("Cannot parse runtime config: %v", err)
	}
	return runtime
}

// parseMountOptions takes fstab style mount options and parses them for
// use with a standard mount() syscall
func parseMountOptions(options []string) (int, string) {
	var (
		flag int
		data []string
	)
	flags := map[string]struct {
		clear bool
		flag  int
	}{
		"async":         {true, unix.MS_SYNCHRONOUS},
		"atime":         {true, unix.MS_NOATIME},
		"bind":          {false, unix.MS_BIND},
		"defaults":      {false, 0},
		"dev":           {true, unix.MS_NODEV},
		"diratime":      {true, unix.MS_NODIRATIME},
		"dirsync":       {false, unix.MS_DIRSYNC},
		"exec":          {true, unix.MS_NOEXEC},
		"mand":          {false, unix.MS_MANDLOCK},
		"noatime":       {false, unix.MS_NOATIME},
		"nodev":         {false, unix.MS_NODEV},
		"nodiratime":    {false, unix.MS_NODIRATIME},
		"noexec":        {false, unix.MS_NOEXEC},
		"nomand":        {true, unix.MS_MANDLOCK},
		"norelatime":    {true, unix.MS_RELATIME},
		"nostrictatime": {true, unix.MS_STRICTATIME},
		"nosuid":        {false, unix.MS_NOSUID},
		"private":       {false, unix.MS_PRIVATE},
		"rbind":         {false, unix.MS_BIND | unix.MS_REC},
		"relatime":      {false, unix.MS_RELATIME},
		"remount":       {false, unix.MS_REMOUNT},
		"ro":            {false, unix.MS_RDONLY},
		"rw":            {true, unix.MS_RDONLY},
		"shared":        {false, unix.MS_SHARED},
		"slave":         {false, unix.MS_SLAVE},
		"strictatime":   {false, unix.MS_STRICTATIME},
		"suid":          {true, unix.MS_NOSUID},
		"sync":          {false, unix.MS_SYNCHRONOUS},
		"unbindable":    {false, unix.MS_UNBINDABLE},
	}
	for _, o := range options {
		// If the option does not exist in the flags table or the flag
		// is not supported on the platform,
		// then it is a data value for a specific fs type
		if f, exists := flags[o]; exists && f.flag != 0 {
			if f.clear {
				flag &^= f.flag
			} else {
				flag |= f.flag
			}
		} else {
			data = append(data, o)
		}
	}
	return flag, strings.Join(data, ",")
}

// newCgroup creates a cgroup (ie directory) under all directories in /sys/fs/cgroup
// we could use github.com/containerd/cgroups but it has a lot of deps and this is just a sugary mkdir
func newCgroup(cgroup string) error {
	dirs, err := ioutil.ReadDir("/sys/fs/cgroup")
	if err != nil {
		return err
	}

	for _, dir := range dirs {
		if !dir.IsDir() {
			continue
		}
		if err := os.MkdirAll(filepath.Join("/sys/fs/cgroup", dir.Name(), cgroup), 0755); err != nil {
			log.Printf("cgroup error: %v", err)
		}
	}

	return nil
}

// prepareFilesystem sets up the mounts and cgroups, before the container is created
func prepareFilesystem(path string, runtime Runtime) error {
	// execute the runtime config that should be done up front
	// we execute Mounts before Mkdir so you can make a directory under a mount
	// but we do mkdir of the destination path in case missing
	for _, mount := range runtime.Mounts {
		const mode os.FileMode = 0755
		err := os.MkdirAll(mount.Destination, mode)
		if err != nil {
			return fmt.Errorf("Cannot create directory for mount destination %s: %v", mount.Destination, err)
		}
		// also mkdir upper and work directories on overlay
		for _, o := range mount.Options {
			eq := strings.SplitN(o, "=", 2)
			if len(eq) == 2 && (eq[0] == "upperdir" || eq[0] == "workdir") {
				err := os.MkdirAll(eq[1], mode)
				if err != nil {
					return fmt.Errorf("Cannot create directory for overlay %s=%s: %v", eq[0], eq[1], err)
				}
			}
		}
		opts, data := parseMountOptions(mount.Options)
		if err := unix.Mount(mount.Source, mount.Destination, mount.Type, uintptr(opts), data); err != nil {
			return fmt.Errorf("Failed to mount %s: %v", mount.Source, err)
		}
	}
	for _, dir := range runtime.Mkdir {
		// in future we may need to change the structure to set mode, ownership
		const mode os.FileMode = 0755
		err := os.MkdirAll(dir, mode)
		if err != nil {
			return fmt.Errorf("Cannot create directory %s: %v", dir, err)
		}
	}

	for _, cgroup := range runtime.Cgroups {
		// currently no way to specify resource limits on new cgroups at creation time
		if err := newCgroup(cgroup); err != nil {
			return fmt.Errorf("Cannot create cgroup %s: %v", cgroup, err)
		}
	}

	return nil
}

// bind mount a namespace file
func bindNS(ns string, path string, pid int) error {
	if path == "" {
		return nil
	}
	// the path and file need to exist for the bind to succeed, so try to create
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("Cannot create leading directories %s for bind mount destination: %v", dir, err)
	}
	fi, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("Cannot create a mount point for namespace bind at %s: %v", path, err)
	}
	if err := fi.Close(); err != nil {
		return err
	}
	if err := unix.Mount(fmt.Sprintf("/proc/%d/ns/%s", pid, ns), path, "", unix.MS_BIND, ""); err != nil {
		return fmt.Errorf("Failed to bind %s namespace at %s: %v", ns, path, err)
	}
	return nil
}

// prepareProcess sets up anything that needs to be done after the container process is created, but before it runs
// for example networking
func prepareProcess(pid int, runtime Runtime) error {
	for _, iface := range runtime.Interfaces {
		if iface.Name == "" {
			return fmt.Errorf("Interface requires a name")
		}

		var link netlink.Link
		var ns interface{} = netlink.NsPid(pid)
		var move bool
		var err error

		if iface.Peer != "" && iface.Add == "" {
			// must be a veth if specify peer
			iface.Add = "veth"
		}

		// if create in root is set, create in root namespace first, then move
		// also do the same for a veth pair
		if iface.CreateInRoot || iface.Add == "veth" {
			ns = nil
			move = true
		}

		if iface.Add != "" {
			switch iface.Add {
			case "veth":
				if iface.Peer == "" {
					return fmt.Errorf("Creating a veth pair %s requires a peer to be set", iface.Name)
				}
				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns}
				link = &netlink.Veth{LinkAttrs: la, PeerName: iface.Peer}
			default:
				// no special creation options needed
				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns}
				link = &netlink.GenericLink{la, iface.Add}
			}
			if err := netlink.LinkAdd(link); err != nil {
				return fmt.Errorf("Link add %s of type %s failed: %v", iface.Name, iface.Add, err)
			}
			fmt.Fprintf(os.Stderr, "Created interface %s type %s\n", iface.Name, iface.Add)
		} else {
			// find existing interface
			link, err = netlink.LinkByName(iface.Name)
			if err != nil {
				return fmt.Errorf("Cannot find interface %s: %v", iface.Name, err)
			}
			// then move into namespace
			move = true
		}
		if move {
			if err := netlink.LinkSetNsPid(link, int(pid)); err != nil {
				return fmt.Errorf("Cannot move interface %s into namespace: %v", iface.Name, err)
			}
			fmt.Fprintf(os.Stderr, "Moved interface %s to pid %d\n", iface.Name, pid)
		}
	}

	binds := []struct {
		ns   string
		path string
	}{
		{"cgroup", runtime.BindNS.Cgroup},
		{"ipc", runtime.BindNS.Ipc},
		{"mnt", runtime.BindNS.Mnt},
		{"net", runtime.BindNS.Net},
		{"pid", runtime.BindNS.Pid},
		{"user", runtime.BindNS.User},
		{"uts", runtime.BindNS.Uts},
	}

	for _, b := range binds {
		if err := bindNS(b.ns, b.path, pid); err != nil {
			return err
		}
	}

	return nil
}

// cleanup functions are best efforts only, mainly for rw onboot containers
func cleanup(path string) {
	// remove the root mount
	rootfs := filepath.Join(path, "rootfs")
	_ = unix.Unmount(rootfs, 0)
}
<commit_msg>Make relative paths in runtime mkdir be relative to container rootfs<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/opencontainers/runtime-spec/specs-go"
	log "github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

// Note these definitions are from moby/tool/src/moby/config.go and should be kept in sync

// Runtime is the type of config processed at runtime, not used to build the OCI spec
type Runtime struct {
	Cgroups    []string      `yaml:"cgroups" json:"cgroups,omitempty"`
	Mounts     []specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
	Mkdir      []string      `yaml:"mkdir" json:"mkdir,omitempty"`
	Interfaces []Interface   `yaml:"interfaces" json:"interfaces,omitempty"`
	BindNS     Namespaces    `yaml:"bindNS" json:"bindNS,omitempty"`
}

// Namespaces is the type for configuring paths to bind namespaces
type Namespaces struct {
	Cgroup string `yaml:"cgroup" json:"cgroup,omitempty"`
	Ipc    string `yaml:"ipc" json:"ipc,omitempty"`
	Mnt    string `yaml:"mnt" json:"mnt,omitempty"`
	Net    string `yaml:"net" json:"net,omitempty"`
	Pid    string `yaml:"pid" json:"pid,omitempty"`
	User   string `yaml:"user" json:"user,omitempty"`
	Uts    string `yaml:"uts" json:"uts,omitempty"`
}

// Interface is the runtime config for network interfaces
type Interface struct {
	Name         string `yaml:"name" json:"name,omitempty"`
	Add          string `yaml:"add" json:"add,omitempty"`
	Peer         string `yaml:"peer" json:"peer,omitempty"`
	CreateInRoot bool   `yaml:"createInRoot" json:"createInRoot"`
}

func getRuntimeConfig(path string) Runtime {
	var runtime Runtime
	conf, err := ioutil.ReadFile(filepath.Join(path, "runtime.json"))
	if err != nil {
		// if it does not exist it is fine to return an empty runtime, to not do anything
		if os.IsNotExist(err) {
			return runtime
		}
		log.Fatalf("Cannot read runtime config: %v", err)
	}
	if err := json.Unmarshal(conf, &runtime); err != nil {
		log.Fatalf("Cannot parse runtime config: %v", err)
	}
	return runtime
}

// parseMountOptions takes fstab style mount options and parses them for
// use with a standard mount() syscall
func parseMountOptions(options []string) (int, string) {
	var (
		flag int
		data []string
	)
	flags := map[string]struct {
		clear bool
		flag  int
	}{
		"async":         {true, unix.MS_SYNCHRONOUS},
		"atime":         {true, unix.MS_NOATIME},
		"bind":          {false, unix.MS_BIND},
		"defaults":      {false, 0},
		"dev":           {true, unix.MS_NODEV},
		"diratime":      {true, unix.MS_NODIRATIME},
		"dirsync":       {false, unix.MS_DIRSYNC},
		"exec":          {true, unix.MS_NOEXEC},
		"mand":          {false, unix.MS_MANDLOCK},
		"noatime":       {false, unix.MS_NOATIME},
		"nodev":         {false, unix.MS_NODEV},
		"nodiratime":    {false, unix.MS_NODIRATIME},
		"noexec":        {false, unix.MS_NOEXEC},
		"nomand":        {true, unix.MS_MANDLOCK},
		"norelatime":    {true, unix.MS_RELATIME},
		"nostrictatime": {true, unix.MS_STRICTATIME},
		"nosuid":        {false, unix.MS_NOSUID},
		"private":       {false, unix.MS_PRIVATE},
		"rbind":         {false, unix.MS_BIND | unix.MS_REC},
		"relatime":      {false, unix.MS_RELATIME},
		"remount":       {false, unix.MS_REMOUNT},
		"ro":            {false, unix.MS_RDONLY},
		"rw":            {true, unix.MS_RDONLY},
		"shared":        {false, unix.MS_SHARED},
		"slave":         {false, unix.MS_SLAVE},
		"strictatime":   {false, unix.MS_STRICTATIME},
		"suid":          {true, unix.MS_NOSUID},
		"sync":          {false, unix.MS_SYNCHRONOUS},
		"unbindable":    {false, unix.MS_UNBINDABLE},
	}
	for _, o := range options {
		// If the option does not exist in the flags table or the flag
		// is not supported on the platform,
		// then it is a data value for a specific fs type
		if f, exists := flags[o]; exists && f.flag != 0 {
			if f.clear {
				flag &^= f.flag
			} else {
				flag |= f.flag
			}
		} else {
			data = append(data, o)
		}
	}
	return flag, strings.Join(data, ",")
}

// newCgroup creates a cgroup (ie directory) under all directories in /sys/fs/cgroup
// we could use github.com/containerd/cgroups but it has a lot of deps and this is just a sugary mkdir
func newCgroup(cgroup string) error {
	dirs, err := ioutil.ReadDir("/sys/fs/cgroup")
	if err != nil {
		return err
	}

	for _, dir := range dirs {
		if !dir.IsDir() {
			continue
		}
		if err := os.MkdirAll(filepath.Join("/sys/fs/cgroup", dir.Name(), cgroup), 0755); err != nil {
			log.Printf("cgroup error: %v", err)
		}
	}

	return nil
}

// prepareFilesystem sets up the mounts and cgroups, before the container is created
func prepareFilesystem(path string, runtime Runtime) error {
	// execute the runtime config that should be done up front
	// we execute Mounts before Mkdir so you can make a directory under a mount
	// but we do mkdir of the destination path in case missing
	rootfs := filepath.Join(path, "rootfs")
	for _, mount := range runtime.Mounts {
		const mode os.FileMode = 0755
		err := os.MkdirAll(mount.Destination, mode)
		if err != nil {
			return fmt.Errorf("Cannot create directory for mount destination %s: %v", mount.Destination, err)
		}
		// also mkdir upper and work directories on overlay
		for _, o := range mount.Options {
			eq := strings.SplitN(o, "=", 2)
			if len(eq) == 2 && (eq[0] == "upperdir" || eq[0] == "workdir") {
				err := os.MkdirAll(eq[1], mode)
				if err != nil {
					return fmt.Errorf("Cannot create directory for overlay %s=%s: %v", eq[0], eq[1], err)
				}
			}
		}
		opts, data := parseMountOptions(mount.Options)
		if err := unix.Mount(mount.Source, mount.Destination, mount.Type, uintptr(opts), data); err != nil {
			return fmt.Errorf("Failed to mount %s: %v", mount.Source, err)
		}
	}
	for _, dir := range runtime.Mkdir {
		// in future we may need to change the structure to set mode, ownership
		const mode os.FileMode = 0755
		// relative paths are relative to rootfs of container
		if !filepath.IsAbs(dir) {
			dir = filepath.Join(rootfs, dir)
		}
		err := os.MkdirAll(dir, mode)
		if err != nil {
			return fmt.Errorf("Cannot create directory %s: %v", dir, err)
		}
	}

	for _, cgroup := range runtime.Cgroups {
		// currently no way to specify resource limits on new cgroups at creation time
		if err := newCgroup(cgroup); err != nil {
			return fmt.Errorf("Cannot create cgroup %s: %v", cgroup, err)
		}
	}

	return nil
}

// bind mount a namespace file
func bindNS(ns string, path string, pid int) error {
	if path == "" {
		return nil
	}
	// the path and file need to exist for the bind to succeed, so try to create
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("Cannot create leading directories %s for bind mount destination: %v", dir, err)
	}
	fi, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("Cannot create a mount point for namespace bind at %s: %v", path, err)
	}
	if err := fi.Close(); err != nil {
		return err
	}
	if err := unix.Mount(fmt.Sprintf("/proc/%d/ns/%s", pid, ns), path, "", unix.MS_BIND, ""); err != nil {
		return fmt.Errorf("Failed to bind %s namespace at %s: %v", ns, path, err)
	}
	return nil
}

// prepareProcess sets up anything that needs to be done after the container process is created, but before it runs
// for example networking
func prepareProcess(pid int, runtime Runtime) error {
	for _, iface := range runtime.Interfaces {
		if iface.Name == "" {
			return fmt.Errorf("Interface requires a name")
		}

		var link netlink.Link
		var ns interface{} = netlink.NsPid(pid)
		var move bool
		var err error

		if iface.Peer != "" && iface.Add == "" {
			// must be a veth if specify peer
			iface.Add = "veth"
		}

		// if create in root is set, create in root namespace first, then move
		// also do the same for a veth pair
		if iface.CreateInRoot || iface.Add == "veth" {
			ns = nil
			move = true
		}

		if iface.Add != "" {
			switch iface.Add {
			case "veth":
				if iface.Peer == "" {
					return fmt.Errorf("Creating a veth pair %s requires a peer to be set", iface.Name)
				}
				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns}
				link = &netlink.Veth{LinkAttrs: la, PeerName: iface.Peer}
			default:
				// no special creation options needed
				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns}
				link = &netlink.GenericLink{la, iface.Add}
			}
			if err := netlink.LinkAdd(link); err != nil {
				return fmt.Errorf("Link add %s of type %s failed: %v", iface.Name, iface.Add, err)
			}
			fmt.Fprintf(os.Stderr, "Created interface %s type %s\n", iface.Name, iface.Add)
		} else {
			// find existing interface
			link, err = netlink.LinkByName(iface.Name)
			if err != nil {
				return fmt.Errorf("Cannot find interface %s: %v", iface.Name, err)
			}
			// then move into namespace
			move = true
		}
		if move {
			if err := netlink.LinkSetNsPid(link, int(pid)); err != nil {
				return fmt.Errorf("Cannot move interface %s into namespace: %v", iface.Name, err)
			}
			fmt.Fprintf(os.Stderr, "Moved interface %s to pid %d\n", iface.Name, pid)
		}
	}

	binds := []struct {
		ns   string
		path string
	}{
		{"cgroup", runtime.BindNS.Cgroup},
		{"ipc", runtime.BindNS.Ipc},
		{"mnt", runtime.BindNS.Mnt},
		{"net", runtime.BindNS.Net},
		{"pid", runtime.BindNS.Pid},
		{"user", runtime.BindNS.User},
		{"uts", runtime.BindNS.Uts},
	}

	for _, b := range binds {
		if err := bindNS(b.ns, b.path, pid); err != nil {
			return err
		}
	}

	return nil
}

// cleanup functions are best efforts only, mainly for rw onboot containers
func cleanup(path string) {
	// remove the root mount
	rootfs := filepath.Join(path, "rootfs")
	_ = unix.Unmount(rootfs, 0)
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2016 Red Hat, Inc.

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

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

Unless required by applicable law or 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 cluster

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/machine/drivers/virtualbox"
	"github.com/docker/machine/libmachine"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/host"
	"github.com/docker/machine/libmachine/state"
	"github.com/golang/glog"
	"github.com/jimmidyson/minishift/pkg/minikube/constants"
	"github.com/jimmidyson/minishift/pkg/minikube/sshutil"
	"github.com/jimmidyson/minishift/pkg/util"
	kubeApi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)

var (
	certs = []string{"apiserver.crt", "apiserver.key"}
)

//This init function is used to set the logtostderr variable to false so that INFO level log info does not clutter the CLI
//INFO lvl logging is displayed due to the kubernetes api calling flag.Set("logtostderr", "true") in its init()
//see: https://github.com/kubernetes/kubernetes/blob/master/pkg/util/logs.go#L32-34
func init() {
	flag.Set("logtostderr", "false")
}

// StartHost starts a host VM.
func StartHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
	if exists, err := api.Exists(constants.MachineName); err != nil {
		return nil, fmt.Errorf("Error checking if host exists: %s", err)
	} else if exists {
		glog.Infoln("Machine exists!")
		h, err := api.Load(constants.MachineName)
		if err != nil {
			return nil, fmt.Errorf("Error loading existing host: %s", err)
		}
		s, err := h.Driver.GetState()
		if err != nil {
			return nil, fmt.Errorf("Error getting state for host: %s", err)
		}
		if s != state.Running {
			if err := h.Driver.Start(); err != nil {
				return nil, fmt.Errorf("Error starting stopped host: %s", err)
			}
			if err := api.Save(h); err != nil {
				return nil, fmt.Errorf("Error saving started host: %s", err)
			}
		}
		if err := h.ConfigureAuth(); err != nil {
			return nil, fmt.Errorf("Error configuring auth on host: %s", err)
		}
		return h, nil
	} else {
		return createHost(api, config)
	}
}

// StopHost stops the host VM.
func StopHost(api libmachine.API) error {
	host, err := api.Load(constants.MachineName)
	if err != nil {
		return err
	}
	if err := host.Stop(); err != nil {
		return err
	}
	return nil
}

type multiError struct {
	Errors []error
}

func (m *multiError) Collect(err error) {
	if err != nil {
		m.Errors = append(m.Errors, err)
	}
}

func (m multiError) ToError() error {
	if len(m.Errors) == 0 {
		return nil
	}

	errStrings := []string{}
	for _, err := range m.Errors {
		errStrings = append(errStrings, err.Error())
	}
	return fmt.Errorf(strings.Join(errStrings, "\n"))
}

// DeleteHost deletes the host VM.
func DeleteHost(api libmachine.API) error {
	host, err := api.Load(constants.MachineName)
	if err != nil {
		return err
	}
	m := multiError{}
	m.Collect(host.Driver.Remove())
	m.Collect(api.Remove(constants.MachineName))
	return m.ToError()
}

// GetHostStatus gets the status of the host VM.
func GetHostStatus(api libmachine.API) (string, error) {
	dne := "Does Not Exist"
	exists, err := api.Exists(constants.MachineName)
	if err != nil {
		return "", err
	}
	if !exists {
		return dne, nil
	}

	host, err := api.Load(constants.MachineName)
	if err != nil {
		return "", err
	}

	s, err := host.Driver.GetState()
	if s.String() == "" {
		return dne, err
	}
	return s.String(), err
}

type sshAble interface {
	RunSSHCommand(string) (string, error)
}

// MachineConfig contains the parameters used to start a cluster.
type MachineConfig struct {
	MinikubeISO string
	Memory      int
	CPUs        int
	DiskSize    int
	VMDriver    string
}

// StartCluster starts a k8s cluster on the specified Host.
func StartCluster(h sshAble, ip string) error {
	commands := []string{stopCommand, GetStartCommand(ip)}

	for _, cmd := range commands {
		glog.Infoln(cmd)
		output, err := h.RunSSHCommand(cmd)
		glog.Infoln(output)
		if err != nil {
			return err
		}
	}

	return nil
}

type fileToCopy struct {
	AssetName   string
	TargetDir   string
	TargetName  string
	Permissions string
}

var assets = []fileToCopy{
	{
		AssetName:   "out/openshift",
		TargetDir:   "/usr/local/bin",
		TargetName:  "openshift",
		Permissions: "0777",
	},
}

func UpdateCluster(d drivers.Driver) error {
	client, err := sshutil.NewSSHClient(d)
	if err != nil {
		return err
	}

	for _, a := range assets {
		contents, err := Asset(a.AssetName)
		if err != nil {
			glog.Infof("Error loading asset %s: %s", a.AssetName, err)
			return err
		}

		if err := sshutil.Transfer(contents, a.TargetDir, a.TargetName, a.Permissions, client); err != nil {
			return err
		}
	}
	return nil
}

// SetupCerts gets the generated credentials required to talk to the APIServer.
func SetupCerts(d drivers.Driver) error {
	localPath := constants.Minipath
	ipStr, err := d.GetIP()
	if err != nil {
		return err
	}
	glog.Infoln("Setting up certificates for IP: %s", ipStr)

	ip := net.ParseIP(ipStr)
	publicPath := filepath.Join(localPath, "apiserver.crt")
	privatePath := filepath.Join(localPath, "apiserver.key")
	if err := GenerateCerts(publicPath, privatePath, ip); err != nil {
		return err
	}

	client, err := sshutil.NewSSHClient(d)
	if err != nil {
		return err
	}

	for _, cert := range certs {
		p := filepath.Join(localPath, cert)
		data, err := ioutil.ReadFile(p)
		if err != nil {
			return err
		}
		if err := sshutil.Transfer(data, util.DefaultCertPath, cert, "0644", client); err != nil {
			return err
		}
	}
	return nil
}

func createHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
	var driver interface{}

	switch config.VMDriver {
	case "virtualbox":
		d := virtualbox.NewDriver(constants.MachineName, constants.Minipath)
		d.Boot2DockerURL = config.MinikubeISO
		d.Memory = config.Memory
		d.CPU = config.CPUs
		d.DiskSize = int(config.DiskSize)
		driver = d
	case "vmwarefusion":
		driver = createVMwareFusionHost(config)
	case "kvm":
		driver = createKVMHost(config)
	case "xhyve":
		driver = createXhyveHost(config)
	case "hyperv":
		driver = createHypervHost(config)
	default:
		glog.Exitf("Unsupported driver: %s\n", config.VMDriver)
	}

	data, err := json.Marshal(driver)
	if err != nil {
		return nil, err
	}

	h, err := api.NewHost(config.VMDriver, data)
	if err != nil {
		return nil, fmt.Errorf("Error creating new host: %s", err)
	}

	h.HostOptions.AuthOptions.CertDir = constants.Minipath
	h.HostOptions.AuthOptions.StorePath = constants.Minipath
	h.HostOptions.EngineOptions = &engine.Options{}

	if err := api.Create(h); err != nil {
		// Wait for all the logs to reach the client
		time.Sleep(2 * time.Second)
		return nil, fmt.Errorf("Error creating. %s", err)
	}

	if err := api.Save(h); err != nil {
		return nil, fmt.Errorf("Error attempting to save store: %s", err)
	}
	return h, nil
}

// GetHostDockerEnv gets the necessary docker env variables to allow the use of docker through minikube's vm
func GetHostDockerEnv(api libmachine.API) (map[string]string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return nil, err
	}
	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, err
	}

	tcpPrefix := "tcp://"
	portDelimiter := ":"
	port := "2376"

	envMap := map[string]string{
		"DOCKER_TLS_VERIFY": "1",
		"DOCKER_HOST":       tcpPrefix + ip + portDelimiter + port,
		"DOCKER_CERT_PATH":  constants.MakeMiniPath("certs"),
	}
	return envMap, nil
}

// GetHostLogs gets the openshift logs of the host VM.
func GetHostLogs(api libmachine.API) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}
	s, err := host.RunSSHCommand(logsCommand)
	if err != nil {
		return "", nil
	}
	return s, err
}

func checkIfApiExistsAndLoad(api libmachine.API) (*host.Host, error) {
	exists, err := api.Exists(constants.MachineName)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, fmt.Errorf("Machine does not exist for api.Exists(%s)", constants.MachineName)
	}

	host, err := api.Load(constants.MachineName)
	if err != nil {
		return nil, err
	}
	return host, nil
}

func CreateSSHShell(api libmachine.API, args []string) error {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return err
	}

	currentState, err := host.Driver.GetState()
	if err != nil {
		return err
	}

	if currentState != state.Running {
		return fmt.Errorf("Error: Cannot run ssh command: Host %q is not running", constants.MachineName)
	}

	client, err := host.CreateSSHClient()
	if err != nil {
		return err
	}
	return client.Shell(strings.Join(args, " "))
}

func GetConsoleURL(api libmachine.API) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("https://%s:%d", ip, constants.APIServerPort), nil
}

func GetServiceURL(api libmachine.API, namespace, service string) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return "", err
	}

	port, err := getServicePort(namespace, service)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("http://%s:%d", ip, port), nil
}

type serviceGetter interface {
	Get(name string) (*kubeApi.Service, error)
}

func getServicePort(namespace, service string) (int, error) {
	services, err := getKubernetesServicesWithNamespace(namespace)
	if err != nil {
		return 0, err
	}
	return getServicePortFromServiceGetter(services, service)
}

func getServicePortFromServiceGetter(services serviceGetter, service string) (int, error) {
	svc, err := services.Get(service)
	if err != nil {
		return 0, fmt.Errorf("Error getting %s service: %s", service, err)
	}
	nodePort := 0
	if len(svc.Spec.Ports) > 0 {
		nodePort = int(svc.Spec.Ports[0].NodePort)
	}
	if nodePort == 0 {
		return 0, fmt.Errorf("Service %s does not have a node port. To have one assigned automatically, the service type must be NodePort or LoadBalancer, but this service is of type %s.", service, svc.Spec.Type)
	}
	return nodePort, nil
}

func getKubernetesServicesWithNamespace(namespace string) (serviceGetter, error) {
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	configOverrides := &clientcmd.ConfigOverrides{}
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
	}
	client, err := unversioned.New(config)
	if err != nil {
		return nil, err
	}
	services := client.Services(namespace)
	return services, nil
}
<commit_msg>Minor refactor in cluster.go. Also improve error message.<commit_after>/*
Copyright (C) 2016 Red Hat, Inc.

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

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

Unless required by applicable law or 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 cluster

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/machine/drivers/virtualbox"
	"github.com/docker/machine/libmachine"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/host"
	"github.com/docker/machine/libmachine/state"
	"github.com/golang/glog"
	"github.com/jimmidyson/minishift/pkg/minikube/constants"
	"github.com/jimmidyson/minishift/pkg/minikube/sshutil"
	"github.com/jimmidyson/minishift/pkg/util"
	kubeApi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)

var (
	certs = []string{"apiserver.crt", "apiserver.key"}
)

//This init function is used to set the logtostderr variable to false so that INFO level log info does not clutter the CLI
//INFO lvl logging is displayed due to the kubernetes api calling flag.Set("logtostderr", "true") in its init()
//see: https://github.com/kubernetes/kubernetes/blob/master/pkg/util/logs.go#L32-34
func init() {
	flag.Set("logtostderr", "false")
}

// StartHost starts a host VM.
func StartHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
	exists, err := api.Exists(constants.MachineName)
	if err != nil {
		return nil, fmt.Errorf("Error checking if host exists: %s", err)
	}
	if !exists {
		return createHost(api, config)
	}

	glog.Infoln("Machine exists!")
	h, err := api.Load(constants.MachineName)
	if err != nil {
		return nil, fmt.Errorf(
			"Error loading existing host: %s. Please try running [minikube delete], then run [minikube start] again.", err)
	}

	s, err := h.Driver.GetState()
	glog.Infoln("Machine state: ", s)
	if err != nil {
		return nil, fmt.Errorf("Error getting state for host: %s", err)
	}

	if s != state.Running {
		if err := h.Driver.Start(); err != nil {
			return nil, fmt.Errorf("Error starting stopped host: %s", err)
		}
		if err := api.Save(h); err != nil {
			return nil, fmt.Errorf("Error saving started host: %s", err)
		}
	}

	if err := h.ConfigureAuth(); err != nil {
		return nil, fmt.Errorf("Error configuring auth on host: %s", err)
	}
	return h, nil
}

// StopHost stops the host VM.
func StopHost(api libmachine.API) error {
	host, err := api.Load(constants.MachineName)
	if err != nil {
		return err
	}
	if err := host.Stop(); err != nil {
		return err
	}
	return nil
}

type multiError struct {
	Errors []error
}

func (m *multiError) Collect(err error) {
	if err != nil {
		m.Errors = append(m.Errors, err)
	}
}

func (m multiError) ToError() error {
	if len(m.Errors) == 0 {
		return nil
	}

	errStrings := []string{}
	for _, err := range m.Errors {
		errStrings = append(errStrings, err.Error())
	}
	return fmt.Errorf(strings.Join(errStrings, "\n"))
}

// DeleteHost deletes the host VM.
func DeleteHost(api libmachine.API) error {
	host, err := api.Load(constants.MachineName)
	if err != nil {
		return err
	}
	m := multiError{}
	m.Collect(host.Driver.Remove())
	m.Collect(api.Remove(constants.MachineName))
	return m.ToError()
}

// GetHostStatus gets the status of the host VM.
func GetHostStatus(api libmachine.API) (string, error) {
	dne := "Does Not Exist"
	exists, err := api.Exists(constants.MachineName)
	if err != nil {
		return "", err
	}
	if !exists {
		return dne, nil
	}

	host, err := api.Load(constants.MachineName)
	if err != nil {
		return "", err
	}

	s, err := host.Driver.GetState()
	if s.String() == "" {
		return dne, err
	}
	return s.String(), err
}

type sshAble interface {
	RunSSHCommand(string) (string, error)
}

// MachineConfig contains the parameters used to start a cluster.
type MachineConfig struct {
	MinikubeISO string
	Memory      int
	CPUs        int
	DiskSize    int
	VMDriver    string
}

// StartCluster starts a k8s cluster on the specified Host.
func StartCluster(h sshAble, ip string) error {
	commands := []string{stopCommand, GetStartCommand(ip)}

	for _, cmd := range commands {
		glog.Infoln(cmd)
		output, err := h.RunSSHCommand(cmd)
		glog.Infoln(output)
		if err != nil {
			return err
		}
	}

	return nil
}

type fileToCopy struct {
	AssetName   string
	TargetDir   string
	TargetName  string
	Permissions string
}

var assets = []fileToCopy{
	{
		AssetName:   "out/openshift",
		TargetDir:   "/usr/local/bin",
		TargetName:  "openshift",
		Permissions: "0777",
	},
}

func UpdateCluster(d drivers.Driver) error {
	client, err := sshutil.NewSSHClient(d)
	if err != nil {
		return err
	}

	for _, a := range assets {
		contents, err := Asset(a.AssetName)
		if err != nil {
			glog.Infof("Error loading asset %s: %s", a.AssetName, err)
			return err
		}

		if err := sshutil.Transfer(contents, a.TargetDir, a.TargetName, a.Permissions, client); err != nil {
			return err
		}
	}
	return nil
}

// SetupCerts gets the generated credentials required to talk to the APIServer.
func SetupCerts(d drivers.Driver) error {
	localPath := constants.Minipath
	ipStr, err := d.GetIP()
	if err != nil {
		return err
	}
	glog.Infoln("Setting up certificates for IP: %s", ipStr)

	ip := net.ParseIP(ipStr)
	publicPath := filepath.Join(localPath, "apiserver.crt")
	privatePath := filepath.Join(localPath, "apiserver.key")
	if err := GenerateCerts(publicPath, privatePath, ip); err != nil {
		return err
	}

	client, err := sshutil.NewSSHClient(d)
	if err != nil {
		return err
	}

	for _, cert := range certs {
		p := filepath.Join(localPath, cert)
		data, err := ioutil.ReadFile(p)
		if err != nil {
			return err
		}
		if err := sshutil.Transfer(data, util.DefaultCertPath, cert, "0644", client); err != nil {
			return err
		}
	}
	return nil
}

func createHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
	var driver interface{}

	switch config.VMDriver {
	case "virtualbox":
		d := virtualbox.NewDriver(constants.MachineName, constants.Minipath)
		d.Boot2DockerURL = config.MinikubeISO
		d.Memory = config.Memory
		d.CPU = config.CPUs
		d.DiskSize = int(config.DiskSize)
		driver = d
	case "vmwarefusion":
		driver = createVMwareFusionHost(config)
	case "kvm":
		driver = createKVMHost(config)
	case "xhyve":
		driver = createXhyveHost(config)
	case "hyperv":
		driver = createHypervHost(config)
	default:
		glog.Exitf("Unsupported driver: %s\n", config.VMDriver)
	}

	data, err := json.Marshal(driver)
	if err != nil {
		return nil, err
	}

	h, err := api.NewHost(config.VMDriver, data)
	if err != nil {
		return nil, fmt.Errorf("Error creating new host: %s", err)
	}

	h.HostOptions.AuthOptions.CertDir = constants.Minipath
	h.HostOptions.AuthOptions.StorePath = constants.Minipath
	h.HostOptions.EngineOptions = &engine.Options{}

	if err := api.Create(h); err != nil {
		// Wait for all the logs to reach the client
		time.Sleep(2 * time.Second)
		return nil, fmt.Errorf("Error creating. %s", err)
	}

	if err := api.Save(h); err != nil {
		return nil, fmt.Errorf("Error attempting to save store: %s", err)
	}
	return h, nil
}

// GetHostDockerEnv gets the necessary docker env variables to allow the use of docker through minikube's vm
func GetHostDockerEnv(api libmachine.API) (map[string]string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return nil, err
	}
	ip, err := host.Driver.GetIP()
	if err != nil {
		return nil, err
	}

	tcpPrefix := "tcp://"
	portDelimiter := ":"
	port := "2376"

	envMap := map[string]string{
		"DOCKER_TLS_VERIFY": "1",
		"DOCKER_HOST":       tcpPrefix + ip + portDelimiter + port,
		"DOCKER_CERT_PATH":  constants.MakeMiniPath("certs"),
	}
	return envMap, nil
}

// GetHostLogs gets the openshift logs of the host VM.
func GetHostLogs(api libmachine.API) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}
	s, err := host.RunSSHCommand(logsCommand)
	if err != nil {
		return "", nil
	}
	return s, err
}

func checkIfApiExistsAndLoad(api libmachine.API) (*host.Host, error) {
	exists, err := api.Exists(constants.MachineName)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, fmt.Errorf("Machine does not exist for api.Exists(%s)", constants.MachineName)
	}

	host, err := api.Load(constants.MachineName)
	if err != nil {
		return nil, err
	}
	return host, nil
}

func CreateSSHShell(api libmachine.API, args []string) error {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return err
	}

	currentState, err := host.Driver.GetState()
	if err != nil {
		return err
	}

	if currentState != state.Running {
		return fmt.Errorf("Error: Cannot run ssh command: Host %q is not running", constants.MachineName)
	}

	client, err := host.CreateSSHClient()
	if err != nil {
		return err
	}
	return client.Shell(strings.Join(args, " "))
}

func GetConsoleURL(api libmachine.API) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("https://%s:%d", ip, constants.APIServerPort), nil
}

func GetServiceURL(api libmachine.API, namespace, service string) (string, error) {
	host, err := checkIfApiExistsAndLoad(api)
	if err != nil {
		return "", err
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		return "", err
	}

	port, err := getServicePort(namespace, service)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("http://%s:%d", ip, port), nil
}

type serviceGetter interface {
	Get(name string) (*kubeApi.Service, error)
}

func getServicePort(namespace, service string) (int, error) {
	services, err := getKubernetesServicesWithNamespace(namespace)
	if err != nil {
		return 0, err
	}
	return getServicePortFromServiceGetter(services, service)
}

func getServicePortFromServiceGetter(services serviceGetter, service string) (int, error) {
	svc, err := services.Get(service)
	if err != nil {
		return 0, fmt.Errorf("Error getting %s service: %s", service, err)
	}
	nodePort := 0
	if len(svc.Spec.Ports) > 0 {
		nodePort = int(svc.Spec.Ports[0].NodePort)
	}
	if nodePort == 0 {
		return 0, fmt.Errorf("Service %s does not have a node port. To have one assigned automatically, the service type must be NodePort or LoadBalancer, but this service is of type %s.", service, svc.Spec.Type)
	}
	return nodePort, nil
}

func getKubernetesServicesWithNamespace(namespace string) (serviceGetter, error) {
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	configOverrides := &clientcmd.ConfigOverrides{}
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
	}
	client, err := unversioned.New(config)
	if err != nil {
		return nil, err
	}
	services := client.Services(namespace)
	return services, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

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

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

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

package driver

import (
	"fmt"
	"net"

	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
)

// ControlPlaneEndpoint returns the location where callers can reach this cluster
func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
	if NeedsPortForward(driverName) {
		port, err := oci.ForwardedPort(cc.Driver, cc.Name, cp.Port)
		if err != nil {
			klog.Warningf("failed to get forwarded control plane port %v", err)
		}
		hostname := oci.DaemonHost(driverName)

		ip := net.ParseIP(hostname)
		if ip == nil {
			return hostname, ip, port, fmt.Errorf("failed to parse ip for %q", hostname)
		}

		// https://github.com/kubernetes/minikube/issues/3878
		if cc.KubernetesConfig.APIServerName != constants.APIServerName {
			hostname = cc.KubernetesConfig.APIServerName
		}
		return hostname, ip, port, err
	}

	// https://github.com/kubernetes/minikube/issues/3878
	hostname := cp.IP
	if cc.KubernetesConfig.APIServerName != constants.APIServerName {
		hostname = cc.KubernetesConfig.APIServerName
	}
	ips, err := net.LookupIP(cp.IP)
	if err != nil || len(ips) == 0 {
		return hostname, nil, cp.Port, fmt.Errorf("failed to lookup ip for %q", cp.IP)
	}
	// get last IP as it's IPv4
	ip := ips[len(ips)-1]
	return hostname, ip, cp.Port, nil
}

// AutoPauseProxyEndpoint returns the endpoint for the auto-pause (reverse proxy to api-sever)
func AutoPauseProxyEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
	cp.Port = constants.AutoPauseProxyPort
	return ControlPlaneEndpoint(cc, cp, driverName)
}
<commit_msg>add for port forwarding as well<commit_after>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

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

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

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

package driver

import (
	"fmt"
	"net"

	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
)

// ControlPlaneEndpoint returns the location where callers can reach this cluster
func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
	if NeedsPortForward(driverName) {
		port, err := oci.ForwardedPort(cc.Driver, cc.Name, cp.Port)
		if err != nil {
			klog.Warningf("failed to get forwarded control plane port %v", err)
		}
		hostname := oci.DaemonHost(driverName)

		ips, err := net.LookupIP(hostname)
		if err != nil || len(ips) == 0 {
			return hostname, nil, port, fmt.Errorf("failed to lookup ip for %q", hostname)
		}

		// https://github.com/kubernetes/minikube/issues/3878
		if cc.KubernetesConfig.APIServerName != constants.APIServerName {
			hostname = cc.KubernetesConfig.APIServerName
		}
		return hostname, ips[0], port, err
	}

	// https://github.com/kubernetes/minikube/issues/3878
	hostname := cp.IP
	if cc.KubernetesConfig.APIServerName != constants.APIServerName {
		hostname = cc.KubernetesConfig.APIServerName
	}
	ips, err := net.LookupIP(cp.IP)
	if err != nil || len(ips) == 0 {
		return hostname, nil, cp.Port, fmt.Errorf("failed to lookup ip for %q", cp.IP)
	}
	return hostname, ips[0], cp.Port, nil
}

// AutoPauseProxyEndpoint returns the endpoint for the auto-pause (reverse proxy to api-sever)
func AutoPauseProxyEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName string) (string, net.IP, int, error) {
	cp.Port = constants.AutoPauseProxyPort
	return ControlPlaneEndpoint(cc, cp, driverName)
}
<|endoftext|>


<commit_before>package zd

import "fmt"

// Ticket struct
type Ticket struct {
	ID                  *float64            `json:"id,omitempty"`
	URL                 *string             `json:"url,omitempty"`
	ExternalID          *string             `json:"external_id,omitempty"`
	Type                *string             `json:"type,omitempty"`
	Subject             *string             `json:"subject,omitempty"`
	Description         *string             `json:"description,omitempty"`
	Priority            *string             `json:"priority,omitempty"`
	Status              *string             `json:"status,omitempty"`
	Recipient           *string             `json:"recipient,omitempty"`
	RequesterID         *float64            `json:"requester_id,omitempty"`
	SubmitterID         *float64            `json:"submitter_id,omitempty"`
	AssigneeID          *float64            `json:"assignee_id,omitempty"`
	OrganizationID      *float64            `json:"organization_id,omitempty"`
	GroupID             *float64            `json:"group_id,omitempty"`
	CollaboratorIDs     []float64           `json:"collaborator_ids,omitempty"`
	ForumTopicID        *float64            `json:"forum_topic_id,omitempty"`
	ProblemID           *float64            `json:"problem_id,omitempty"`
	HasIncidents        *bool               `json:"has_incidents,omitempty"`
	DueAt               *string             `json:"due_at,omitempty"`
	Tags                []string            `json:"tags,omitempty"`
	Via                 *Via                `json:"via,omitempty"`
	CustomFields        []CustomField       `json:"custom_fields,omitempty"`
	SatisfactionRating  *SatisfactionRating `json:"satisfaction_rating,omitempty"`
	SharingAgreementIds []float64           `json:"sharing_agreement_ids,omitempty"`
	FollowupIds         []float64           `json:"followup_ids,omitempty"`
	TicketFormID        *float64            `json:"ticket_form_id,omitempty"`
	BrandID             *float64            `json:"brand_id,omitempty"`
	CreatedAt           *string             `json:"created_at,omitempty"`
	UpdatedAt           *string             `json:"updated_at,omitempty"`
}

// Via struct
type Via struct {
	Channel *string      `json:"channel,omitempty"`
	Source  *interface{} `json:"source,omitempty"`
}

// CustomField struct
type CustomField struct {
	ID    *float64     `json:"id,omitempty"`
	Value *interface{} `json:"value,omitempty"`
}

// SatisfactionRating struct
type SatisfactionRating struct {
	ID      *float64 `json:"id,omitempty"`
	Score   *string  `json:"score,omitempty"`
	Comment *string  `json:"comment,omitempty"`
}

// TicketResponse struct
type TicketResponse struct {
	Results  []Ticket `json:"tickets"`
	Next     *string  `json:"next_page,omitempty"`
	Previous *string  `json:"previous_page,omitempty"`
	Count    *int     `json:"count,omitempty"`
}

// TicketUserGroupResponse struct
type TicketUserGroupResponse struct {
	Tickets  []Ticket `json:"tickets,omitempty"`
	Users    []User   `json:"users,omitempty"`
	Groups   []Group  `json:"groups,omitempty"`
	Next     *string  `json:"next_page,omitempty"`
	Previous *string  `json:"previous_page,omitempty"`
	Count    *int     `json:"count,omitempty"`
}

// TicketService struct
type TicketService struct {
	client *Client
}

// List returns a slice of all products
func (s *TicketService) List() ([]Ticket, error) {
	resource := []Ticket{}

	rp, next, _, err := s.getPage("")

	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// ListByView function
func (s *TicketService) ListByView(id string) ([]Ticket, error) {
	resource := []Ticket{}

	url := fmt.Sprintf("views/%s/tickets.json", id)
	rp, next, _, err := s.getPage(url)
	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// ListByViewUG function
func (s *TicketService) ListByViewUG(id string) ([]Ticket, []User, []Group, error) {

	tickets := []Ticket{}
	users := []User{}
	groups := []Group{}

	url := fmt.Sprintf("views/%s/tickets.json?include=users,groups", id)
	tkts, usrs, grps, next, _, err := s.getPageUG(url)
	if err != nil {
		return nil, nil, nil, err
	}

	tickets = append(tickets, tkts...)
	users = append(users, usrs...)
	groups = append(groups, grps...)

	for next != nil {
		tkts, usrs, grps, nx, _, err := s.getPageUG(*next)
		if err != nil {
			return nil, nil, nil, err
		}
		next = nx
		tickets = append(tickets, tkts...)
		users = append(users, usrs...)
		groups = append(groups, grps...)
	}

	return tickets, users, groups, err
}

// GetProblemIncidents gets all problem tickets
func (s *TicketService) GetProblemIncidents(id string) ([]Ticket, error) {
	resource := []Ticket{}

	url := fmt.Sprintf("tickets/%s/incidents.json", id)

	rp, next, _, err := s.getPage(url)
	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// GetProblemIncidentsCount gets only the first page of tickets which includes the count field
func (s *TicketService) GetProblemIncidentsCount(id string) (int, error) {

	url := fmt.Sprintf("tickets/%s/incidents.json", id)

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return 0, err
	}

	response := TicketResponse{}
	_, err = s.client.Do(req, response)
	if err != nil {
		return 0, err
	}

	resource := response.Count
	return *resource, err
}

func (s *TicketService) getPage(url string) ([]Ticket, *string, *Response, error) {

	if url == "" {
		url = "tickets.json"
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, nil, err
	}

	response := TicketResponse{}
	resp, err := s.client.Do(req, response)
	if err != nil {
		return nil, nil, resp, err
	}

	next := response.Next
	resource := response.Results
	return resource, next, resp, err
}

func (s *TicketService) getPageUG(url string) ([]Ticket, []User, []Group, *string, *Response, error) {

	if url == "" {
		url = "tickets.json?include=users,groups"
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	response := TicketUserGroupResponse{}
	resp, err := s.client.Do(req, response)
	if err != nil {
		return nil, nil, nil, nil, resp, err
	}

	next := response.Next
	tickets := response.Tickets
	users := response.Users
	groups := response.Groups
	return tickets, users, groups, next, resp, err
}

// GetOne method
func (s *TicketService) GetOne(id string) (*Ticket, *Response, error) {
	url := fmt.Sprintf("tickets/%s.json", id)

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	ticket := &Ticket{}
	resp, err := s.client.Do(req, &ticket)
	if err != nil {
		return nil, resp, err
	}

	return ticket, resp, err
}
<commit_msg>resolving conflict<commit_after>package zd

import (
	"encoding/json"
	"fmt"
)

// Ticket struct
type Ticket struct {
	ID                  *float64            `json:"id,omitempty"`
	URL                 *string             `json:"url,omitempty"`
	ExternalID          *string             `json:"external_id,omitempty"`
	Type                *string             `json:"type,omitempty"`
	Subject             *string             `json:"subject,omitempty"`
	Comment             *string             `json:"comment,omitempty"`
	Description         *string             `json:"description,omitempty"`
	Priority            *string             `json:"priority,omitempty"`
	Status              *string             `json:"status,omitempty"`
	Recipient           *string             `json:"recipient,omitempty"`
	RequesterID         *float64            `json:"requester_id,omitempty"`
	Requester           *User               `json:"requester,omitempty"`
	SubmitterID         *float64            `json:"submitter_id,omitempty"`
	AssigneeID          *float64            `json:"assignee_id,omitempty"`
	OrganizationID      *float64            `json:"organization_id,omitempty"`
	GroupID             *float64            `json:"group_id,omitempty"`
	CollaboratorIDs     []float64           `json:"collaborator_ids,omitempty"`
	ForumTopicID        *float64            `json:"forum_topic_id,omitempty"`
	ProblemID           *float64            `json:"problem_id,omitempty"`
	HasIncidents        *bool               `json:"has_incidents,omitempty"`
	DueAt               *string             `json:"due_at,omitempty"`
	Tags                []string            `json:"tags,omitempty"`
	Via                 *Via                `json:"via,omitempty"`
	CustomFields        []CustomField       `json:"custom_fields,omitempty"`
	SatisfactionRating  *SatisfactionRating `json:"satisfaction_rating,omitempty"`
	SharingAgreementIds []float64           `json:"sharing_agreement_ids,omitempty"`
	FollowupIds         []float64           `json:"followup_ids,omitempty"`
	TicketFormID        *float64            `json:"ticket_form_id,omitempty"`
	BrandID             *float64            `json:"brand_id,omitempty"`
	CreatedAt           *string             `json:"created_at,omitempty"`
	UpdatedAt           *string             `json:"updated_at,omitempty"`
}

// Via struct
type Via struct {
	Channel *string      `json:"channel,omitempty"`
	Source  *interface{} `json:"source,omitempty"`
}

// CustomField struct
type CustomField struct {
	ID    *float64     `json:"id,omitempty"`
	Value *interface{} `json:"value,omitempty"`
}

// SatisfactionRating struct
type SatisfactionRating struct {
	ID      *float64 `json:"id,omitempty"`
	Score   *string  `json:"score,omitempty"`
	Comment *string  `json:"comment,omitempty"`
}

// TicketCollectionResponse struct
type TicketCollectionResponse struct {
	Results  []Ticket `json:"tickets"`
	Next     *string  `json:"next_page,omitempty"`
	Previous *string  `json:"previous_page,omitempty"`
	Count    *int     `json:"count,omitempty"`
}

// TicketResponse struct
type TicketResponse struct {
	Ticket Ticket `json:"ticket"`
}

// TicketUserGroupResponse struct
type TicketUserGroupResponse struct {
	Tickets  []Ticket `json:"tickets,omitempty"`
	Users    []User   `json:"users,omitempty"`
	Groups   []Group  `json:"groups,omitempty"`
	Next     *string  `json:"next_page,omitempty"`
	Previous *string  `json:"previous_page,omitempty"`
	Count    *int     `json:"count,omitempty"`
}

// TicketService struct
type TicketService struct {
	client *Client
}

// List returns a slice of all products
func (s *TicketService) List() ([]Ticket, error) {
	resource := []Ticket{}

	rp, next, _, err := s.getPage("")

	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// ListByView function
func (s *TicketService) ListByView(id string) ([]Ticket, error) {
	resource := []Ticket{}

	url := fmt.Sprintf("views/%s/tickets.json", id)
	rp, next, _, err := s.getPage(url)
	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// ListByViewUG function
func (s *TicketService) ListByViewUG(id string) ([]Ticket, []User, []Group, error) {

	tickets := []Ticket{}
	users := []User{}
	groups := []Group{}

	url := fmt.Sprintf("views/%s/tickets.json?include=users,groups", id)
	tkts, usrs, grps, next, _, err := s.getPageUG(url)
	if err != nil {
		return nil, nil, nil, err
	}

	tickets = append(tickets, tkts...)
	users = append(users, usrs...)
	groups = append(groups, grps...)

	for next != nil {
		tkts, usrs, grps, nx, _, err := s.getPageUG(*next)
		if err != nil {
			return nil, nil, nil, err
		}
		next = nx
		tickets = append(tickets, tkts...)
		users = append(users, usrs...)
		groups = append(groups, grps...)
	}

	return tickets, users, groups, err
}

// GetProblemIncidents gets all problem tickets
func (s *TicketService) GetProblemIncidents(id string) ([]Ticket, error) {
	resource := []Ticket{}

	url := fmt.Sprintf("tickets/%s/incidents.json", id)

	rp, next, _, err := s.getPage(url)
	if err != nil {
		return nil, err
	}

	resource = append(resource, rp...)

	for next != nil {
		rp, nx, _, err := s.getPage(*next)
		if err != nil {
			return nil, err
		}
		next = nx
		resource = append(resource, rp...)
	}

	return resource, err
}

// GetProblemIncidentsCount gets only the first page of tickets which includes the count field
func (s *TicketService) GetProblemIncidentsCount(id string) (int, error) {

	url := fmt.Sprintf("tickets/%s/incidents.json", id)

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return 0, err
	}

	response := TicketCollectionResponse{}
	_, err = s.client.Do(req, response)
	if err != nil {
		return 0, err
	}

	resource := response.Count
	return *resource, err
}

func (s *TicketService) getPage(url string) ([]Ticket, *string, *Response, error) {

	if url == "" {
		url = "tickets.json"
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, nil, err
	}

	response := TicketCollectionResponse{}
	resp, err := s.client.Do(req, response)
	if err != nil {
		return nil, nil, resp, err
	}

	next := response.Next
	resource := response.Results
	return resource, next, resp, err
}

func (s *TicketService) getPageUG(url string) ([]Ticket, []User, []Group, *string, *Response, error) {

	if url == "" {
		url = "tickets.json?include=users,groups"
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	response := TicketUserGroupResponse{}
	resp, err := s.client.Do(req, response)
	if err != nil {
		return nil, nil, nil, nil, resp, err
	}

	next := response.Next
	tickets := response.Tickets
	users := response.Users
	groups := response.Groups
	return tickets, users, groups, next, resp, err
}

// GetOne method
func (s *TicketService) GetOne(id string) (*Ticket, *Response, error) {
	url := fmt.Sprintf("tickets/%s.json", id)

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	ticket := &Ticket{}
	resp, err := s.client.Do(req, &ticket)
	if err != nil {
		return nil, resp, err
	}

	return ticket, resp, err
}

// Create a new Zendesk Ticket
func (s *TicketService) Create(ticket *Ticket) (*Ticket, *Response, error) {
	url := "tickets.json"

	body, err := json.Marshal(&ticket)

	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("POST", url, body)
	if err != nil {
		return nil, nil, err
	}

	response := TicketResponse{}
	resp, err := s.client.Do(req, response)
	if err != nil {
		return nil, resp, err
	}

	return ticket, resp, err
}
<|endoftext|>


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

package registry

import (
	"fmt"
	"os"
	"sort"

	"github.com/golang/glog"
)

var (
	// globalRegistry is a globally accessible driver registry
	globalRegistry = newRegistry()
)

// DriverState is metadata relating to a driver and status
type DriverState struct {
	Name     string
	Priority Priority
	State    State
}

func (d DriverState) String() string {
	if d.Priority == Experimental {
		return fmt.Sprintf("%s (experimental)", d.Name)
	}
	return d.Name
}

// List lists drivers in global registry
func List() []DriverDef {
	return globalRegistry.List()
}

// Register registers driver with the global registry
func Register(driver DriverDef) error {
	return globalRegistry.Register(driver)
}

// Driver gets a named driver from the global registry
func Driver(name string) DriverDef {
	return globalRegistry.Driver(name)
}

// Available returns a list of available drivers in the global registry
func Available() []DriverState {
	sts := []DriverState{}
	glog.Infof("Querying for installed drivers using PATH=%s", os.Getenv("PATH"))

	for _, d := range globalRegistry.List() {
		if d.Status == nil {
			glog.Errorf("%q does not implement Status", d.Name)
			continue
		}
		s := d.Status()
		glog.Infof("%s priority: %d, state: %+v", d.Name, d.Priority, s)

		priority := d.Priority
		if !s.Healthy {
			priority = Unhealthy
		}

		sts = append(sts, DriverState{Name: d.Name, Priority: priority, State: s})
	}

	// Descending priority for predictability
	sort.Slice(sts, func(i, j int) bool {
		return sts[i].Priority > sts[j].Priority
	})
	return sts
}

// Status returns the state of a driver within the global registry
func Status(name string) State {
	d := globalRegistry.Driver(name)
	if d.Empty() {
		return State{}
	}
	return d.Status()
}
<commit_msg>Add --vm flag for users who want to autoselect only VM's<commit_after>/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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

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

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

package registry

import (
	"fmt"
	"os"
	"sort"

	"github.com/golang/glog"
)

var (
	// globalRegistry is a globally accessible driver registry
	globalRegistry = newRegistry()
)

// DriverState is metadata relating to a driver and status
type DriverState struct {
	Name     string
	Priority Priority
	State    State
}

func (d DriverState) String() string {
	if d.Priority == Experimental {
		return fmt.Sprintf("%s (experimental)", d.Name)
	}
	return d.Name
}

// List lists drivers in global registry
func List() []DriverDef {
	return globalRegistry.List()
}

// Register registers driver with the global registry
func Register(driver DriverDef) error {
	return globalRegistry.Register(driver)
}

// Driver gets a named driver from the global registry
func Driver(name string) DriverDef {
	return globalRegistry.Driver(name)
}

// Available returns a list of available drivers in the global registry
func Available() []DriverState {
	sts := []DriverState{}
	glog.Infof("Querying for installed drivers using PATH=%s", os.Getenv("PATH"))

	for _, d := range globalRegistry.List() {
		if d.Status == nil {
			glog.Errorf("%q does not implement Status", d.Name)
			continue
		}
		s := d.Status()
		glog.Infof("%s priority: %d, state: %+v", d.Name, d.Priority, s)

		priority := d.Priority
		if !s.Healthy {
			priority = Unhealthy
		}

		sts = append(sts, DriverState{Name: d.Name, Priority: priority, State: s})
	}

	// Descending priority for predictability
	sort.Slice(sts, func(i, j int) bool {
		return sts[i].Priority > sts[j].Priority
	})
	return sts
}

// Status returns the state of a driver within the global registry
func Status(name string) State {
	d := globalRegistry.Driver(name)
	if d.Empty() {
		return State{}
	}
	return d.Status()
}
<|endoftext|>


<commit_before>package oauth2

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/mgo.v2/bson"
)

func TestTokenStrategy_GetJWTSigningMethods_mongo(t *testing.T) {
	settingsLegacy := TokenStrategy{Settings: bson.M{"secret": "foo-bar"}}
	methodsLegacy, err := settingsLegacy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 1, len(methodsLegacy))
	assert.Equal(t, "HS256", methodsLegacy[0].Alg)
	assert.Equal(t, "foo-bar", methodsLegacy[0].Key)

	settingsList := TokenStrategy{Settings: []interface{}{bson.M{"alg": "HS256", "key": "foo"}, bson.M{"alg": "RS256", "key": "bar"}}}
	methodsList, err := settingsList.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 2, len(methodsList))
	assert.Equal(t, "HS256", methodsList[0].Alg)
	assert.Equal(t, "foo", methodsList[0].Key)
	assert.Equal(t, "RS256", methodsList[1].Alg)
	assert.Equal(t, "bar", methodsList[1].Key)

	settingsLegacyError := TokenStrategy{Settings: bson.M{"foo": "bar"}}
	_, err = settingsLegacyError.GetJWTSigningMethods()
	assert.Equal(t, ErrJWTSecretMissing, err)

	settingsInvalid := TokenStrategy{Settings: bson.M{}}
	_, err = settingsInvalid.GetJWTSigningMethods()
	assert.Error(t, err)
}

func TestTokenStrategy_GetJWTSigningMethods_file(t *testing.T) {
	repo := &FileSystemRepository{}

	oauthServerLegacy := repo.parseOAuthServer([]byte(fileLegacy))
	methodsLegacy, err := oauthServerLegacy.TokenStrategy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 1, len(methodsLegacy))
	assert.Equal(t, "HS256", methodsLegacy[0].Alg)
	assert.Equal(t, "foo-bar", methodsLegacy[0].Key)

	oauthServerList := repo.parseOAuthServer([]byte(fileList))

	methodsList, err := oauthServerList.TokenStrategy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 2, len(methodsList))
	assert.Equal(t, "HS256", methodsList[0].Alg)
	assert.Equal(t, "foo", methodsList[0].Key)
	assert.Equal(t, "RS256", methodsList[1].Alg)
	assert.Equal(t, "bar", methodsList[1].Key)

	oauthServerLegacyError := repo.parseOAuthServer([]byte(fileLegacyError))
	_, err = oauthServerLegacyError.TokenStrategy.GetJWTSigningMethods()
	assert.Equal(t, ErrJWTSecretMissing, err)

	oauthServerInvalid := repo.parseOAuthServer([]byte(fileBad))
	_, err = oauthServerInvalid.TokenStrategy.GetJWTSigningMethods()
	assert.Error(t, err)
}

const (
	fileLegacy = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {"secret": "foo-bar"}
    }
}`
	fileList = `{
    "name" : "list",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : [
            {"alg": "HS256", "key": "foo"},
            {"alg": "RS256", "key": "bar"}
        ]
    }
}`
	fileLegacyError = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {"foo": "bar"}
    }
}`
	fileBad = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {}
    }
}`
)
<commit_msg>Added more tests<commit_after>package oauth2

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/mgo.v2/bson"
)

func TestAccessRulesWithWrongPredicate(t *testing.T) {
	rules := AccessRule{
		Action:    "wrong",
		Predicate: "wrong.predicate",
	}
	_, err := rules.IsAllowed(make(map[string]interface{}))
	require.Error(t, err)
}

func TestAccessRulesWithEmptyPredicate(t *testing.T) {
	rules := AccessRule{
		Action:    "wrong",
		Predicate: "",
	}
	_, err := rules.IsAllowed(map[string]interface{}{"test": true})
	require.Error(t, err)
}

func TestAccessRulesWithPredicateThatDoesntMatch(t *testing.T) {
	rules := AccessRule{
		Action:    "wrong",
		Predicate: "test = false",
	}
	_, err := rules.IsAllowed(map[string]interface{}{"test": true})
	require.Error(t, err)
}

func TestTokenStrategyWithInvalidSettings(t *testing.T) {
	settingsLegacy := TokenStrategy{Settings: make(chan int)}
	_, err := settingsLegacy.GetJWTSigningMethods()
	require.Error(t, err)
}

func TestTokenStrategyWithEmptySecret(t *testing.T) {
	settingsLegacy := TokenStrategy{Settings: bson.M{"secret": ""}}
	_, err := settingsLegacy.GetJWTSigningMethods()
	require.Error(t, err)
}

func TestTokenStrategy_GetJWTSigningMethods_mongo(t *testing.T) {
	settingsLegacy := TokenStrategy{Settings: bson.M{"secret": "foo-bar"}}
	methodsLegacy, err := settingsLegacy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 1, len(methodsLegacy))
	assert.Equal(t, "HS256", methodsLegacy[0].Alg)
	assert.Equal(t, "foo-bar", methodsLegacy[0].Key)

	settingsList := TokenStrategy{Settings: []interface{}{bson.M{"alg": "HS256", "key": "foo"}, bson.M{"alg": "RS256", "key": "bar"}}}
	methodsList, err := settingsList.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 2, len(methodsList))
	assert.Equal(t, "HS256", methodsList[0].Alg)
	assert.Equal(t, "foo", methodsList[0].Key)
	assert.Equal(t, "RS256", methodsList[1].Alg)
	assert.Equal(t, "bar", methodsList[1].Key)

	settingsLegacyError := TokenStrategy{Settings: bson.M{"foo": "bar"}}
	_, err = settingsLegacyError.GetJWTSigningMethods()
	assert.Equal(t, ErrJWTSecretMissing, err)

	settingsInvalid := TokenStrategy{Settings: bson.M{}}
	_, err = settingsInvalid.GetJWTSigningMethods()
	assert.Error(t, err)
}

func TestTokenStrategy_GetJWTSigningMethods_file(t *testing.T) {
	repo := &FileSystemRepository{}

	oauthServerLegacy := repo.parseOAuthServer([]byte(fileLegacy))
	methodsLegacy, err := oauthServerLegacy.TokenStrategy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 1, len(methodsLegacy))
	assert.Equal(t, "HS256", methodsLegacy[0].Alg)
	assert.Equal(t, "foo-bar", methodsLegacy[0].Key)

	oauthServerList := repo.parseOAuthServer([]byte(fileList))

	methodsList, err := oauthServerList.TokenStrategy.GetJWTSigningMethods()
	require.NoError(t, err)

	require.Equal(t, 2, len(methodsList))
	assert.Equal(t, "HS256", methodsList[0].Alg)
	assert.Equal(t, "foo", methodsList[0].Key)
	assert.Equal(t, "RS256", methodsList[1].Alg)
	assert.Equal(t, "bar", methodsList[1].Key)

	oauthServerLegacyError := repo.parseOAuthServer([]byte(fileLegacyError))
	_, err = oauthServerLegacyError.TokenStrategy.GetJWTSigningMethods()
	assert.Equal(t, ErrJWTSecretMissing, err)

	oauthServerInvalid := repo.parseOAuthServer([]byte(fileBad))
	_, err = oauthServerInvalid.TokenStrategy.GetJWTSigningMethods()
	assert.Error(t, err)
}

const (
	fileLegacy = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {"secret": "foo-bar"}
    }
}`
	fileList = `{
    "name" : "list",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : [
            {"alg": "HS256", "key": "foo"},
            {"alg": "RS256", "key": "bar"}
        ]
    }
}`
	fileLegacyError = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {"foo": "bar"}
    }
}`
	fileBad = `{
    "name" : "legacy",
    "oauth_endpoints" : {
        "token" : {
            "preserve_host" : false,
            "listen_path" : "/auth/token",
            "upstream_url" : "http://auth-service:8080/token",
            "strip_path" : true,
            "append_path" : false,
            "enable_load_balancing" : false,
            "methods" : [
                "GET",
                "POST"
            ]
        }
    },
    "token_strategy" : {
        "name" : "jwt",
        "settings" : {}
    }
}`
)
<|endoftext|>