<commit_before>package lxd

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"

	"github.com/gorilla/websocket"
	"gopkg.in/macaroon-bakery.v2-unstable/bakery"
	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"

	neturl "net/url"
)

// ProtocolLXD represents a LXD API server
type ProtocolLXD struct {
	server *api.Server

	eventListeners     []*EventListener
	eventListenersLock sync.Mutex

	http            *http.Client
	httpCertificate string
	httpHost        string
	httpProtocol    string
	httpUserAgent   string

	bakeryClient         *httpbakery.Client
	bakeryInteractor     httpbakery.Interactor
	requireAuthenticated bool
}

// GetConnectionInfo returns the basic connection information used to interact with the server
func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
	info := ConnectionInfo{}
	info.Certificate = r.httpCertificate
	info.Protocol = "lxd"

	urls := []string{}
	if r.httpProtocol == "https" {
		urls = append(urls, r.httpHost)
	}

	if len(r.server.Environment.Addresses) > 0 {
		for _, addr := range r.server.Environment.Addresses {
			url := fmt.Sprintf("https://%s", addr)
			if !shared.StringInSlice(url, urls) {
				urls = append(urls, url)
			}
		}
	}
	info.Addresses = urls

	return &info, nil
}

// GetHTTPClient returns the http client used for the connection. This can be used to set custom http options.
func (r *ProtocolLXD) GetHTTPClient() (*http.Client, error) {
	if r.http == nil {
		return nil, fmt.Errorf("HTTP client isn't set, bad connection")
	}

	return r.http, nil
}

// Do performs a Request, using macaroon authentication if set.
func (r *ProtocolLXD) do(req *http.Request) (*http.Response, error) {
	if r.bakeryClient != nil {
		r.addMacaroonHeaders(req)
		return r.bakeryClient.Do(req)
	}

	return r.http.Do(req)
}

func (r *ProtocolLXD) addMacaroonHeaders(req *http.Request) {
	req.Header.Set(httpbakery.BakeryProtocolHeader, fmt.Sprint(bakery.LatestVersion))

	for _, cookie := range r.http.Jar.Cookies(req.URL) {
		req.AddCookie(cookie)
	}
}

// RequireAuthenticated sets whether we expect to be authenticated with the server
func (r *ProtocolLXD) RequireAuthenticated(authenticated bool) {
	r.requireAuthenticated = authenticated
}

// RawQuery allows directly querying the LXD API
//
// This should only be used by internal LXD tools.
func (r *ProtocolLXD) RawQuery(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
	// Generate the URL
	url := fmt.Sprintf("%s%s", r.httpHost, path)

	return r.rawQuery(method, url, data, ETag)
}

// RawWebsocket allows directly connection to LXD API websockets
//
// This should only be used by internal LXD tools.
func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
	return r.websocket(path)
}

// Internal functions
func (r *ProtocolLXD) parseResponse(resp *http.Response) (*api.Response, string, error) {
	// Get the ETag
	etag := resp.Header.Get("ETag")

	// Decode the response
	decoder := json.NewDecoder(resp.Body)
	response := api.Response{}

	err := decoder.Decode(&response)
	if err != nil {
		// Check the return value for a cleaner error
		if resp.StatusCode != http.StatusOK {
			return nil, "", fmt.Errorf("Failed to fetch %s: %s", resp.Request.URL.String(), resp.Status)
		}

		return nil, "", err
	}

	// Handle errors
	if response.Type == api.ErrorResponse {
		return nil, "", fmt.Errorf(response.Error)
	}

	return &response, etag, nil
}

func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag string) (*api.Response, string, error) {
	var req *http.Request
	var err error

	// Log the request
	logger.Debug("Sending request to LXD",
		"method", method,
		"url", url,
		"etag", ETag,
	)

	// Get a new HTTP request setup
	if data != nil {
		// Encode the provided data
		buf := bytes.Buffer{}
		err := json.NewEncoder(&buf).Encode(data)
		if err != nil {
			return nil, "", err
		}

		// Some data to be sent along with the request
		// Use a reader since the request body needs to be seekable
		req, err = http.NewRequest(method, url, bytes.NewReader(buf.Bytes()))
		if err != nil {
			return nil, "", err
		}

		// Set the encoding accordingly
		req.Header.Set("Content-Type", "application/json")

		// Log the data
		logger.Debugf(logger.Pretty(data))
	} else {
		// No data to be sent along with the request
		req, err = http.NewRequest(method, url, nil)
		if err != nil {
			return nil, "", err
		}
	}

	// Set the user agent
	if r.httpUserAgent != "" {
		req.Header.Set("User-Agent", r.httpUserAgent)
	}

	// Set the ETag
	if ETag != "" {
		req.Header.Set("If-Match", ETag)
	}

	// Set the authentication header
	if r.requireAuthenticated {
		req.Header.Set("X-LXD-authenticated", "true")
	}

	// Send the request
	resp, err := r.do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	return r.parseResponse(resp)
}

func (r *ProtocolLXD) query(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
	// Generate the URL
	url := fmt.Sprintf("%s/1.0%s", r.httpHost, path)

	return r.rawQuery(method, url, data, ETag)
}

func (r *ProtocolLXD) queryStruct(method string, path string, data interface{}, ETag string, target interface{}) (string, error) {
	resp, etag, err := r.query(method, path, data, ETag)
	if err != nil {
		return "", err
	}

	err = resp.MetadataAsStruct(&target)
	if err != nil {
		return "", err
	}

	// Log the data
	logger.Debugf("Got response struct from LXD")
	logger.Debugf(logger.Pretty(target))

	return etag, nil
}

func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
	// Attempt to setup an early event listener
	listener, err := r.GetEvents()
	if err != nil {
		listener = nil
	}

	// Send the query
	resp, etag, err := r.query(method, path, data, ETag)
	if err != nil {
		if listener != nil {
			listener.Disconnect()
		}

		return nil, "", err
	}

	// Get to the operation
	respOperation, err := resp.MetadataAsOperation()
	if err != nil {
		if listener != nil {
			listener.Disconnect()
		}

		return nil, "", err
	}

	// Setup an Operation wrapper
	op := Operation{
		Operation: *respOperation,
		r:         r,
		listener:  listener,
		chActive:  make(chan bool),
	}

	// Log the data
	logger.Debugf("Got operation from LXD")
	logger.Debugf(logger.Pretty(op.Operation))

	return &op, etag, nil
}

func (r *ProtocolLXD) rawWebsocket(url string) (*websocket.Conn, error) {
	// Grab the http transport handler
	httpTransport := r.http.Transport.(*http.Transport)

	// Setup a new websocket dialer based on it
	dialer := websocket.Dialer{
		NetDial:         httpTransport.Dial,
		TLSClientConfig: httpTransport.TLSClientConfig,
		Proxy:           httpTransport.Proxy,
	}

	// Set the user agent
	headers := http.Header{}
	if r.httpUserAgent != "" {
		headers.Set("User-Agent", r.httpUserAgent)
	}

	if r.requireAuthenticated {
		headers.Set("X-LXD-authenticated", "true")
	}

	// Set macaroon headers if needed
	if r.bakeryClient != nil {
		u, err := neturl.Parse(r.httpHost) // use the http url, not the ws one
		if err != nil {
			return nil, err
		}
		req := &http.Request{URL: u, Header: headers}
		r.addMacaroonHeaders(req)
	}

	// Establish the connection
	conn, _, err := dialer.Dial(url, headers)
	if err != nil {
		return nil, err
	}

	// Log the data
	logger.Debugf("Connected to the websocket")

	return conn, err
}

func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
	// Generate the URL
	var url string
	if strings.HasPrefix(r.httpHost, "https://") {
		url = fmt.Sprintf("wss://%s/1.0%s", strings.TrimPrefix(r.httpHost, "https://"), path)
	} else {
		url = fmt.Sprintf("ws://%s/1.0%s", strings.TrimPrefix(r.httpHost, "http://"), path)
	}

	return r.rawWebsocket(url)
}

func (r *ProtocolLXD) setupBakeryClient() {
	r.bakeryClient = httpbakery.NewClient()
	r.bakeryClient.Client = r.http
	if r.bakeryInteractor != nil {
		r.bakeryClient.AddInteractor(r.bakeryInteractor)
	}
}
<commit_msg>Fix for older gorilla websocket package<commit_after>package lxd

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"

	"github.com/gorilla/websocket"
	"gopkg.in/macaroon-bakery.v2-unstable/bakery"
	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"

	neturl "net/url"
)

// ProtocolLXD represents a LXD API server
type ProtocolLXD struct {
	server *api.Server

	eventListeners     []*EventListener
	eventListenersLock sync.Mutex

	http            *http.Client
	httpCertificate string
	httpHost        string
	httpProtocol    string
	httpUserAgent   string

	bakeryClient         *httpbakery.Client
	bakeryInteractor     httpbakery.Interactor
	requireAuthenticated bool
}

// GetConnectionInfo returns the basic connection information used to interact with the server
func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
	info := ConnectionInfo{}
	info.Certificate = r.httpCertificate
	info.Protocol = "lxd"

	urls := []string{}
	if r.httpProtocol == "https" {
		urls = append(urls, r.httpHost)
	}

	if len(r.server.Environment.Addresses) > 0 {
		for _, addr := range r.server.Environment.Addresses {
			url := fmt.Sprintf("https://%s", addr)
			if !shared.StringInSlice(url, urls) {
				urls = append(urls, url)
			}
		}
	}
	info.Addresses = urls

	return &info, nil
}

// GetHTTPClient returns the http client used for the connection. This can be used to set custom http options.
func (r *ProtocolLXD) GetHTTPClient() (*http.Client, error) {
	if r.http == nil {
		return nil, fmt.Errorf("HTTP client isn't set, bad connection")
	}

	return r.http, nil
}

// Do performs a Request, using macaroon authentication if set.
func (r *ProtocolLXD) do(req *http.Request) (*http.Response, error) {
	if r.bakeryClient != nil {
		r.addMacaroonHeaders(req)
		return r.bakeryClient.Do(req)
	}

	return r.http.Do(req)
}

func (r *ProtocolLXD) addMacaroonHeaders(req *http.Request) {
	req.Header.Set(httpbakery.BakeryProtocolHeader, fmt.Sprint(bakery.LatestVersion))

	for _, cookie := range r.http.Jar.Cookies(req.URL) {
		req.AddCookie(cookie)
	}
}

// RequireAuthenticated sets whether we expect to be authenticated with the server
func (r *ProtocolLXD) RequireAuthenticated(authenticated bool) {
	r.requireAuthenticated = authenticated
}

// RawQuery allows directly querying the LXD API
//
// This should only be used by internal LXD tools.
func (r *ProtocolLXD) RawQuery(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
	// Generate the URL
	url := fmt.Sprintf("%s%s", r.httpHost, path)

	return r.rawQuery(method, url, data, ETag)
}

// RawWebsocket allows directly connection to LXD API websockets
//
// This should only be used by internal LXD tools.
func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
	return r.websocket(path)
}

// Internal functions
func (r *ProtocolLXD) parseResponse(resp *http.Response) (*api.Response, string, error) {
	// Get the ETag
	etag := resp.Header.Get("ETag")

	// Decode the response
	decoder := json.NewDecoder(resp.Body)
	response := api.Response{}

	err := decoder.Decode(&response)
	if err != nil {
		// Check the return value for a cleaner error
		if resp.StatusCode != http.StatusOK {
			return nil, "", fmt.Errorf("Failed to fetch %s: %s", resp.Request.URL.String(), resp.Status)
		}

		return nil, "", err
	}

	// Handle errors
	if response.Type == api.ErrorResponse {
		return nil, "", fmt.Errorf(response.Error)
	}

	return &response, etag, nil
}

func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag string) (*api.Response, string, error) {
	var req *http.Request
	var err error

	// Log the request
	logger.Debug("Sending request to LXD",
		"method", method,
		"url", url,
		"etag", ETag,
	)

	// Get a new HTTP request setup
	if data != nil {
		// Encode the provided data
		buf := bytes.Buffer{}
		err := json.NewEncoder(&buf).Encode(data)
		if err != nil {
			return nil, "", err
		}

		// Some data to be sent along with the request
		// Use a reader since the request body needs to be seekable
		req, err = http.NewRequest(method, url, bytes.NewReader(buf.Bytes()))
		if err != nil {
			return nil, "", err
		}

		// Set the encoding accordingly
		req.Header.Set("Content-Type", "application/json")

		// Log the data
		logger.Debugf(logger.Pretty(data))
	} else {
		// No data to be sent along with the request
		req, err = http.NewRequest(method, url, nil)
		if err != nil {
			return nil, "", err
		}
	}

	// Set the user agent
	if r.httpUserAgent != "" {
		req.Header.Set("User-Agent", r.httpUserAgent)
	}

	// Set the ETag
	if ETag != "" {
		req.Header.Set("If-Match", ETag)
	}

	// Set the authentication header
	if r.requireAuthenticated {
		req.Header.Set("X-LXD-authenticated", "true")
	}

	// Send the request
	resp, err := r.do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	return r.parseResponse(resp)
}

func (r *ProtocolLXD) query(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
	// Generate the URL
	url := fmt.Sprintf("%s/1.0%s", r.httpHost, path)

	return r.rawQuery(method, url, data, ETag)
}

func (r *ProtocolLXD) queryStruct(method string, path string, data interface{}, ETag string, target interface{}) (string, error) {
	resp, etag, err := r.query(method, path, data, ETag)
	if err != nil {
		return "", err
	}

	err = resp.MetadataAsStruct(&target)
	if err != nil {
		return "", err
	}

	// Log the data
	logger.Debugf("Got response struct from LXD")
	logger.Debugf(logger.Pretty(target))

	return etag, nil
}

func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
	// Attempt to setup an early event listener
	listener, err := r.GetEvents()
	if err != nil {
		listener = nil
	}

	// Send the query
	resp, etag, err := r.query(method, path, data, ETag)
	if err != nil {
		if listener != nil {
			listener.Disconnect()
		}

		return nil, "", err
	}

	// Get to the operation
	respOperation, err := resp.MetadataAsOperation()
	if err != nil {
		if listener != nil {
			listener.Disconnect()
		}

		return nil, "", err
	}

	// Setup an Operation wrapper
	op := Operation{
		Operation: *respOperation,
		r:         r,
		listener:  listener,
		chActive:  make(chan bool),
	}

	// Log the data
	logger.Debugf("Got operation from LXD")
	logger.Debugf(logger.Pretty(op.Operation))

	return &op, etag, nil
}

func (r *ProtocolLXD) rawWebsocket(url string) (*websocket.Conn, error) {
	// Grab the http transport handler
	httpTransport := r.http.Transport.(*http.Transport)

	// Setup a new websocket dialer based on it
	dialer := websocket.Dialer{
		NetDial:         httpTransport.Dial,
		TLSClientConfig: httpTransport.TLSClientConfig,
	}

	// Set the user agent
	headers := http.Header{}
	if r.httpUserAgent != "" {
		headers.Set("User-Agent", r.httpUserAgent)
	}

	if r.requireAuthenticated {
		headers.Set("X-LXD-authenticated", "true")
	}

	// Set macaroon headers if needed
	if r.bakeryClient != nil {
		u, err := neturl.Parse(r.httpHost) // use the http url, not the ws one
		if err != nil {
			return nil, err
		}
		req := &http.Request{URL: u, Header: headers}
		r.addMacaroonHeaders(req)
	}

	// Establish the connection
	conn, _, err := dialer.Dial(url, headers)
	if err != nil {
		return nil, err
	}

	// Log the data
	logger.Debugf("Connected to the websocket")

	return conn, err
}

func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
	// Generate the URL
	var url string
	if strings.HasPrefix(r.httpHost, "https://") {
		url = fmt.Sprintf("wss://%s/1.0%s", strings.TrimPrefix(r.httpHost, "https://"), path)
	} else {
		url = fmt.Sprintf("ws://%s/1.0%s", strings.TrimPrefix(r.httpHost, "http://"), path)
	}

	return r.rawWebsocket(url)
}

func (r *ProtocolLXD) setupBakeryClient() {
	r.bakeryClient = httpbakery.NewClient()
	r.bakeryClient.Client = r.http
	if r.bakeryInteractor != nil {
		r.bakeryClient.AddInteractor(r.bakeryInteractor)
	}
}
<|endoftext|>


<commit_before>package client

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/docker/docker/pkg/namesgenerator"
	"github.com/hyperhq/hyperd/utils"
	"github.com/hyperhq/runv/hypervisor/pod"
	"github.com/hyperhq/runv/lib/term"

	gflag "github.com/jessevdk/go-flags"
	"net/http"
)

// hyperctl run [OPTIONS] image [COMMAND] [ARGS...]
func (cli *HyperClient) HyperCmdRun(args ...string) (err error) {
	var opts struct {
		PodFile       string   `short:"p" long:"podfile" value-name:"\"\"" description:"Create and Run a pod based on the pod file"`
		K8s           string   `short:"k" long:"kubernetes" value-name:"\"\"" description:"Create and Run a pod based on the kubernetes pod file"`
		Yaml          bool     `short:"y" long:"yaml" default:"false" default-mask:"-" description:"Create a pod based on Yaml file"`
		Name          string   `long:"name" value-name:"\"\"" description:"Assign a name to the container"`
		Attach        bool     `short:"a" long:"attach" default:"false" default-mask:"-" description:"(from podfile) Attach the stdin, stdout and stderr to the container"`
		Detach        bool     `short:"d" long:"detach" default:"false" default-mask:"-" description:"(from cmdline) Not Attach the stdin, stdout and stderr to the container"`
		Workdir       string   `long:"workdir" default:"/" value-name:"\"\"" default-mask:"-" description:"Working directory inside the container"`
		Tty           bool     `short:"t" long:"tty" default:"false" default-mask:"-" description:"the run command in tty, such as bash shell"`
		Cpu           int      `long:"cpu" default:"1" value-name:"1" default-mask:"-" description:"CPU number for the VM"`
		Memory        int      `long:"memory" default:"128" value-name:"128" default-mask:"-" description:"Memory size (MB) for the VM"`
		Env           []string `long:"env" value-name:"[]" default-mask:"-" description:"Set environment variables"`
		EntryPoint    string   `long:"entrypoint" value-name:"\"\"" default-mask:"-" description:"Overwrite the default ENTRYPOINT of the image"`
		RestartPolicy string   `long:"restart" default:"never" value-name:"\"\"" default-mask:"-" description:"Restart policy to apply when a container exits (never, onFailure, always)"`
		LogDriver     string   `long:"log-driver" value-name:"\"\"" description:"Logging driver for Pod"`
		LogOpts       []string `long:"log-opt" description:"Log driver options"`
		Remove        bool     `long:"rm" default:"false" default-mask:"-" description:"Automatically remove the pod when it exits"`
		Portmap       []string `long:"publish" value-name:"[]" default-mask:"-" description:"Publish a container's port to the host, format: --publish [tcp/udp:]hostPort:containerPort"`
		Labels        []string `long:"label" value-name:"[]" default-mask:"-" description:"Add labels for Pod, format: --label key=value"`
	}

	var (
		podId   string
		vmId    string
		podJson string
		attach  bool = false
	)
	var parser = gflag.NewParser(&opts, gflag.Default|gflag.IgnoreUnknown)
	parser.Usage = "run [OPTIONS] IMAGE [COMMAND] [ARG...]\n\nCreate a pod, and launch a new VM to run the pod"
	args, err = parser.ParseArgs(args)
	if err != nil {
		if !strings.Contains(err.Error(), "Usage") {
			return err
		} else {
			return nil
		}
	}

	if opts.PodFile != "" {
		attach = opts.Attach
		podJson, err = cli.JsonFromFile(opts.PodFile, opts.Yaml, false)
	} else if opts.K8s != "" {
		attach = opts.Attach
		podJson, err = cli.JsonFromFile(opts.K8s, opts.Yaml, true)
	} else {
		if len(args) == 0 {
			return fmt.Errorf("%s: \"run\" requires a minimum of 1 argument, please provide the image.", os.Args[0])
		}
		attach = !opts.Detach
		podJson, err = cli.JsonFromCmdline(args, opts.Env, opts.Portmap, opts.LogDriver, opts.LogOpts,
			opts.Name, opts.Workdir, opts.RestartPolicy, opts.Cpu, opts.Memory, opts.Tty, opts.Labels, opts.EntryPoint)
	}

	if err != nil {
		return err
	}

	t1 := time.Now()

	var (
		spec  pod.UserPod
		code  int
		async = true
		tty   = false
	)
	json.Unmarshal([]byte(podJson), &spec)

	vmId, err = cli.client.CreateVm(spec.Resource.Vcpu, spec.Resource.Memory, async)
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			cli.client.RmVm(vmId)
		}
	}()

	podId, code, err = cli.client.CreatePod(&spec)
	if err != nil {
		if code == http.StatusNotFound {
			err = cli.PullImages(&spec)
			if err != nil {
				return
			}
			podId, code, err = cli.client.CreatePod(&spec)
		}
		if err != nil {
			return
		}
	}
	if !attach {
		fmt.Printf("POD id is %s\n", podId)
	}

	if opts.Remove {
		defer func() {
			rmerr := cli.client.RmPod(podId)
			if rmerr != nil {
				fmt.Fprintf(cli.out, "failed to rm pod, %v\n", rmerr)
			}
		}()
	}

	if attach {
		if opts.PodFile == "" && opts.K8s == "" {
			tty = opts.Tty
		} else {
			tty = spec.Tty || spec.Containers[0].Tty
		}

		if tty {
			p, err := cli.client.GetPodInfo(podId)
			if err == nil {
				cli.monitorTtySize(p.Spec.Containers[0].ContainerID, "")
			}

			oldState, err := term.SetRawTerminal(cli.inFd)
			if err != nil {
				return err
			}
			defer term.RestoreTerminal(cli.inFd, oldState)
		}

	}

	_, err = cli.client.StartPod(podId, vmId, attach, tty, cli.in, cli.out, cli.err)
	if err != nil {
		return
	}

	if !attach {
		t2 := time.Now()
		fmt.Printf("Time to run a POD is %d ms\n", (t2.UnixNano()-t1.UnixNano())/1000000)
	}
	return nil
}

func (cli *HyperClient) JsonFromFile(filename string, yaml, k8s bool) (string, error) {
	if _, err := os.Stat(filename); err != nil {
		return "", err
	}

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

	if yaml == true {
		jsonbody, err = cli.ConvertYamlToJson(jsonbody)
		if err != nil {
			return "", err
		}
	}

	if k8s {
		var kpod pod.KPod

		if err := json.Unmarshal(jsonbody, &kpod); err != nil {
			return "", err
		}
		userpod, err := kpod.Convert()
		if err != nil {
			return "", err
		}
		jsonbody, err = json.Marshal(*userpod)
		if err != nil {
			return "", err
		}
	}

	return string(jsonbody), nil
}

func (cli *HyperClient) JsonFromCmdline(cmdArgs, cmdEnvs, cmdPortmaps []string, cmdLogDriver string, cmdLogOpts []string,
	cmdName, cmdWorkdir, cmdRestartPolicy string, cpu, memory int, tty bool, cmdLabels []string, entrypoint string) (string, error) {

	var (
		name    = cmdName
		image   = cmdArgs[0]
		command = []string{}
		env     = []pod.UserEnvironmentVar{}
		ports   = []pod.UserContainerPort{}
		logOpts = make(map[string]string)
		labels  = make(map[string]string)
	)
	if len(cmdArgs) > 1 {
		command = cmdArgs[1:]
	}
	if name == "" {
		name = imageToName(image)
	}
	if memory == 0 {
		memory = 128
	}
	if cpu == 0 {
		cpu = 1
	}
	for _, v := range cmdEnvs {
		if eqlIndex := strings.Index(v, "="); eqlIndex > 0 {
			env = append(env, pod.UserEnvironmentVar{
				Env:   v[:eqlIndex],
				Value: v[eqlIndex+1:],
			})
		}
	}

	for _, v := range cmdLogOpts {
		eql := strings.Index(v, "=")
		if eql > 0 {
			logOpts[v[:eql]] = v[eql+1:]
		} else {
			logOpts[v] = ""
		}
	}

	for _, v := range cmdPortmaps {
		p, err := parsePortMapping(v)
		if err != nil {
			return "", err
		}
		ports = append(ports, *p)
	}

	for _, v := range cmdLabels {
		label := strings.Split(v, "=")
		if len(label) == 2 {
			labels[label[0]] = label[1]
		} else {
			return "", fmt.Errorf("Label '%s' is not in 'k=v' format", v)
		}
	}

	entrypoints := make([]string, 0, 1)
	if len(entrypoint) > 0 {
		entrypoints = append(entrypoints, entrypoint)
	}

	containerList := []pod.UserContainer{{
		Name:          name,
		Image:         image,
		Command:       command,
		Workdir:       cmdWorkdir,
		Entrypoint:    entrypoints,
		Ports:         ports,
		Envs:          env,
		Volumes:       []pod.UserVolumeReference{},
		Files:         []pod.UserFileReference{},
		RestartPolicy: cmdRestartPolicy,
	}}

	userPod := &pod.UserPod{
		Name:       name,
		Containers: containerList,
		Labels:     labels,
		Resource:   pod.UserResource{Vcpu: cpu, Memory: memory},
		Files:      []pod.UserFile{},
		Volumes:    []pod.UserVolume{},
		LogConfig: pod.PodLogConfig{
			Type:   cmdLogDriver,
			Config: logOpts,
		},
		Tty: tty,
	}

	jsonString, _ := json.Marshal(userPod)
	return string(jsonString), nil
}

func parsePortMapping(portmap string) (*pod.UserContainerPort, error) {

	var (
		port  = pod.UserContainerPort{}
		proto string
		hPort string
		cPort string
		err   error
	)

	fields := strings.Split(portmap, ":")
	if len(fields) < 2 {
		return nil, fmt.Errorf("flag needs host port and container port: --publish")
	} else if len(fields) == 2 {
		proto = "tcp"
		hPort = fields[0]
		cPort = fields[1]
	} else {
		proto = fields[0]
		if proto != "tcp" && proto != "udp" {
			return nil, fmt.Errorf("flag needs protocol(tcp or udp): --publish")
		}
		hPort = fields[1]
		cPort = fields[2]
	}

	port.Protocol = proto
	port.HostPort, err = strconv.Atoi(hPort)
	if err != nil {
		return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
	}
	port.ContainerPort, err = strconv.Atoi(cPort)
	if err != nil {
		return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
	}

	return &port, nil
}

func imageToName(image string) string {
	name := image
	fields := strings.Split(image, "/")
	if len(fields) > 1 {
		name = fields[len(fields)-1]
	}
	fields = strings.Split(name, ":")
	if len(fields) < 2 {
		name = name + "-" + utils.RandStr(10, "number")
	} else {
		name = fields[0] + "-" + fields[1] + "-" + utils.RandStr(10, "number")
	}

	validContainerNameChars := `[a-zA-Z0-9][a-zA-Z0-9_.-]`
	validContainerNamePattern := regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
	if !validContainerNamePattern.MatchString(name) {
		name = namesgenerator.GetRandomName(0)
	}
	return name
}
<commit_msg>add --volume support for hyperctl<commit_after>package client

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

	"github.com/docker/docker/pkg/namesgenerator"
	"github.com/hyperhq/hyperd/utils"
	"github.com/hyperhq/runv/hypervisor/pod"
	"github.com/hyperhq/runv/lib/term"

	gflag "github.com/jessevdk/go-flags"
	"net/http"
)

// hyperctl run [OPTIONS] image [COMMAND] [ARGS...]
func (cli *HyperClient) HyperCmdRun(args ...string) (err error) {
	var opts struct {
		PodFile       string   `short:"p" long:"podfile" value-name:"\"\"" description:"Create and Run a pod based on the pod file"`
		K8s           string   `short:"k" long:"kubernetes" value-name:"\"\"" description:"Create and Run a pod based on the kubernetes pod file"`
		Yaml          bool     `short:"y" long:"yaml" default:"false" default-mask:"-" description:"Create a pod based on Yaml file"`
		Name          string   `long:"name" value-name:"\"\"" description:"Assign a name to the container"`
		Attach        bool     `short:"a" long:"attach" default:"false" default-mask:"-" description:"(from podfile) Attach the stdin, stdout and stderr to the container"`
		Detach        bool     `short:"d" long:"detach" default:"false" default-mask:"-" description:"(from cmdline) Not Attach the stdin, stdout and stderr to the container"`
		Workdir       string   `long:"workdir" default:"/" value-name:"\"\"" default-mask:"-" description:"Working directory inside the container"`
		Tty           bool     `short:"t" long:"tty" default:"false" default-mask:"-" description:"the run command in tty, such as bash shell"`
		Cpu           int      `long:"cpu" default:"1" value-name:"1" default-mask:"-" description:"CPU number for the VM"`
		Memory        int      `long:"memory" default:"128" value-name:"128" default-mask:"-" description:"Memory size (MB) for the VM"`
		Env           []string `long:"env" value-name:"[]" default-mask:"-" description:"Set environment variables"`
		EntryPoint    string   `long:"entrypoint" value-name:"\"\"" default-mask:"-" description:"Overwrite the default ENTRYPOINT of the image"`
		RestartPolicy string   `long:"restart" default:"never" value-name:"\"\"" default-mask:"-" description:"Restart policy to apply when a container exits (never, onFailure, always)"`
		LogDriver     string   `long:"log-driver" value-name:"\"\"" description:"Logging driver for Pod"`
		LogOpts       []string `long:"log-opt" description:"Log driver options"`
		Remove        bool     `long:"rm" default:"false" default-mask:"-" description:"Automatically remove the pod when it exits"`
		Portmap       []string `long:"publish" value-name:"[]" default-mask:"-" description:"Publish a container's port to the host, format: --publish [tcp/udp:]hostPort:containerPort"`
		Labels        []string `long:"label" value-name:"[]" default-mask:"-" description:"Add labels for Pod, format: --label key=value"`
		Volumes       []string `short:"v" long:"volume" value-name:"[]" default-mask:"-" description:"Mount host file/directory as a data file/volume, format: -v|--volume=[[hostDir:]containerDir[:options]]"`
	}

	var (
		podId   string
		vmId    string
		podJson string
		attach  bool = false
	)
	var parser = gflag.NewParser(&opts, gflag.Default|gflag.IgnoreUnknown)
	parser.Usage = "run [OPTIONS] IMAGE [COMMAND] [ARG...]\n\nCreate a pod, and launch a new VM to run the pod"
	args, err = parser.ParseArgs(args)
	if err != nil {
		if !strings.Contains(err.Error(), "Usage") {
			return err
		} else {
			return nil
		}
	}

	if opts.PodFile != "" {
		attach = opts.Attach
		podJson, err = cli.JsonFromFile(opts.PodFile, opts.Yaml, false)
	} else if opts.K8s != "" {
		attach = opts.Attach
		podJson, err = cli.JsonFromFile(opts.K8s, opts.Yaml, true)
	} else {
		if len(args) == 0 {
			return fmt.Errorf("%s: \"run\" requires a minimum of 1 argument, please provide the image.", os.Args[0])
		}
		attach = !opts.Detach
		podJson, err = cli.JsonFromCmdline(args, opts.Env, opts.Portmap, opts.LogDriver, opts.LogOpts,
			opts.Name, opts.Workdir, opts.RestartPolicy, opts.Cpu, opts.Memory, opts.Tty, opts.Labels, opts.EntryPoint, opts.Volumes)
	}

	if err != nil {
		return err
	}

	t1 := time.Now()

	var (
		spec  pod.UserPod
		code  int
		async = true
		tty   = false
	)
	json.Unmarshal([]byte(podJson), &spec)

	vmId, err = cli.client.CreateVm(spec.Resource.Vcpu, spec.Resource.Memory, async)
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			cli.client.RmVm(vmId)
		}
	}()

	podId, code, err = cli.client.CreatePod(&spec)
	if err != nil {
		if code == http.StatusNotFound {
			err = cli.PullImages(&spec)
			if err != nil {
				return
			}
			podId, code, err = cli.client.CreatePod(&spec)
		}
		if err != nil {
			return
		}
	}
	if !attach {
		fmt.Printf("POD id is %s\n", podId)
	}

	if opts.Remove {
		defer func() {
			rmerr := cli.client.RmPod(podId)
			if rmerr != nil {
				fmt.Fprintf(cli.out, "failed to rm pod, %v\n", rmerr)
			}
		}()
	}

	if attach {
		if opts.PodFile == "" && opts.K8s == "" {
			tty = opts.Tty
		} else {
			tty = spec.Tty || spec.Containers[0].Tty
		}

		if tty {
			p, err := cli.client.GetPodInfo(podId)
			if err == nil {
				cli.monitorTtySize(p.Spec.Containers[0].ContainerID, "")
			}

			oldState, err := term.SetRawTerminal(cli.inFd)
			if err != nil {
				return err
			}
			defer term.RestoreTerminal(cli.inFd, oldState)
		}

	}

	_, err = cli.client.StartPod(podId, vmId, attach, tty, cli.in, cli.out, cli.err)
	if err != nil {
		return
	}

	if !attach {
		t2 := time.Now()
		fmt.Printf("Time to run a POD is %d ms\n", (t2.UnixNano()-t1.UnixNano())/1000000)
	}
	return nil
}

func (cli *HyperClient) JsonFromFile(filename string, yaml, k8s bool) (string, error) {
	if _, err := os.Stat(filename); err != nil {
		return "", err
	}

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

	if yaml == true {
		jsonbody, err = cli.ConvertYamlToJson(jsonbody)
		if err != nil {
			return "", err
		}
	}

	if k8s {
		var kpod pod.KPod

		if err := json.Unmarshal(jsonbody, &kpod); err != nil {
			return "", err
		}
		userpod, err := kpod.Convert()
		if err != nil {
			return "", err
		}
		jsonbody, err = json.Marshal(*userpod)
		if err != nil {
			return "", err
		}
	}

	return string(jsonbody), nil
}

func (cli *HyperClient) JsonFromCmdline(cmdArgs, cmdEnvs, cmdPortmaps []string, cmdLogDriver string, cmdLogOpts []string,
	cmdName, cmdWorkdir, cmdRestartPolicy string, cpu, memory int, tty bool, cmdLabels []string, entrypoint string, cmdVols []string) (string, error) {

	var (
		name       = cmdName
		image      = cmdArgs[0]
		command    = []string{}
		env        = []pod.UserEnvironmentVar{}
		ports      = []pod.UserContainerPort{}
		logOpts    = make(map[string]string)
		labels     = make(map[string]string)
		volumesRef = []pod.UserVolumeReference{}
		volumes    = []pod.UserVolume{}
	)
	if len(cmdArgs) > 1 {
		command = cmdArgs[1:]
	}
	if name == "" {
		name = imageToName(image)
	}
	if memory == 0 {
		memory = 128
	}
	if cpu == 0 {
		cpu = 1
	}
	for _, v := range cmdEnvs {
		if eqlIndex := strings.Index(v, "="); eqlIndex > 0 {
			env = append(env, pod.UserEnvironmentVar{
				Env:   v[:eqlIndex],
				Value: v[eqlIndex+1:],
			})
		}
	}

	for _, v := range cmdLogOpts {
		eql := strings.Index(v, "=")
		if eql > 0 {
			logOpts[v[:eql]] = v[eql+1:]
		} else {
			logOpts[v] = ""
		}
	}

	for _, v := range cmdPortmaps {
		p, err := parsePortMapping(v)
		if err != nil {
			return "", err
		}
		ports = append(ports, *p)
	}

	for _, v := range cmdLabels {
		label := strings.Split(v, "=")
		if len(label) == 2 {
			labels[label[0]] = label[1]
		} else {
			return "", fmt.Errorf("Label '%s' is not in 'k=v' format", v)
		}
	}

	for _, v := range cmdVols {
		vol, volRef, err := parseVolume(v)
		if err != nil {
			return "", err
		}
		volumes = append(volumes, *vol)
		volumesRef = append(volumesRef, *volRef)
	}

	entrypoints := make([]string, 0, 1)
	if len(entrypoint) > 0 {
		entrypoints = append(entrypoints, entrypoint)
	}

	containerList := []pod.UserContainer{{
		Name:          name,
		Image:         image,
		Command:       command,
		Workdir:       cmdWorkdir,
		Entrypoint:    entrypoints,
		Ports:         ports,
		Envs:          env,
		Volumes:       volumesRef,
		Files:         []pod.UserFileReference{},
		RestartPolicy: cmdRestartPolicy,
	}}

	userPod := &pod.UserPod{
		Name:       name,
		Containers: containerList,
		Labels:     labels,
		Resource:   pod.UserResource{Vcpu: cpu, Memory: memory},
		Files:      []pod.UserFile{},
		Volumes:    volumes,
		LogConfig: pod.PodLogConfig{
			Type:   cmdLogDriver,
			Config: logOpts,
		},
		Tty: tty,
	}

	jsonString, _ := json.Marshal(userPod)
	return string(jsonString), nil
}

func parseVolume(volStr string) (*pod.UserVolume, *pod.UserVolumeReference, error) {

	var (
		srcName   string
		destPath  string
		volName   string
		readOnly  = false
		volDriver = "vfs"
	)

	fields := strings.Split(volStr, ":")
	if len(fields) == 3 {
		// cmd: -v host-src:container-dest:rw
		srcName = fields[0]
		destPath = fields[1]
		if fields[2] != "ro" && fields[2] != "rw" {
			return nil, nil, fmt.Errorf("flag only support(ro or rw): --volume")
		}
		if fields[2] == "ro" {
			readOnly = true
		}
	} else if len(fields) == 2 {
		// cmd: -v host-src:container-dest
		srcName = fields[0]
		destPath = fields[1]
	} else if len(fields) == 1 {
		// -v container-dest
		destPath = fields[0]
	} else {
		return nil, nil, fmt.Errorf("flag format should be like : --volume=[host-src:]container-dest[:rw|ro]")
	}

	if srcName == "" {
		// Set default volume driver and use destPath as volume Name
		volDriver = ""
		_, volName = filepath.Split(destPath)
	} else {
		_, volName = filepath.Split(srcName)
		// Auto create the source folder on the host , otherwise hyperd will complain
		if _, err := os.Stat(srcName); err != nil && os.IsNotExist(err) {
			if err := os.MkdirAll(srcName, os.FileMode(0777)); err != nil {
				return nil, nil, err
			}
		}
	}

	vol := pod.UserVolume{
		// Avoid name collision
		Name:   volName + utils.RandStr(5, "number"),
		Source: srcName,
		Driver: volDriver,
	}

	volRef := pod.UserVolumeReference{
		Volume:   vol.Name,
		Path:     destPath,
		ReadOnly: readOnly,
	}

	return &vol, &volRef, nil
}

func parsePortMapping(portmap string) (*pod.UserContainerPort, error) {

	var (
		port  = pod.UserContainerPort{}
		proto string
		hPort string
		cPort string
		err   error
	)

	fields := strings.Split(portmap, ":")
	if len(fields) < 2 {
		return nil, fmt.Errorf("flag needs host port and container port: --publish")
	} else if len(fields) == 2 {
		proto = "tcp"
		hPort = fields[0]
		cPort = fields[1]
	} else {
		proto = fields[0]
		if proto != "tcp" && proto != "udp" {
			return nil, fmt.Errorf("flag needs protocol(tcp or udp): --publish")
		}
		hPort = fields[1]
		cPort = fields[2]
	}

	port.Protocol = proto
	port.HostPort, err = strconv.Atoi(hPort)
	if err != nil {
		return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
	}
	port.ContainerPort, err = strconv.Atoi(cPort)
	if err != nil {
		return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
	}

	return &port, nil
}

func imageToName(image string) string {
	name := image
	fields := strings.Split(image, "/")
	if len(fields) > 1 {
		name = fields[len(fields)-1]
	}
	fields = strings.Split(name, ":")
	if len(fields) < 2 {
		name = name + "-" + utils.RandStr(10, "number")
	} else {
		name = fields[0] + "-" + fields[1] + "-" + utils.RandStr(10, "number")
	}

	validContainerNameChars := `[a-zA-Z0-9][a-zA-Z0-9_.-]`
	validContainerNamePattern := regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
	if !validContainerNamePattern.MatchString(name) {
		name = namesgenerator.GetRandomName(0)
	}
	return name
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"github.com/coreos/fleet/schema"
	"github.com/juju/errgo"
	execPkg "os/exec"
	"strings"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
	driver   string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	driver := ""
	cmd := execPkg.Command(FLEETCTL, "--version")
	output, err := exec(cmd)
	if err != nil {
		return nil
	}

	if strings.Contains(output, "0.10") {
		fmt.Printf("Adding driver option for version 0.10\n")
		driver = "--driver etcd"
	} else {
		fmt.Printf("Not adding driver option: %s\n", output)
	}

	return &ClientCLI{
		etcdPeer: etcdPeer,
		driver:   driver,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	for _, arg := range cmd.Args {
		fmt.Printf(" %s", arg)
	}
	fmt.Printf("\n")
	output, err := exec(cmd)

	if err != nil {
		fmt.Printf("Error in submit: %s %s\n", err, output)
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Unit(name string) (*schema.Unit, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Unit")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Load(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "load", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<commit_msg>changed command<commit_after>package client

import (
	"fmt"
	"github.com/coreos/fleet/schema"
	"github.com/juju/errgo"
	execPkg "os/exec"
	"strings"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
	driver   string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	driver := ""
	cmd := execPkg.Command(FLEETCTL, "--version")
	output, err := exec(cmd)
	if err != nil {
		return nil
	}

	if strings.Contains(output, "0.10") {
		fmt.Printf("Adding driver option for version 0.10\n")
		driver = "--driver=etcd"
	} else {
		fmt.Printf("Not adding driver option: %s\n", output)
	}

	return &ClientCLI{
		etcdPeer: etcdPeer,
		driver:   driver,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	for _, arg := range cmd.Args {
		fmt.Printf(" %s", arg)
	}
	fmt.Printf("\n")
	output, err := exec(cmd)

	if err != nil {
		fmt.Printf("Error in submit: %s %s\n", err, output)
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Unit(name string) (*schema.Unit, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Unit")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Load(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "load", "--no-block=false", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, this.driver, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 remote

import (
	"runtime"
	"strings"

	"github.com/GoogleContainerTools/kaniko/pkg/config"
	"github.com/GoogleContainerTools/kaniko/pkg/creds"
	"github.com/GoogleContainerTools/kaniko/pkg/util"

	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/remote"

	"github.com/sirupsen/logrus"
)

var (
	manifestCache = make(map[string]v1.Image)
)

// RetrieveRemoteImage retrieves the manifest for the specified image from the specified registry
func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatform string) (v1.Image, error) {
	logrus.Infof("Retrieving image manifest %s", image)

	cachedRemoteImage := manifestCache[image]
	if cachedRemoteImage != nil {
		logrus.Infof("Returning cached image manifest")
		return cachedRemoteImage, nil
	}

	ref, err := name.ParseReference(image, name.WeakValidation)
	if err != nil {
		return nil, err
	}

	if ref.Context().RegistryStr() == name.DefaultRegistry {
		ref, err := normalizeReference(ref, image)
		if err != nil {
			return nil, err
		}

		for _, registryMirror := range opts.RegistryMirrors {
			var newReg name.Registry
			if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) {
				newReg, err = name.NewRegistry(registryMirror, name.WeakValidation, name.Insecure)
			} else {
				newReg, err = name.NewRegistry(registryMirror, name.StrictValidation)
			}
			if err != nil {
				return nil, err
			}
			ref := setNewRegistry(ref, newReg)

			logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror)
			remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts, customPlatform)...)
			if err != nil {
				logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err)
				continue
			}

			manifestCache[image] = remoteImage

			return remoteImage, nil
		}
	}

	registryName := ref.Context().RegistryStr()
	if opts.InsecurePull || opts.InsecureRegistries.Contains(registryName) {
		newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
		if err != nil {
			return nil, err
		}
		ref = setNewRegistry(ref, newReg)
	}

	logrus.Infof("Retrieving image %s from registry %s", ref, registryName)

	remoteImage, err := remote.Image(ref, remoteOptions(registryName, opts, customPlatform)...)

	if remoteImage != nil {
		manifestCache[image] = remoteImage
	}

	return remoteImage, err
}

// normalizeReference adds the library/ prefix to images without it.
//
// It is mostly useful when using a registry mirror that is not able to perform
// this fix automatically.
func normalizeReference(ref name.Reference, image string) (name.Reference, error) {
	if !strings.ContainsRune(image, '/') {
		return name.ParseReference("library/"+image, name.WeakValidation)
	}

	return ref, nil
}

func setNewRegistry(ref name.Reference, newReg name.Registry) name.Reference {
	switch r := ref.(type) {
	case name.Tag:
		r.Repository.Registry = newReg
		return r
	case name.Digest:
		r.Repository.Registry = newReg
		return r
	default:
		return ref
	}
}

func remoteOptions(registryName string, opts config.RegistryOptions, customPlatform string) []remote.Option {
	tr := util.MakeTransport(opts, registryName)

	// on which v1.Platform is this currently running?
	platform := currentPlatform(customPlatform)

	return []remote.Option{remote.WithTransport(tr), remote.WithAuthFromKeychain(creds.GetKeychain()), remote.WithPlatform(platform)}
}

// CurrentPlatform returns the v1.Platform on which the code runs
func currentPlatform(customPlatform string) v1.Platform {
	if customPlatform != "" {
		customPlatformArray := strings.Split(customPlatform, "/")
		imagePlatform := v1.Platform{}
		imagePlatform.OS = customPlatformArray[0]
		if len(customPlatformArray) > 1 {
			imagePlatform.Architecture = customPlatformArray[1]
			if len(customPlatformArray) > 2 {
				imagePlatform.Variant = customPlatformArray[2]
			}
		}
		return imagePlatform
	}
	return v1.Platform{
		OS:           runtime.GOOS,
		Architecture: runtime.GOARCH,
	}
}
<commit_msg>Support mirror registries with path component (#1707)<commit_after>/*
Copyright 2018 Google LLC

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 remote

import (
	"runtime"
	"strings"

	"github.com/GoogleContainerTools/kaniko/pkg/config"
	"github.com/GoogleContainerTools/kaniko/pkg/creds"
	"github.com/GoogleContainerTools/kaniko/pkg/util"

	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/remote"

	"github.com/sirupsen/logrus"
)

var (
	manifestCache = make(map[string]v1.Image)
)

// RetrieveRemoteImage retrieves the manifest for the specified image from the specified registry
func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatform string) (v1.Image, error) {
	logrus.Infof("Retrieving image manifest %s", image)

	cachedRemoteImage := manifestCache[image]
	if cachedRemoteImage != nil {
		logrus.Infof("Returning cached image manifest")
		return cachedRemoteImage, nil
	}

	ref, err := name.ParseReference(image, name.WeakValidation)
	if err != nil {
		return nil, err
	}

	if ref.Context().RegistryStr() == name.DefaultRegistry {
		ref, err := normalizeReference(ref, image)
		if err != nil {
			return nil, err
		}

		for _, registryMirror := range opts.RegistryMirrors {
			var newRepo name.Repository
			if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) {
				newRepo, err = name.NewRepository(registryMirror, name.WeakValidation, name.Insecure)
			} else {
				newRepo, err = name.NewRepository(registryMirror, name.StrictValidation)
			}
			if err != nil {
				return nil, err
			}
			ref := setNewRepository(ref, newRepo)

			logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror)
			remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts, customPlatform)...)
			if err != nil {
				logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err)
				continue
			}

			manifestCache[image] = remoteImage

			return remoteImage, nil
		}
	}

	registryName := ref.Context().RegistryStr()
	if opts.InsecurePull || opts.InsecureRegistries.Contains(registryName) {
		newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
		if err != nil {
			return nil, err
		}
		ref = setNewRegistry(ref, newReg)
	}

	logrus.Infof("Retrieving image %s from registry %s", ref, registryName)

	remoteImage, err := remote.Image(ref, remoteOptions(registryName, opts, customPlatform)...)

	if remoteImage != nil {
		manifestCache[image] = remoteImage
	}

	return remoteImage, err
}

// normalizeReference adds the library/ prefix to images without it.
//
// It is mostly useful when using a registry mirror that is not able to perform
// this fix automatically.
func normalizeReference(ref name.Reference, image string) (name.Reference, error) {
	if !strings.ContainsRune(image, '/') {
		return name.ParseReference("library/"+image, name.WeakValidation)
	}

	return ref, nil
}

func setNewRepository(ref name.Reference, newRepo name.Repository) name.Reference {
	switch r := ref.(type) {
	case name.Tag:
		r.Repository = newRepo
		return r
	case name.Digest:
		r.Repository = newRepo
		return r
	default:
		return ref
	}
}

func setNewRegistry(ref name.Reference, newReg name.Registry) name.Reference {
	switch r := ref.(type) {
	case name.Tag:
		r.Repository.Registry = newReg
		return r
	case name.Digest:
		r.Repository.Registry = newReg
		return r
	default:
		return ref
	}
}

func remoteOptions(registryName string, opts config.RegistryOptions, customPlatform string) []remote.Option {
	tr := util.MakeTransport(opts, registryName)

	// on which v1.Platform is this currently running?
	platform := currentPlatform(customPlatform)

	return []remote.Option{remote.WithTransport(tr), remote.WithAuthFromKeychain(creds.GetKeychain()), remote.WithPlatform(platform)}
}

// CurrentPlatform returns the v1.Platform on which the code runs
func currentPlatform(customPlatform string) v1.Platform {
	if customPlatform != "" {
		customPlatformArray := strings.Split(customPlatform, "/")
		imagePlatform := v1.Platform{}
		imagePlatform.OS = customPlatformArray[0]
		if len(customPlatformArray) > 1 {
			imagePlatform.Architecture = customPlatformArray[1]
			if len(customPlatformArray) > 2 {
				imagePlatform.Variant = customPlatformArray[2]
			}
		}
		return imagePlatform
	}
	return v1.Platform{
		OS:           runtime.GOOS,
		Architecture: runtime.GOARCH,
	}
}
<|endoftext|>


<commit_before>// Copyright 2016-2020 Authors of Cilium
//
// 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 version keeps track of the Kubernetes version the client is
// connected to
package version

import (
	"context"
	"fmt"

	k8sconfig "github.com/cilium/cilium/pkg/k8s/config"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/versioncheck"

	"github.com/blang/semver/v4"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "k8s")

// ServerCapabilities is a list of server capabilities derived based on
// version, the Kubernetes discovery API, or probing of individual API
// endpoints.
type ServerCapabilities struct {
	// MinimalVersionMet is true when the minimal version of Kubernetes
	// required to run Cilium has been met
	MinimalVersionMet bool

	// EndpointSlice is the ability of k8s server to support endpoint slices
	EndpointSlice bool

	// LeasesResourceLock is the ability of K8s server to support Lease type
	// from coordination.k8s.io/v1 API for leader election purposes(currently only in operator).
	// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#lease-v1-coordination-k8s-io
	//
	// This capability was introduced in K8s version 1.14, prior to which
	// we don't support HA mode for the cilium-operator.
	LeasesResourceLock bool

	// APIExtensionsV1CRD is set to true when the K8s server supports
	// apiextensions/v1 CRDs. TODO: Add link to docs
	//
	// This capability was introduced in K8s version 1.16, prior to which
	// apiextensions/v1beta1 CRDs were used exclusively.
	APIExtensionsV1CRD bool

	// WatchPartialObjectMetadata is set to true when the K8s server supports a
	// watch operation on the metav1.PartialObjectMetadata (and metav1.Table)
	// resource.
	//
	// This capability was introduced in K8s version 1.15, prior to which
	// watches cannot be performed on the aforementioned resources.
	//
	// Source:
	//   - KEP:
	//   https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190322-server-side-get-to-ga.md#goals
	//   - PR: https://github.com/kubernetes/kubernetes/pull/71548
	WatchPartialObjectMetadata bool
}

type cachedVersion struct {
	mutex        lock.RWMutex
	capabilities ServerCapabilities
	version      semver.Version
}

const (
	// MinimalVersionConstraint is the minimal version that Cilium supports to
	// run kubernetes.
	MinimalVersionConstraint = "1.16.0"
)

var (
	cached = cachedVersion{}

	discoveryAPIGroup      = "discovery.k8s.io/v1beta1"
	coordinationV1APIGroup = "coordination.k8s.io/v1"
	endpointSliceKind      = "EndpointSlice"
	leaseKind              = "Lease"

	// Constraint to check support for Lease type from coordination.k8s.io/v1.
	// Support for Lease resource was introduced in K8s version 1.14.
	isGEThanLeaseSupportConstraint = versioncheck.MustCompile(">=1.14.0")

	// Constraint to check support for apiextensions/v1 CRD types. Support for
	// v1 CRDs was introduced in K8s version 1.16.
	isGEThanAPIExtensionsV1CRD = versioncheck.MustCompile(">=1.16.0")

	// Constraint to check support for watching metav1.PartialObjectMetadata
	// and metav1.Table types. Support was introduced in K8s 1.15.
	isGEThanWatchPartialObjectMeta = versioncheck.MustCompile(">=1.15.0")

	// isGEThanMinimalVersionConstraint is the minimal version required to run
	// Cilium
	isGEThanMinimalVersionConstraint = versioncheck.MustCompile(">=" + MinimalVersionConstraint)
)

// Version returns the version of the Kubernetes apiserver
func Version() semver.Version {
	cached.mutex.RLock()
	c := cached.version
	cached.mutex.RUnlock()
	return c
}

// Capabilities returns the capabilities of the Kubernetes apiserver
func Capabilities() ServerCapabilities {
	cached.mutex.RLock()
	c := cached.capabilities
	cached.mutex.RUnlock()
	return c
}

func updateVersion(version semver.Version) {
	cached.mutex.Lock()
	defer cached.mutex.Unlock()

	cached.version = version

	cached.capabilities.MinimalVersionMet = isGEThanMinimalVersionConstraint(version)
	cached.capabilities.APIExtensionsV1CRD = isGEThanAPIExtensionsV1CRD(version)
	cached.capabilities.WatchPartialObjectMetadata = isGEThanWatchPartialObjectMeta(version)
}

func updateServerGroupsAndResources(apiResourceLists []*metav1.APIResourceList) {
	cached.mutex.Lock()
	defer cached.mutex.Unlock()

	cached.capabilities.EndpointSlice = false
	cached.capabilities.LeasesResourceLock = false
	for _, rscList := range apiResourceLists {
		if rscList.GroupVersion == discoveryAPIGroup {
			for _, rsc := range rscList.APIResources {
				if rsc.Kind == endpointSliceKind {
					cached.capabilities.EndpointSlice = true
					break
				}
			}
		}

		if rscList.GroupVersion == coordinationV1APIGroup {
			for _, rsc := range rscList.APIResources {
				if rsc.Kind == leaseKind {
					cached.capabilities.LeasesResourceLock = true
					break
				}
			}
		}
	}
}

// Force forces the use of a specific version
func Force(version string) error {
	ver, err := versioncheck.Version(version)
	if err != nil {
		return err
	}
	updateVersion(ver)
	return nil
}

func endpointSlicesFallbackDiscovery(client kubernetes.Interface) error {
	// Discovery of API groups requires the API services of the apiserver to be
	// healthy. Such API services can depend on the readiness of regular pods
	// which require Cilium to function correctly. By treating failure to
	// discover API groups as fatal, a critial loop can be entered in which
	// Cilium cannot start because the API groups can't be discovered.
	//
	// Here we acknowledge the lack of discovery ability as non Fatal and fall back to probing
	// the API directly.
	_, err := client.DiscoveryV1beta1().EndpointSlices("default").Get(context.TODO(), "kubernetes", metav1.GetOptions{})
	if err == nil {
		cached.mutex.Lock()
		cached.capabilities.EndpointSlice = true
		cached.mutex.Unlock()
		return nil
	}

	if errors.IsNotFound(err) {
		log.WithError(err).Info("Unable to retrieve EndpointSlices for default/kubernetes. Disabling EndpointSlices")
		// StatusNotFound is a safe error, EndpointSlices are
		// disabled and the agent can continue.
		return nil
	}

	// Unknown error, we can't derive whether to enable or disable
	// EndpointSlices and need to error out.
	return fmt.Errorf("unable to validate EndpointSlices support: %s", err)
}

func leasesFallbackDiscovery(client kubernetes.Interface, conf k8sconfig.Configuration) error {
	// K8sEnableLeasesFallbackDiscovery is used to fallback leases discovery to directly
	// probing the API when we cannot discover API groups.
	// We require to check for Leases capabilities in operator only, which uses Leases
	// for leader election purposes in HA mode.
	if !conf.K8sLeasesFallbackDiscoveryEnabled() {
		log.Debugf("Skipping Leases support fallback discovery")
		return nil
	}

	cached.mutex.RLock()
	// Here we check if we are running a K8s version that has support for Leases.
	if !isGEThanLeaseSupportConstraint(cached.version) {
		cached.mutex.RUnlock()
		return nil
	}
	cached.mutex.RUnlock()

	// Similar to endpointSlicesFallbackDiscovery here we fallback to probing the Kubernetes
	// API directly. `kube-controller-manager` creates a lease in the kube-system namespace
	// and here we try and see if that Lease exists.
	_, err := client.CoordinationV1().Leases("kube-system").Get(context.TODO(), "kube-controller-manager", metav1.GetOptions{})
	if err == nil {
		cached.mutex.Lock()
		cached.capabilities.LeasesResourceLock = true
		cached.mutex.Unlock()
		return nil
	}

	if errors.IsNotFound(err) {
		log.WithError(err).Info("Unable to retrieve Leases for kube-controller-manager. Disabling LeasesResourceLock")
		// StatusNotFound is a safe error, Leases are
		// disabled and the agent can continue
		return nil
	}

	// Unknown error, we can't derive whether to enable or disable
	// LeasesResourceLock and need to error out
	return fmt.Errorf("unable to validate LeasesResourceLock support: %s", err)
}

func updateK8sServerVersion(client kubernetes.Interface) error {
	var ver semver.Version

	sv, err := client.Discovery().ServerVersion()
	if err != nil {
		return err
	}

	// Try GitVersion first. In case of error fallback to MajorMinor
	if sv.GitVersion != "" {
		// This is a string like "v1.9.0"
		ver, err = versioncheck.Version(sv.GitVersion)
		if err == nil {
			updateVersion(ver)
			return nil
		}
	}

	if sv.Major != "" && sv.Minor != "" {
		ver, err = versioncheck.Version(fmt.Sprintf("%s.%s", sv.Major, sv.Minor))
		if err == nil {
			updateVersion(ver)
			return nil
		}
	}

	return fmt.Errorf("cannot parse k8s server version from %+v: %s", sv, err)
}

// Update retrieves the version of the Kubernetes apiserver and derives the
// capabilities. This function must be called after connectivity to the
// apiserver has been established.
//
// Discovery of capabilities only works if the discovery API of the apiserver
// is functional. If it is not available, a warning is logged and the discovery
// falls back to probing individual API endpoints.
func Update(client kubernetes.Interface, conf k8sconfig.Configuration) error {
	err := updateK8sServerVersion(client)
	if err != nil {
		return err
	}

	if conf.K8sAPIDiscoveryEnabled() {
		// Discovery of API groups requires the API services of the
		// apiserver to be healthy. Such API services can depend on the
		// readiness of regular pods which require Cilium to function
		// correctly. By treating failure to discover API groups as
		// fatal, a critical loop can be entered in which Cilium cannot
		// start because the API groups can't be discovered and th API
		// groups will only become discoverable once Cilium is up.
		_, apiResourceLists, err := client.Discovery().ServerGroupsAndResources()
		if err != nil {
			// It doesn't make sense to retry the retrieval of this
			// information at a later point because the capabilities are
			// primiarly used while the agent is starting up. Instead, fall
			// back to probing API endpoints directly.
			log.WithError(err).Warning("Unable to discover API groups and resources")
			if err := endpointSlicesFallbackDiscovery(client); err != nil {
				return err
			}

			return leasesFallbackDiscovery(client, conf)
		}

		updateServerGroupsAndResources(apiResourceLists)
	} else {
		if err := endpointSlicesFallbackDiscovery(client); err != nil {
			return err
		}

		return leasesFallbackDiscovery(client, conf)
	}

	return nil
}
<commit_msg>Revert "k8s: Add capability flag for watching metav1.POM"<commit_after>// Copyright 2016-2020 Authors of Cilium
//
// 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 version keeps track of the Kubernetes version the client is
// connected to
package version

import (
	"context"
	"fmt"

	k8sconfig "github.com/cilium/cilium/pkg/k8s/config"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/versioncheck"

	"github.com/blang/semver/v4"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "k8s")

// ServerCapabilities is a list of server capabilities derived based on
// version, the Kubernetes discovery API, or probing of individual API
// endpoints.
type ServerCapabilities struct {
	// MinimalVersionMet is true when the minimal version of Kubernetes
	// required to run Cilium has been met
	MinimalVersionMet bool

	// EndpointSlice is the ability of k8s server to support endpoint slices
	EndpointSlice bool

	// LeasesResourceLock is the ability of K8s server to support Lease type
	// from coordination.k8s.io/v1 API for leader election purposes(currently only in operator).
	// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#lease-v1-coordination-k8s-io
	//
	// This capability was introduced in K8s version 1.14, prior to which
	// we don't support HA mode for the cilium-operator.
	LeasesResourceLock bool

	// APIExtensionsV1CRD is set to true when the K8s server supports
	// apiextensions/v1 CRDs. TODO: Add link to docs
	//
	// This capability was introduced in K8s version 1.16, prior to which
	// apiextensions/v1beta1 CRDs were used exclusively.
	APIExtensionsV1CRD bool
}

type cachedVersion struct {
	mutex        lock.RWMutex
	capabilities ServerCapabilities
	version      semver.Version
}

const (
	// MinimalVersionConstraint is the minimal version that Cilium supports to
	// run kubernetes.
	MinimalVersionConstraint = "1.16.0"
)

var (
	cached = cachedVersion{}

	discoveryAPIGroup      = "discovery.k8s.io/v1beta1"
	coordinationV1APIGroup = "coordination.k8s.io/v1"
	endpointSliceKind      = "EndpointSlice"
	leaseKind              = "Lease"

	// Constraint to check support for Lease type from coordination.k8s.io/v1.
	// Support for Lease resource was introduced in K8s version 1.14.
	isGEThanLeaseSupportConstraint = versioncheck.MustCompile(">=1.14.0")

	// Constraint to check support for apiextensions/v1 CRD types. Support for
	// v1 CRDs was introduced in K8s version 1.16.
	isGEThanAPIExtensionsV1CRD = versioncheck.MustCompile(">=1.16.0")

	// isGEThanMinimalVersionConstraint is the minimal version required to run
	// Cilium
	isGEThanMinimalVersionConstraint = versioncheck.MustCompile(">=" + MinimalVersionConstraint)
)

// Version returns the version of the Kubernetes apiserver
func Version() semver.Version {
	cached.mutex.RLock()
	c := cached.version
	cached.mutex.RUnlock()
	return c
}

// Capabilities returns the capabilities of the Kubernetes apiserver
func Capabilities() ServerCapabilities {
	cached.mutex.RLock()
	c := cached.capabilities
	cached.mutex.RUnlock()
	return c
}

func updateVersion(version semver.Version) {
	cached.mutex.Lock()
	defer cached.mutex.Unlock()

	cached.version = version

	cached.capabilities.MinimalVersionMet = isGEThanMinimalVersionConstraint(version)
	cached.capabilities.APIExtensionsV1CRD = isGEThanAPIExtensionsV1CRD(version)
}

func updateServerGroupsAndResources(apiResourceLists []*metav1.APIResourceList) {
	cached.mutex.Lock()
	defer cached.mutex.Unlock()

	cached.capabilities.EndpointSlice = false
	cached.capabilities.LeasesResourceLock = false
	for _, rscList := range apiResourceLists {
		if rscList.GroupVersion == discoveryAPIGroup {
			for _, rsc := range rscList.APIResources {
				if rsc.Kind == endpointSliceKind {
					cached.capabilities.EndpointSlice = true
					break
				}
			}
		}

		if rscList.GroupVersion == coordinationV1APIGroup {
			for _, rsc := range rscList.APIResources {
				if rsc.Kind == leaseKind {
					cached.capabilities.LeasesResourceLock = true
					break
				}
			}
		}
	}
}

// Force forces the use of a specific version
func Force(version string) error {
	ver, err := versioncheck.Version(version)
	if err != nil {
		return err
	}
	updateVersion(ver)
	return nil
}

func endpointSlicesFallbackDiscovery(client kubernetes.Interface) error {
	// Discovery of API groups requires the API services of the apiserver to be
	// healthy. Such API services can depend on the readiness of regular pods
	// which require Cilium to function correctly. By treating failure to
	// discover API groups as fatal, a critial loop can be entered in which
	// Cilium cannot start because the API groups can't be discovered.
	//
	// Here we acknowledge the lack of discovery ability as non Fatal and fall back to probing
	// the API directly.
	_, err := client.DiscoveryV1beta1().EndpointSlices("default").Get(context.TODO(), "kubernetes", metav1.GetOptions{})
	if err == nil {
		cached.mutex.Lock()
		cached.capabilities.EndpointSlice = true
		cached.mutex.Unlock()
		return nil
	}

	if errors.IsNotFound(err) {
		log.WithError(err).Info("Unable to retrieve EndpointSlices for default/kubernetes. Disabling EndpointSlices")
		// StatusNotFound is a safe error, EndpointSlices are
		// disabled and the agent can continue.
		return nil
	}

	// Unknown error, we can't derive whether to enable or disable
	// EndpointSlices and need to error out.
	return fmt.Errorf("unable to validate EndpointSlices support: %s", err)
}

func leasesFallbackDiscovery(client kubernetes.Interface, conf k8sconfig.Configuration) error {
	// K8sEnableLeasesFallbackDiscovery is used to fallback leases discovery to directly
	// probing the API when we cannot discover API groups.
	// We require to check for Leases capabilities in operator only, which uses Leases
	// for leader election purposes in HA mode.
	if !conf.K8sLeasesFallbackDiscoveryEnabled() {
		log.Debugf("Skipping Leases support fallback discovery")
		return nil
	}

	cached.mutex.RLock()
	// Here we check if we are running a K8s version that has support for Leases.
	if !isGEThanLeaseSupportConstraint(cached.version) {
		cached.mutex.RUnlock()
		return nil
	}
	cached.mutex.RUnlock()

	// Similar to endpointSlicesFallbackDiscovery here we fallback to probing the Kubernetes
	// API directly. `kube-controller-manager` creates a lease in the kube-system namespace
	// and here we try and see if that Lease exists.
	_, err := client.CoordinationV1().Leases("kube-system").Get(context.TODO(), "kube-controller-manager", metav1.GetOptions{})
	if err == nil {
		cached.mutex.Lock()
		cached.capabilities.LeasesResourceLock = true
		cached.mutex.Unlock()
		return nil
	}

	if errors.IsNotFound(err) {
		log.WithError(err).Info("Unable to retrieve Leases for kube-controller-manager. Disabling LeasesResourceLock")
		// StatusNotFound is a safe error, Leases are
		// disabled and the agent can continue
		return nil
	}

	// Unknown error, we can't derive whether to enable or disable
	// LeasesResourceLock and need to error out
	return fmt.Errorf("unable to validate LeasesResourceLock support: %s", err)
}

func updateK8sServerVersion(client kubernetes.Interface) error {
	var ver semver.Version

	sv, err := client.Discovery().ServerVersion()
	if err != nil {
		return err
	}

	// Try GitVersion first. In case of error fallback to MajorMinor
	if sv.GitVersion != "" {
		// This is a string like "v1.9.0"
		ver, err = versioncheck.Version(sv.GitVersion)
		if err == nil {
			updateVersion(ver)
			return nil
		}
	}

	if sv.Major != "" && sv.Minor != "" {
		ver, err = versioncheck.Version(fmt.Sprintf("%s.%s", sv.Major, sv.Minor))
		if err == nil {
			updateVersion(ver)
			return nil
		}
	}

	return fmt.Errorf("cannot parse k8s server version from %+v: %s", sv, err)
}

// Update retrieves the version of the Kubernetes apiserver and derives the
// capabilities. This function must be called after connectivity to the
// apiserver has been established.
//
// Discovery of capabilities only works if the discovery API of the apiserver
// is functional. If it is not available, a warning is logged and the discovery
// falls back to probing individual API endpoints.
func Update(client kubernetes.Interface, conf k8sconfig.Configuration) error {
	err := updateK8sServerVersion(client)
	if err != nil {
		return err
	}

	if conf.K8sAPIDiscoveryEnabled() {
		// Discovery of API groups requires the API services of the
		// apiserver to be healthy. Such API services can depend on the
		// readiness of regular pods which require Cilium to function
		// correctly. By treating failure to discover API groups as
		// fatal, a critical loop can be entered in which Cilium cannot
		// start because the API groups can't be discovered and th API
		// groups will only become discoverable once Cilium is up.
		_, apiResourceLists, err := client.Discovery().ServerGroupsAndResources()
		if err != nil {
			// It doesn't make sense to retry the retrieval of this
			// information at a later point because the capabilities are
			// primiarly used while the agent is starting up. Instead, fall
			// back to probing API endpoints directly.
			log.WithError(err).Warning("Unable to discover API groups and resources")
			if err := endpointSlicesFallbackDiscovery(client); err != nil {
				return err
			}

			return leasesFallbackDiscovery(client, conf)
		}

		updateServerGroupsAndResources(apiResourceLists)
	} else {
		if err := endpointSlicesFallbackDiscovery(client); err != nil {
			return err
		}

		return leasesFallbackDiscovery(client, conf)
	}

	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

// todo enforce login/logout to work only through POST

//func requireMethod(method string, handler http.HandlerFunc) http.HandlerFunc {
//	return func(w http.ResponseWriter, r *http.Request) {
//		if r.Method != method {
//			http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
//			return
//		}
//
//		handler(w, r)
//	}
//}

//func requirePost(handler http.HandlerFunc) http.HandlerFunc {
//	return requireMethod(http.MethodPost, handler)
//}

func staticFilesHandler(path string, root http.FileSystem) http.Handler {
	return http.StripPrefix(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "login.html" && !strings.HasPrefix(r.URL.Path, "img/") {
			if isUnauthorized(r) {
				http.Redirect(w, r, "/ui/login.html", http.StatusTemporaryRedirect)
				return
			}
		}

		fileServer := http.FileServer(root)
		fileServer.ServeHTTP(w, r)
	}))
}

func handleAutoRedirect(w http.ResponseWriter, r *http.Request) {
	if redirect := r.URL.Query().Get("redirect"); redirect != "" {
		http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
	}
}

func writeJSON(w http.ResponseWriter, obj interface{}) {
	// todo handle errors
	res, _ := json.Marshal(obj)
	fmt.Fprint(w, string(res))
}
<commit_msg>Fix serving webui/public (it should be always served w/o auth check)<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	pathlib "path"
	"strings"
)

// todo enforce login/logout to work only through POST

//func requireMethod(method string, handler http.HandlerFunc) http.HandlerFunc {
//	return func(w http.ResponseWriter, r *http.Request) {
//		if r.Method != method {
//			http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
//			return
//		}
//
//		handler(w, r)
//	}
//}

//func requirePost(handler http.HandlerFunc) http.HandlerFunc {
//	return requireMethod(http.MethodPost, handler)
//}

func stringHasAnyPrefix(str string, prefix ...string) bool {
	for _, prefix := range prefix {
		if strings.HasPrefix(str, prefix) {
			return true
		}
	}

	return false
}

func staticFilesHandler(path string, root http.FileSystem) http.Handler {
	return http.StripPrefix(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "login.html" && !stringHasAnyPrefix(pathlib.Clean(r.URL.Path), "public/") {
			if isUnauthorized(r) {
				http.Redirect(w, r, "/ui/login.html", http.StatusTemporaryRedirect)
				return
			}
		}

		fileServer := http.FileServer(root)
		fileServer.ServeHTTP(w, r)
	}))
}

func handleAutoRedirect(w http.ResponseWriter, r *http.Request) {
	if redirect := r.URL.Query().Get("redirect"); redirect != "" {
		http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
	}
}

func writeJSON(w http.ResponseWriter, obj interface{}) {
	// todo handle errors
	res, _ := json.Marshal(obj)
	fmt.Fprint(w, string(res))
}
<|endoftext|>


<commit_before>// +build linux darwin openbsd netbsd solaris

/*
 * MinIO Cloud Storage, (C) 2017 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 sys

import "syscall"

// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process.
func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) {
	var rlimit syscall.Rlimit
	if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil {
		curLimit = rlimit.Cur
		maxLimit = rlimit.Max
	}

	return curLimit, maxLimit, err
}

// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process.
func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error {
	rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit}
	return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
}
<commit_msg>on darwin fallback to maximum possible rlimit (#9859)<commit_after>// +build linux darwin openbsd netbsd solaris

/*
 * MinIO Cloud Storage, (C) 2017 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 sys

import (
	"runtime"
	"syscall"
)

// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process.
func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) {
	var rlimit syscall.Rlimit
	if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil {
		curLimit = rlimit.Cur
		maxLimit = rlimit.Max
	}

	return curLimit, maxLimit, err
}

// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process.
func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error {
	if runtime.GOOS == "darwin" && curLimit > 10240 {
		// The max file limit is 10240, even though
		// the max returned by Getrlimit is 1<<63-1.
		// This is OPEN_MAX in sys/syslimits.h.
		// refer https://github.com/golang/go/issues/30401
		curLimit = 10240
	}
	rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit}
	return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
}
<|endoftext|>


<commit_before>//go:build linux || freebsd || darwin
// +build linux freebsd darwin

package system // import "github.com/docker/docker/pkg/system"

import (
	"fmt"
	"os"
	"strings"

	"golang.org/x/sys/unix"
)

// IsProcessAlive returns true if process with a given pid is running.
func IsProcessAlive(pid int) bool {
	err := unix.Kill(pid, 0)
	if err == nil || err == unix.EPERM {
		return true
	}

	return false
}

// KillProcess force-stops a process.
func KillProcess(pid int) {
	unix.Kill(pid, unix.SIGKILL)
}

// IsProcessZombie return true if process has a state with "Z"
// http://man7.org/linux/man-pages/man5/proc.5.html
func IsProcessZombie(pid int) (bool, error) {
	dataBytes, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	data := string(dataBytes)
	sdata := strings.SplitN(data, " ", 4)
	if len(sdata) >= 3 && sdata[2] == "Z" {
		return true, nil
	}

	return false, nil
}
<commit_msg>pkg/system: IsProcessZombie() skip conversion to string, use bytes instead<commit_after>//go:build linux || freebsd || darwin
// +build linux freebsd darwin

package system // import "github.com/docker/docker/pkg/system"

import (
	"bytes"
	"fmt"
	"os"

	"golang.org/x/sys/unix"
)

// IsProcessAlive returns true if process with a given pid is running.
func IsProcessAlive(pid int) bool {
	err := unix.Kill(pid, 0)
	if err == nil || err == unix.EPERM {
		return true
	}

	return false
}

// KillProcess force-stops a process.
func KillProcess(pid int) {
	unix.Kill(pid, unix.SIGKILL)
}

// IsProcessZombie return true if process has a state with "Z"
// http://man7.org/linux/man-pages/man5/proc.5.html
func IsProcessZombie(pid int) (bool, error) {
	data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	if cols := bytes.SplitN(data, []byte(" "), 4); len(cols) >= 3 && string(cols[2]) == "Z" {
		return true, nil
	}
	return false, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 Google Inc. All Rights Reserved.

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

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

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

// Rule-level API for inspecting and modifying a build.File syntax tree.

package build

import (
	"path/filepath"
	"strings"
)

// A Rule represents a single BUILD rule.
type Rule struct {
	Call         *CallExpr
	ImplicitName string // The name which should be used if the name attribute is not set. See the comment on File.implicitRuleName.
}

func (f *File) Rule(call *CallExpr) *Rule {
	r := &Rule{call, ""}
	if r.AttrString("name") == "" {
		r.ImplicitName = f.implicitRuleName()
	}
	return r
}

// Rules returns the rules in the file of the given kind (such as "go_library").
// If kind == "", Rules returns all rules in the file.
func (f *File) Rules(kind string) []*Rule {
	var all []*Rule

	for _, stmt := range f.Stmt {
		Walk(stmt, func(x Expr, stk []Expr) {
			call, ok := x.(*CallExpr)
			if !ok {
				return
			}

			// Skip nested calls.
			for _, frame := range stk {
				if _, ok := frame.(*CallExpr); ok {
					return
				}
			}

			// Check if the rule kind is correct.
			rule := f.Rule(call)
			if kind != "" && rule.Kind() != kind {
				return
			}
			all = append(all, rule)
		})
	}

	return all
}

// RuleAt returns the rule in the file that starts at the specified line, or null if no such rule.
func (f *File) RuleAt(linenum int) *Rule {

	for _, stmt := range f.Stmt {
		call, ok := stmt.(*CallExpr)
		if !ok {
			continue
		}
		start, end := call.X.Span()
		if start.Line <= linenum && linenum <= end.Line {
			return f.Rule(call)
		}
	}
	return nil
}

// DelRules removes rules with the given kind and name from the file.
// An empty kind matches all kinds; an empty name matches all names.
// It returns the number of rules that were deleted.
func (f *File) DelRules(kind, name string) int {
	var i int
	for _, stmt := range f.Stmt {
		if call, ok := stmt.(*CallExpr); ok {
			r := f.Rule(call)
			if (kind == "" || r.Kind() == kind) &&
				(name == "" || r.Name() == name) {
				continue
			}
		}
		f.Stmt[i] = stmt
		i++
	}
	n := len(f.Stmt) - i
	f.Stmt = f.Stmt[:i]
	return n
}

// If a build file contains exactly one unnamed rule, and no rules in the file explicitly have the
// same name as the name of the directory the build file is in, we treat the unnamed rule as if it
// had the name of the directory containing the BUILD file.
// This is following a convention used in the Pants build system to cut down on boilerplate.
func (f *File) implicitRuleName() string {
	// We disallow empty names in the top-level BUILD files.
	dir := filepath.Dir(f.Path)
	if dir == "." {
		return ""
	}
	sawAnonymousRule := false
	possibleImplicitName := filepath.Base(dir)

	for _, stmt := range f.Stmt {
		call, ok := stmt.(*CallExpr)
		if !ok {
			continue
		}
		temp := &Rule{call, ""}
		if temp.AttrString("name") == possibleImplicitName {
			// A target explicitly has the name of the dir, so no implicit targets are allowed.
			return ""
		}
		if temp.Kind() != "" && temp.AttrString("name") == "" {
			if sawAnonymousRule {
				return ""
			}
			sawAnonymousRule = true
		}
	}
	if sawAnonymousRule {
		return possibleImplicitName
	}
	return ""
}

// Kind returns the rule's kind (such as "go_library").
// The kind of the rule may be given by a literal or it may be a sequence of dot expressions that
// begins with a literal, if the call expression does not conform to either of these forms, an
// empty string will be returned
func (r *Rule) Kind() string {
	var names []string
	expr := r.Call.X
	for {
		x, ok := expr.(*DotExpr)
		if !ok {
			break
		}
		names = append(names, x.Name)
		expr = x.X
	}
	x, ok := expr.(*Ident)
	if !ok {
		return ""
	}
	names = append(names, x.Name)
	// Reverse the elements since the deepest expression contains the leading literal
	for l, r := 0, len(names)-1; l < r; l, r = l+1, r-1 {
		names[l], names[r] = names[r], names[l]
	}
	return strings.Join(names, ".")
}

// SetKind changes rule's kind (such as "go_library").
func (r *Rule) SetKind(kind string) {
	names := strings.Split(kind, ".")
	var expr Expr
	expr = &Ident{Name: names[0]}
	for _, name := range names[1:] {
		expr = &DotExpr{X: expr, Name: name}
	}
	r.Call.X = expr
}

// Name returns the rule's target name.
// If the rule has no explicit target name, Name returns the implicit name if there is one, else the empty string.
func (r *Rule) Name() string {
	explicitName := r.AttrString("name")
	if explicitName == "" {
		return r.ImplicitName
	}
	return explicitName
}

// AttrKeys returns the keys of all the rule's attributes.
func (r *Rule) AttrKeys() []string {
	var keys []string
	for _, expr := range r.Call.List {
		if binExpr, ok := expr.(*BinaryExpr); ok && binExpr.Op == "=" {
			if keyExpr, ok := binExpr.X.(*Ident); ok {
				keys = append(keys, keyExpr.Name)
			}
		}
	}
	return keys
}

// AttrDefn returns the BinaryExpr defining the rule's attribute with the given key.
// That is, the result is a *BinaryExpr with Op == "=".
// If the rule has no such attribute, AttrDefn returns nil.
func (r *Rule) AttrDefn(key string) *BinaryExpr {
	for _, kv := range r.Call.List {
		as, ok := kv.(*BinaryExpr)
		if !ok || as.Op != "=" {
			continue
		}
		k, ok := as.X.(*Ident)
		if !ok || k.Name != key {
			continue
		}
		return as
	}
	return nil
}

// Attr returns the value of the rule's attribute with the given key
// (such as "name" or "deps").
// If the rule has no such attribute, Attr returns nil.
func (r *Rule) Attr(key string) Expr {
	as := r.AttrDefn(key)
	if as == nil {
		return nil
	}
	return as.Y
}

// DelAttr deletes the rule's attribute with the named key.
// It returns the old value of the attribute, or nil if the attribute was not found.
func (r *Rule) DelAttr(key string) Expr {
	list := r.Call.List
	for i, kv := range list {
		as, ok := kv.(*BinaryExpr)
		if !ok || as.Op != "=" {
			continue
		}
		k, ok := as.X.(*Ident)
		if !ok || k.Name != key {
			continue
		}
		copy(list[i:], list[i+1:])
		r.Call.List = list[:len(list)-1]
		return as.Y
	}
	return nil
}

// SetAttr sets the rule's attribute with the given key to value.
// If the rule has no attribute with the key, SetAttr appends
// one to the end of the rule's attribute list.
func (r *Rule) SetAttr(key string, val Expr) {
	as := r.AttrDefn(key)
	if as != nil {
		as.Y = val
		return
	}

	r.Call.List = append(r.Call.List,
		&BinaryExpr{
			X:  &Ident{Name: key},
			Op: "=",
			Y:  val,
		},
	)
}

// AttrLiteral returns the literal form of the rule's attribute
// with the given key (such as "cc_api_version"), only when
// that value is an identifier or number.
// If the rule has no such attribute or the attribute is not an identifier or number,
// AttrLiteral returns "".
func (r *Rule) AttrLiteral(key string) string {
	value := r.Attr(key)
	if ident, ok := value.(*Ident); ok {
		return ident.Name
	}
	if literal, ok := value.(*LiteralExpr); ok {
		return literal.Token
	}
	return ""
}

// AttrString returns the value of the rule's attribute
// with the given key (such as "name"), as a string.
// If the rule has no such attribute or the attribute has a non-string value,
// Attr returns the empty string.
func (r *Rule) AttrString(key string) string {
	str, ok := r.Attr(key).(*StringExpr)
	if !ok {
		return ""
	}
	return str.Value
}

// AttrStrings returns the value of the rule's attribute
// with the given key (such as "srcs"), as a []string.
// If the rule has no such attribute or the attribute is not
// a list of strings, AttrStrings returns a nil slice.
func (r *Rule) AttrStrings(key string) []string {
	return Strings(r.Attr(key))
}

// Strings returns expr as a []string.
// If expr is not a list of string literals,
// Strings returns a nil slice instead.
// If expr is an empty list of string literals,
// returns a non-nil empty slice.
// (this allows differentiating between these two cases)
func Strings(expr Expr) []string {
	list, ok := expr.(*ListExpr)
	if !ok {
		return nil
	}
	all := []string{} // not nil
	for _, l := range list.List {
		str, ok := l.(*StringExpr)
		if !ok {
			return nil
		}
		all = append(all, str.Value)
	}
	return all
}
<commit_msg>Sync rule.go (#539)<commit_after>/*
Copyright 2016 Google Inc. All Rights Reserved.

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

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

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

// Rule-level API for inspecting and modifying a build.File syntax tree.

package build

import (
	"path/filepath"
	"strings"
)

// A Rule represents a single BUILD rule.
type Rule struct {
	Call         *CallExpr
	ImplicitName string // The name which should be used if the name attribute is not set. See the comment on File.implicitRuleName.
}

// NewRule is a simple constructor for Rule.
func NewRule(call *CallExpr) *Rule {
	return &Rule{call, ""}
}

func (f *File) Rule(call *CallExpr) *Rule {
	r := &Rule{call, ""}
	if r.AttrString("name") == "" {
		r.ImplicitName = f.implicitRuleName()
	}
	return r
}

// Rules returns the rules in the file of the given kind (such as "go_library").
// If kind == "", Rules returns all rules in the file.
func (f *File) Rules(kind string) []*Rule {
	var all []*Rule

	for _, stmt := range f.Stmt {
		Walk(stmt, func(x Expr, stk []Expr) {
			call, ok := x.(*CallExpr)
			if !ok {
				return
			}

			// Skip nested calls.
			for _, frame := range stk {
				if _, ok := frame.(*CallExpr); ok {
					return
				}
			}

			// Check if the rule kind is correct.
			rule := f.Rule(call)
			if kind != "" && rule.Kind() != kind {
				return
			}
			all = append(all, rule)
		})
	}

	return all
}

// RuleAt returns the rule in the file that starts at the specified line, or null if no such rule.
func (f *File) RuleAt(linenum int) *Rule {

	for _, stmt := range f.Stmt {
		call, ok := stmt.(*CallExpr)
		if !ok {
			continue
		}
		start, end := call.X.Span()
		if start.Line <= linenum && linenum <= end.Line {
			return f.Rule(call)
		}
	}
	return nil
}

// DelRules removes rules with the given kind and name from the file.
// An empty kind matches all kinds; an empty name matches all names.
// It returns the number of rules that were deleted.
func (f *File) DelRules(kind, name string) int {
	var i int
	for _, stmt := range f.Stmt {
		if call, ok := stmt.(*CallExpr); ok {
			r := f.Rule(call)
			if (kind == "" || r.Kind() == kind) &&
				(name == "" || r.Name() == name) {
				continue
			}
		}
		f.Stmt[i] = stmt
		i++
	}
	n := len(f.Stmt) - i
	f.Stmt = f.Stmt[:i]
	return n
}

// If a build file contains exactly one unnamed rule, and no rules in the file explicitly have the
// same name as the name of the directory the build file is in, we treat the unnamed rule as if it
// had the name of the directory containing the BUILD file.
// This is following a convention used in the Pants build system to cut down on boilerplate.
func (f *File) implicitRuleName() string {
	// We disallow empty names in the top-level BUILD files.
	dir := filepath.Dir(f.Path)
	if dir == "." {
		return ""
	}
	sawAnonymousRule := false
	possibleImplicitName := filepath.Base(dir)

	for _, stmt := range f.Stmt {
		call, ok := stmt.(*CallExpr)
		if !ok {
			continue
		}
		temp := &Rule{call, ""}
		if temp.AttrString("name") == possibleImplicitName {
			// A target explicitly has the name of the dir, so no implicit targets are allowed.
			return ""
		}
		if temp.Kind() != "" && temp.AttrString("name") == "" {
			if sawAnonymousRule {
				return ""
			}
			sawAnonymousRule = true
		}
	}
	if sawAnonymousRule {
		return possibleImplicitName
	}
	return ""
}

// Kind returns the rule's kind (such as "go_library").
// The kind of the rule may be given by a literal or it may be a sequence of dot expressions that
// begins with a literal, if the call expression does not conform to either of these forms, an
// empty string will be returned
func (r *Rule) Kind() string {
	var names []string
	expr := r.Call.X
	for {
		x, ok := expr.(*DotExpr)
		if !ok {
			break
		}
		names = append(names, x.Name)
		expr = x.X
	}
	x, ok := expr.(*Ident)
	if !ok {
		return ""
	}
	names = append(names, x.Name)
	// Reverse the elements since the deepest expression contains the leading literal
	for l, r := 0, len(names)-1; l < r; l, r = l+1, r-1 {
		names[l], names[r] = names[r], names[l]
	}
	return strings.Join(names, ".")
}

// SetKind changes rule's kind (such as "go_library").
func (r *Rule) SetKind(kind string) {
	names := strings.Split(kind, ".")
	var expr Expr
	expr = &Ident{Name: names[0]}
	for _, name := range names[1:] {
		expr = &DotExpr{X: expr, Name: name}
	}
	r.Call.X = expr
}

// Name returns the rule's target name.
// If the rule has no explicit target name, Name returns the implicit name if there is one, else the empty string.
func (r *Rule) Name() string {
	explicitName := r.AttrString("name")
	if explicitName == "" && r.Kind() != "package" {
		return r.ImplicitName
	}
	return explicitName
}

// AttrKeys returns the keys of all the rule's attributes.
func (r *Rule) AttrKeys() []string {
	var keys []string
	for _, expr := range r.Call.List {
		if binExpr, ok := expr.(*BinaryExpr); ok && binExpr.Op == "=" {
			if keyExpr, ok := binExpr.X.(*Ident); ok {
				keys = append(keys, keyExpr.Name)
			}
		}
	}
	return keys
}

// AttrDefn returns the BinaryExpr defining the rule's attribute with the given key.
// That is, the result is a *BinaryExpr with Op == "=".
// If the rule has no such attribute, AttrDefn returns nil.
func (r *Rule) AttrDefn(key string) *BinaryExpr {
	for _, kv := range r.Call.List {
		as, ok := kv.(*BinaryExpr)
		if !ok || as.Op != "=" {
			continue
		}
		k, ok := as.X.(*Ident)
		if !ok || k.Name != key {
			continue
		}
		return as
	}
	return nil
}

// Attr returns the value of the rule's attribute with the given key
// (such as "name" or "deps").
// If the rule has no such attribute, Attr returns nil.
func (r *Rule) Attr(key string) Expr {
	as := r.AttrDefn(key)
	if as == nil {
		return nil
	}
	return as.Y
}

// DelAttr deletes the rule's attribute with the named key.
// It returns the old value of the attribute, or nil if the attribute was not found.
func (r *Rule) DelAttr(key string) Expr {
	list := r.Call.List
	for i, kv := range list {
		as, ok := kv.(*BinaryExpr)
		if !ok || as.Op != "=" {
			continue
		}
		k, ok := as.X.(*Ident)
		if !ok || k.Name != key {
			continue
		}
		copy(list[i:], list[i+1:])
		r.Call.List = list[:len(list)-1]
		return as.Y
	}
	return nil
}

// SetAttr sets the rule's attribute with the given key to value.
// If the rule has no attribute with the key, SetAttr appends
// one to the end of the rule's attribute list.
func (r *Rule) SetAttr(key string, val Expr) {
	as := r.AttrDefn(key)
	if as != nil {
		as.Y = val
		return
	}

	r.Call.List = append(r.Call.List,
		&BinaryExpr{
			X:  &Ident{Name: key},
			Op: "=",
			Y:  val,
		},
	)
}

// AttrLiteral returns the literal form of the rule's attribute
// with the given key (such as "cc_api_version"), only when
// that value is an identifier or number.
// If the rule has no such attribute or the attribute is not an identifier or number,
// AttrLiteral returns "".
func (r *Rule) AttrLiteral(key string) string {
	value := r.Attr(key)
	if ident, ok := value.(*Ident); ok {
		return ident.Name
	}
	if literal, ok := value.(*LiteralExpr); ok {
		return literal.Token
	}
	return ""
}

// AttrString returns the value of the rule's attribute
// with the given key (such as "name"), as a string.
// If the rule has no such attribute or the attribute has a non-string value,
// Attr returns the empty string.
func (r *Rule) AttrString(key string) string {
	str, ok := r.Attr(key).(*StringExpr)
	if !ok {
		return ""
	}
	return str.Value
}

// AttrStrings returns the value of the rule's attribute
// with the given key (such as "srcs"), as a []string.
// If the rule has no such attribute or the attribute is not
// a list of strings, AttrStrings returns a nil slice.
func (r *Rule) AttrStrings(key string) []string {
	return Strings(r.Attr(key))
}

// Strings returns expr as a []string.
// If expr is not a list of string literals,
// Strings returns a nil slice instead.
// If expr is an empty list of string literals,
// returns a non-nil empty slice.
// (this allows differentiating between these two cases)
func Strings(expr Expr) []string {
	list, ok := expr.(*ListExpr)
	if !ok {
		return nil
	}
	all := []string{} // not nil
	for _, l := range list.List {
		str, ok := l.(*StringExpr)
		if !ok {
			return nil
		}
		all = append(all, str.Value)
	}
	return all
}
<|endoftext|>